在游戏开发中,我们常常会用到一些物体的状态切换,比如场景和动画剧情等等,对于常规的不需要变通的我们可以使用switch case语句实现,但是一旦使用你的逻辑就是固定的,不能添加和修改,所以对于不确定会不会增加需求的,采用状态模式会更好
状态模式的核心思想就是通过GameManager来管理状态接口,通过状态接口来对每一个实现接口的状态类进行切换,怎么进行切换不需要我们的GameManager关心,切换条件将会在每个类自己的方法里面实现,这样再有新的状态出现,我们只需要改写或者添加这些状态类就可以了
下面看一下简单的状态模式的实现
先定义对我们的状态接口,这个接口需要包含这些状态所要切换的时候做的事
public interface IState
{
void Handle(int arg);
}
然后在我们的管理类里面对接口进行使用,主要是用来传递状态,不做什么逻辑操作
public class Context
{
private IState mState;
public void SetState(IState state)
{
mState = state;
}
public void Handle(int arg)
{
mState.Handle(arg);
}
}
下面定义两个实例类来使用这个接口
public class ConcreteStateA : IState
{
private Context mContext;
public ConcreteStateA(Context context)
{
mContext = context;
}
public void Handle(int arg)
{
Debug.Log("ConcreateStateA.Handle " + arg);
if (arg > 10)
{
mContext.SetState(new ConcreteStateB(mContext));
}
}
}
public class ConcreteStateB : IState
{
private Context mContext;
public ConcreteStateB(Context context)
{
mContext = context;
}
public void Handle(int arg)
{
Debug.Log("ConcreateStateB.Handle " + arg);
if (arg <= 10)
{
mContext.SetState(new ConcreteStateA(mContext));
}
}
}
这样我们一个简单的状态模式就出来了,我们只需要在初始化给定当前状态即可,后续状态会通过每个类的实现方式自己进行切换,如果有需要添加的就直接在后面用同样的方式添加状态类即可
void Start()
{
//通过管理类调用我们的状态
Context context = new Context();
context.SetState(new ConcreteStateA(context));
context.Handle(5);
context.Handle(20);
context.Handle(30);
context.Handle(4);
context.Handle(6);
}
下面我们用这个状态模式进行游戏场景切换的一些工作
当然第一步依旧是定义我们的管理器类和状态接口
//接口
public class ISceneState
{
private string mSceneName;
protected SceneStateController mController;
public ISceneState(string sceneName,SceneStateController controller)
{
mSceneName = sceneName;
mController = controller;
}
public string SceneName
{
get { return mSceneName; }
}
//每次进入到这个状态的时候调用,初始化使用
public virtual void StateStart() { }
//需要结束状态的时候调用,主要是用来释放
public virtual void StateEnd() { }
//当前的状态行为逻辑
public virtual void StateUpdate(){}
}
//管理器类
public class SceneStateController
{
private ISceneState mState;
private AsyncOperation mAO;//异步加载场景
private bool mIsRunStart = false;//是否启动当前状态,主要是判断场景是否加载完毕
//设置状态的方法,参数一是传入当前状态类,第二个是是否立即启动当前状态类,默认是启动的,但是因为我们这个是
//加载场景使用的,所以第一个场景默认是加载好的,在这里我们的初始场景只是展示一个logo不需要做其他操作
public void SetState(ISceneState state,bool isLoadScene=true)
{
if (mState != null)
{
mState.StateEnd();//让上一个场景状态做一下清理工作
}
mState = state;
if (isLoadScene)
{
mAO = SceneManager.LoadSceneAsync(mState.SceneName);
mIsRunStart = false;
} else
{
mState.StateStart();//启动当前状态的初始化操作
mIsRunStart = true;
}
}
public void StateUpdate()
{
if (mAO != null && mAO.isDone == false) return;//当前场景正在加载但是没有完成,直接返回
if (mIsRunStart==false&& mAO != null && mAO.isDone == true)//同样判断当前场景是否加载完,但是需要跳过初始场景
{
mState.StateStart();
mIsRunStart = true;
}
if (mState != null)
{
mState.StateUpdate();
}
}
}
下面我们开始实现我们的各个场景的类以及这些场景的逻辑
//第一个场景,显示logo从暗变亮,2秒之后切换到下一个场景
public class StartState:ISceneState
{
public StartState(SceneStateController controller):base("01StartScene",controller)
{
}
private Image mLogo;
private float mSmoothingSpeed = 1;
private float mWaitTime = 2;
public override void StateStart()
{
mLogo = GameObject.Find("Logo").GetComponent<Image>();
mLogo.color = Color.black;
}
public override void StateUpdate()
{
mLogo.color = Color.Lerp(mLogo.color, Color.white, mSmoothingSpeed * Time.deltaTime);
mWaitTime -= Time.deltaTime;
if (mWaitTime <= 0)
{
mController.SetState(new MainMenuState(mController));
}
}
}
//第二个场景,初始化StateStart()方法里面绑定按钮事件,通过按钮事件切换到下一个场景,按钮事件里面可以写异步加载以及进度条等等
public class MainMenuState:ISceneState
{
public MainMenuState(SceneStateController controller) : base("02MainMenuScene", controller) { }
public override void StateStart()
{
GameObject.Find("StartButton").GetComponent<Button>().onClick.AddListener(OnStartButtonClick);
}
private void OnStartButtonClick()
{
mController.SetState(new BattleState(mController));
}
}
public class BattleState:ISceneState
{
public BattleState(SceneStateController controller):base("03BattleScene",controller) { }
public override void StateStart()
{
print("....");
}
}
这样的化我们只需要调用SceneStateController来管理这些状态即可,新的状态也只是需要重写这个ISceneState接口,然后调用SetState方法切换场景即可.
public class GameLoop : MonoBehaviour {
private SceneStateController controller = null;
void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
// Use this for initialization
void Start () {
controller = new SceneStateController();
controller.SetState(new StartState(controller),false);//初始化调用第一个场景
}
// Update is called once per frame
void Update () {
if(controller!=null)
controller.StateUpdate();
}
}