跳转到主要内容

Python arcade的场景和事件管理器

项目描述

Build Status Codecov PyPI version

A simple animation maker

一个使用Arcade和Arcade-Curtains编写的简单动画制作应用程序,包含170行代码(包括启用UI按钮元素的代码约为300行)。查看代码!

Showcasing curtains

一个小游戏,展示了Arcade-Curtains的所有功能。这是一个受宝可梦/最终幻想启发的战斗场景,用500行代码编写,仅使用原始形状(这些形状的组合构成了这500行中的很大一部分)。查看代码!

简介

Arcade-curtains是Arcade的一个基本场景和事件管理器。主要目标是提供一个编写事件驱动游戏的方法,而不是在代码中填充if和else。这是通过编写事件处理程序来实现的。

Showcasing scenes

展示场景的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 窗口时,它将立即绑定到窗口事件方法(例如 updateon_drawon_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 以具有场景,我们可以开始添加事件。

如上所述,有两种类型的事件。不幸的是,这些类型在这个阶段仍然有些模糊,并且以相同的方式处理,从代码角度来看。唯一的区别是,对于精灵事件,在注册处理器时,您需要将精灵提供给管理器。

精灵事件通常发生在与特定精灵交互时。让我们创建一个场景,添加一个演员,并添加一个 hoveroutclick 事件。

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)

回调

精灵方法animateSequence.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及其所有子类都配备了toplefttoprightbottomleftbottomright坐标。它的工作方式与位置相同,意味着它返回x和y坐标。

一个小声明,为了与位置一致,这些属性的返回值是(x, y),这与命名相反,但“lefttop”听起来并不顺口。

ObservableSprite

ObservableSpritearcade.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)

Showcasing Anchor

触发器

ObservableSprite允许您定义一个触发器,在满足特定条件时运行。例如,您希望当生命值等于或低于0时运行处理程序。

from arcade_curtains import TriggerAttr
# Build a triggerable attribute definition
health = TriggerAttr('health')
sprite.trigger(health < 0, sprite.die)

小部件

WidgetSprite分组到小部件的基本类。它允许多个精灵协同工作,同时仍然可以精细控制每个精灵。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)

此示例显示所有球体都锚定在定义在最大球体位置的锚点上。当移动大球体时,较小的环绕球体也会移动。

Showcasing Anchor

小部件

您可以将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 (13.8 MB 查看哈希值)

上传时间

构建分布

arcade_curtains-0.4.1-py2.py3-none-any.whl (17.6 kB 查看哈希值)

上传时间 Python 2 Python 3

由以下组织支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误记录 StatusPage StatusPage 状态页面