• 实现技能释放与技能切换功能,做一个居合斩技能
  • 实现技能管理类SkillManager管理方向键注册的技能以及切换当前选中技能
  • 实现Skill类,包含释放技能需要满足的条件以及技能开始,执行,结束过程的逻辑
  • 实现技能释放状态,在状态中执行当前选中技能的逻辑

实现技能管理类SkillManager

在场景中添加一个空物体,挂载SkillManager脚本。SkillManager是单例,所有类都访问同一个对象。

image-20250603105442340

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class PlayerSkillManager : MonoBehaviour
{
public static PlayerSkillManager instance; // 单例实例
public bool canChangeSkill = true; // 是否允许切换技能
public Player player; // 玩家对象引用

public Skill upSkill; // 上方向技能
public Skill downSkill; // 下方向技能
public Skill leftSkill; // 左方向技能
public Skill rightSkill; // 右方向技能

public Skill currentSkill; // 当前选中的技能

// 初始化单例和技能对象
void Awake()
{
if (instance != null)
Destroy(instance.gameObject); // 保证只有一个实例
else instance = this;

upSkill = new LightCut(); // 实例化上方向技能
downSkill = new ShiledCast(); // 实例化下方向技能
leftSkill = new HolySlash(); // 实例化左方向技能
}

// 注册十字键技能切换事件
void Start()
{
player.playerInput.GamePlay.ChangeSkill.performed += OnDPadPerformed;
}

// 十字键输入回调,根据方向切换技能
private void OnDPadPerformed(UnityEngine.InputSystem.InputAction.CallbackContext context)
{
if (!canChangeSkill) return; // 不允许切换时直接返回

Vector2 dpad = context.ReadValue<Vector2>();
if (dpad == Vector2.zero) return;

// 优先判断上下左右
if (dpad.y > 0.5f)
currentSkill = upSkill;
else if (dpad.y < -0.5f)
currentSkill = downSkill;
else if (dpad.x < -0.5f)
currentSkill = leftSkill;
else if (dpad.x > 0.5f)
currentSkill = rightSkill;

Debug.Log("当前技能切换为: " + currentSkill.skillName);
}

// 注销事件
void OnDestroy()
{
player.playerInput.GamePlay.ChangeSkill.performed -= OnDPadPerformed;
}
}
  • SkillManager管理当前四个方向注册的技能和当前选中的技能

  • PlayerSkillManager 这个类的实例是在场景加载时,当它所在的 GameObject 被激活时,由 Unity 自动调用其 Awake() 方法进行初始化的。

    具体流程如下:

    1. 你在场景中挂载了一个带有 PlayerSkillManager 脚本的 GameObject(比如叫“PlayerSkillManager”)。
    2. 当场景加载,这个 GameObject 被激活时,Unity 会自动调用 Awake() 方法。
    3. Awake() 里,instance = this; 这行代码把当前对象赋值给静态变量 instance,实现单例模式。
    4. 这样,PlayerSkillManager.instance 就可以在全局访问到当前唯一的技能管理器实例。

实现Skill类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 技能基类,所有具体技能继承自此类
public class Skill
{
public string skillName; // 技能名称
public bool skillFinished = false; // 技能是否释放完成
public bool skillTrigger = false; // 技能触发标志,用在一些特殊技能中

// 判断技能是否可用,子类可重写
public virtual bool IsSkillAvailable()
{
return true;
}

// 释放技能的主逻辑,子类可重写
public virtual void ReleaseSkill()
{
Debug.Log("Releasing skill: " + skillName);
skillFinished = false; // 技能释放开始,标记为未完成
}

// 技能释放结束时调用,子类可重写
public virtual void SkillReleaseOver()
{
Debug.Log("Finish skill: " + skillName);
}

// 使用技能(如主动触发),子类可重写
public virtual void UseSkill()
{
Debug.Log("Using skill: " + skillName);
}

// 技能的每帧更新,子类可重写
public virtual void Update()
{

}

// 技能的物理帧更新,子类可重写
public virtual void FixedUpdate()
{

}
}

实现技能释放逻辑

  • 在PlayerState中注册技能释放输入事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public virtual void Enter()
{
//Debug.Log("Enter"+animBoolName);
// 获取玩家组件
rigidBody = player.rigidBody;
animator = player.animator;
playerInput = player.playerInput;
physicsCheck = player.physicsCheck;
animFinTrigger = false;


// 刚进入时读取当前输入,防止状态切换时输入丢失
inputXY = playerInput.GamePlay.Move.ReadValue<Vector2>();

// 设置动画Bool参数
if (!string.IsNullOrEmpty(animBoolName) && HasAnimatorParameter(animator, animBoolName, AnimatorControllerParameterType.Bool))
animator.SetBool(animBoolName, true);
// 设置动画Trigger参数
if (!string.IsNullOrEmpty(animTriggerName) && HasAnimatorParameter(animator, animTriggerName, AnimatorControllerParameterType.Trigger))
animator.SetTrigger(animTriggerName);

// 注册技能释放输入事件
player.playerInput.GamePlay.ReleaseSkill.started += ReleaseSkill;
}

private void ReleaseSkill(InputAction.CallbackContext context)
{
// 检查当前技能是否不为空且可用
if (PlayerSkillManager.instance.currentSkill != null && PlayerSkillManager.instance.currentSkill.IsSkillAvailable())
player.stateMachine.ChangeState(player.realseSkillState);
else
Debug.Log("Current skill is not available or null");
}

实现技能释放状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class PlayerRealseSkillState : PlayerState
{
private Skill currentSkill; // 当前释放的技能

// 构造函数,初始化释放技能状态
public PlayerRealseSkillState(Player player, PlayerStateMachine stateMachine, string animBoolName, string animTriggerName) : base(player, stateMachine, animBoolName, animTriggerName)
{

}

// 进入释放技能状态时调用
public override void Enter()
{
base.Enter();
PlayerSkillManager.instance.canChangeSkill = false; // 禁止切换技能
currentSkill = PlayerSkillManager.instance.currentSkill; // 获取当前技能
currentSkill.ReleaseSkill(); // 执行技能释放逻辑
}

// 退出释放技能状态时调用
public override void Exit()
{
base.Exit();
currentSkill.SkillReleaseOver(); // 技能释放结束处理
PlayerSkillManager.instance.canChangeSkill = true; // 允许切换技能
}

// 逻辑更新,每帧调用
public override void LogicUpdate()
{
base.LogicUpdate();
if(currentSkill.skillFinished) stateMachine.ChangeState(player.idleState); // 技能释放完成后切换到待机状态
}

// 物理更新,每物理帧调用
public override void PhysicsUpdate()
{
currentSkill.FixedUpdate(); // 调用技能的物理更新
}
}

实现LightCut技能

  • LightCut分为四段:收刀入鞘,蓄力,冲刺斩击,结束后摇
  • 收刀状态为前摇动画,不能取消
  • 蓄力过程需要玩家按住右扳机
  • 玩家松开扳机就向前冲刺,对冲刺中碰到的敌人造成伤害

LightCut类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class LightCut : Skill
{
public override bool IsSkillAvailable()
{
// 检查玩家是否在地面上,并且可以切换技能
if (PlayerManager.instance.player.physicsCheck.isGrounded && PlayerSkillManager.instance.canChangeSkill) return true;
else return false;
}

//在Enter中调用
public override void ReleaseSkill()
{
base.ReleaseSkill();
PlayerManager.instance.player.animator.SetTrigger("LightCut");
PlayerManager.instance.player.playerInput.GamePlay.ReleaseSkill.canceled += Dash;
}

//在Exit中调用
public override void SkillReleaseOver()
{
Debug.Log("Finish skill: " + skillName);
PlayerManager.instance.player.playerInput.GamePlay.ReleaseSkill.canceled -= Dash;
}

private void Dash(InputAction.CallbackContext context)
{
PlayerManager.instance.player.animator.SetTrigger("LightCutRelease");
}

//在LogicUpdate中调用
public override void Update()
{

}

//在PhysicsUpdate中调用
public override void FixedUpdate()
{
//技能特殊事件被触发
if (skillTrigger)
PlayerManager.instance.player.SetVelocity(2 * PlayerManager.instance.player.facingDir * PlayerManager.instance.player.dashSpeed, 0);
else
PlayerManager.instance.player.SetZeroVelocity();
}
}

Animator状态机设置

image-20250603111051539

蓄力&&冲刺斩击的实现

  • 按下扳机,进入技能释放状态,Enter调用技能的ReleaseSkill函数,触发ToLightCut动画,也就是收刀前摇
  • 在State中注册扳机键的canceled事件,当松开按键就触发Dash,播放LightCut动画,否则循环播放蓄力动画
  • 给冲刺斩击的动画注册动画事件,控制skillTrigger,实现斩击过程中的冲刺
1
2
3
4
5
6
7
8
9
10
11
//PlayerAniEvent

public void AnimationSkillTrigger()
{
PlayerSkillManager.instance.currentSkill.skillTrigger = true;
}

public void AnimationSkillTriggerFalse()
{
PlayerSkillManager.instance.currentSkill.skillTrigger = false;
}

image-20250603112827964