Python arcade的场景和事件管理器
项目描述
一个使用Arcade和Arcade-Curtains编写的简单动画制作应用程序,包含170行代码(包括启用UI按钮元素的代码约为300行)。查看代码!
一个小游戏,展示了Arcade-Curtains的所有功能。这是一个受宝可梦/最终幻想启发的战斗场景,用500行代码编写,仅使用原始形状(这些形状的组合构成了这500行中的很大一部分)。查看代码!
简介
Arcade-curtains是Arcade的一个基本场景和事件管理器。主要目标是提供一个编写事件驱动游戏的方法,而不是在代码中填充if和else。这是通过编写事件处理程序来实现的。
展示场景的gif。 查看代码!
目录
事件
有两种类型的事件。
精灵鼠标事件
up
down
click
hover
out
drag
全局事件
frame
before_draw
after_draw
关键字
您可以为每个精灵设置事件处理器。这意味着每个精灵/事件组合可以拥有独特的事件处理器。
场景
场景是将事件传递到特定上下文的一种方式。您可以在一个场景中定义精灵和事件,当您进入另一个场景时,它们将变为非活动状态,对于该场景您可以定义一组全新的精灵和事件。这还允许您在进入或离开场景时编写一些设置或拆除代码。
当从一个场景切换到另一个场景时,上一个场景的上下文和状态仍然保留。这意味着您可以轻松地在场景之间切换,并继续之前停止的地方。一个简单的例子就是在关卡中间访问菜单或库存。
动画
这个库提供了一种以“发射后不管”的方式对精灵进行动画的方法。您提供起始状态、结束状态,如果需要还可以提供中间状态。然后库将负责在给定时间内将精灵在这两种状态之间进行动画处理。一个高级示例,该库支持哪些类型的动画,可以在资源文件夹中找到。
缺点
尽管这种方式编写游戏允许更模块化的方法,从而最终导致代码更易于阅读,但在调试时容易丢失执行流程。因此,建议编写处理器直接处理您想要实现的内容,而不是根据游戏状态分散到多个不同的代码路径。
此外,这个库还没有经过基准测试,所以不知道事件处理器在什么点会饱和。
入门
将Curtains绑定到Arcade
由于库本身相当基础,所以入门相对简单。您首先必须创建一个 Curtains 类的实例,并将其绑定到您的 Arcade 窗口。
import arcade
from arcade_curtains import Curtains
class Window(arcade.Window):
def __init__(self):
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
self.curtains = Curtains(self)
或者
import arcade
from arcade_curtains import Curtains
class Window(arcade.Window):
def __init__(self):
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
curtains = Curtains()
window = Window()
curtains.bind(window)
当您将 Curtains 实例绑定到 Arcade 窗口时,它将立即绑定到窗口事件方法(例如 update
、on_draw
、on_mouse_motion
)。从那时起,它将事件传递到当前选中场景的事件管理器。
您仍然可以像平常一样覆盖这些 Arcade 窗口方法,但这样做并不推荐。如果您这样做,请记住,这些函数中的代码将首先执行,然后才是 Curtains 处理器。
创建场景
场景是这个库的基础,使用 curtains 时,每个游戏至少需要一个。
一旦您定义了 Curtains 实例,您就可以向其中添加一个场景。但为了能够这样做,您将不得不扩展由 arcade-curtains
提供的 BaseScene
类,并重载设置方法。在设置方法中,您可以运行所有创建精灵、精灵列表和连接您的处理器的代码。
所有扩展 BaseScene
的内容将自动检测 SpriteList
实例,并在每一帧自动绘制。
import arcade
from arcade_curtains import Curtains, BaseScene
class MyOpeningScene(BaseScene):
def setup(self):
# Actors will automatically be picked up and drawn on each frame
self.actors = arcade.SpriteList()
self.actor = arcade.Sprite()
class Window(arcade.Window):
def __init__(self):
super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
self.curtains = Curtains(self)
self.curtains.add_scene('opening_scene', MyOpeningScene())
self.curtains.set_scene('opening_scene')
精灵事件
现在我们已经初始化 curtains 以具有场景,我们可以开始添加事件。
如上所述,有两种类型的事件。不幸的是,这些类型在这个阶段仍然有些模糊,并且以相同的方式处理,从代码角度来看。唯一的区别是,对于精灵事件,在注册处理器时,您需要将精灵提供给管理器。
精灵事件通常发生在与特定精灵交互时。让我们创建一个场景,添加一个演员,并添加一个 hover
、out
和 click
事件。
import arcade
from arcade_curtains import BaseScene
from .my_custom_sprites import CustomSprite
def paint_border(sprite, x, y):
sprite.set_border_texture()
def unpaint_border(sprite, x, y):
sprite.unset_border_texture()
def kill_actor(sprite, x, y):
sprite.play_death_animation(callback=sprite.kill)
class MyOpeningScene(BaseScene):
def setup(self):
self.actors = arcade.SpriteList()
self.actor = CustomSprite()
# add a hover event to this scene that paints a border whenever the mouse hovers over the sprite
self.events.hover(self.actor, paint_border)
# add an out event that reverts back to the original texture
self.events.out(self.actor, unpaint_border)
# and one that kills the actor when clicked
self.events.click(self.actor, kill_actor)
您可以通过以下方式提供要传递给回调函数的默认参数:
events.click(self.actor, kill_actor, {'method': 'very slowly'})
全局事件
某些事件无法与精灵链接,但您仍然希望为其定义一些处理器。例如,frame
事件,它在每一帧都会触发。您可以将其视为精灵事件,但这样做没有意义,因为它不会由于精灵交互而触发。相反,您可以将一个处理函数附加到 frame
事件,该处理函数与所需的精灵交互。
import arcade
from arcade_curtains import BaseScene
class CustomSprite(arcade.Sprite):
def spin(self, delta_time):
self.angle += delta_time
self.angle %= 360
class MyOpeningScene(BaseScene):
def setup(self):
self.actors = arcade.SpriteList()
self.actor = CustomSprite()
self.events.frame(sprite.spin)
或者,您可以添加一个不与精灵以任何方式交互的处理程序。
import random
import arcade
from arcade_curtains import BaseScene
COLORS = [getattr(arcade.color, color) for color in dir(arcade.color)]
def trip_balls(delta):
arcade.set_background_color(random.choice(COLORS))
class MyOpeningScene(BaseScene):
def setup(self):
self.events.frame(trip_balls)
添加键盘事件同样简单,并使用Arcade键映射来定义处理程序
import sys
import arcade
from arcade_curtains import BaseScene
def exit(key):
sys.exit(0)
class MyOpeningScene(BaseScene):
def setup(self):
self.events.key(arcade.key.ESCAPE, exit)
动画
Arcade-Curtains中的动画应该很容易实现。因此,arcade Sprite
类新增了一个名为animate()
的方法。不用担心,如果你从arcade.Sprite
派生子类,这个方法不会丢失。
使用animate
方法,你可以在精灵的起始和结束状态之间制作动画。如果你想有更多的控制,以及中间状态,库提供了额外的对象来构建更复杂的动画
Sprite.animate()
开始的最简单、最快的方式是任何精灵实例上新暴露的animate
方法。
import arcade
from arcade_curtains import BaseScene
class MyOpeningScene(BaseScene):
def setup(self):
self.actors = arcade.SpriteList()
self.actor = arcade.Sprite()
self.actors.append(self.actor)
def enter_scene(self, previous_scene):
self.actor.animate(
duration=1, # duration of the animation in seconds
position=(100, 100), # will move the sprite from its current position to (100, 100) in 1 second
)
当调用此方法时,它将使用当前活动场景的AnimationManager
。如果需要,可以显式提供管理器。
import arcade
from arcade_curtains import BaseScene
class MyOpeningScene(BaseScene):
def leave_scene(self, next_scene):
next_scene.actor.animate(
duration=1, # duration of the animation in seconds
position=(100, 100),
manager=next_scene.animations
)
关键帧
如果你想对自己的动画有更多的控制,你可以使用KeyFrame
来定义动画期间想要访问的状态,并将它们封装在Sequence
中。
KeyFrames
允许你在特定时间点定义精灵想要的状态。KeyFrame
类允许你设置任何精灵设置的属性来设置其状态。
from arcade_curtains import KeyFrame
frame = KeyFrame(
center_x=10,
center_y=10,
position=(10, 10), # position will be considered if both center_x/center_y and position are set
angle=50,
scale=1,
width=100,
height=100, # width/height will be considered if both widht/height and scale are set
alpha=255,
# left = 100,
# right = 100,
# top = 100,
# bottom = 100,
)
序列
Sequence
用于将你定义的KeyFrame
粘合在一起。因为你可能想要在动画的不同时间设置精灵的相同KeyFrame
状态,所以Sequence
类是我们定义想要在何时达到状态的类。
from arcade_curtains import KeyFrame, Sequence
frame1 = KeyFrame(position=(10, 10))
frame2 = KeyFrame(position=(100, 100))
seq = Sequence()
seq.add_keyframe(0, frame1) # We want the sprite to reach the state of frame 1 after 0 seconds
seq.add_keyframe(1, frame2) # We want the sprite to reach the state of frame 2 after 1 second
seq.add_keyframe(2, frame1) # We want the sprite to reach the state of frame 1 again after 2 seconds
# The animation duration of this sequence is 2 seconds.
一旦你有了Sequence
,你就可以通过使用场景的动画管理器来明确地触发它,或者将其传递给精灵的animate
方法。
import arcade
from arcade_curtains import BaseScene, KeyFrame, Sequence
class MyOpeningScene(BaseScene):
def setup(self):
self.actors = arcade.SpriteList()
self.actor = arcade.Sprite()
self.actors.append(self.actor)
def enter_scene(self, previous_scene):
seq = Sequence()
seq.add_keyframes(
(0, KeyFrame(angle=0)),
(1, KeyFrame(angle=180))
)
self.actor.animate(seq)
# Alternatively use self.animations.fire(self.actor, seq)
或者,你可以以下方式与序列交互
import arcade
from arcade_curtains import BaseScene, KeyFrame, Sequence
class MyOpeningScene(BaseScene):
def setup(self):
self.actors = arcade.SpriteList()
self.actor = arcade.Sprite()
self.actors.append(self.actor)
def enter_scene(self, previous_scene):
seq = Sequence()
seq[0].frame = KeyFrame(angle=0)
seq[1].frame = KeyFrame(angle=180)
seq[.5].callback = self.actor.do_something
self.actor.animate(seq)
循环序列
你可以选择使你的序列无限循环。一旦它达到了最后一个关键帧,它将重新开始其动画的第一个关键帧。
from arcade_curtains import Sequence
seq = Sequence(loop=True)
反转序列
你可以反转一个序列,使最后一个关键帧首先动画化。
from arcade_curtains import Sequence
seq = Sequence(is_reversed=True)
回调
精灵方法animate
和Sequence.add_keyframe
都允许你在达到某个关键帧时执行回调。当使用sprite.animate
定义回调时,回调默认为最后一个KeyFrame
。
from arcade_curtains import KeyFrame, Sequence
from my_game.triggers import trigger_end_animation_handler
seq = Sequence()
seq.add_keyframe(0, KeyFrame(position=(10, 10)))
seq.add_keyframe(1, KeyFrame(position=(100, 100)), callback=trigger_end_animation_handler)
你还可以独立于关键帧定义回调,以在动画中的某个时间点执行。
from arcade_curtains import KeyFrame, Sequence
from my_game.triggers import set_sprite_attack_intent_animation
seq = Sequence()
seq.add_keyframe(0, KeyFrame(position=(10, 10)))
seq.add_keyframe(1, KeyFrame(position=(100, 100)))
seq.add_callback(.5, callback=set_sprite_attack_intent_animation)
链式动画
有时你可能只想在另一个动画完成后才开始动画。那么,朋友,我有个好消息要告诉你!
当然,你可以通过链式调用回调来实现这一点,但这个库提供了一个更方便的方法,并且可以循环,并且有自己的“链结束”回调。
from arcade_curtains import KeyFrame, Sequence
from my_game.triggers import set_sprite_attack_intent_animation
from my_game.scenes import start_scene
seq1 = Sequence()
seq1.add_keyframes(
(0, KeyFrame(position=(10, 10))),
(1, KeyFrame(position=(100, 100)))
)
seq2 = Sequence()
seq2.add_keyframes(
(0, KeyFrame(position=(100, 100))),
(1, KeyFrame(position=(10, 10))
)
chain = Chain(loop=True)
chain.add_sequences(
(my_first_sprite, sequence1),
(my_second_sprite, sequence2)
)
start_scene.animations.fire(None, chain)
动画实用函数
从精灵创建关键帧
从精灵的当前状态创建关键帧
from arcade_curtains import KeyFrame
frame = KeyFrame.from_sprite(my_sprite)
frame = KeyFrame.from_sprite(my_sprite, only_keys=['angle', 'scale'])
从精灵创建序列
使用精灵的当前状态在0时间点创建一个包含一个关键帧的序列
from arcade_curtains import Sequence
seq = Sequence.from_sprite(my_sprite)
辅助工具
此模块提供了一些有趣的功能,可以帮助你更快、更顺利地编写游戏。
通用工具
delay_set_attribute
这是一个小型辅助函数,允许你将对象上的属性设置为回调。
# Will set the health attribute to 10 when clicked
events.click(sprite, delay_set_attribute(sprite, 'health', 10))
位置辅助
arcade.Sprite
及其所有子类都配备了topleft
、topright
、bottomleft
、bottomright
坐标。它的工作方式与位置相同,意味着它返回x和y坐标。
一个小声明,为了与位置一致,这些属性的返回值是(x, y),这与命名相反,但“lefttop”听起来并不顺口。
ObservableSprite
ObservableSprite
是arcade.Sprite
的一个子类,它允许你将处理程序附加到属性修改。
before_change/after_change事件
您可以定义一个回调,并在目标属性更改时调用它。
sprite.before_change('health', controller.validate_health_change)
sprite.after_change('health', controller.notify_health_change)
在下面的示例中,下方的圆圈在每个帧中都会移动,并定义了一个after_change
处理程序,如下所示
mod = 1
def keep_pace(sprite, attribute, old, new):
setattr(sprite2, attribute, new)
sprite1.after_change('center_x', keep_pace)
触发器
ObservableSprite
允许您定义一个触发器,在满足特定条件时运行。例如,您希望当生命值等于或低于0时运行处理程序。
from arcade_curtains import TriggerAttr
# Build a triggerable attribute definition
health = TriggerAttr('health')
sprite.trigger(health < 0, sprite.die)
小部件
Widget
是Sprite
分组到小部件的基本类。它允许多个精灵协同工作,同时仍然可以精细控制每个精灵。Widget
使用AnchorPoint
作为组来工作。
锚点
AnchorPoint
是一个简单的x, y
坐标对象,但您可以将精灵附加到该锚点。每次移动锚点时,所有附加的精灵都会随之移动。
sprite1.position = (100, 100)
sprite2.position = (200, 200)
anchor = AnchorPoint.from_sprite(sprite1, 'position')
anchor.dock(sprite2)
# Will move sprite1 and sprite2 relative to the anchor
anchor.position = (300, 300)
assert sprite1.position == (300, 300)
assert sprite2.position == (400, 400)
此示例显示所有球体都锚定在定义在最大球体位置的锚点上。当移动大球体时,较小的环绕球体也会移动。
小部件
您可以将Widget
视为一个“视图”,在其中可以定义与(0, 0)坐标相对的精灵。Widget
的起始坐标将从该小部件中定义的精灵中推断出来。初始化后,您可以操作这些坐标,将小部件移动到所需的位置。
基本类配备了self.sprites
,您可以在此处添加小部件中定义的精灵。初始化您的Widget
后,您需要注册一个精灵列表,以便在屏幕上绘制精灵。
class MyWidget(Widget):
def setup_widget(self):
sprite1 = arcade.Sprite(TEXTURE1)
sprite1.bottomleft = (0, 0)
sprite2 = arcade.Sprite(TEXTURE2)
sprite2.bottomleft = sprite1.topleft
self.sprites.extend([sprite1, sprite2])
spritelist = arcade.SpriteList()
widget = MyWidget()
widget.position = (SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2)
# register the sprites to an arcade spritelist
widget.register(spritelist)
项目详情
arcade-curtains-0.4.1.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | eea3bc3e9748ad4f3fa0b26fa8bc1dba7faab54fbfe0b5f703e53db6a972c748 |
|
MD5 | 2b5ed586281cd67c753f744217233815 |
|
BLAKE2b-256 | d1bf106aceb4f049dbc5513ec48e060d1547fe2a4f351847b49f42d3abacbfa2 |
arcade_curtains-0.4.1-py2.py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | c0b9e399f118151e0ec0e3bf3325b3a933bdf38504e48db5f0d6c049a65d7a44 |
|
MD5 | 5d7c693b4ad66f4b90a413f98c16eac4 |
|
BLAKE2b-256 | 2da13f2dfa92d0df61d09e62b051e69c4dfa3f607d7c14aed2002dfb04642898 |