游戏编程模式 - 状态模式

栏目: 后端 · 发布时间: 5年前

内容简介:自然界中游戏世界中,每个物体也是如此。他们都有一系列状态,通过状态之间的切换,去描述本身的行为。所以,游戏中每个物体都可能都会存在大量的状态,在编程过程中,我们也需要控制物体不同状态间的切换。在某一款游戏中,存在这样一个角色: 它能够走、跑、跳跃和滑行四个状态。通常情况下我们控制这个角色的状态切换,会使用条件语句,如下:

自然界中 状态 属于物体本身。猫咪正在睡觉、吃、喝水,这些动作都属于猫咪自己,它通过自己的大脑调整改变自己每刻的状态。

游戏世界中,每个物体也是如此。他们都有一系列状态,通过状态之间的切换,去描述本身的行为。所以,游戏中每个物体都可能都会存在大量的状态,在编程过程中,我们也需要控制物体不同状态间的切换。

看看设计需求

在某一款游戏中,存在这样一个角色: 它能够走、跑、跳跃和滑行四个状态。通常情况下我们控制这个角色的状态切换,会使用条件语句,如下:

void SwitchState()
{
    if (Input.GetKeyDown(KeyCode.RightArrow))
    {
        _player.Walk();
    }
    else if (Input.GetKeyDown(KeyCode.LeftShift))
    {
        _player.Run();
    }
    else if (Input.GetKeyDown(KeyCode.Space))
    {
        _player.Jump();
    }
    else if (Input.GetKeyDown(KeyCode.RightShift))
    {
        _player.Slide();
    }
}

上面的代码某些情况下会出现一些问题:

  • 在上面的代码中,我们将角色的行为和输入控制器绑定在了一起,角色的状态改变全听从用户的输入,但有些时候角色也需要自己改变自己的状态,而不需要外部干预。比如:角色执行完 “滑倒” 动作越过障碍之后,需要自己立马切换回 ‘Run’ 的状态。

因此一个好的 “角色” 设计应该是它的状态既可以对外被更新,也可以对内被更新,控制权由设计者决定。

  • 每次输入对应着一个状态的切换,往往一个角色执行一个状态时会需要一定时间,比如说 滑倒 动作,角色可能需要播放一个滑倒的动画。但此时若紧接着按下 Space 按键,那么角色就处于 “滑倒式跳跃” 的动画中,是不是很怪异。有问题就有解决方案,加入一个控制变量控制某个状态执行的时候不能执行其他状态,如下面的代码:
private bool _isSliding = false;
void SwitchState()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        if (!_isSliding)
        {
            _player.Jump();
        }
    }
    else if (Input.GetKeyDown(KeyCode.RightShift))
    {
        _isSliding = true;
        _player.Slide();
    }
}

通过加入一个控制变量,问题确实得到了解决。但是,随着状态的增多,你的控制变量也能会随之增多。条件语句嵌套会更加 “彻底”。这样不好,代码不易维护扩展也不够优雅。

也许状态模式就是为这些扰人的需求情况而诞生的吧!

定义

《游戏编程模式》 一书中这样定义状态模式:

允许对象在当内部状态改变时改变其行为,就好像此对象改变了自己的类一样。状态模式属于一种行为模式。

实现

  • 引入抽象类或接口,声明状态类基本该有的功能
public interface State
{
    void Handle();
}
  • 实现具体状态类,继承或实现状态抽象类和接口,实现当前具体状态类需要实现的行为
public class JumpState: State
{
    protected Player _player;

    public JumpState(Player player)
    {
        _player = player;
    }

    public void Handle()
    {
        // Execute state
        _player.Jump();

        // Change state
        TimerInvoke("JumpFinished", 2000);

        // Do nothing for input
    }

    private void JumpFinished(object obj, ElapsedEventArgs args)
    {
        _player.State = new IdleState(_player);
        Debug.Log("Jump Done! Switch to Run state...");
    }
}

JumpState 类的 Handle 方法中,实现了 “跳跃” 状态所需做的事情(也许是一个动画,或者是播放一段音效)。动作完成之后,由 “跳跃” 态进入到 “Idle” 态。状态的切换隐匿在了这个状态类内部,改变的是 _player 所拥有的 State 变量;当切换成功后又会开始执行下一个状态所处的行为(另一个实现了 State 接口的具体状态类的 Handle 方法被执行)。

不仅仅是自身主动切换状态,我们还可以在 Handle 方法内部根据玩家的输入,来控制状态切换:

public void Handle()
{
    _player.Idle();
    if (Input.GetKey(KeyCode.RightArrow))
    {
        _player.State = new WalkState(_player);
        _player.SwitchState();
    }
    else if (Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.RightArrow))
    {
        _player.State = new RunState(_player);
        _player.SwitchState();
    }
    else if (Input.GetKeyDown(KeyCode.Space))
    {
        _player.State = new JumpState(_player);
        _player.SwitchState();
    }
}

上面是 IdleState 类( 源码 )的 Handle 方法。可以看到,我们将玩家的输入也添加了进来,从而玩家可以主动控制状态切换,也就是状态 “对外” 可被更新。

玩家的在具体状态类中对状态的更新,主要是通过 “角色” 类,毕竟这才是玩家看的到的。紧接着,就来看看角色类吧。

  • 状态的拥有者(角色)
public class Player: MonoBehaviour
{
    private State _state;

    public void Jump()
    {
        Debug.Log("Jump...!");
    }

    public void SwitchState()
    {
        _state.Handle();
    }

    private void Update()
    {
        SwitchState();
    }

    // ...
}

可以看出,角色直接面向玩家。角色拥有状态,并且掌控者玩家的输入 “大权”。 状态被改变,同时角色行为也就发生改变 ,角色持有的是 State 接口类变量,它不需要知道自己究竟需要切换到 “哪个状态”,但它知道自己需要切换 “状态”,同时它自己也定义了一些列状态的实现供状态类帮它执行。在不同状态类内部主动切换至其它状态(或是响应输入而切换),切换后的状态也可以切换去任意其它所定义的状态类。状态属于物体,就像我们的实现中,状态属于 Player 类,所有的状态切换都服务于它。

  • 带有控制变量的状态类
public class JumpState: State
{
    private static bool _sIsJumping = false;

    public void Handle()
    {
        if (_sIsJumping)
        {
            return;
        }

        _sIsJumping = true;

        // Execute state...
        // Change state...
    }

    private void JumpFinished(object obj, ElapsedEventArgs args)
    {
        _sIsJumping = false;

        // Do change state
    }
}

在原来 JumpState 中我们增加了 _sIsJumping 用来保证 “跳跃” 状态不被重复执行(处于 “跳跃” 状态不能再跳跃)。

多状态之间切换控制问题,最初我们为了控制某个状态 “不变形”,而加入了很多控制变量。后面随着状态增多而增多了控制变量,导致代码难以维护。现在引入了状态模式后,我们可以为每个具体状态类引入对应的控制变量,这样无论是对于以后的需求扩展还是代码得维护都会变得更加容易。而每个单独的状态类都能够限制此个状态下能否响应哪些输入、能切换哪些状态,这正是我们需要的!

同时,新增状态类也就意味着你的项目中的具体状态类可能无限增长(属于行为模式的 设计模式 好像都无法绕过这一点,因为设计初衷就是为此而生);在我们的实现中,所有的状态切换都是先 new 出来新状态类再切换,这有可能导致对象数量增多(对于不具有垃圾回收的语言,切记做好内存释放工作),不过也可以对相同的状态实现状态类实例共享来减少 GC。

看完上面关于状态模式的定义、实现介绍等,感觉似曾相识。多个状态间切换,物体始终处于某一态,等待着某些指令进入下一态,这不就是状态机吗? 是的,这就是一个简单的有限状态机。状态模式将状态切换内部封装,状态自己拥有自己的控制变量从而和外部解耦;由于所有的具体状态类实现了通用状态接口且对象持有接口类,对象不需要关注下一步我是哪个状态,只需要执行状态就行;对于新增状态只需要实现接口并做好对象状态切换工作即可。

示例源码


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript权威指南

JavaScript权威指南

弗拉纳根 / 东南大学出版社 / 2007-6 / 99.00元

《JavaScript权威指南(影印版)(第5版)》已经经过全面地修订和扩展,涵盖了构建当今Web2.0应用程序所需的JavaScript技术。《JavaScript权威指南(影印版)(第5版)》不仅是一本实例驱动的程序员指南,同时也是一本可以摆在桌边随时查阅的参考手册,它以全新的章节阐述了有效使用Javascript脚本所需要知道的一切,包括: 脚本化的HTTP和Ajax;XML处理;使用......一起来看看 《JavaScript权威指南》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具