如何使图像在pyglet窗口中移动?

问题描述 投票:0回答:2

我想用pyglet制作一个动画。所以首先我尝试了一个简单的动画,以直线移动图像。理想情况下,我想让它从左到右跳动。

这是我的代码。

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

这段代码产生了这样的效果

enter image description here

我怎样才能让整个心脏在窗口中移动?

所需的心形轨迹应该是这样的。

enter image description here

盒子是框架 拱门是轨迹 O是精灵。所以心会在每个单词的第一个字母上跳动,然后在精灵上跳动。

python animation pyglet
2个回答
1
投票

所以主要的问题是 Animation 假设在一个大图像内有一系列的图像。这就是所谓的精灵动画,它本质上只是你想要的动作的系列条带(通常是一排或一个网格模式)。它对行走、攻击和其他类似的游戏机制的动画很有用。

但要在画布上移动一个对象,你需要以某种方式手动操作顶点或图像位置。你自己的解决方案的工作原理是检查是否有 X 大于或小于 minmax 的限制。而我只是想在此基础上补充展示一些技巧,让大家更方便快捷的进行动作和方向的操作。下面我已经用 位元运算 来控制运动方向,这使得心脏围绕着父窗口(窗口)的宽度和高度限制而跳动。

此外,我还通过继承 pyglet Window 类变成一个对象类,同时也使 heart 它自己的类,以便更容易区分什么时候和在什么对象上被调用。

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

基本原理是一样的,操作 sprite.x 横向移动它,并且 sprite.y 纵向的。还有更多的优化工作要做,例如,更新应该根据最后一次渲染来缩放。这样做是为了避免你的显卡跟不上时出现故障。这可能会变得相当复杂相当快,所以我给你留下一个 例子 如何计算这些动作。

更进一步说,你可能想要批量渲染,而不是直接渲染精灵。这对于大型项目来说,会加快不少渲染进程。

如果你对位元运算不熟悉,简单的描述就是它的运算方式是在一个 二进制 级别(4 == 0100 为例),并做 XOR 的值进行操作。UP, DOWN, LEFTRIGHT. 我们可以通过合并来添加删除方向。01000001 导致 0101 为例。然后我们可以做二进制的 AND 不像传统 and 操作符)来确定一个值是否包含一个 1 在第三个位置上(0100),具体做法是 self.direction & 0100 这将导致 1 如果是 True. 这是一种方便快捷的检查 "状态 "的方法,如果你愿意的话。


0
投票

我的解决方案使用两个固定的Sprite之间的中点来决定移动的Sprite应该向上还是向下。为此,我把所有的字母都做成了独立的Sprite,每个字母一个png。

希望这张图片能更好地解释下面的代码。enter image description here

#!/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()

© www.soinside.com 2019 - 2024. All rights reserved.