首页 > 解决方案 > 如何使图像通过 pyglet 窗口移动?

问题描述

我正在尝试使用 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()

此代码产生:

在此处输入图像描述

怎样才能让整颗心穿过窗子?

理想的心脏轨迹是这样的:

在此处输入图像描述

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

标签: pythonanimationpyglet

解决方案


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

但是要在画布上移动对象,您需要以某种方式手动操作顶点或图像位置。您自己的解决方案的工作原理是检查是否X大于或小于minmax限制。我只想在此基础上添加一些技巧,以更轻松、更快速地处理动作和方向。下面我使用按位运算来确定运动方向,这使得心脏围绕宽度和高度的父(窗口)约束反弹。

我还冒昧地通过将 pygletWindow类继承到一个对象/类中来使整个项目更加面向对象,并使其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操作。作为示例,我们可以通过合并和生成来添加/删除方向。然后我们可以做二进制(不像传统的运算符)来确定一个值是否在第三个位置()上包含 a ,这将导致if it's 。如果您愿意,这是检查“状态”的一种方便快捷的方法。UPDOWNLEFTRIGHT010000010101ANDand10100self.direction & 01001True


推荐阅读