游戏编程模式 - 状态模式

栏目: 后端 · 发布时间: 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。

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

示例源码


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

查看所有标签

猜你喜欢:

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

Tagging

Tagging

Gene Smith / New Riders / 2007-12-27 / GBP 28.99

Tagging is fast becoming one of the primary ways people organize and manage digital information. Tagging complements traditional organizational tools like folders and search on users desktops as well ......一起来看看 《Tagging》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具