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

news2024/12/24 3:20:54

本章项目成果展示

我们打开上一篇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.coloradmin.cn/o/2137230.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity】Unity Shader样例:顶点根据时间放大缩小

文章目录 案例说明效果展示适用模型范围代码示例 案例说明 本案例提供一个单独的Shader&#xff0c;使得模型顶点&#xff08;仅渲染&#xff09;根据时间放大缩小&#xff0c;往复循环。 效果展示 适用模型范围 全部 代码示例 Shader "Unlit/Sha_TestScale" {P…

Multiscale Vision Transformers(MViT)

Multiscale Vision Transformers 多尺度视觉Transformer (MViT) 是一种新型的视觉识别模型&#xff0c;主要用于处理图像和视频。它结合了传统多尺度特征层次结构与Transformer模型的优势&#xff0c;旨在提高视频和图像的识别性能。 1. 多尺度视觉Transformer (MViT) 的概念…

数据可视化与分析:数据时代的关键工具

一、引言 数据可视化与分析是大数据时代中最为重要的技术之一。随着数据量的不断增加&#xff0c;如何有效地理解、解释和利用数据&#xff0c;已经成为各行各业面临的关键挑战。数据可视化通过图表、图形和互动界面将数据以直观的方式呈现&#xff0c;帮助用户快速识别数据中…

运行QWen2-1.5b模型时报错“RuntimeError: cutlassF: no kernel found to launch!”

运行QWen2-1.5b模型时报错“RuntimeError: cutlassF: no kernel found to launch!” #问题&#xff1a;成功加载QWen2-1.5b模型&#xff0c;但是推理时 “model.generate( model_inputs.input_ids, top_pself.top_p, max_new_tokens512 )时”&#xff0c;报错“RuntimeError: …

TCP客户端编码和解码处理:发送和接收指定编码消息

文章目录 引言基于Netty实现TCP客户端Netty发送GBK编码指令Netty接收GBK编码基于Channel发送指令基于ChannelHandlerContext发送指令:建立连接时发送登陆指令开启日志,查看报文信息基于ChannelInboundHandlerAdapter进行业务逻辑处理原生API实现TCP客户端基于DataOutputStrea…

vue2制作高复用页面

记录一下页面搭建记录&#xff0c;利用vue2组件化开发的思想。这个页面适合于大部分信息管理系统~。模板固定&#xff0c;每次使用&#xff0c;直接修改表单表格参数&#xff0c;api接口等。 以上图页面为例&#xff0c;一个基础数据信息页面可以分为&#xff0c;分类&#xff…

数据恢复软件推荐:轻松找回删除的文件!

在使用电脑的过程中&#xff0c;有时我们会误操作或者因为其他原因而删除了一些重要的文件。当我们需要这些文件却找不到时&#xff0c;就会产生很大的困扰。那么&#xff0c;如果你的电脑中的文件被误删了&#xff0c;应该怎么找回被删除的文件呢?下面&#xff0c;小编整理出…

办公必备的高效翻译工具大揭秘

网易翻译是我最早接触的一款翻译工具&#xff0c;随着翻译时候需求的增加让开始了解其他的翻译工具。如果你也正在为你的翻译需求头疼那不妨看看我今天要介绍的工具有没有符合你需求的。 1.福昕在线翻译 链接直达&#xff1a;https://fanyi.pdf365.cn/doc 这个工具比较适合…

【大模型专栏—实战篇】基于RAG从0到1搭建AI科研知识库

大模型专栏介绍 &#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文为大模型专栏子篇&#xff0c;大模型专栏将持续更新&#xff0c;主要讲解大模型从入门到实战打怪升级。如有兴趣&#xff0c;欢迎您的阅读。 &#x1f4…

nvidia-docker Failed to initialize NVML: Unknown Error

nvidia镜像拉下来了&#xff0c;但是运行不了。 执行以下命令 sudo docker run --rm --runtimenvidia --gpus all ubuntu nvidia-smi报错 Failed to initialize NVML: Unknown Error参考 https://stackoverflow.com/questions/72932940/failed-to-initialize-nvml-unknown-er…

基于springboot 校园闲置物品交换平台设计与实现---附源码74557

摘 要 随着我国互联网技术的飞速发展&#xff0c;网络购物已经成为人们日常生活的重要组成部分。特别是在校园中&#xff0c;由于学生群体的特殊性&#xff0c;闲置物品交易的需求日益增长。然而&#xff0c;目前校园闲置物品物品交易市场仍然存在许多问题&#xff0c;如信息不…

声波的波数,通常用k表示

声波的波数&#xff08;通常用 k 表示&#xff09;是描述声波空间变化的一个参数&#xff0c;它与声波的频率和介质中的传播速度有关。波数 k 是一个具体值&#xff0c;并且在均匀介质中它是固定的&#xff0c;计算公式如下&#xff1a; 均匀介质中的波数是一个具体值&#xff…

react crash course 2024 (1)理论概念

state的作用 react hooks 而无需写一个class jsx 样式用 spa

判断当前环境是否为docker容器下

判断当前环境是否为docker容器下 webshell后或登录到系统后台&#xff0c;判断是否为docker容器可使用如下方法&#xff1a; 方式一&#xff1a;使用ls -alh命令查看是否存在.dockerenv来判断是否在docker容器环境内 ls -alh /.dockerenv如下图无.dockerenv文件&#xff0c;所…

Clickhouse使用笔记

clickhouse官方文档&#xff1a;https://clickhouse.com/docs/zh/sql-reference/data-types/decimal 一&#xff0c;建表 create table acitivity_user_record ( id String DEFAULT generateUUIDv4(), -- 主键自增 activityId String, userId String, userName Nullable(Strin…

【论文解读】图像序列识别:CRNN技术在场景文本识别中的应用与突破(附论文地址)

论文地址&#xff1a;https://arxiv.org/pdf/1507.05717 这篇文章的标题是《An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition》&#xff0c;作者是Baoguang Shi, Xiang Bai和Cong Yao&#xff0c…

代码随想录训练营 Day58打卡 图论part08 拓扑排序 dijkstra朴素版 + 堆优化版

代码随想录训练营 Day58打卡 图论part08 一、拓扑排序 例题&#xff1a;卡码117. 软件构建 题目描述 某个大型软件项目的构建系统拥有 N 个文件&#xff0c;文件编号从 0 到 N - 1&#xff0c;在这些文件中&#xff0c;某些文件依赖于其他文件的内容&#xff0c;这意味着如果…

不同的二叉搜索树

题目 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff…

JSP经典设计模式流程分析:JSP+JavaBean设计模式+MVC设计模式

JSP两种经典设计模式 Model1设计模式:JSPJavaBean 架构图 什么是JavaBean JavaBean是一种JAVA语言写成的可重用组件&#xff0c;它遵循特定的编程规范&#xff0c;如类必须是公共的、具有无参构造函数&#xff0c;并提供getter/setter方法等。这里的JavaBean不单单指的是实体…

五、回溯算法-算法总结

文章目录 五、回溯算法5.1 背景5.2 模板5.3 集合类5.3.1 子集5.3.2 子集2 5.4 排列类5.4.1 全排列5.4.2 全排列2 5.5 组合类5.5.1 组合总和5.5.2 电话号码的字母组合 五、回溯算法 5.1 背景 回溯法&#xff08;backtrack&#xff09;常用于遍历列表所有子集&#xff0c;是 DF…