『功能项目』怪物的有限状态机【42】
本章项目成果展示
我们打开上一篇41项目优化 - 框架加载资源的项目,
本章要做的事情是按照框架的思想构建项目并完成怪物的自动巡逻状态,当主角靠近怪物时,怪物会朝向主角释放技能
首先新建脚本:BossCtrl.cs
(通常把xxxCtrl.cs脚本写在中间层,后续会增加xxxOpt.cs脚本进行调用xxxCtrl.cs中的函数)
(xxxOpt.cs脚本在上层调用)
using UnityEngine;
public class BossCtrl : MonoBehaviour{
protected bool isDead;
Animator animator;
public int hp;
public int currentHp;
public int attackValue;
public int defineValue;
public void Init() {
isDead = false;
hp = currentHp = 1000;
attackValue = 700;
defineValue = 700;
animator = GetComponent<Animator>();
animator.SetBool("IsMoving", false);
}
}
将控制层(xxxCtrl.cs)增加在资源框架脚本中:
此时怪物身上就有了血量攻击力防御力等数据
接下来再增加一个xxxOpt.cs脚本调用xxxCtrl.cs脚本的数据以及通用机制(此时xxxCtrl.cs还没有写通用机制,可以理解为后续将机制写在xxxCtrl.cs中 而调用写在xxxOpt.cs脚本中)
新建脚本:BossOpt.cs
using UnityEngine;
public class BossOpt : MonoBehaviour{
FSM fsm;
public BossBlackboard blackboard;
Vector3 playerPos;
Vector3 selfPos;
Transform blackboardTransform;
Vector3 blackboardTargetPos;
Animator animator;
public void Start(){
blackboardTransform = GameObject.Find("Boss01").gameObject.transform;
blackboardTargetPos = new Vector3 (0, 45, 15);
blackboard = new BossBlackboard(5,3, blackboardTransform, blackboardTargetPos, transform.position);
fsm = new FSM(blackboard);
fsm.AddState(StateType.Idle, new AI_IdleState(fsm));
fsm.AddState(StateType.Move, new AI_MoveState(fsm));
blackboard.initPos = transform.position;
fsm.SwitchState(StateType.Idle);
animator = GetComponent<Animator>();
}
void Update(){
selfPos = transform.position;
playerPos = GameObject.Find("PlayerNormal").gameObject.transform.position;
if (Vector3.Distance(selfPos, playerPos) < 10) {
animator.SetBool("IsSkill", true);
transform.LookAt(playerPos + new Vector3(0,3,0));
}
if (Vector3.Distance(selfPos, playerPos) >= 10) {
animator.SetBool("IsSkill", false);
fsm.OnUpdate();
transform.LookAt(blackboard.targetPos);
}
}
}
此时会有很多红色报错,因为少了一些自定义的类,接下来我们创建FSM类
也就是说BossOpt.cs是调用FSM(有限状态机)类的脚本,接下来我们需要写有限状态机类:
新建脚本:Blackboard.cs
using System;
[Serializable]
public class Blackboard{
//此处存储共享数据 或者向外展示的数据 可配置数据
}
新建脚本:BossBlackboard.cs
using System;
using UnityEngine;
[Serializable]
public class BossBlackboard : Blackboard{
//闲置时间
public float idleTime;
public float moveSpeed;
public Transform transform;
public Vector3 targetPos;
public Vector3 initPos;
public BossBlackboard(float idleTime, float moveSpeed,
Transform transform, Vector3 targetPos, Vector3 initPos){
this.idleTime = idleTime;
this.moveSpeed = moveSpeed;
this.transform = transform;
this.targetPos = targetPos;
this.initPos = initPos;
}
}
新建脚本:IState.cs
public interface IState{
void OnEnter();
void OnExit();
void OnUpdate();
}
新建脚本:FSM.cs
using System.Collections.Generic;
public enum StateType{
Idle,
Move,
}
public class FSM {
public IState curState;
public Dictionary<StateType, IState> states;
public Blackboard blackboard;
public FSM(Blackboard blackboard) {
this.states = new Dictionary<StateType, IState>();
this.blackboard = blackboard;
}
//外部使用 - 增加状态
public void AddState(StateType stateType, IState state) {
if (states.ContainsKey(stateType)) {
return;
}
states.Add(stateType,state);
}
//外部使用 - 切换状态
public void SwitchState(StateType stateType) {
if (!states.ContainsKey(stateType)) {
return;
}
if (curState != null) {
curState.OnExit();
}
curState = states[stateType];
curState.OnEnter();
}
public void OnUpdate() {
curState.OnUpdate();
}
}
新建脚本:AI_IdleState.cs
using UnityEngine;
public class AI_IdleState : IState{
//闲置计时器
public float idleTimer;
public AI_IdleState(FSM fsm){
this.fsm = fsm;
blackboard = fsm.blackboard as BossBlackboard;
}
FSM fsm;
BossBlackboard blackboard;
public void OnEnter(){
idleTimer = 0;
}
public void OnUpdate(){
idleTimer += Time.deltaTime;
if (idleTimer > blackboard.idleTime){
fsm.SwitchState(StateType.Move);
}
}
public void OnExit() { }
}
新建脚本:AI_MoveState.cs
using UnityEngine;
public class AI_MoveState : IState{
Animator animator;
public float idleTimer;
FSM fsm;
BossBlackboard blackboard;
public AI_MoveState(FSM fsm){
this.fsm = fsm;
blackboard = fsm.blackboard as BossBlackboard;
}
public void OnEnter(){
animator = GameObject.Find("Boss01").GetComponent<Animator>();
float randomAngle = Random.Range(0, 360);
float randomRadius = Random.Range(0, 7);
blackboard.targetPos = new Vector3(
blackboard.initPos.x + Mathf.Cos(Mathf.Deg2Rad * randomAngle) * randomRadius,
blackboard.transform.position.y,
blackboard.initPos.z + Mathf.Sin(Mathf.Deg2Rad * randomAngle) * randomRadius
);
}
public void OnExit() { }
public void OnUpdate(){
if (Vector3.Distance(blackboard.transform.position, blackboard.targetPos) < 0.1f){
fsm.SwitchState(StateType.Idle);
animator.SetBool("IsMoving", false);
}
else{
blackboard.transform.position = Vector3.MoveTowards(blackboard.transform.position,
blackboard.targetPos, blackboard.moveSpeed * Time.deltaTime);
animator.SetBool("IsMoving", true);
}
}
}
保存代码将调用FSM(有限状态机类)的BossOpt.cs脚本增加到GameManager.cs资源框架上
运行项目 - Boss就会Idle状态5秒钟后随机移动任意方向5秒钟进行循环并且不会超过以自身为原点半径为7的圆范围
本章利用有限状态机FSM做了Idle与Move下的转换,并且当主角靠近怪物时 怪物会释放技能
接下来利用前几章的知识增加一些脚本,增加技能特效,怪物UI信息,以及伤害计算让主角持续掉血
首先创建怪物UI信息
以前文章有制作教程
将UI对象放在指定文件夹下
之前导入的技能包中可找到该技能或者重新导入个新技能修改其名字放进指定文件夹即可
修改脚本:
using System.Collections;
using UnityEngine;
public class BossOpt : MonoBehaviour{
FSM fsm;
public BossBlackboard blackboard;
Vector3 playerPos;
Vector3 selfPos;
Transform blackboardTransform;
Vector3 blackboardTargetPos;
Animator animator;
#region UI信息
GameObject infoUIPrefab;
GameObject infoUIInstance;
bool Count;
#endregion
#region 技能特效
GameObject boss01SkillPrefab;
#endregion
#region 伤害计算
GameManager gm;
BossCtrl bossCtrl;
#endregion
public void Start(){
blackboardTransform = GameObject.Find("Boss01").gameObject.transform;
blackboardTargetPos = new Vector3 (0, 45, 15);
blackboard = new BossBlackboard(5,3, blackboardTransform, blackboardTargetPos, transform.position);
fsm = new FSM(blackboard);
fsm.AddState(StateType.Idle, new AI_IdleState(fsm));
fsm.AddState(StateType.Move, new AI_MoveState(fsm));
blackboard.initPos = transform.position;
fsm.SwitchState(StateType.Idle);
animator = GetComponent<Animator>();
#region UI信息
infoUIPrefab = Resources.Load<GameObject>("Prefabs/Images/Boss01UI");
Count = false;
#endregion
#region 技能特效
boss01SkillPrefab = Resources.Load<GameObject>("Prefabs/Effects/Boss01Effect");
#endregion
#region 伤害计算
gm = GameManager.Instance;
bossCtrl = gameObject.GetComponent<BossCtrl>();
#endregion
}
void Update(){
selfPos = transform.position;
playerPos = GameObject.Find("PlayerNormal").gameObject.transform.position;
if (Vector3.Distance(selfPos, playerPos) < 10) {
animator.SetBool("IsSkill", true);
transform.LookAt(playerPos + new Vector3(0,3,0));
#region UI信息 -> 技能特效
if (!Count) {
infoUIInstance = Instantiate(infoUIPrefab, new Vector3(0f, -50f, 0f), Quaternion.identity);
infoUIInstance.transform.SetParent(GameObject.Find("CurrentCanvas").transform, false);
Count = true;
infoUIInstance.AddComponent<Boss01UIInfo>();
#region 技能特效
StartCoroutine(WaitTwoSStartBoss01Skill());
#endregion
}
#endregion
}
if (Vector3.Distance(selfPos, playerPos) >= 10) {
animator.SetBool("IsSkill", false);
fsm.OnUpdate();
transform.LookAt(blackboard.targetPos);
#region UI信息 -> 技能特效
if (Count) {
Destroy(infoUIInstance);
Count = false;
#region 技能特效
Destroy(GameObject.Find("Boss01Effect(Clone)").gameObject);
#endregion
}
#endregion
}
}
#region 技能特效
IEnumerator WaitTwoSStartBoss01Skill(){
yield return new WaitForSeconds(2);
Instantiate(boss01SkillPrefab, transform.position, transform.localRotation);
while (true) {
gm.infoSys.playerCurrentHP -= bossCtrl.attackValue - gm.infoSys.defineValue;
yield return new WaitForSeconds(2);
if (Vector3.Distance(selfPos, playerPos) >= 10)
break;
}
}
#endregion
}
新建脚本:Boss01UIInfo.cs
using UnityEngine;
using UnityEngine.UI;
public class Boss01UIInfo : MonoBehaviour{
BossCtrl bossCtrl;
Slider hp;
void Start(){
bossCtrl = FindObjectOfType<BossCtrl>();
hp = transform.Find("Slider").GetComponent<Slider>();
}
void Update(){
if (hp != null)
hp.value = bossCtrl.currentHp;
}
}
运行项目即可实现 - 主角不在怪物攻击范围之内 ,怪物进行停留五秒随机行走三秒但不会超过半径为7米的圆范围, 一旦主角进入敌人范围,怪物会朝向主角释放技能,生成怪物UI信息,并且每次释放技能会让主角失去一定血量,当主角离开范围内,怪物回到巡逻状态
本章做了主角不在怪物攻击范围之内 ,怪物进行停留五秒随机行走三秒但不会超过半径为7米的圆范围, 一旦主角进入敌人范围,怪物会朝向主角释放技能,生成怪物UI信息,并且每次释放技能会让主角失去一定血量,当主角离开范围内,怪物回到巡逻状态的功能
接下来要实现:
1.战士职业平A(按A键)使怪物掉血的功能
2.窗口可拖拽脚本
3.点击名称寻找地点功能
4.隐藏怪物的生成
5.怪物I攻击范围内的主动攻击
6.掉落坐骑蛋的获取
7.异步传送转换场景
以及开放回合制、坐骑系统、宠物系统、背包系统、神炼系统、商城系统、Boss的目标跟随任务导航系统以及UI播放3D动画效果等等。
具体项目运行效果请关注water1024的b站视频项目演示《破碎纪元》
【Unity回合2.5D】破碎纪元_单机游戏热门视频 (bilibili.com)https://www.bilibili.com/video/BV1rZY4e9Ebs/?spm_id_from=333.999.0.0&vd_source=547091a95b03acfa8e8a9e46ef499cd6