当前位置: 首页 > article >正文

『功能项目』怪物的有限状态机【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)icon-default.png?t=O83Ahttps://www.bilibili.com/video/BV1rZY4e9Ebs/?spm_id_from=333.999.0.0&vd_source=547091a95b03acfa8e8a9e46ef499cd6


http://www.kler.cn/a/307970.html

相关文章:

  • #include<string>和#include<string.h>有什么区别
  • 【操作系统】守护进程
  • 1111111111待修改--大流量分析(三)-BUUCTF
  • 数字孪生在智慧能源项目中的关键作用,你了解多少?
  • 车载空气净化器语音芯片方案
  • STM32单片机WIFI语音识别智能衣柜除湿消毒照明
  • 神经网络卷积层和最大池化
  • 2024 年最佳 Chrome 验证码扩展,解决 reCAPTCHA 问题
  • 小程序——生命周期
  • STM32 移植FATFS时遇到ff_oem2uni函数未定义问题
  • SQLyou基础知识总结(带案例)
  • 3286、穿越网格图的安全路径
  • Elasticsearch之bool查询
  • Vue页面中实现自动播放报警音
  • Python实现Socket.IO的完整指南
  • JavaSE - 易错题集 - 006
  • 学懂C++(六十):C++ 11、C++ 14、C++ 17、C++ 20新特性大总结(万字详解大全)
  • Idea 中的一些配置
  • PointNet++改进策略 :模块改进 | Residual MLP | PointMLP,简化原本复杂的局部几何特征提取器,减少计算同时提高效率
  • 记录近期iOS开发几个报错及解决方案
  • 在线查看 Android 系统源代码 AOSPXRef and AndroidXRef
  • Spring 的循环依赖
  • Git提交类型
  • 3D云渲染农场为何怎么贵?主要消耗成本介绍
  • Vue路由二(嵌套多级路由、路由query传参、路由命名、路由params传参、props配置、<router-link>的replace属性)
  • 【GIS开发小课堂】写一个高德地图巡航功能的小DEMO