状态模式(有限状态机)
前言:
游戏开发过程中,各种游戏状态的切换无处不在。但很多时候,简单粗暴的if else加标志位的方式并不能很地道地解决状态复杂变换的问题,这时,就可以运用到状态模式以及状态机来高效地完成任务。状态模式与状态机,因为他们关联紧密,常常放在一起讨论和运用。
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
原理:状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。
通常命令模式的接口中只有一个方法。而状态模式的类中有一个或者多个方法。状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。
优点:
降低状态类间的耦合性
代码结构化,易维护、拓展
那么问题来啦?神马时候适用于当前设计模式呢?
人物的行为(动画)、切换场景的时候、AI状态、账号登陆状态等等
下面为它的基本结构图:
它封装了转换规则。
上代码:
IState(状态接口类):将各种具体的状态类抽象出来。、制定状态的接口,负责规范Context在特定状态下要表现的行为。
//==========================
// - FileName: IState.cs
// - Created: true.
// - CreateTime: 2020/02/29 23:32:29
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 规范行为
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IState
{
void Handle();
}
Context(状态拥有者):是一个具有“状态”属性的类,可以定制相关的接口,让外界能够得知状态的改变或者通过操作让状态改变。
//==========================
// - FileName: Context.cs
// - Created: true.
// - CreateTime: 2020/02/29 23:34:45
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 游戏当前状态、数据、情况
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//状态机
public class Context
{
//当前状态
private IState mIState;
//设置状态方法,暴露给外部
public void SetState(IState state)
{
mIState = state;
}
//当前状态下需要操作的方法
public void Handle()
{
mIState.Handle();
}
}
Context A:(具体状态类):继承自State,实现Context在特定状态下该有的行为。
//==========================
// - FileName: EatMeals.cs
// - Created: true.
// - CreateTime: 2020/03/01 15:29:32
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 具体测试类
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EatMeals : IState {
//拿到状态机的引用、当前状态的持有者
private Context mContext;
public EatMeals(Context context)
{
//持有你的状态是谁
mContext = context;
}
public void Handle()
{
Debug.Log("我们在吃饭");
}
}
测试类:游戏初始化以及定期调用更新操作、最后会打印出来 我们在吃饭;(天大地大吃饭最大哈哈)
//==========================
// - FileName: DPstate.cs
// - Created: true.
// - CreateTime: 2020/02/29 22:42:40
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 具体测试类
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DPstate : MonoBehaviour {
void Start () {
Context context = new Context();
//拿到状态机并且设置状态
context.SetState(new EatMeals (context));
//或者写成
//IState state = new EatMeals (context);
//context.SetState(state);
//让状态机开始执行
context.Handle();
}
}
作者这里成功的通过 Button控制人物的行为了,懒得插视频啦
OK当你上面的理解了以后,我们再来看一个例子:这里我是使用Button按钮来控制人物的各种行为
//==========================
// - FileName: IStatePlayer.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:22:46
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 规范行为
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IStatePlayer
{
void Handle();
}
状态机:暴露给外部接口方法
//==========================
// - FileName: Cond.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:25:43
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description:
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Cond
{
//当前状态
private IStatePlayer statePlayer;
//暴露给外部方法
public void SetState(IStatePlayer statePl)
{
statePlayer = statePl;
}
public void Handle()
{
statePlayer.Handle();
}
}
这里是控制角色的默认状态
//==========================
// - FileName: IDEPlayer.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:28:33
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description:
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IDEPlayer : IStatePlayer
{
//拿到状态机的引用
private Cond mCond;
private Animator animator;
public IDEPlayer(Cond cond)
{
//持有你的状态
mCond = cond;
}
public void Handle()
{
animator = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
animator.SetInteger("Walk", 0);
animator.SetInteger("Run", 0);
Debug.Log("主角是 IDE 状态");
}
}
主角行走的状态:
//==========================
// - FileName: WalkPlayer.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:28:55
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 走动的行为
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WalkPlayer : IStatePlayer
{
private Cond mCond;
//TODO
private Animator animator;
public WalkPlayer(Cond cond)
{
mCond = cond;
}
public void Handle()
{
Debug.Log("主角正在行走");
animator = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
animator.SetInteger("Walk", 2);
animator.SetInteger("Run", 0);
}
}
主角跑动的状态:
//==========================
// - FileName: RunPlayer.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:28:44
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description:
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RunPlayer : IStatePlayer
{
//拿到状态机的引用
private Cond mCond;
private Animator animator;
public RunPlayer(Cond cond)
{
//持有你的状态是神马
mCond = cond;
}
public void Handle()
{
Debug.Log("主角正在跑动");
animator = GameObject.FindGameObjectWithTag("Player").GetComponent<Animator>();
animator.SetInteger("Run", 2);
}
}
最后就是我们的测试代码。
//==========================
// - FileName: UIContrlPlayer.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:34:06
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description:
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIContrlPlayer : MonoBehaviour
{
//按钮
public Button Btn_Ide;
public Button Btn_Walk;
public Button Btn_Run;
void Start()
{
//origin = transform.rotation;
Btn_Ide.onClick.AddListener(() =>
{
Cond cond = new Cond();
cond.SetState(new IDEPlayer(cond));
cond.Handle();
});
Btn_Walk.onClick.AddListener(() =>
{
Cond cond = new Cond();
cond.SetState(new WalkPlayer(cond));
cond.Handle();
});
Btn_Run.onClick.AddListener(() =>
{
Cond cond = new Cond();
cond.SetState(new RunPlayer(cond));
cond.Handle();
});
}
}
OK,当你这一步也已经走完了以后,还有加强版的。
这个地方的加强版本实现的功能就是:
大头(敌人)开始的时候会根据链表中的定义的四个位置循环寻路,小黑(主角)头上会有一个红色的大灯到处晃,只要大头出现了小黑的范围夹角之内的话。小黑就会看向大头,并且跟着大头一起进行锻炼(跑向大头)。
下面是挂载 在小黑身上的类了(主角)
//==========================
// - FileName: UIContrlPlayer.cs
// - Created: true.
// - CreateTime: 2020/03/01 16:34:06
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 控制主角行为
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ContrlPlayer : MonoBehaviour
{
float distance = 2f;// 敌人视觉范围
private Quaternion origin;// 敌人初始方向
public Transform player;// 假设是玩家
public Transform enemy; //敌人
float dis; // 敌人与玩家之间的距离
void Start()
{
origin = transform.rotation;
}
void Update()
{
// 勾股定理计算敌人与玩家之间的距离
dis = Mathf.Sqrt(Mathf.Pow((player.transform.position.x - enemy.position.x), 2) + Mathf.Pow((player.transform.position.y - enemy.position.y), 2));
//Debug.Log(dis);
if (dis < distance)
{
Debug.Log("看向敌人");
Cond cond = new Cond();
cond.SetState(new RunPlayer(cond));
cond.Handle();
player.transform.LookAt(enemy.transform.localPosition);
}
else if (dis > distance)
{
Cond cond = new Cond();
cond.SetState(new IDEPlayer(cond));
cond.Handle();
transform.rotation = origin;
}
}
}
接下来就行敌人AI (大头)的类啦
//==========================
// - FileName: ToolMove.cs
// - Created: true.
// - CreateTime: 2020/03/01 17:51:49
// - Email: 1670328571@qq.com
// - Region: China WUHAN
// - Description: 敌人自动寻路
//==========================
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ToolMove : MonoBehaviour {
public Transform toolPos;
//大头的链表位置
//四个个目标点的链表
public List<Transform> Points;
//当前目标点的索引
private int index = 0;
void Update()
{
Move(5f,3f);
}
private void Move(float backSpeed,float moveSpeed)
{
//获得当前位置和目标点的距离
float dis = Vector3.Distance(transform.position, Points[index].localPosition);
//如果获取的距离小于0.5米,说明已经到达,
if (dis <= 0.5f)
{
//Debug.Log("已经到达");
index++;
if (index == Points.Count)
{
index = 0;
}
//移动到下一个点
transform.position = Vector3.MoveTowards(transform.position, Points[index].localPosition, backSpeed * Time.deltaTime);
}
else
{
transform.position = Vector3.MoveTowards(transform.position, Points[index].localPosition, moveSpeed * Time.deltaTime);
transform.LookAt(Points[index].transform.position);
}
}
}
虽然说很好用但是也是有缺点哦
缺点:如果游戏中有非常多的场景需要进行状态管理,有个能造成类爆炸,导致后期维护难度上升。