一、有限状态机

一、状态基类

在创建一个FSM的有限状态机的缩写脚本


例:比如枚举这个状态,现在不确定是给敌人还是玩家,那么就写一个枚举的基类

在这里先创建了三个抽象方法,进行状态的切换;

并且这是一个状态基类,不需要挂载和继承Mono

using System;
/// <summary>
/// 状态对象基类
/// 之后所有状态都继承这个基类
/// Idle,Walk,Attack
/// </summary>
public abstract class StateBase 
{//当前状态对象 代表的枚举状态public Enum StateType;//进入public abstract void OnEnter();//更新public abstract void OnUpdate();//退出public abstract void OnExit();
}

初始化函数中传入这个枚举的变量

这是首次的初始化

至此,这个状态基类完成


二、玩家结构说明

三、玩家输入层与音效层

1、输入层

这个输入层的代码是一个类,不需要继承

使用水平和纵轴,不需要再重新定义,直接lambo表达式解决

public class Player_Input 
{public float Horizontal {  get=>Input.GetAxis("Horizontal"); }public float Vertical { get => Input.GetAxis("Vertical"); }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Player_Input 
{private KeyCode runKeyCode = KeyCode.LeftShift;private KeyCode attackKeyCode=KeyCode.J;public float Horizontal {  get=>Input.GetAxis("Horizontal"); }public float Vertical { get => Input.GetAxis("Vertical"); }//按键持续按下状态public bool GetKey(KeyCode key){return Input.GetKey(key);}//按键按下瞬间public bool GetKeyDown(KeyCode key){return Input.GetKeyDown(key);}//获取当前Run按键有没有持续按下中public bool GetRunKey()//判断持续按下的按键{return  GetKey(runKeyCode);}//获取当前Attack按键有没有持续按下中public bool GetAttackKeyDown(){return GetKeyDown(runKeyCode);}
}

2、音效层

注:这里的audio有一个波浪线

原因是FSM继承的Mono里面有废弃的api,这里意思是询问你是否用了一个新的audio,而不是mono的api
所以前面加上一个new即可

public class Player_Audio 
{private AudioSource audioSource;public Player_Audio(AudioSource audioSource){this.audioSource = audioSource;}//播放指定的音效public void PlayAudio(AudioClip audioClip){audioSource.PlayOneShot(audioClip);}
}

3、脚本控制

第一

新建一个空物体,坐标归置为0,改名为Player

将模型作为子物体

这样做的好处是:功能实现在父物体上,子物体可以随时更换模型

第二

新建一个脚本“玩家控制”

先继承FSM,实现抽象方法

然后对音效和输入进行一个初始化;这里的音效需要传入一个自身的组件,因为他是一个构造函数

public class Player_Controller : FSMController
{public override Enum CurrentState { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }private Player_Input input;private new Player_Audio audio;private void Start(){input=new Player_Input();audio = new Player_Audio(GetComponent<AudioSource>());}
}

至此,玩家的音效和输入层结束


四、角色移动动画设置

为玩家的AnimatorController配置基于动画混合树的移动动画

(一)调整动画资源

由于这个动画镜像之后,左右不相同

所以复制一份动画,并进行相应的调整,为右侧动画即可

先来左边:取消他的镜像Mirror,按照初始动画设置

将偏移值设置为20

之后又边:偏移值则为-20,不使用镜像即可调整

注:这里最好把动画的Y轴全部勾选

(二)动画混合树

先建一个混合树

这里的类型选择2D的自由方向

新建一个混合树,会自动添加一个Blend参数,这里并不需要,直接删除

添加一个动画片段

添加两个参数,这里使用中文不会有任何影响


个人理解:动画混合树,就是对多个动画的一种平滑过渡,依靠的是数值

举个例子:WS前后移动,中间有一个动画状态为静止,那么按下W会从0~1的数值增长,对应的动画也进行平滑的过渡。

前移加上奔跑制作方法:向前的移动数值是1,而奔跑可能是1.5或者2,那么将奔跑动画的数值设置为2即可。在代码中,让这个W的值为1时在加一个1,即可实现奔跑效果

(三)混合树的制作

这里我取消了根运动

在混合树中添加第一个状态(待机),不需要按下任何按键,所以XY是0;

在混合树中添加第二个状态(前进),需要按下W,所以Y轴是1时,完全是前进动画

在混合树中添加第三个状态(后退),需要按下S,所以Y轴是-1时,完全是后退动画

在混合树中添加第四个状态(向左),需要按下A,所以X轴是-1时,完全是向左动画

在混合树中添加第五个状态(向右),需要按下D,所以X轴是-1时,完全是向右动画

在混合树中添加第六个状态(奔跑),需要长按A,所以当X轴是1加上一个1时,完全是奔跑动画


五、角色移动状态实现

1、实现人物移动未同步动画

这里的PlayMove来持有playcontroller,所以把它设置为了属性

这是第一步

下面是第二步

public enum PlayerState
{//移动Player_Move,
}public class Player_Controller : FSMController
{public override Enum CurrentState { get => playerState; set => playerState=(PlayerState)value; }private PlayerState playerState;public Player_Input input { get; private set; }public new Player_Audio audio { get; private set; }public CharacterController characterController { get; private set; }private void Start(){input=new Player_Input();audio = new Player_Audio(GetComponent<AudioSource>());characterController = GetComponent<CharacterController>();//默认是移动状态ChangeState(PlayerState.Player_Move);}
}

这是第一步的代码

注:这里的SimpleMove方法传入的ying'ga'shi'yi

public class Player_Move : StateBase
{public Player_Controller player;private float moveSpeed = 90;private float rotateSpeed = 90;public override void Init(FSMController controller, Enum stateType){base.Init(controller, stateType);player=controller as Player_Controller;}public override void OnUpdate(){float h = player.input.Horizontal;float v = player.input.Vertical;Move(h, v);}private void Move(float h,float v){//移动Vector3 dir= new Vector3(0,0,h);dir=player.transform.TransformDirection(dir);player.characterController.SimpleMove(dir*moveSpeed);//旋转Vector3 rot = new Vector3(0,v,0);player.transform.Rotate(rot*Time.deltaTime*rotateSpeed);//todo:同步模型动画}public override void OnEnter(){}public override void OnExit(){}
}

2、实现人物移动并同步动画

新建代码用于模型控制

在这个代码中包含武器、动画、特效等

将这个代码拖拽给人物模型

先持有玩家控制的脚本以及动画组件

并对变量进行初始化

这里是一个意思

然后更新相关的移动参数 ,设置动画参数,实现关联

//动画、武器层、刀光效果
public class Player_Model : MonoBehaviour
{private Player_Controller player;private Animator animator;public void Init(Player_Controller player){this.player = player;animator = GetComponent<Animator>();}//更新移动相关参数public void UpdateMovePar(float x,float y){animator.SetFloat("左右", x);animator.SetFloat("前后", y);}
}

然后再玩家控制脚本中,添加这个属性 ;并在开始时初始化

在玩家控制脚本中,实现玩家的shift奔跑效果

先定义变量奔跑动画过渡时间、移动速度、旋转速度

添加一个bool的属性,可以只得到,不设置: 当玩家按下w和shift时,重新赋值移动速度,并返回这个bool变量

在update中,定义局部变量水平和纵轴

如果处于奔跑状态,并且过渡时间小于1,那么过渡时间加到1

如果出于非奔跑状态并且过渡时间大于零,那么过渡时间慢慢减到0

如果没有移动,过渡时间大于零了,那么过渡时间会逐渐至0

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Player_Move : StateBase
{public Player_Controller player;private float runTransition = 0;private float moveSpeed = 90;private float rotateSpeed = 90;private bool isRun{get{bool temp= player.input.GetRunKey() && player.input.Vertical > 0;if (temp) moveSpeed = 200f;else moveSpeed = 100f;return temp;}}public override void Init(FSMController controller, Enum stateType){base.Init(controller, stateType);player=controller as Player_Controller;}public override void OnUpdate(){float h = player.input.Horizontal;float v = player.input.Vertical;if (v >= 0){if (isRun && runTransition < 1) runTransition += Time.deltaTime / 2;//慢慢加到1else if (!isRun && runTransition > 0) runTransition -= Time.deltaTime / 2;//慢慢减到0}else if (runTransition > 0) runTransition -= Time.deltaTime / 2;Move(h, v+runTransition);}private void Move(float h,float v){//移动Vector3 dir= new Vector3(0,0,v);dir=player.transform.TransformDirection(dir);player.characterController.SimpleMove(dir *Time.deltaTime * moveSpeed);//旋转Vector3 rot = new Vector3(0,h,0);player.transform.Rotate(rot*Time.deltaTime*rotateSpeed);//同步模型动画player.model.UpdateMovePar(h, v);}public override void OnEnter(){}public override void OnExit(){}
}

六、虚拟相机Cinemachne

常规方法是:虚拟摄像机始终跟随玩家,但一些游戏设定,例如:屏幕晃动,摄像机消失等
这些效果,操作虚拟摄像机较为复杂

所以,在玩家下面创建一个空物体,让虚拟摄像机跟随这个空物体,效果也实现在这个空物体上

摄像机的绑定选择始终看向目标

X Y Z的平滑值设置设置为0.5

七、状态机重构

1、Enum的Bug

Enum他是一个Class的引用类型,比较的是值而不是引用

所以这里永远不会相等

这里其实不相等的

修改为Equals()进行比较,只考虑值而不考虑引用

2、状态机去除enum

因为这里的Enum不知道是玩家的还是怪物的,他是一个具体的类型

所以在这里重构为泛型

把当前代码中的所有Enum都改成T泛型

此时会有大量报错

同时要确定stateBase的类型


这个代码继承了Fsm,而Fsm是一个泛型,所以在派生类中传入PlayState的枚举类型

那么基类的泛型T就会接收到这个枚举类型

以后的怪物状态等,也使用这个方法

这就是泛型的应用


这里的报错就是枚举类型和泛型类型无法比较,使用强转划不来

此时确定StateBase的类型

将所有的stateBase基类都加上泛型

3、优化循环

这里的循环,每一个集合里面的对象,但是对象如果有大量的,就会走一步卡一步,在这里消耗性能,所以在这里使用字典

把集合改成了字典,这样就可以每次获取,不需要查找

那他的Key就是T(玩家状态或怪物状态)

接下来优化代码中的反射

原因是:使用反射对性能的消耗很大

增加一个泛型K,并且是泛型约束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/468272.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++20 概念与约束(2)—— 初识概念与约束

1、概念 C20 中引入新的编译期关键字 concept 用于创建概念。个人认为将其翻译为“构思”更为贴切。直接使用时&#xff0c;它更像一个只能用于模板的布尔类型关键字。 而如果用于模板中&#xff0c;他会将模板类型先带入自身&#xff0c;当自身条件为 true 才会实例化模板&…

程序员会被AI取代吗?

时间&#xff1a;2024年 11月 10日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 音频&#xff1a;喜马拉雅 近年来&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的发展&#xff0c;技术圈内关于“程序员会被AI取代…

2024 第五次周赛

A: 直接遍历即可 #include<bits/stdc.h> using namespace std;typedef long long ll; typedef pair<ll, ll>PII; const int N 2e6 10; const int MOD 998244353; const int INF 0X3F3F3F3F;int n, m; int main() {cin >> n;int cnt 0;for(int i 0; i …

十五、Linux线程(二)

4.线程的分离属性 通过属性设置线程的分离 1.线程属性类型&#xff1a; pthread_attr_t attr; 2.线程属性操作函数&#xff1a; &#xff08;1&#xff09;对线程属性变量的初始化 int pthread_attr_init(pthread_attr_t* attr); &#xff08;2&#xff09;设置线程分离属…

stm32 ADC实例解析(3)-多通道采集互相干扰的问题

文章目录 一、问题现象&#xff1a;二、原因分析&#xff1a;1、测量值不准问题分析&#xff1a;2、采样干扰问题分析 三、解决办法&#xff1a;1、硬件&#xff1a;&#xff08;1&#xff09;、电源供电&#xff08;2&#xff09;、引脚电容&#xff08;3&#xff09;、减少采…

定制ShardingSphere-Proxy镜像满足业务需求

Sharding官方提供的proxy镜像是基础版的&#xff0c;如果我们使用Sharding有以下任意需求&#xff0c;就需要添加额外的依赖到容器{path}/ext-lib目录下。 向Docker容器中添加jar包的方式多种多样&#xff0c;推荐采取使用Dockerfile的方式添加依赖。将原有的镜像作为基础镜像&…

【数据分享】1901-2023年我国省市县镇四级的逐年降水数据(免费获取/Shp/Excel格式)

之前我们分享过1901-2023年1km分辨率逐月降水栅格数据和Shp和Excel格式的省市县四级逐月降水数据&#xff0c;原始的逐月降水栅格数据来源于彭守璋学者在国家青藏高原科学数据中心平台上分享的数据&#xff01;基于逐月数据我们采用求年累计值的方法得到逐年降水栅格数据&#…

virtualBox部署minikube+istio

环境准备 virtualBox安装 直接官网下载后安装即可&#xff0c;网上也有详细教程。镜像使用的centos7。 链接&#xff08;不保证还可用&#xff09;&#xff1a;http://big.dxiazaicc.com/bigfile/100/virtualbox_v6.1.26_downcc.com.zip?auth_key1730185635-pWBtV8LynsxPD0-0-…

深入浅出WebSocket(实践聊天室demo)

文章目录 什么是WebSocket?WebSocket连接过程WebSocket与Http的区别重连机制完整代码使用方法心跳机制实现聊天室demo(基于Socket.io)参考文章、视频小广告~什么是WebSocket? WebSocket 是一种在单个TCP连接上进行全双工通信的协议(计算机网络应用层的协议) 在 WebSocket A…

[CKS] Audit Log Policy

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于audit policy的题目。 What’s the audit policy 使用K8s Audit Policy&#xff0c;管理员可以定义哪些操作需要被审计&#xff0c;包括创建、删除、更新和查看集群中的资源。审计记录包括操作的时…

【C++】map和set的介绍及使用

前言&#xff1a; map和 set 是 C STL&#xff08;标准模板库&#xff09;中的两种非常重要的容器&#xff0c;它们基于一种叫做平衡二叉搜索树&#xff08;通常是红黑树&#xff09;的数据结构来实现。在 C 中&#xff0c;map 是一个键值对容器&#xff0c;set 只存储唯一的键…

ai外呼机器人的作用有哪些?

ai外呼机器人具有极高的工作效率。日拨打成千上万通不是问题&#xff0c;同时&#xff0c;机器人还可以快速筛选潜在客户&#xff0c;将更多精力集中在有价值的客户身上&#xff0c;进一步提升营销效果。183-3601-7550 ai外呼机器人的作用&#xff1a; 1、搭建系统&#xff0c…

QT版发送邮件程序

简单的TCP邮箱程序 **教学与实践目的&#xff1a;**学会网络邮件发送的程序设计技术。 1.SMTP协议 邮件传输协议包括 SMTP&#xff08;简单邮件传输协议&#xff0c;RFC821&#xff09;及其扩充协议 MIME&#xff1b; 邮件接收协议包括 POP3 和功能更强大的 IMAP 协议。 服务…

汽车牌照识别系统的设计与仿真(论文+源码)

1设计原理 车牌识别系统的设计是一项利用车辆的动态视频或者静态图像实现牌照区域定位车牌号码识别的技术。其硬件部分通常包括触发设备、拍摄设备、照明设备、图像收集设备、进行车牌号码识别的处理器等&#xff0c;其软件的关键部分包含车牌区域定位的算法、车牌字符的分割算…

vue通过iframe方式嵌套grafana图表

文章目录 前言一、iframe方式实现xxx.xxx.com拒绝连接登录不跳转Cookie 的SameSite问题解决不显示额外区域(kiosk1) 前言 我们的前端是vue实现的&#xff0c;监控图表是在grafana中的&#xff0c;需要在项目web页面直接显示grafana图表 一、iframe方式实现 xxx.xxx.com拒绝连…

学习笔记:黑马程序员JavaWeb开发教程(2024.11.9)

9.1 Mybatis-基础操作-环境准备 这里也没做&#xff0c;到时候写案例&#xff0c;如果需要环境配置什么的&#xff0c;可以看看这个 9.2 Mybatis-基础操作-删除 删除需要动态获取需要删除的id&#xff0c;使用方法传参&#xff0c;#{}的方式实现 在编写的delete方法中&a…

[Docker#3] LXC | 详解安装docker | docker的架构与生态

目录 1.LXC容器操作 安装LXC LXC容器操作步骤 2.理论 LXC 是什么&#xff1f; Docker 是什么 Docker 和虚拟机的区别 Docker 和 JVM 虚拟化的区别 Docker 版本 ⭕Docker 官方网站&#xff08;建议收藏&#xff09; Docker 架构 生活案例 Docker 生态 Docker 解决…

Spark的学习-02

Spark Standalone集群的安装 架构&#xff1a;普通分布式主从架构 主&#xff1a;Master&#xff1a;管理节点&#xff1a;管理从节点、接客、资源管理和任务 调度&#xff0c;等同于YARN中的ResourceManager 从&#xff1a;Worker&#xff1a;计算节点&#xff1a;负责利用自己…

白杨SEO:百度在降低个人备案类网站搜索关键词排名和流量?怎样应对?【参考】

很久没有写百度或者网站这块内容了&#xff0c;一是因为做百度网站朋友越来越少&#xff0c;不管是个人还是企业&#xff1b;二是百度上用户搜索与百度给到网站的流量都越来越少。 为什么想到今天又来写这个呢&#xff1f;因为上个月有个朋友来咨询我说网站百度排名全没了&…

一个怀旧,俺的第一个共享软件

今天网友说起了 福彩双色球的程序。俺就想起这个来了&#xff0c;这是俺的第一个共享软件&#xff0c;收入大约15000。在当时来说&#xff0c;速度算是最快的。有些地方用了汇编优化&#xff08;题外话&#xff0c;最近俺看到新闻&#xff0c;FFmpeg的作者也用汇编优化 性能提升…