How to make an image move through a pyglet window?

大兔子大兔子 提交于 2020-06-17 13:25:07

问题


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.


回答1:


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.




回答2:


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()



来源:https://stackoverflow.com/questions/61837338/how-to-make-an-image-move-through-a-pyglet-window

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!