How to make this code work: Just have pyglet installed and change "assassin1.png" and "assassin2.png" with the name of an images stored in the directory where you saved this code to a file.
import pyglet
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch):
pyglet.sprite.Sprite.__init__(self, pyglet.resource.image("assassin1.png"))
self.x = 50
self.y = 30
def forward_movement(self):
pass # How do I continously change between 'assassin1.png' and 'assassin2.png'?
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.player = Assassin(batch = self.batch_draw)
self.fps_display = pyglet.clock.ClockDisplay()
self.keys_held = []
self.schedule = pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
def on_draw(self):
self.clear()
self.fps_display.draw()
self.batch_draw.draw()
self.player.draw()
def on_key_press(self, symbol, modifiers):
self.keys_held.append(symbol)
if symbol == pyglet.window.key.RIGHT:
self.player.forward_movement()
print "The 'RIGHT' key was pressed"
def on_key_release(self, symbol, modifiers):
self.keys_held.pop(self.keys_held.index(symbol))
def update(self, interval):
if pyglet.window.key.RIGHT in self.keys_held:
self.player.x += 50 * interval
if __name__ == "__main__":
window = Game()
pyglet.app.run()
Description: This code creates a black background screen, where the fps are displayed and an
image "assassin1.png" is displayed at position (50, 30). As long as the right direction button is held down the image will move to the right.
Goal: I would like to implement that whenever the right direction button is held and the image is moving, the assassin1.png image is changed periodically (every 0.25 secs or so) with a second image assassin2.png. This in order to create the vague illusion that the image is walking.
How do I achieve this goal?
I already created an empty forward_movement() method in the Assassin class which would seem an appropriate place to put the code to achieve my goal. But if you would want to place the code in another place thats ok too.
The pyglet.sprite.Sprite class allows you to edit its image to an animation at anytime. So, in the sprites constructor, we define a walk animation:
def __init__(self, batch):
# The image to display when not moving
self._img_main = pyglet.image.load('assassin.png')
self._img_right_1 = pyglet.image.load('assassin1.png')
self._img_right_2 = pyglet.image.load('assassin2.png')
self.anim_right = pyglet.image.Animation.from_image_sequence([
self._img_right_1, self._img_right_2], 0.5, True)
# 0.5 is the number in seconds between frames
# True means to keep looping (We stop it later)
pyglet.sprite.Sprite.__init__(self, self._img_main)
#...
Next we add a function to make it easier to change animations:
def forward_movement(self, flag=True):
if flag:
self.image = self.anim_right # Now our sprite animates
else:
self.image = self._img_main
Finally we call the function at the appropriate time:
#...
def on_key_press(self, symbol, modifiers):
self.keys_held.append(symbol)
if symbol == pyglet.window.key.RIGHT:
self.player.forward_movement(True)
print "The 'RIGHT' key was pressed"
def on_key_release(self, symbol, modifiers):
self.keys_held.pop(self.keys_held.index(symbol))
if symbol == pyglet.window.key.RIGHT:
self.player.forward_movement(False) # We have stopped moving
#...
And voilĂ ! When the user has the right-key down, the sprite moves and animates!
Related
I would like to move two different turtle objects(slider_1 and slider_2) upwards and downwards using the onkeypress function from Python's turtle module.
My issue is that when I move slider_1 then I can't move slider_2 without releasing the key for slider_1, and vice-versa. How can I fix my code, so that I can control slider_1 and slider_2 independently, without any pauses in movement.
main.py:
from turtle import Screen, Turtle
from slider import Slider_1, Slider_2
HEIGHT = 800
WIDTH = 600
screen = Screen()
screen2 = Screen()
screen.bgcolor("black")
screen.title("PONG")
screen.setup(height=HEIGHT, width=WIDTH)
screen.listen()
slider_1 = Slider_1()
screen.onkeypress(key="Up", fun=slider_1.up)
screen.onkeypress(key="Down", fun=slider_1.down)
slider_2 = Slider_2()
screen.onkeypress(key="w", fun=slider_2.up)
screen.onkeypress(key="s", fun=slider_2.down)
screen.exitonclick()
Slider.py:
from turtle import Turtle
SIZE = 20
SPEED = 10
class Slider(Turtle):
def __init__(self):
super().__init__()
self.penup()
self.speed("fastest")
self.shape("square")
self.shapesize(SIZE)
self.shapesize(stretch_wid=1, stretch_len=3)
# self.move = True
def up(self):
self.setheading(90)
if self.ycor() >= 360:
return
else:
self.forward(SPEED)
def down(self):
self.setheading(270)
if self.ycor() <= -360:
return
else:
self.forward(SPEED)
class Slider_1(Slider):
def __init__(self):
super().__init__()
self.color("white")
self.goto(x=-600 / 2 + SIZE, y=0)
self.setheading(90)
class Slider_2(Slider):
def __init__(self):
super().__init__()
self.color("blue")
self.goto(x=600 / 2 - SIZE, y=0)
self.setheading(90)
The issue here is the hardware key handling. You are only checking for keypress. The keyboard "key repeat" handler only triggers key repeating for the last key that was pressed.
To do what you want, your code is going to get more complicated. You will need to have key press and key release handlers for all 4 of those buttons. The key press handlers will need to set a flag, and the key release handlers will clear the flag. Then, you will need a periodic function (using screen.ontimer) running in a timer that checks the flags: "Is left moving up? Is left moving down? Is right moving up? Is right moving down?" Handle all four possibilities, then exit and wait for the next call.
I am trying to make an animation using pyglet. So first I tried a simple animation, moving the image in a strait line. Idealy I would like it to bounce around from left to right.
Here is my code:
import pyglet
def center_image(image):
"""Sets an image's anchor point to its center"""
image.anchor_x = image.width // 2
image.anchor_y = image.height // 2
# Make window.
window = pyglet.window.Window(width=640, height=480)
# Load image.
pyglet.resource.path = ['images']
pyglet.resource.reindex()
heart_img = pyglet.resource.image('red-heart.png')
center_image(heart_img)
# Make animation sprite.
heart_grid = pyglet.image.ImageGrid(heart_img, rows=1, columns=5)
heart_ani = pyglet.image.Animation.from_image_sequence(heart_grid, duration=0.1)
heart_sprite = pyglet.sprite.Sprite(heart_ani, x=100, y=300)
heart_sprite.update(scale=0.05)
#window.event
def on_draw():
window.clear()
heart_sprite.draw()
if __name__ == '__main__':
pyglet.app.run()
This code produces this:
How can I make the whole heart move through the window?
The desired trajectory of the heart would be something like this:
Where the box is the frame, the arches are the trajectory and O is a sprite. So the heart would bounce of the first letter of each word and then bounce of the sprite.
So the main issue is that Animation assumes a series of images within a large image. It's called sprite animations and it's essentially just a series strip (usually in a row or a grid pattern) of the movements you want. It's useful for animating walking, attacking and other similar game mechanics.
But to move an object around the canvas, you would need to manipulate the vertices or the image location manually in some way. Your own solution works on the principle of checking if X is greater or less than min and max restrictions. And I would just like to add ontop of that to show some techniques to make it easier and faster to work with the movements and directions. Below I've worked with bitwise operations to determain the direction of movement and this makes the heart bounce around the parent (window) constraints of width and height.
I've also taken the liberty to make the whole project more object oriented by inheriting the pyglet Window class into one object/class as well as make heart it's own class to easier separate what is called when and on what object.
from pyglet import *
from pyglet.gl import *
key = pyglet.window.key
# Indented oddly on purpose to show the pattern:
UP = 0b0001
DOWN = 0b0010
LEFT = 0b0100
RIGHT = 0b1000
class heart(pyglet.sprite.Sprite):
def __init__(self, parent, image='heart.png', x=0, y=0):
self.texture = pyglet.image.load(image)
pyglet.sprite.Sprite.__init__(self, self.texture, x=x, y=y)
self.parent = parent
self.direction = UP | RIGHT # Starting direction
def update(self):
# We can use the pattern above with bitwise operations.
# That way, one direction can be merged with another without collision.
if self.direction & UP:
self.y += 1
if self.direction & DOWN:
self.y -= 1
if self.direction & LEFT:
self.x -= 1
if self.direction & RIGHT:
self.x += 1
if self.x+self.width > self.parent.width:
self.direction = self.direction ^ RIGHT # Remove the RIGHT indicator
self.direction = self.direction ^ LEFT # Start moving to the LEFT
if self.y+self.height > self.parent.height:
self.direction = self.direction ^ UP # Remove the UP indicator
self.direction = self.direction ^ DOWN # Start moving DOWN
if self.y < 0:
self.direction = self.direction ^ DOWN
self.direction = self.direction ^ UP
if self.x < 0:
self.direction = self.direction ^ LEFT
self.direction = self.direction ^ RIGHT
def render(self):
self.draw()
# This class just sets up the window,
# self.heart <-- The important bit
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.x, self.y = 0, 0
self.heart = heart(self, x=100, y=100)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.heart.update()
self.heart.render()
## Add stuff you want to render here.
## Preferably in the form of a batch.
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main()
x.run()
The basic principle is the same, manipulating sprite.x to move it laterally, and sprite.y vertically. There's more optimizations to be done, for instance, updates should be scaled according to last render. This is done to avoid glitches if your graphics card can't keep up. It can get quite complicate quite fast, so I'll leave you with an example of how to calculate those movements.
Further more, you probably want to render a batch, not the sprite directly. Which would speed up rendering processes quite a lot for larger projects.
If you're unfamiliar with bitwise operations, a short description would be that it operates on a bit/binary level (4 == 0100 as an example), and doing XOR operations on the values of UP, DOWN, LEFT and RIGHT. We can add/remove directions by merging 0100 and 0001 resulting in 0101 as an example. We can then do binary AND (not like the traditional and operator) to determinate if a value contains a 1 on the third position (0100) by doing self.direction & 0100 which will result in 1 if it's True. It's a handy quick way of checking "states" if you will.
My solution uses the midpoint between two fixed Sprites to determine whether the moving Sprite should go up or down. For this I made all letters individual Sprites, one png for each letter.
Hopefully this image will explain the code below a bit better.
#!/usr/bin/env python
import pyglet
CURR_BOUNCE = 0
MIDPOINTS = []
ENDPOINTS = []
def calculate_midpoint(s1, s2):
""" Calculate the midpoint between two sprites on the x axis. """
return (s1.x + s2.x) // 2
def should_move_down():
""" Decides if the Sprite is going up or down. """
global CURR_BOUNCE
# If the Sprite completed all bounces the app closes (not the best solution).
if max(len(MIDPOINTS), len(ENDPOINTS), CURR_BOUNCE) == CURR_BOUNCE:
raise SystemExit
# Move down if the Sprite is between the midpoint and the end (landing) point.
if MIDPOINTS[CURR_BOUNCE] <= heart_sprite.x <= ENDPOINTS[CURR_BOUNCE]:
return True
# If the Sprite has passed both the mid and end point then it can move on to the next bounce.
if max(MIDPOINTS[CURR_BOUNCE], heart_sprite.x, ENDPOINTS[CURR_BOUNCE]) == heart_sprite.x:
CURR_BOUNCE += 1
# Default behaviour is to keep going up.
return False
def update(dt):
"""
Move Sprite by number of pixels in each tick.
The Sprite always moves to the right on the x-axis.
The default movement on the y-axis is up.
"""
heart_sprite.x += dt * heart_sprite.dx
if should_move_down():
# To go down simply make the movement on the y-axis negative.
heart_sprite.y -= dt * heart_sprite.dy
else:
heart_sprite.y += dt * heart_sprite.dy
def center_image(image):
""" Sets an image's anchor point to its centre """
image.anchor_x = image.width // 2
image.anchor_y = image.height // 2
# Make window.
window = pyglet.window.Window(width=640, height=480)
# Set image path.
pyglet.resource.path = ['images']
pyglet.resource.reindex()
# Load images.
heart_img = pyglet.resource.image('red-heart.png')
cupcake_img = pyglet.resource.image('cupcake.png')
s_img = pyglet.resource.image('S.png')
# Add all letters here ...
t_img = pyglet.resource.image('t.png')
# Center images.
center_image(heart_img)
center_image(cupcake_img)
center_image(s_img)
# Centre all letters here ...
center_image(t_img)
# Make sprites.
half_window_height = window.height // 2
heart_sprite = pyglet.sprite.Sprite(img=heart_img, x=100, y=300)
# Set Sprite's speed.
heart_sprite.dx = 200
heart_sprite.dy = 90
cupcake_sprite = pyglet.sprite.Sprite(img=cupcake_img, x=550, y=half_window_height)
s_sprite = pyglet.sprite.Sprite(img=s_img, x=100, y=half_window_height)
# Make all letters into Sprites and adjust the x-axis coordinates...
t_sprite = pyglet.sprite.Sprite(img=t_img, x=310, y=half_window_height)
# Calculate midpoints.
# Here the midpoint between the 'bouncing point' and the 'landing point' is calculated.
# This is done for all bounces the Sprite makes.
MIDPOINTS.append(calculate_midpoint(s_sprite, t_sprite))
MIDPOINTS.append(calculate_midpoint(t_sprite, cupcake_sprite))
# The 'landing point' must be saved to be able to determine when one bounce has finished
# and move on to the next.
ENDPOINTS.append(t_sprite.x)
ENDPOINTS.append(cupcake_sprite.x)
# Rescale sprites.
heart_sprite.update(scale=0.05)
cupcake_sprite.update(scale=0.1)
s_sprite.update(scale=0.3)
# Resize all letter Sprites here ...
t_sprite.update(scale=0.3)
#window.event
def on_draw():
window.clear()
cupcake_sprite.draw()
heart_sprite.draw()
s_sprite.draw()
# Draw all letter Sprites here ...
t_sprite.draw()
#window.event
def on_mouse_press(x, y, button, modifiers):
"""
I only put the schedule_interval inside a mouse_press event so that I can control
when the animation begins by clicking on it. Otherwise the last line in this method
can be placed directly above the 'pyglet.app.run()' line. This would run the
animation automatically when the app starts.
"""
# Call update 60 times a second
pyglet.clock.schedule_interval(update, 1/60.)
if __name__ == '__main__':
pyglet.app.run()
I need to create a fighting game that gives prompts and accepts input through text, such as a raw input and then performs the animation, while still have the characters animated, e.g. moving back and forth in a ready to fight stance. How would I go about this?
Please note that this is not going to be your typical answer. StackOverflow is to help after all that you can do on your part when you are stuck, it's not meant as a place to come for code, but since I'm assuming other people new to programming will also be confused on things such as these. So I'm going to write some code, and some psuedo code, just so that you get the just of what you would do in such a scenario.
# TODO put your imports up here
pygame.init()
clock = pygame.time.Clock()
gameSurface = pygame.display.set_mode((600, 400)) # 2/3 aspect ratio
FPS = 40 # Set to your own Frames per second
class Animator:
def __init__(self, surface, rows, cols, time_between_frames, on_finish):
self.images = []
self.current_image = 0
self.time_between_frames = time_between_frames # time animator waits before changing surface
self.current_time # tracks time for frames to change
self.on_finish = on_finish # function to call when animation finishes
surf_width = (surface.get_width() / cols) # calculate width
surf_height = (surface.get_height() / rows) # calculate height
for x in range(cols):
for y in range(rows):
surf = pygame.Surface(surface.get_size()) # temp surface
from_rect = pygame.Rect(x * surf_width, y * surf_height, surf_width, surf_height) # rect to blit from
surf.blit(surface, (0,0), from_rect) # draw to temp surface
self.images.append(surf) # add temp surface to the images list
def update(delta):
self.current_time += delta # update current time
if (self.current_time >= self.time_between_frames): # if time to switch surfaces
self.current_time -= self.time_between_frames # take away time from current time
self.current_image += 1 # change image
if self.current_image >= len(self.images): # if current image would throw an out of bounds exception
self.current_image = 0 # reset the current image to the first
def get_frame(self):
return self.images[self.current_image]
class Player:
resting = 0
abdomenKick = 1
def __init__(self, x, y):
self.x = x
self.y = y
self.action = Player.resting
self.restingAnimation = Animation(pygame.image.load("resting.png"), 2, 3, 500)
self.abdomenKickAnimation = Animation(pygame.image.load("abdomenKick.png"), 4, 6, 50)
self.currentAnimation = self.restingAnimation
def update(self, delta):
self.currentAnimation.update(delta)
def draw(self, surface):
surface.blit(self.currentAnimation.get_frame(), (self.x, self.y))
def abdomenKick(self):
self.currentAnimation = self.restingAnimation
class Game:
def __init__(self):
self.player = Player()
def update(self, delta):
self.player.update(delta)
def draw_screen(self, surface):
self.player.draw(surface)
def gameLoop():
game = Game()
while True:
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == A:
game.player.abdomenKick() #Or whatever move you have
game.update(clock.get_rawtime())
game.draw_screen()
clock.tick(FPS)
So here is just a brief showcase you can call it of what this might look like.
This code displays the image assassin1.png on a black background. As soon as I press the key the image moves to the right and stops moving as soon as I release the key.
As soon as I press the key it should also change to the image assassin2.png and when I release the key it should change back to assassin1.png.
This code however never displays the assassin2.png image while moving. Why is this so and how can I fix this?
import pyglet
class Assassin(pyglet.sprite.Sprite):
def __init__(self, batch, img):
pyglet.sprite.Sprite.__init__(self, img, x = 50, y = 30)
def stand(self):
self.img = pyglet.image.load("assassin1.png")
return self
def move(self):
self.img = pyglet.image.load('assassin2.png')
return self
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.player = Assassin(batch = self.batch_draw, img = pyglet.image.load("assassin1.png"))
self.keys_held = []
self.schedule = pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
def on_draw(self):
self.clear()
self.batch_draw.draw()
self.player.draw()
def on_key_press(self, symbol, modifiers):
self.keys_held.append(symbol)
if symbol == pyglet.window.key.RIGHT:
self.player = self.player.move()
print "The 'RIGHT' key was pressed"
def on_key_release(self, symbol, modifiers):
self.keys_held.pop(self.keys_held.index(symbol))
self.player = self.player.stand()
print "The 'RIGHT' key was released"
def update(self, interval):
if pyglet.window.key.RIGHT in self.keys_held:
self.player.x += 50 * interval
if __name__ == "__main__":
window = Game()
pyglet.app.run()
I looked at the pyglet's source code and i think the problem is here:
self.img = pyglet.image.load(...
The image is stored in self.image variable, not self.img. So changing to:
self.image = pyglet.image.load(...
should update the image that the sprite is using.
How do you make this code work? Just have pyglet installed and change "fireball.png" with the name of an image stored in the directory where you saved this code to a file.
import pyglet
class Fireball(pyglet.sprite.Sprite):
def __init__(self, batch):
pyglet.sprite.Sprite.__init__(self, pyglet.resource.image("fireball.png"))
# replace "fireball.png" with your own image stored in dir of fireball.py
self.x = 10 # Initial x coordinate of the fireball
self.y = 10 # Initial y coordinate of the fireball
class Game(pyglet.window.Window):
def __init__(self):
pyglet.window.Window.__init__(self, width = 315, height = 220)
self.batch_draw = pyglet.graphics.Batch()
self.fps_display = pyglet.clock.ClockDisplay()
self.fireball = []
def on_draw(self):
self.clear()
self.fps_display.draw()
self.batch_draw.draw()
if len(self.fireball) != 0: # Allow drawing of multiple
for i in range(len(self.fireball)): # fireballs on screen
self.fireball[i].draw() # at the same time
def on_key_press(self, symbol, modifiers):
if symbol == pyglet.window.key.A:
self.fireball.append(Fireball(batch = self.batch_draw))
pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)
print "The 'A' key was pressed"
def update(self, interval):
for i in range(len(self.fireball)):
self.fireball[i].x += 1 # why do fireballs get faster and faster?
if __name__ == "__main__":
window = Game()
pyglet.app.run()
This code creates a black background screen, where the fps are displayed and a fireball is shot along the x direction from the position (10, 10) whenever you press the A key.
You will notice that the more fireballs you shoot, the faster all fireballs will start to go.
Questions:
Why do the fireballs go faster and faster each time I press A ?
How should I stop the fireballs from accelerating each time I press A ?
The fireball goes faster and faster because every time you press the A you add another call of self.update to the scheduler. So self.update is called more and more times each time resulting in more updates of the position. To fix that move the line below to the __init__().
pyglet.clock.schedule_interval(func = self.update, interval = 1/60.)