有什么方法可以将游戏逻辑与动画和绘制循环分开?


9

我以前只制作过Flash游戏,使用MovieClips等将动画与游戏逻辑分开。现在,我开始尝试制作适用于Android的游戏,但是围绕这些问题的游戏编程理论仍然让我感到困惑。我来自开发非游戏Web应用程序的背景,因此我精通于更多的MVC之类的模式,并且在我从事游戏编程时一直牢牢记住这种思维方式。

我想做一些类似抽象游戏的事情,例如,通过一个游戏板类,其中包含一个图块网格的数据,每个图块类的实例都包含属性。我可以赋予我的绘制循环访问权,并让它根据游戏板上每个图块的属性来绘制游戏板,但是我不知道确切的动画应该去哪里。据我所知,动画位于抽象的游戏逻辑(模型)和绘制循环(视图)之间。以我的MVC思维方式,尝试确定动画实际上应该去哪里令人沮丧。它会像模型一样具有大量与之关联的数据,但似乎需要与绘制循环紧密结合,以具有诸如帧独立动画之类的功能。

如何摆脱这种思维模式,开始思考对游戏更有意义的模式?

Answers:


6

动画仍然可以完美地划分为逻辑和渲染。动画的抽象数据状态将是图形API渲染动画所必需的信息。

例如,在2D游戏中,它可能是一个矩形区域,该区域标记了显示需要绘制的Sprite工作表的当前部分的区域(当您的工作表由30张80x80的图形组成,其中包含角色的各个步骤跳跃,坐下,移动等)。它也可以是您不需要渲染的任何类型的数据,但可能是用于管理动画本身的数据,例如直到当前动画步骤到期为止的时间或动画的名称(“行走”,“站立”等等。所有这些都可以用您想要的任何方式表示。这就是逻辑部分。

在渲染部分,您只需照常进行操作,从模型中获取该矩形,然后使用渲染器实际执行对图形API的调用。

在代码中(此处使用C ++语法):

class Sprite //Model
{
    private:
       Rectangle subrect;
       Vector2f position;
       //etc.

    public:
       Rectangle GetSubrect() 
       {
           return subrect;
       }
       //etc.
};

class AnimatedSprite : public Sprite, public Updatable //arbitrary interface for classes that need to change their state on a regular basis
{
    AnimationController animation_controller;
    //etc.
    public:
        void Update()
        {
            animation_controller.Update(); //Good OOP design ;) It will take control of changing animations in time etc. for you
            this.SetSubrect(animation_controller.GetCurrentAnimation().GetRect());
        }
        //etc.
};

那就是数据。渲染器将​​获取该数据并进行绘制。由于正常的精灵和动画的精灵都是以相同的方式绘制的,因此您可以在此处使用多态!

class Renderer
{
    //etc.
    public:
       void Draw(const Sprite &spr)
       {
           graphics_api_pointer->Draw(spr.GetAllTheDataThatINeed());
       }
};

TMV:

我想出了另一个例子。假设您有一个RPG。例如,代表世界地图的模型可能需要将角色在世界中的位置存储为地图上的图块坐标。但是,当您移动角色时,它们一次走几像素到下一个正方形。您是否将此“拼贴之间”位置存储在动画对象中?当角色最终“到达”地图上的下一个平铺坐标时,如何更新模型?

世界地图不直接知道玩家的位置(它没有Vector2f或类似直接存储玩家position =的东西,而是直接引用了玩家对象本身,而后者又是从AnimatedSprite派生的因此您可以轻松地将其传递给渲染器,并从中获取所有必要的数据。

不过,总的来说,您的tilemap不能仅做所有事情-我会有一个“ TileMap”类来管理所有的tile,也许还可以在我移交给它的对象之间进行碰撞检测,地图上的瓷砖。然后,我将有另一个“ RPGMap”类,或者您想调用它,它同时具有对tilemap和播放器的引用,并对播放器和您的播放器进行实际的Update()调用。平铺地图。

当玩家移动时,您想如何更新模型取决于您要做什么。

是否允许您的播放器在瓷砖之间独立移动(塞尔达风格)?只需处理输入,并在每一帧中相应地移动播放器。还是您想让玩家按下“向右”,并且您的角色自动向右移动一个图块?让您的RPGMap类插值玩家的位置,直到他到达目的地,同时锁定所有的移动键输入处理。

无论哪种方式,如果您想使自己更轻松,则所有模型如果实际上需要一些逻辑来更新自身(而不只是更改变量的值),都将具有Update()方法-您不会放弃控制器以这种方式在MVC模式中,您只需将代码从“上一步”(控制器)向下移动到模型,并且所有控制器要做的就是调用模型的Update()方法(在本例中,控制器将是RPGMap)。您仍然可以轻松地交换逻辑代码-您可以直接更改类的代码,或者如果您需要完全不同的行为,则可以从模型类派生并仅重写Update()方法。

这种方法减少了方法调用和类似的事情-曾经是纯MVC模式的主要缺点之一(您最终非常频繁地调用GetThis()GetThat())-它使代码既长又冗长。一点点都更难阅读,也更慢-即使您的编译器可能会优化许多类似的东西来解决。


您是将动画数据保存在包含游戏逻辑的类中,还是包含游戏循环的类中,还是两者分开?而且,完全取决于循环或包含循环的类来了解如何将动画数据转换为实际绘制屏幕的方式,对吗?它通常不会像获得一个代表小精灵图纸的一部分的rect并使用它来裁剪小精灵图纸中的位图绘制那样简单。
TMV

我想出了另一个例子。假设您有一个RPG。例如,代表世界地图的模型可能需要将角色在世界中的位置存储为地图上的图块坐标。但是,当您移动角色时,它们一次要走几个像素才能到达下一个正方形。您是否将此“拼贴之间”位置存储在动画对象中?当角色最终“到达”地图上的下一个平铺坐标时,如何更新模型?
TMV

我对您的问题的答案进行了编辑,因为注释不允许有足够的字符。
TravisG 2011年

如果我正确理解所有内容:
TMV

您可以在View中拥有一个“ Animator”类的实例,并且它具有一个公共的“ update”方法,该方法在视图中被每一帧调用。update方法调用其中的各种单独动画对象的实例的“ update”方法。动画制作器及其内部的动画具有对模型的引用(通过其构造函数向下传递),因此,如果动画会更改模型数据,则它们可以更新模型数据。然后,在绘制循环中,您可以通过“视图”可以理解并绘制的方式从动画师内部的动画中获取数据。
TMV

2

如果您愿意,我可以对此进行开发,但是我有一个中央渲染器被告知在循环中绘制。而不是

handle input

for every entity:
    update entity

for every entity:
    draw entity

我的系统更像

handle input (well, update the state. Mine is event driven so this is null)

for every entity:
    update entity //still got game logic here

renderer.draw();

renderer类仅包含对对象的可绘制组件的引用列表。为简单起见,这些在构造函数中分配。

对于您的示例,我将拥有一个带有多个Tiles的GameBoard类。每个图块显然都知道其位置,并且我假设某种动画。将其分解为图块拥有的某种Animation类,并将其自身的引用传递给Renderer类。在那里,所有人都分开了。当您更新Tile时,它将在动画上调用Update ..或对其本身进行更新。当Renderer.Draw()被调用时,它绘制动画。

独立于帧的动画与绘制循环的关系不大。


0

我最近一直在自己学习范例,因此,如果这个答案不完整,我相信有人会加入。

对于游戏设计来说,最有意义的方法是将逻辑与屏幕输出分开。

在大多数情况下,您可能希望使用多线程方法,如果您不熟悉该主题,那么它本身就是一个问题,这是Wiki的入门手册。本质上,您希望您的游戏逻辑在一个线程中执行,锁定需要访问的变量以确保数据完整性。如果您的逻辑循环非常快(超级大型动画3d乒乓球?),您可以尝试通过使线程休眠一小段时间来固定循环执行的频率(此论坛建议将120 hz用于游戏物理循环)。同时,另一个线程正在使用更新的变量重画屏幕(在其他主题中建议使用60 hz),并再次要求在访问变量之前对其进行锁定。

在那种情况下,动画或过渡等会进入绘图线程,但您必须通过某种标志(也许是全局状态变量)发出信号,游戏逻辑线程什么也不能做(或做其他事情……设置)新的地图参数)。

一旦掌握了并发性,剩下的就可以理解了。如果您没有并发经验,我强烈建议您编写一些简单的测试程序,以便您了解流程的发生方式。

希望这可以帮助 :)

[edit]在不支持多线程的系统上,动画仍然可以进入绘制循环,但是您想要以某种方式设置状态,以向逻辑发出信号,表明发生了一些不同的事情,并且不继续处理当前级别/地图/等...


1
我不同意这里。在大多数情况下,您不希望使用多线程,尤其是在小型游戏中。
共产党鸭子

@TheCommunistDuck足够公平,多线程的开销和复杂性肯定会使其过大,如果游戏很小,它应该能够快速更新。
斯蒂芬
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.