“让一个对象的行为因为内部状态的改变而变化,该对象也想是换了类一样”。状态模式是游戏设计中应用最为频繁的设计模式之一。主要是因为“状态”频繁的应用在游戏中,比如ai状态,角色状态,登陆状态,场景切换状态等等。本篇将采用书籍最为典型的角色形态状态切换来举例。

#1实现场景切换的直接方式

public class SenceManger 
{
    private string m_Sence = "start";
    //该换场景
    public void ChangeScene(string sceneName)
    {
        m_Sence = sceneName;
        switch (m_Sence)
        {
            case "MainMenuSence":
                Application.LoadLevel("MainMenuSence");
            case "GameSence":
                Application.LoadLevel("GameSence");
                break;
        }
    }
    public void Updata()
    {
        switch (m_Sence)
        {
            case "start":
            ......
            case "MainMenuSence":
            ......
            case "GameSence":
            ......
            break;
        }
    }
}

既然已经有了直接的场景转换的方法,为什么还要用到状态模式来解决问题呢?因为当项目随着时间开发需要的场景会越来越多,可每当添加一个场景就需要在switch里添加新的语句块。并且与每一个状态有关的对象,都必须要在ScenceManger中申明,当这些对象被多个状态共享时,就会出现不知道调用哪个对象,产生开发混淆,造成很大的开发困扰。

最关键的缺点就时该类太过依赖与低层模块,让SceneManger不容易移植到其他项目中,违反了上篇所讲到的依赖倒置原则,那么为了避免这种情况发生,我们希望使用一个“场景类”来负责维护一个场景,好让与此场景相关的程序和对象能整合在一起。

#2“状态类”的实现

这里为更好理解状态类举一个例子:王者荣耀,LOL大家都玩过,里面类似的状态改变的英雄有很多,例如裴擒虎和豹女。他们都有两个形态也就是两个状态,他们变换外形的能力可以看成一种“内部状态的转换”,不同状态之间可以相互转换,并且不同状态也对应不同的行为方法。但无论怎么变化玩家操作的都是角色本身,他的“操作方法”和“信息沟通”的方式都不会改变。讲到这里是不是能自然联想到程序语言中某个关键字。没错就是abstract,这样无论角色是什么状态都可以通过重新定义的方式,来呈现出“操作方法”的不同表现形式。

2.1状态类的组成

通过上面例子的分析可以得出只少需要三个参与者来共同完成分别是:State状态接口类,Context状态拥有者,ConcreteState具体状态类。

首先定义Context状态拥有者类

public class Context
{
    //状态拥有着拥有的状态
    public State m_stata = null;
    //状态拥有者的内部状态改变函数
    public void Change(int Value)
    {
        m_stata.Request(Value);
    }
    //构造函数
    public void SetState(State state)
    {
        Debug.Log("Context.SetState:" + state);
        m_stata = state;
    }
}

State状态类

public abstract class State
{
    protected Context m_context = null;
    public State(Context context)
    {
        m_context = context;
    }//产生state类时,绑定状态拥有者
    public abstract void Request(int Value);
}

ConcreteState具体状态类

public class ConcreteStateA : State
{
    //每次形态的产生都会与状态拥有者进行绑定
    public ConcreteStateA(Context context) : base(context) { }
    //重写操作方法
    public  override void Request(int Value)
    {
        Debug.Log("A形态");
        //内部状态转变的判断方式
        if (Value > 10)
        {
            m_context.SetState(new ConcreteStateB(m_context));
        }
    }
}
public class ConcreteStateB : State
{
    public ConcreteStateB(Context context) : base(context) { }
    public override void Request(int Value)
    {
        Debug.Log("B形态");
        if (Value > 20)
        {
            m_context.SetState(new ConcreteStateC(m_context));
        }
    }
}
public class ConcreteStateC : State
{
    public ConcreteStateC(Context context) : base(context) { }
    public override void Request(int Value)
    {
        Debug.Log("C形态");
        if (Value > 30)
        {
            m_context.SetState(new ConcreteStateA(m_context));
        }
    }
}

这样一个状态模式就写好了,当我们游戏开始时会初始化new一个A形态

Context context=new Context()
context.SetState(new ConcreteStateA(context))

随着角色数组随游戏进行不断提升时就会完成不同形态之间的转换

context.Change(15)//变为B形态
context.Change(25)//变为C形态
context.Change(35)//变为A形态

这样设计状态模式不仅完美解决依赖问题还很好的体现出依赖倒置的两大主题:1.高层模块不应该依赖与低层模块,两者都应该依赖与抽象概念。2.抽象接口不应该依赖于实现,而实现应该依赖于抽象接口。还巧妙的符合了“开——闭”原则。详细可参考上一篇的设计原则。

本篇基于设计模式与完美开发并作为学习笔记发表,希望得到各位的批评与指正。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐