『功能项目』怪物的有限状态机【42】

news/2024/9/20 0:41:19 标签: Unity引擎, C#, 黑板有限状态机

本章项目成果展示

我们打开上一篇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.niftyadmin.cn/n/5666394.html

相关文章

封装svg图片

前言 项目中有大量svg图片&#xff0c;为了方便引入&#xff0c;所以对svg进行了处理 一、svg是什么&#xff1f; svg是可缩放矢量图形&#xff0c;是一种图片格式 二、使用步骤 1.创建icons文件夹 将icons文件夹放进src中&#xff0c;并创建一个svg文件夹和index.js&…

基于windows下docker安装HDDM并运行

安装主要教程 如何安装HDDM(基于windows下 docker 和 linux) | 传鹏的实验室 (chuan-peng-lab.netlify.app) 安装时遇到的问题 1.下载完docker安装包&#xff0c;安装提示不适合本电脑 解决办法&#xff1a; 第一步&#xff1a;开启CPU虚拟化 Windows电脑如何开启CPU虚拟化…

VirtualBox增加磁盘并给docker用

在VirtualBox新增磁盘 在虚拟机停止的情况下依次选择&#xff0c;然后创建新磁盘 虚拟机新磁盘创建分区、格式化、挂载分区 开机自动挂载新磁盘分区/dev/sdb1&#xff1a; nano /etc/fstab末尾添加一行&#xff1a; /dev/sdb1 /disk02 e…

在CentOS上搭建NFS服务器

环境:CentOS5.2 由于CentOS里面已经装了NFS服务&#xff0c;故不需要安装该服务&#xff0c;只需要进行如下的设置即可&#xff1a; 1、 修改/etc/exports文件&#xff0c;添加如下内容&#xff1a; /home/guochongxin *(rw,sync,no_root_squash) 保存后执行&#xff1a; e…

3D虚拟商城是什么?有哪些优势?

在数字化转型的澎湃浪潮中&#xff0c;3D虚拟商店作为一股革新力量&#xff0c;正逐步构筑起商业展示与交易的全新维度&#xff0c;成为企业及商户不可或缺的战略资产。视创云展为品牌搭建3D虚拟商城提供技术支持&#xff0c;凭借高度精细的三维模拟空间&#xff0c;不仅为顾客…

(黑马点评)二、短信登录功能实现

2.1 基于传统Session实现的短信登录及其校验 2.1.1 基于Session登录校验的流程设计 2.1.2 实现短信验证码发送功能 请求接口/user/code请求类型post请求参数phone返回值无 /*** 发送手机验证码*/PostMapping("/code")public Result sendCode(RequestParam("ph…

Java数据结构 (泛型第二节) 泛型擦除机制/泛型的限制/上界下界

书接上回&#xff1a;Java数据结构 (泛型第一节) 为什么要有泛型/泛型语法/泛型方法-CSDN博客 访问作者Github: https://github.com/Joeysoda/Github_java/blob/main/20240908%E6%B3%9B%E5%9E%8B/src/%E6%B3%9B%E5%9E%8B.java 目录 1. 为什么要有擦除机制&#xff1f; 2. 类…

C++ const引用(这里不讲返回,只是讲引用,拷贝构造篇章会有更详细的讲解)

个人主页&#xff1a;Jason_from_China-CSDN博客 所属栏目&#xff1a;C系统性学习_Jason_from_China的博客-CSDN博客 所属栏目&#xff1a;C知识点的补充_Jason_from_China的博客-CSDN博客 使用规则 引用常量对象&#xff1a;可以引用一个常量对象&#xff0c;但必须使用常量引…