场景切换
需求:
现在有三个游戏场景,Start、Main、Battle,要求写一个切换场景的功能。
错误示范:
using System;
using UnityEngine;
public class SceneManager : MonoBehaviour
{
public enum SceneEnum
{
Start,
Main,
Battle
}
private SceneEnum m_Scene = SceneEnum.Start;
public void ChangeScene(SceneEnum scene)
{
m_Scene = scene;
switch (scene)
{
case SceneEnum.Start:
Application.LoadLevel("StartScene");
break;
case SceneEnum.Main:
Application.LoadLevel("MainMenuScene");
break;
case SceneEnum.Battle:
Application.LoadLevel("GameScene");
break;
default:
return;
}
}
public void Update()
{
switch (m_Scene)
{
case SceneEnum.Start:
break;
case SceneEnum.Main:
break;
case SceneEnum.Battle:
break;
default:
return;
}
}
}
存在的问题:
-
如果现在新增一个场景,那么就会涉及到修改原程序代码的问题(需要增加枚举,修改switch中的逻辑代码)。
-
如果需要在场景加载结束时增加逻辑代码,以上代码无法满足。
状态模式
让一个对象的行为随着内部状态的改变而变化,该对象也想是换了类一样。
状态模式的说明
- Context(状态拥有者)
- 是要给具有“状态”属性的类,可以制定相关的接口,让外界能够得知状态的改变或通过操作让状态改变。
- 有状态属性的类,例如:游戏角色有潜行、攻击、施法等;好友上线、脱机、忙碌等状态;这些类中会有一个ConcreteState 子类的对象为其成员,用来代表当前的状态。
- State(状态接口类):制定状态的接口,负责规范Contex(状态拥有者)在特定状态下要表现的行为。
- ConcreteState(具体状态类)
- 继承自State(状态接口类)。
- 实现Contex(状态拥有者)在特定状态下该有的行为,例如,实现角色在潜行状态时该有的行动变缓、3D模型半透明、不能被敌方角色觉察等行为。
具体实现:
using UnityEngine;
using System.Collections;
namespace DesignPattern_State
{
// 持有目前的狀態,並將有關的訊息傳給狀態
public class Context
{
State m_State = null;
public void Request(int Value)
{
m_State.Handle(Value);
}
public void SetState(State theState)
{
Debug.Log("Context.SetState:" + theState);
m_State = theState;
}
}
// 負責封裝當Context處於特定狀態時所該展現的行為
public abstract class State
{
protected Context m_Context = null;
public State(Context theContext)
{
m_Context = theContext;
}
public abstract void Handle(int Value);
}
// 狀態A
public class ConcreteStateA : State
{
public ConcreteStateA(Context theContext) : base(theContext)
{
}
public override void Handle(int Value)
{
Debug.Log("ConcreteStateA.Handle");
if (Value > 10)
m_Context.SetState(new ConcreteStateB(m_Context));
}
}
// 狀態B
public class ConcreteStateB : State
{
public ConcreteStateB(Context theContext) : base(theContext)
{
}
public override void Handle(int Value)
{
Debug.Log("ConcreteStateB.Handle");
if (Value > 20)
m_Context.SetState(new ConcreteStateC(m_Context));
}
}
// 狀態C
public class ConcreteStateC : State
{
public ConcreteStateC(Context theContext) : base(theContext)
{
}
public override void Handle(int Value)
{
Debug.Log("ConcreteStateC.Handle");
if (Value > 30)
m_Context.SetState(new ConcreteStateA(m_Context));
}
}
}
using UnityEngine;
using System.Collections;
using DesignPattern_State;
public class StateTest : MonoBehaviour
{
// Use this for initialization
void Start()
{
UnitTest();
}
//
void UnitTest()
{
Context theContext = new Context();
theContext.SetState(new ConcreteStateA(theContext));
theContext.Request(5);
theContext.Request(15);
theContext.Request(25);
theContext.Request(35);
}
}
再谈场景切换
现在我们可以把场景抽象为一个状态对象(ISceneState)(可以是基类、也可以是接口),抽象场景在不同时间段可能需要实现的逻辑接口。然后我们根据不同场景实现不同的实体子类(StartState),在子类中重写基类的逻辑方法。
在外部,我们还需要一个管理类(SceneStateController)来对场景进行统一切换。
现在在反观开始提到的两个问题,Scene扩展和场景加载结束时逻辑,都迎刃而解。
状态模式的优点
-
减少错误发生并降低维护难度
不在使用Swith(m_state)来判断当前的状态,这样可以减少新增游戏状态时,因未能检查到所有switch(m_state)程序代码而造成的错误
-
状态执行环境单一化
与每一个状态有关的对象及操作都被实现在一个场景状态类下,对程序设计来说,这样可以清楚的了解每一个状态执行时所需要的对象及配合的类。
状态模式的应用
- 角色AI:使用状态模式来控制角色在不同状态下的AI行为。
- 游戏服务器连线状态:网络游戏的客户端,需要处理与游戏服务器的连线状态,一般包含开始连线、连线中、断线等状态,而在不同的状态下,会有不同的封包信息处理方式,需要分别实现。
- 关卡进行状态:如果是通关型游戏,进入关卡时通常会分成不同的阶段,包含加载数据,显示关卡信息、倒数通知开始、关卡进行、关卡结束和分数计算,这些不同的阶段可以使用不同的状态来负责实现。