添加新的状态

新增的跳跃和下落以及冲刺状态如下,之所以要区分跳跃和下落两个状态是因为有些动作只能在下落时执行,比如攀墙。

image-20250516100822311

位置状态检测

​ 在上面的状态转换条件里可以看到,要切换到Jump和Dash状态都必须满足isGround==true的条件,也就是满足玩家位于地面。因此需要实现一个玩家状态检测的功能。使用静态的Raycast方法检测,Raycast检测从原点往某一方向上一段距离内指定图层的碰撞体,并返回这个碰撞体的引用。

image-20250525103004436

新建一个脚本PhysicsCheck挂载到玩家GameObject上。

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
public class PhysicsCheck : MonoBehaviour
{
[SerializeField]private Entity entity;
public bool isGrounded; //是否位于地面
public Transform wallDetect; //检测位置起点
public float groundDetectDist; //检测距离
public LayerMask groundLayer; //检测图层

void Awake()
{
}

private void Update()
{
CheckGround();
}

private void CheckGround()
{
if (groundDetect == null) return;

isGrounded = Physics2D.Raycast(groundDetect.position, Vector2.down, groundDetectDist, groundLayer);
}

private void OnDrawGizmosSelected() //当选中时能在Scene窗口显示检测的线
{
Gizmos.color = Color.green;
Gizmos.DrawLine(groundDetect.position, groundDetect.position + Vector3.down * groundDetectDist);
}
}

给玩家角色创建一个子物体,在Inspector中配置好对应的成员变量。

image-20250525103230456

对原有状态进行抽象

因为我们希望在上一节实现的状态都能够进行跳跃操作,因此与其在每个状态中都添加切换到跳跃状态的逻辑,不如抽象出一个父类,让父类能够切换到Jump状态。冲刺也是同理。

PlayerGroundState

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
public class PlayerGroundState : PlayerState
{
public PlayerGroundState(Player player, PlayerStateMachine stateMachine, string animBoolName) : base(player, stateMachine, animBoolName)
{
}

public override void Enter()
{
base.Enter();
playerInput.GamePlay.Jump.started += Jump;
playerInput.GamePlay.Dash.started += Dash;
playerInput.GamePlay.BackDash.started += BackDash;
}

private void BackDash(InputAction.CallbackContext context)
{
if (physicsCheck.isGrounded()) //只有在地面时才能切换状态
stateMachine.ChangeState(player.backDashState);
}

private void Dash(InputAction.CallbackContext context)
{
if (physicsCheck.isGrounded())
stateMachine.ChangeState(player.dashState);
}

protected void Jump(InputAction.CallbackContext context)
{
if(physicsCheck.isGrounded())
stateMachine.ChangeState(player.jumpState);
}

public override void Exit() //记得在退出时要取消按键事件绑定
{
base.Exit();
playerInput.GamePlay.Jump.started -= Jump;
playerInput.GamePlay.Dash.started -= Dash;
playerInput.GamePlay.BackDash.started -= BackDash;
}

public override void LogicUpdate()
{
base.LogicUpdate();
if (!physicsCheck.isGrounded())
{
stateMachine.ChangeState(player.fallState);
}
}

public override void PhysicsUpdate()
{
base.PhysicsUpdate();
}
}

具体实现

动画状态机

image-20250516101529871

Dash

​ 先以更简单的Dash为例,我们希望任何地面状态都可以切换到Dash动画,因此用AnyStates连接到Dash,为了避免非PlayerGroundState的状态下的动画切换到Dash,需要确保AnyStates到Dash的Animator参数只能由PlayerGroundState转到DashState时触发。

PlayerDashState

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
public class PlayerDashState : PlayerState
{
//public bool dashDir;
public PlayerDashState(Player player, PlayerStateMachine stateMachine, string animBoolName) : base(player, stateMachine, animBoolName)
{
}

public override void Enter()
{
base.Enter();
animator.SetTrigger(animBoolName+"Trigger");
rigidBody2D.AddForce(new Vector2(player.facingDir * player.jumpForce * 1.25f, 0), ForceMode2D.Impulse);
}

public override void Exit()
{
base.Exit();
}

public override void LogicUpdate()
{
base.LogicUpdate();
if(animaFinTrigger) stateMachine.ChangeState(player.idleState);
}

public override void PhysicsUpdate()
{

}
}

​ DashState重写了Enter方法,AddForce是给物体添加一个面朝方向的水平力,值得注意的是多了一个SetTrigger语句,这是为了避免使用bool类型参数,如果一个状态对应有几段动画,那么在后面几段动画播放时,会因为此时状态还没有退出,bool变量依然为true而导致后面几段动画重复地从AnyState进到第一段动画,如果使用Trigger,由于只触发一次,就可以解决这个问题。不过这里Dash只有一段,倒是不会出现这个问题。

​ 至于动画以及状态的退出,还是用动画最后一帧绑定事件触发器的方式退出。

Jump

image-20250516105120990

PlayerJumpState

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
public class PlayerJumpState : PlayerState
{
public PlayerJumpState(Player player, PlayerStateMachine stateMachine, string animBoolName) : base(player, stateMachine, animBoolName)
{
}

public override void Enter()
{
base.Enter();
animator.SetTrigger(animBoolName+"Trigger");
rigidBody2D.AddForce(new Vector2(0, player.jumpForce), ForceMode2D.Impulse);
}

public override void Exit()
{
base.Exit();
}

public override void LogicUpdate()
{
base.LogicUpdate();
if (rigidBody2D.velocity.y<=0) stateMachine.ChangeState(player.fallState);
}

public override void PhysicsUpdate()
{
base.PhysicsUpdate();
}
}

PlayerFallState

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
public class PlayerFallState : PlayerState
{
public PlayerFallState(Player player, PlayerStateMachine stateMachine, string animBoolName) : base(player, stateMachine, animBoolName)
{
}

public override void Enter()
{
base.Enter();
animator.SetTrigger(animBoolName + "Trigger");
}

public override void Exit()
{
base.Exit();
}

public override void LogicUpdate()
{
base.LogicUpdate();
if(physicsCheck.isGrounded()) stateMachine.ChangeState(player.idleState);
if(physicsCheck.isGrounded() && inputXY.x>0.5) stateMachine.ChangeState(player.runState);
}

public override void PhysicsUpdate()
{
base.PhysicsUpdate();
}
}