状态模式C#版

目录

例子

普通方式

实现

问题

解决方式

枚举方式

实现

问题

状态模式

状态接口

状态对象

游戏玩家



作为设计模式中的一种,状态模式在软件、游戏设计中有很重要的作用。为了理解这种模式,本文首先用普通方式实现了一个例子,然后分别用枚举方式和状态模式实现了这个例子,所有的代码经过测试可以在Unity(2017.2)中运行。
 

例子

游戏中有一个处于站立姿态的女英雄,按“↑”时会跳跃;按住“”时,会卧倒,松开时则会恢复站立;在跳跃姿态按“↓”时会下斩

 

普通方式

实现

 不考虑状态模式,我们分别实现这三个状态及其过渡。

using UnityEngine;

public class HeroBasic : MonoBehaviour
{
    private void Update()
    {
        //站立到跳跃
        if(Input.GetKeyDown(KeyCode.UpArrow))
        {
            print("jump");
        }
        //站立到卧倒
        else if(Input.GetKeyDown(KeyCode.DownArrow))
        {
            print("duck");
        }
        //卧倒到站立
        else if(Input.GetKeyUp(KeyCode.DownArrow))
        {
            print("stand");
        }
    }
}

问题

  1. 一直按“↑”,会不断跳跃,产生悬空效果;
  2. 在跳跃姿态下按“↓”,会立即卧倒,同时在卧倒状态下按“↑”,会立即跳跃;
  3. 无法做出下斩动作;

解决方式

引入两个字段分别标志是否跳跃和卧倒,程序看起来如下。

using UnityEngine;

public class HeroBasic : MonoBehaviour
{
    private bool isJumping = false;
    private bool isDucking = false;

    private void Update()
    {
        //站立到跳跃
        if(Input.GetKeyDown(KeyCode.UpArrow))
        {
            if (!isJumping && !isDucking)
            {
                print("jump");
                isJumping = true;
            }
        }
        else if(Input.GetKeyDown(KeyCode.DownArrow))
        {
            //站立到卧倒
            if (!isJumping && !isDucking)
            {
                print("duck");
                isDucking = true;
            }
            //下斩
            if(isJumping)
            {
                isJumping=false;
                print("dive");
            }
        }
        //卧倒到站立
        else if(Input.GetKeyUp(KeyCode.DownArrow))
        {
            if(isDucking)
            {
                print("stand");
                isDucking = false;
            }
        }
    }
}

每次添加一个新的状态过渡时,我们不得不添加一个新的字段,并且需要在源代码中各个位置进行修改;另外,逻辑判断复杂,因为isJumping和isDucking并不会同时为真。

 

枚举方式

实现

我们用枚举数组替代之前的布尔字段,从而得到有限状态机形式的实现。

using UnityEngine;

public enum HeroState
{
    Stand,
    Jump,
    Duck,
    Dive
}

public class HeroEnum : MonoBehaviour
{
    private HeroState state;

    private void Update()
    {
        switch(state)
        {
            case HeroState.Stand:
                if(Input.GetKeyDown(KeyCode.UpArrow))
                {
                    state = HeroState.Jump;
                    print("jump");
                }
                else if(Input.GetKeyDown(KeyCode.DownArrow))
                {
                    state = HeroState.Duck;
                    print("duck");
                }
                break;
            case HeroState.Jump:
                if(Input.GetKeyDown(KeyCode.DownArrow))
                {
                    state = HeroState.Dive;
                    print("dive");
                }
                break;
            case HeroState.Duck:
                if(Input.GetKeyUp(KeyCode.DownArrow))
                {
                    state = HeroState.Stand;
                    print("stand");
                }
                break;
        }
    }
}

问题

此时,如果需要增加一个蓄能动作,角色在俯卧时可以蓄能,蓄能满时可以释放一个特殊攻击,这样,我们需要增加另外一个字段来存储蓄能时间chargeTime。

using UnityEngine;

public enum HeroState
{
    Stand,
    Jump,
    Duck,
    Dive
}

public class HeroEnum : MonoBehaviour
{
    private HeroState state;
    private int chargeTime = 0;
    public int MaxTime = 100;


    private void Update()
    {
        switch (state)
        {
            case HeroState.Stand:
                if (Input.GetKeyDown(KeyCode.UpArrow))
                {
                    state = HeroState.Jump;
                    print("jump");
                }
                else if (Input.GetKeyDown(KeyCode.DownArrow))
                {
                    state = HeroState.Duck;
                    print("duck");
                }
                chargeTime = 0;
                break;
            case HeroState.Jump:
                if (Input.GetKeyDown(KeyCode.DownArrow))
                {
                    state = HeroState.Dive;
                    print("dive");
                }
                chargeTime = 0;
                break;
            case HeroState.Duck:
                if (Input.GetKeyUp(KeyCode.DownArrow))
                {
                    state = HeroState.Stand;
                    print("stand");
                }
                chargeTime ++;
                if(chargeTime > MaxTime)
                {
                    chargeTime = 0;
                    print("super");
                }
                break;
        }
    }
}

这样,又出现了普通方式一样的问题,程序修改变得混乱不堪,纠其原因,角色状态和角色本身耦合性太高,如果需要新增一个状态,必然会使角色本身进行修改。于是,我们需要一种新的思维方式来构建我们的程序,这种思维方式应该可以解耦角色状态和角色本身,可以让角色状态内部改变角色本身状态,从而要新增一个角色状态,只需要修改角色状态,而不需要修改角色本身。

 

状态模式

状态模式被GoF这样描述:

允许一个对象在其内部状态发生改变时改变自己的行为,看起来就像是修改了本身的类型

状态接口

为所有的状态定义一个接口,在该接口中定义了所有状态类必须实现的方法。

public interface IHeroState
{
    void HandleInput(Hero hero);
}

状态对象

为所有状态定义一个具体的类,继承状态接口,并实现状态接口中的方法。

//站立状态
using UnityEngine;
using System.Threading;

public class IdleState : IHeroState
{
    public void HandleInput(Hero hero)
    {
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            hero.printMessage("duck");
            hero.State = new DuckingState();
        }

        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            hero.printMessage("jump");
            hero.State = new JumpingState();
            Thread waitForSeconds = new Thread(WaitingSeconds);
            waitForSeconds.Start(hero);
        }
    }

    void WaitingSeconds(object hero)
    {
        Thread.Sleep(1000);
        Hero heroo = (Hero)hero;
        heroo.printMessage("stand");
        heroo.State = this;
    }
}
//下斩状态
public class DiveState : IHeroState
{
    public void HandleInput(Hero hero)
    {
    }
}
//跳起状态
using UnityEngine;
using System.Threading;

public class JumpingState : IHeroState
{
    public void HandleInput(Hero hero)
    {
        if(Input.GetKeyDown(KeyCode.DownArrow))
        {
            hero.printMessage("dive");
            hero.State = new DiveState();
            Thread waitForSeconds = new Thread(WaitingSeconds);
            waitForSeconds.Start(hero);
        }
    }

    void WaitingSeconds(object hero)
    {
        Thread.Sleep(1000);
        Hero heroo = (Hero)hero;
        heroo.printMessage("stand");
        heroo.State = this;
    }
}
//下蹲状态
public class DiveState : IHeroState
{
    public void HandleInput(Hero hero)
    {
    }
}

 

游戏玩家

最后,在游戏玩家中定义一个状态变量,该状态变量不再是之前的枚举类型,而是状态接口类型。

using UnityEngine;


public class Hero : MonoBehaviour
{
    public IHeroState State;

    private void Start()
    {
        State = new IdleState();
    }

    private void Update()
    {
        HandleInput();
    }

    void HandleInput()
    {
        State.HandleInput(this);
    }

    public void printMessage(string s)
    {
        print(s);
    }
}

 这样我们就实现了状态模式。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值