赞
踩
前面我们已经写过了使用有限状态机制作一个敌人AI:【unity实战】在Unity中使用有限状态机制作一个敌人AI
那么玩家的状态机要怎么做呢?目前网上这一块内容也很少,当我们对人物的操作越来越多时,有限状态机技术可以很好的帮我们将各部分功能拆开,单独配置逻辑,代码更加优雅,接下来我们就用新输入系统InputSystem设计一个玩家状态机控制系统,其实跟之前的敌人有限状态机类似。
https://bdragon1727.itch.io/16x16-pixel-adventures-character
新输入系统还不会使用的可以参考这篇文章:【推荐100个unity插件之18】Unity 新版输入系统InputSystem的基础使用
其实就是默认的配置加了攻击和闪避的操作
除了攻击动画,其他的都放在第一层
闪避动画我是通过不断修改玩家图片的FlipY值实现的
重点讲讲攻击动画连击
两个参数控制进入,isMeleeAttack主要是有效防止播放最后一段连击后,再播放一次第一段攻击
如果动画播放90%再次按下就会进入下一段攻击
所有动画播放为1,即播放完时退出
定义状态类型枚举
// 定义状态类型枚举
public enum StateType
{
Idle, //待机
Move, //移动
Dodge, //闪避
MeleeAttack, //近战攻击
Hit, //受击
Death //死亡
}
抽象基类,定义了所有状态类的基本结构
//抽象基类,定义了所有状态类的基本结构
public abstract class IState
{
protected FSM manager;// 当前状态机
protected Parameter parameter;// 参数
public abstract void OnEnter();// 进入状态时的方法
public abstract void OnUpdate();// 更新方法
public abstract void OnFixedUpdate();// 固定更新方法
public abstract void OnExit();// 退出状态时的方法
}
可序列化的参数类,存储了角色的各种状态参数和配置
// 可序列化的参数类,存储了角色的各种状态参数和配置 using System; using UnityEngine; [Serializable] public class Parameter { [Header("属性")] public float health; // TODO:生命值 仅仅用于测试,实际生命值可能并不放在这里 [HideInInspector] public Animator animator; // 角色动画控制器 [HideInInspector] public AnimatorStateInfo animatorStateInfo; // 动画状态信息 [HideInInspector] public SpriteRenderer sr; // 精灵渲染器 [HideInInspector] public Rigidbody2D rb; // 刚体 [HideInInspector] public PlayerSystem inputSystem;//新的输入系统 [Header("移动")] public float normalSpeed = 3f; // 默认移动速度 public float attackSpeed = 1f; // 攻击时的移动速度 [HideInInspector] public Vector2 inputDirection; // 输入的移动方向 [HideInInspector] public float currentSpeed; // 当前移动速度 [Header("攻击")] public float meleeAttackDamage; // 近战攻击造成的伤害 [HideInInspector] public bool isMeleeAttack; // 是否进行近战攻击 [Header("闪避")] public float dodgeForce; // 闪避的力量 public float dodgeCooldown = 2f; // 闪避的冷却时间 [HideInInspector] public bool isDodging = false; // 是否在闪避中 [HideInInspector] public bool isDodgeOnCooldown = false; // 是否在闪避冷却中 [Header("受伤与死亡")] [HideInInspector] public bool isHurt; // 是否受伤 [HideInInspector] public bool isDead; // 是否死亡 }
新增玩家状态机
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; // 玩家有限状态机类 public class FSM : MonoBehaviour { private IState currentState; // 当前状态接口 protected Dictionary<StateType, IState> states = new Dictionary<StateType, IState>(); // 状态字典,存储各种状态 public Parameter parameter; // 状态机参数 public virtual void Awake() { parameter.rb = GetComponent<Rigidbody2D>(); parameter.animator = GetComponent<Animator>(); parameter.sr = GetComponent<SpriteRenderer>(); // 初始化各个状态,并添加到状态字典中 states.Add(StateType.Idle, new IdleState(this)); states.Add(StateType.Move, new MoveState(this)); states.Add(StateType.Dodge, new DodgeState(this)); states.Add(StateType.MeleeAttack, new MeleeAttackState(this)); states.Add(StateType.Hit, new HitState(this)); states.Add(StateType.Death, new DeathState(this)); TransitionState(StateType.Idle); // 初始状态为Idle } public virtual void OnEnable() { currentState.OnEnter(); } public virtual void Update() { //有效防止播放最后一段连击后,再播放一次第一段攻击 parameter.animator.SetBool("isMeleeAttack", parameter.isMeleeAttack); parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息 currentState.OnUpdate(); } public virtual void FixedUpdate() { currentState.OnFixedUpdate(); } // 状态转换方法 public void TransitionState(StateType type) { if (currentState != null) currentState.OnExit();// 先调用退出方法 currentState = states[type]; // 更新当前状态为指定类型的状态 currentState.OnEnter(); // 调用新状态的进入方法 } // 切换操作映射 public void SwitchActionMap(InputActionMap actionMap) { parameter.inputSystem.Disable(); // 禁用当前的输入映射 actionMap.Enable(); // 启用新的输入映射 } public void Move() { // 根据当前状态设置角色速度 parameter.currentSpeed = parameter.isMeleeAttack ? parameter.attackSpeed : parameter.normalSpeed; // 设置角色刚体的速度为移动方向乘以当前速度 parameter.rb.velocity = parameter.inputDirection * parameter.currentSpeed; FlipTo(); } // 翻转角色 public void FlipTo() { if (parameter.inputDirection.x < 0) { transform.localScale = new Vector3(-1, 1, 1); } if (parameter.inputDirection.x > 0) { transform.localScale = new Vector3(1, 1, 1); } } // 开始闪避技能冷却的协程 public void DodgeOnCooldown() { StartCoroutine(nameof(DodgeOnCooldownCoroutine)); } public IEnumerator DodgeOnCooldownCoroutine() { yield return new WaitForSeconds(parameter.dodgeCooldown);// 等待闪避冷却时间 parameter.isDodgeOnCooldown = false;// 闪避技能冷却结束,设置为不在冷却状态 } }
待机状态
using UnityEngine; /// <summary> /// 待机状态 /// </summary> public class IdleState : IState { public IdleState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { parameter.animator.Play("Idle"); } public override void OnUpdate() { // parameter.anim.SetFloat("speed", parameter.rb.velocity.magnitude); // 如果受伤 if (parameter.isHurt) { manager.TransitionState(StateType.Hit); } //如果输入的移动方向不为0 if (parameter.inputDirection != Vector2.zero) { manager.TransitionState(StateType.Move); } //闪避 if (parameter.isDodging) { manager.TransitionState(StateType.Dodge); } //真正近战攻击 if (parameter.isMeleeAttack) { manager.TransitionState(StateType.MeleeAttack); } } public override void OnFixedUpdate() { } public override void OnExit() { } }
移动状态
/// <summary> /// 移动状态 /// </summary> public class MoveState : IState { public MoveState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { parameter.animator.Play("Run"); } public override void OnUpdate() { // 如果想按速度切换移动或奔跑动画 // parameter.animator.SetFloat("speed", player.rb.velocity.magnitude); //受伤 if (parameter.isHurt) { manager.TransitionState(StateType.Hit); } //速度为0 if (parameter.rb.velocity.magnitude < 0.01f) { manager.TransitionState(StateType.Idle); } //闪避 if (parameter.isDodging) { manager.TransitionState(StateType.Dodge); } //近战攻击 if (parameter.isMeleeAttack) { manager.TransitionState(StateType.MeleeAttack); } } public override void OnFixedUpdate() { manager.Move(); } public override void OnExit() { } }
近战攻击状态
/// <summary> /// 近战攻击状态 /// </summary> public class MeleeAttackState : IState { public MeleeAttackState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { parameter.animator.SetTrigger("MeleeAttack"); } public override void OnUpdate() { //受伤 if (parameter.isHurt) { manager.TransitionState(StateType.Hit); } // 动画播放95%切换到待机状态 if (parameter.animatorStateInfo.normalizedTime >= .95f) { manager.TransitionState(StateType.Idle); } } public override void OnFixedUpdate() { manager.Move(); } public override void OnExit() { parameter.isMeleeAttack = false; } }
闪避状态
using System.Collections; using UnityEngine; /// <summary> /// 闪避状态 /// </summary> public class DodgeState : IState { public DodgeState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { parameter.animator.Play("Dodge"); } public override void OnUpdate() { //受击 if (parameter.isHurt) { manager.TransitionState(StateType.Hit); } //动画播放95%切换到待机状态 if (parameter.animatorStateInfo.normalizedTime >= .95f) { manager.TransitionState(StateType.Idle); } } public override void OnFixedUpdate() { manager.Move(); if(parameter.isDodging) Dodge(); } public override void OnExit() { parameter.isDodging = false; } // 进行闪避操作的方法 public void Dodge() { // 施加闪避力量,根据输入方向和设定的闪避力量 parameter.rb.AddForce(parameter.inputDirection * parameter.dodgeForce, ForceMode2D.Impulse); parameter.isDodgeOnCooldown = true; manager.DodgeOnCooldown();// 开始闪避冷却 } }
受击状态
/// <summary> /// 受击状态 /// </summary> public class HitState : IState { public HitState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { parameter.animator.Play("Hit"); //TODO:仅用于测试 parameter.health --; // 减少角色生命值 } public override void OnUpdate() { //TODO:仅用于测试,如果角色生命值小于等于0,转换到死亡状态 if (parameter.health <= 0) { manager.TransitionState(StateType.Death); } //动画播放95%切换到待机状态 if (parameter.animatorStateInfo.normalizedTime >= .95f) { manager.TransitionState(StateType.Idle); } } public override void OnFixedUpdate() { manager.Move(); } public override void OnExit() { parameter.isHurt = false; } }
死亡状态
/// <summary> /// 死亡状态 /// </summary> public class DeathState : IState { public DeathState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { parameter.isDead = true; manager.SwitchActionMap(parameter.inputSystem.UI);//切换为UI输入 parameter.animator.Play("Dead"); } public override void OnUpdate() { } public override void OnFixedUpdate() { } public override void OnExit() { } }
新增PlayerController,获取玩家的输入
using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : FSM { public override void Awake() { base.Awake(); // 初始化输入控制 parameter.inputSystem = new PlayerSystem(); parameter.inputSystem.Player.Move.performed += Move; parameter.inputSystem.Player.Move.canceled += StopMove; parameter.inputSystem.Player.Dodge.started += Dodge; parameter.inputSystem.Player.MeleeAttack.started += MeleeAttack; SwitchActionMap(parameter.inputSystem.Player); // 切换到游戏操作的输入映射 } void OnDisable() { // 禁用所有输入 parameter.inputSystem.Disable(); } public override void Update() { //TODO:用于测试 如果按下回车键,设置被击中状态为true if (Input.GetKeyDown(KeyCode.Return)) { // PlayerHurt(); parameter.isHurt = true; } base.Update(); } public void Move(InputAction.CallbackContext context) { parameter.inputDirection = parameter.inputSystem.Player.Move.ReadValue<Vector2>(); } // 停止移动,将输入方向设为零向量 public void StopMove(InputAction.CallbackContext context) { parameter.inputDirection = Vector2.zero; } // 触发闪避的方法 public void Dodge(InputAction.CallbackContext context) { //如果当前不在冷却中,则开始闪避 if(!parameter.isDodgeOnCooldown) parameter.isDodging = true; } // 近战攻击 public void MeleeAttack(InputAction.CallbackContext context) { parameter.isMeleeAttack = true; } }
效果
现在的闪避手感你可能觉得很奇怪,正常情况下你可能希望它优先级最高,可以打断所有正在的操作,比如打断攻击
修改动画,再最底层新建个覆盖图层专门控制闪避动画播放,新增参数控制进入和退出
修改Parameter
public float dodgeDuration = 0.5f;//闪避持续时间
[HideInInspector] public float dodgeTimer = 0f;//闪避计时器
修改DodgeState,闪避状态
/// <summary> /// 闪避状态 /// </summary> public class DodgeState : IState { public DodgeState(FSM manager) { this.manager = manager; this.parameter = manager.parameter; } public override void OnEnter() { // parameter.animator.Play("Dodge"); } public override void OnUpdate() { parameter.animator.SetBool("isDodging", parameter.isDodging); //受击 if (parameter.isHurt) { manager.TransitionState(StateType.Hit); } //动画播放95%切换到待机状态 // if (parameter.animatorStateInfo.normalizedTime >= .95f) // { // manager.TransitionState(StateType.Idle); // } if (parameter.isDodging == false) { manager.TransitionState(StateType.Idle); } } public override void OnFixedUpdate() { manager.Move(); Dodge(); } public override void OnExit() { parameter.isDodging = false; } // 进行闪避操作的方法 public void Dodge() { //冷却结束 if (!parameter.isDodgeOnCooldown) { if (parameter.dodgeTimer <= parameter.dodgeDuration) { // 施加闪避力量,根据输入方向和设定的闪避力量 parameter.rb.AddForce(parameter.inputDirection * parameter.dodgeForce, ForceMode2D.Impulse); parameter.dodgeTimer += Time.fixedDeltaTime; } else { parameter.isDodging = false; parameter.isDodgeOnCooldown = true; manager.DodgeOnCooldown();// 开始闪避冷却 parameter.dodgeTimer = 0f; } } } }
效果,现在闪避可以打断任何正在进行的动画
修改玩家受伤和死亡状态脚本的动画触发
parameter.animator.SetTrigger("isHit");
parameter.animator.SetBool("isDead", parameter.isDead);
https://gitcode.net/unity1/playerfsm
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。