添加状态机接口

  • 创建一个script,名称为state.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class_name State extends Node

static var player: Player

func _ready() -> void:
pass

func Enter() -> void:
pass

func Exit() -> void:
pass

func Process(delta: float) -> State:
return null

func PhysicsProcess(delta: float) -> State:
return null

func HandleInput(event: InputEvent) -> State:
return null

这个接口是各个状态机方法属性的定义

为Player添加StateMachine节点

  • 在Player下创建一个StateMachine的节点
    • 在StateMachine下新添加两个子节点Idle和Walk
  • 创建 player_state_machine.gd
    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
    class_name PlayerStateMachine extends Node

    var states: Array[ State ]
    var prev_state: State
    var current_state: State

    // 禁用所有StateMachine以及子节点的_process和_physics_process方法
    func _ready() -> void:
    process_mode = Node.PROCESS_MODE_DISABLED
    pass

    func _process(delta: float) -> void:
    ChangeState(current_state.Process( delta ))

    func _physics_process(delta: float) -> void:
    ChangeState(current_state.PhysicsProcess( delta ))

    func _unhandled_input(event: InputEvent) -> void:
    ChangeState(current_state.HandleInput( event ))

    // 获取所有子节点状态机并将第一个作为初始状态
    func Initialize(player: Player) -> void:
    for c in get_children():
    if c is State:
    states.append(c)
    if states.size() > 0:
    states[0].player = player
    ChangeState(states[0])
    process_mode = Node.PROCESS_MODE_INHERIT

    func ChangeState(new_state: State) -> void:
    if new_state == null || new_state == current_state:
    return
    if current_state:
    current_state.Exit()

    prev_state = current_state
    current_state = new_state
    current_state.Enter()
    • 将 player_state_machine.md 挂载到StateMachine节点上

创建两个子状态的脚本

  • state_idle.gd

    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
    class_name StateIdle extends State

    @onready var walk: StateWalk = $"../Walk"

    func _ready() -> void:
    pass

    func Enter() -> void:
    player.UpdateAnimation("idle")
    pass

    func Exit() -> void:
    pass

    func Process(delta: float) -> State:
    if player.direction != Vector2.ZERO:
    return walk
    player.velocity = Vector2.ZERO
    return null

    func PhysicsProcess(delta: float) -> State:
    return null

    func HandleInput(event: InputEvent) -> State:
    return null
    class_name StateIdle extends State

    @onready var walk: StateWalk = $"../Walk"

    func _ready() -> void:
    pass

    func Enter() -> void:
    player.UpdateAnimation("idle")
    pass

    func Exit() -> void:
    pass

    func Process(delta: float) -> State:
    if player.direction != Vector2.ZERO:
    return walk
    player.velocity = Vector2.ZERO
    return null

    func PhysicsProcess(delta: float) -> State:
    return null

    func HandleInput(event: InputEvent) -> State:
    return null

  • state_walk.gd

    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
    class_name StateWalk extends State

    @onready var idle: StateIdle = $"../Idle"
    var move_speed: float = 100.0

    func _ready() -> void:
    pass

    func Enter() -> void:
    player.UpdateAnimation("walk")
    pass

    func Exit() -> void:
    pass

    func Process(delta: float) -> State:
    if player.direction == Vector2.ZERO:
    return idle

    player.velocity = player.direction * move_speed
    if player.SetDirection():
    player.UpdateAnimation("walk")
    return null

    func PhysicsProcess(delta: float) -> State:
    return null

    func HandleInput(event: InputEvent) -> State:
    return null

修改Player节点的脚本

  • player.gd
    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
    class_name Player extends CharacterBody2D

    var direction: Vector2 = Vector2.ZERO
    var cardinal_direction: Vector2 = Vector2.DOWN

    @onready var animation_player: AnimationPlayer = $AnimationPlayer
    @onready var sprite: Sprite2D = $Sprite2D
    @onready var state_machine: PlayerStateMachine = $StateMachine


    func _ready() -> void:
    state_machine.Initialize(self)
    pass

    func _process(delta: float) -> void:
    direction.x = Input.get_action_strength("right") - Input.get_action_strength("left")
    direction.y = Input.get_action_strength("down") - Input.get_action_strength("up")
    pass

    func _physics_process(delta: float) -> void:
    move_and_slide()

    func SetDirection() -> bool:
    var new_dir: Vector2 = cardinal_direction
    if direction == Vector2.ZERO:
    return false
    if direction.y == 0:
    new_dir = Vector2.LEFT if direction.x < 0 else Vector2.RIGHT
    if direction.x == 0:
    new_dir = Vector2.UP if direction.y < 0 else Vector2.DOWN
    if cardinal_direction == direction:
    return false
    cardinal_direction = new_dir
    sprite.scale.x = -1 if cardinal_direction == Vector2.LEFT else 1
    return true

    func UpdateAnimation(state: String) -> void:
    animation_player.play(state + "_" + AnimationDirection())

    func AnimationDirection() -> String:
    if cardinal_direction == Vector2.DOWN:
    return "down"
    elif cardinal_direction == Vector2.UP:
    return "up"
    else:
    return "side"

总结

  • 创建一个StateMachine的节点,下面挂载多个子节点,通过获取子节点的方式动态扩展
  • 用户节点脚本不再直接调用动画渲染,只修改direction
  • 在执行StateMachine的_process和_physics_process方法时,调用当前State的对应方法来处理动画