【unity实战】使用unity的新输入系统InputSystem+有限状态机设计一个玩家状态机控制——实现玩家的待机 移动 闪避 连击 受击 死亡状态切换
最编程
2024-07-05 07:06:47
...
最终效果
文章目录
- 最终效果
- 前言
- 人物素材
- 新输入系统InputSystem的配置
- 动画配置
- 代码文件路径
- 状态机脚本
- 创建玩家不同的状态脚本
- 玩家控制
- 源码
- 完结
前言
前面我们已经写过了使用有限状态机制作一个敌人AI:【unity实战】在Unity中使用有限状态机制作一个敌人AI
那么玩家的状态机要怎么做呢?目前网上这一块内容也很少,当我们对人物的操作越来越多时,有限状态机技术可以很好的帮我们将各部分功能拆开,单独配置逻辑,代码更加优雅,接下来我们就用新输入系统InputSystem设计一个玩家状态机控制系统,其实跟之前的敌人有限状态机类似。
人物素材
https://bdragon1727.itch.io/16x16-pixel-adventures-character
新输入系统InputSystem的配置
新输入系统还不会使用的可以参考这篇文章:【推荐100个unity插件之18】Unity 新版输入系统InputSystem的基础使用
其实就是默认的配置加了攻击和闪避的操作
动画配置
动画基础知识:【Unity游戏开发教程】零基础带你从小白到超神27——混合状态,混合动画,动画分类
除了攻击动画,其他的都放在第一层
闪避动画我是通过不断修改玩家图片的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; // 是否死亡
[HideInInspector] public bool getHit; // 是否被击中
}
新增玩家状态机
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