【问题标题】:How to make an image move through a pyglet window?如何使图像通过 pyglet 窗口移动?
【发布时间】:2020-09-02 08:20:32
【问题描述】:

我正在尝试使用 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是精灵。因此,心脏会弹跳每个单词的第一个字母,然后弹跳精灵。

【问题讨论】:

  • Idealy I would like it to bounce around from left to right – 你能表现出预期的行为吗?
  • @Alderven 我已经在问题中添加了一个解释。
  • 看起来像您使用的图像,是心脏的单个静态图像,对吗?
  • 那是因为动画旨在为一个图像中的一系列图像设置动画。以下是一些示例:google.com/search?q=png+sprite+animation
  • 我会像您在下面那样解决它,但可能是更简单和更短的版本。如果您想要我可以写一个答案,尽管如果您对当前(自己的)答案感到满意,这将需要大约 10 分钟,这也很好,因为解决方案将是相似的 - 只是更面向对象: )

标签: python animation pyglet


【解决方案1】:

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

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

我还冒昧地通过将 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纵向移动。还有更多优化要做,例如,更新应该根据上次渲染进行缩放。这样做是为了避免在您的显卡跟不上时出现故障。它很快就会变得相当复杂,所以我会给你一个example,告诉你如何计算这些运动。

此外,您可能想要渲染一个批次,而不是直接渲染精灵。对于大型项目,这将大大加快渲染过程。

如果您不熟悉按位运算,简短的描述是它在bit/binary 级别上进行操作(以4 == 0100 为例),并对UP、@ 的值进行XOR 操作987654336@、LEFTRIGHT。我们可以通过合并01000001 来添加/删除方向,从而得到0101 作为示例。然后我们可以执行二进制AND(不像传统的and 运算符)来确定一个值是否在第三个位置(0100)上包含1,方法是执行self.direction &amp; 0100,这将导致1 如果这是True。如果您愿意,这是检查“状态”的一种方便快捷的方法。

【讨论】:

  • 谢谢,这真的很好。我喜欢使用按位运算来控制运动。
【解决方案2】:

我的解决方案使用两个固定 Sprite 之间的中点来确定移动 Sprite 应该向上还是向下。为此,我将所有字母制作为单独的 Sprite,每个字母对应一个 png。

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

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

【讨论】:

    猜你喜欢
    • 2011-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 2018-09-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多