创建一个敌人

  • 新建一个Enemies文件夹
    • 在下面创建Scripts文件夹
    • 创建一个Slime文件夹,里面是史莱姆的相关文件
    • 从网站下载Slime.png放入Slime文件夹
    • 创建一个CharacterBody2D节点的场景,用来保存Slime相关的数据
      • 在这下面创建一个Sprite2D节点,保存Slime图像
      • 再创建一个Sprite2D节点,放置阴影
      • 创建一个CollisionShape2D节点,自己在9层,mask为5层,可以和Wall交互
    • 创建Slime动画
      • 创建idle的up、down、side
      • 创建walk的up、down、side
    • 创建一个enemy.gd脚本,挂载到Slime节点上
      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
      class_name Enemy extends CharacterBody2D

      signal DirectionChanged(dir: Vector2)
      signal EnemyDamaged()

      @export var hp: int = 3

      const DIR_ARR: Array[Vector2] = [Vector2.RIGHT, Vector2.DOWN, Vector2.LEFT, Vector2.UP]
      var direction: Vector2 = Vector2.ZERO
      var cardinal_direction: Vector2 = Vector2.DOWN
      var player: Player
      var invulnerable: bool = false

      @onready var sprite: Sprite2D = $Sprite2D
      @onready var animation_player: AnimationPlayer = $AnimationPlayer
      @onready var state_machine: EnemyStateMachine = $EnemyStateMachine

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

      func _process(_delta: float) -> void:
      pass

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

      func SetDirection(_direction: Vector2) -> bool:
      direction = _direction
      if direction == Vector2.ZERO:
      return false

      var direction_id: int = int(round((direction + cardinal_direction * 0.1).angle() / TAU * DIR_ARR.size()))
      var new_dir = DIR_ARR[direction_id]
      if cardinal_direction == direction:
      return false

      cardinal_direction = new_dir
      DirectionChanged.emit(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"

    • 创建一个enemy_state.gd脚本
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      class_name EnemyState extends Node

      var enemy: Enemy
      var state_machine: EnemyStateMachine

      func init() -> void:
      pass

      func Enter() -> void:
      pass

      func Exit() -> void:
      pass

      func Process(_delta: float) -> EnemyState:
      return null

      func PhysicsProcess(_delta: float) -> EnemyState:
      return null

    • 创建一个enemy_state_machine.gd脚本,并创建一个EnemyStateMachine状态机节点,并将脚本挂载上去
      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
      class_name EnemyStateMachine extends Node

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

      func _ready() -> void:
      process_mode = Node.PROCESS_MODE_DISABLED
      pass

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

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

      func Initialize(enemy: Enemy) -> void:
      for c in get_children():
      if c is EnemyState:
      states.append(c)

      for s in states:
      s.enemy = enemy
      s.state_machine = self
      s.init()

      if states.size() > 0:
      ChangeState(states[0])
      process_mode = Node.PROCESS_MODE_INHERIT

      func ChangeState(new_state: EnemyState) -> 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()

    • 创建一个global_player_manager.gd脚本并配置Autoload
      • 在enemy.gd初始化时引用player
      • 在player.gd初始化时设置PlayerManager
    • 在EnemyStateMachine下创建Idle状态机
      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
      class_name EnemyStateIdle extends EnemyState

      @export var anmi_name: String = "idle"

      @export_category("AI")
      @export var state_duration_min: float = 0.5
      @export var state_duration_max: float = 1.5
      @export var after_idle_state: EnemyState

      var timer: float = 0.0

      func init() -> void:
      pass

      func Enter() -> void:
      enemy.velocity = Vector2.ZERO
      timer = randf_range(state_duration_min, state_duration_max)
      enemy.UpdateAnimation(anmi_name)
      pass

      func Exit() -> void:
      pass

      func Process(_delta: float) -> EnemyState:
      timer -= _delta
      if timer <= 0:
      return after_idle_state
      return null

      func PhysicsProcess(_delta: float) -> EnemyState:
      return null

    • 在EnemyStateMachine下创建Wander状态机
      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
      class_name EnemyStateWander extends EnemyState

      @export var anmi_name: String = "walk"

      @export_category("AI")
      @export var state_animation_duration: float = 0.7
      @export var state_cycle_min: int = 1
      @export var state_cycle_max: int = 3
      @export var wander_speed: float = 30.0
      @export var next_state: EnemyState

      var timer: float = 0.0

      func init() -> void:
      pass

      func Enter() -> void:
      timer = randi_range(state_cycle_min, state_cycle_max) * state_animation_duration
      var direction_id = randi_range(0, 3)
      var direction = enemy.DIR_ARR[direction_id]
      enemy.velocity = direction * wander_speed
      enemy.SetDirection(direction)
      enemy.UpdateAnimation(anmi_name)
      pass

      func Exit() -> void:
      pass

      func Process(_delta: float) -> EnemyState:
      timer -= _delta
      if timer <= 0:
      return next_state
      return null

      func PhysicsProcess(_delta: float) -> EnemyState:
      return null