使用ScriptableObject基于有限状态机AI与自由拓展
参考视频:https://v.qq.com/x/page/c0537cidpg9.html
目标:
- 创建基于FSM(Finite State Machine有限状态机)的游戏AI。
- 实现状态,动作,决策以及转换。
- 使用ScriptableObject实现AI配置。
有限状态机:
- 一台有有限个状态的机器。
- 状态机因为外部输入或者环境改变(持续检测)而转换或者保持状态。
- 每个状态包括一个或多个动作,和一个规定的转换决策。
首先定义一个StateController,每个AI有且只有一个。其中的重要部分是当前状态和保持状态。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class StateController : MonoBehaviour
{
public State currentState;
public State remainState;
public EnemyStats enemyStats;
public Transform eyes;
public float HP = 100f;
[HideInInspector] public NavMeshAgent navMeshAgent;
//[HideInInspector] public Complete.TankShooting tankShooting;
public List<Transform> wayPointList;
[HideInInspector] public int nextWayPoint;
[HideInInspector] public Transform chaseTarget;
[HideInInspector] public float stateTimeElapsed;
private bool aiActive = true;
private bool isDead = false;
void Awake()
{
navMeshAgent = GetComponent<NavMeshAgent>();
}
void Start()
{
}
public void SetupAI(bool aiActivationFromTankManager, List<Transform> wayPointsFromTankManager)
{
wayPointList = wayPointsFromTankManager;
aiActive = aiActivationFromTankManager;
if (aiActive)
{
navMeshAgent.enabled = true;
}
else
{
navMeshAgent.enabled = false;
}
}
private void Update()
{
if (!aiActive)
{
return;
}
currentState.UpdateState(this);
}
private void OnDrawGizmos()
{
Gizmos.color = currentState.sceneGizmoColor;
Gizmos.DrawWireSphere(eyes.position, enemyStats.lookSphereCastRadius);
}
public void TransitionToState(State nextState)
{
if (nextState != remainState)
{
currentState = nextState;
OnExitState();
}
}
public bool CheckIfCountDownElapsed(float duration)
{
stateTimeElapsed += Time.deltaTime;
return stateTimeElapsed >= duration;
}
public void OnExitState()
{
stateTimeElapsed = 0;
}
public void SetTarget(Transform t)
{
chaseTarget = t;
}
public void Damage()
{
HP -= 10;
}
public void AIDead()
{
if (isDead)
return;
isDead = true;
aiActive = false;
this.SendMessage("Dead1");
this.SendMessageUpwards("OneMoreEnemyDead");
this.GetComponent<Collider>().enabled = false;
}
}
然后是定义状态的部分State,其中包含一个每帧执行的UpdateState,运行两个函数:
1执行当前N个Action动作
2执行N个Decision转换检测
using UnityEngine;
[CreateAssetMenu(menuName = "PluggableAI/State")]
public sealed class State : ScriptableObject
{
public Action[] action;
public Transition[] transitions;
public Color sceneGizmoColor = Color.grey;
public void UpdateState(StateController controller)
{
DoActions(controller);
CheckTransitions(controller);
}
private void DoActions(StateController controller)
{
for (int i = 0; i < action.Length; i++)
{
action[i].Act(controller);
}
}
private void CheckTransitions(StateController controller)
{
for (int i = 0; i < transitions.Length; i++)
{
bool decisionSucceeded = transitions[i].decision.Decide(controller);
if (decisionSucceeded)
controller.TransitionToState(transitions[i].trueState);
else
controller.TransitionToState(transitions[i].falseState);
}
}
}
定义Action和Decision
using UnityEngine;
public abstract class Action : ScriptableObject {
public abstract void Act(StateController controller);
}
using UnityEngine;
[CreateAssetMenu(menuName = "PluggableAI/Actions/Chase")]
public class ChaseAction : Action
{
private float chaseStopDis = 5f;
private float QuaterSlerpParam= 0.1f;
public override void Act(StateController controller)
{
Chase(controller);
}
public void Chase(StateController controller)
{
if(controller.chaseTarget == null)return;
// if (Vector3.Distance(controller.transform.position, controller.chaseTarget.position) >= chaseStopDis)
// {
// controller.navMeshAgent.destination = controller.chaseTarget.position;
// controller.navMeshAgent.isStopped = false;
// }
if (controller.navMeshAgent.remainingDistance <= chaseStopDis)
{
if (Vector3.Distance(controller.transform.position, controller.chaseTarget.position) <= chaseStopDis)
{
UnityEngine.AI.NavMeshHit hit;
if (!controller.navMeshAgent.Raycast(controller.chaseTarget.position, out hit))
{
Debug.DrawLine(controller.transform.position, controller.chaseTarget.position, Color.white);
// Target is "visible" from agent position.
controller.navMeshAgent.isStopped = true;
// 目标Rotation
Quaternion t = Quaternion.LookRotation((controller.chaseTarget.position - controller.transform.position), Vector3.up);
// 进行旋转
controller.transform.rotation = Quaternion.Slerp(controller.transform.rotation, t, QuaterSlerpParam);
return;
}
}
}
Debug.DrawLine(controller.transform.position, controller.chaseTarget.position, Color.green);
controller.navMeshAgent.destination = controller.chaseTarget.position;
controller.navMeshAgent.isStopped = false;
}
}
using UnityEngine;
public abstract class Decision : ScriptableObject
{
public abstract bool Decide(StateController controller);
}
using UnityEngine;
[CreateAssetMenu(menuName = "PluggableAI/Decision/LookDecision")]
public class LookDecision : Decision
{
public override bool Decide(StateController controller)
{
return Look(controller);
}
private bool Look(StateController controller)
{
RaycastHit hit;
Debug.DrawRay(controller.eyes.position,controller.eyes.forward*controller.enemyStats.lookRange,Color.green);
if(Physics.SphereCast(controller.eyes.position,controller.enemyStats.lookSphereCastRadius,
controller.eyes.forward,out hit,controller.enemyStats.lookRange)
&&hit.collider.CompareTag("Player"))
{
controller.chaseTarget = hit.transform;
return true;
}
return false;
}
}
这里稍微解释一下,首先AI一上来没有动作,执行一个观察检测,通过射线检测获得玩家目标,一旦获得玩家目标,就会进入到追踪玩家的状态。
这里设计三种不同的AI
巡逻车AI
初始状态按照规定路线巡逻,发现玩家后持续追踪和攻击,玩家离开一定范围后回到原始位置。
飞行器AI
初始状态随机路线巡逻,发现玩家后持续追踪。
炮塔AI
初始在射程范围内检测玩家,检测到之后进行攻击,玩家离开范围后停止攻击。