class_name Player
extends CharacterBody2D
# 角色状态枚举
enum State {
IDLE, # 闲置状态
WALK, # 行走状态(当前逻辑复用RUNNING,可后续扩展)
RUNNING,# 奔跑状态
JUMP, # 跳跃状态
FALL,
LANDING, # 下落状态
WALL_SLIDING,
WALL_JUMP,
ATTACK_1,
ATTACK_2,
ATTACK_3,
DYING,
HURT
}
# 地面状态集合(用于判断是否从地面状态切换)
const GROUND_STATES := [State.IDLE, State.RUNNING,State.LANDING,
State.ATTACK_1,State.ATTACK_2,State.ATTACK_3,]
const WALL_JUMP_VELOCITY := Vector2(380,-320)
# 移动参数配置
const RUN_SPEED := 160.0 # 奔跑速度
const FLOOR_ACCELERATION := RUN_SPEED / 0.2 # 地面加速度(0.2秒加速到全速)
const AIR_ACCELERATION := RUN_SPEED / 0.01 # 空中加速度(更灵敏)
const KNOCKBACK_AMOUNT :=512.0
const JUMP_VELOCITY := -320.0 # 跳跃初速度(负值表示向上)
var default_gravity := ProjectSettings.get("physics/2d/default_gravity") as float# 重力
var is_first_tick:= false
var is_combo_requested := false
var pending_damage:Damage
# 节点引用
@onready var graphics: Node2D = $Graphics
# 角色精灵
@onready var animation_player: AnimationPlayer = $AnimationPlayer # 动画播放器
@onready var coyote_timer: Timer = $CoyoteTimer # Coyote时间计时器(离开地面后短时间仍可跳)
@onready var jump_request_timer: Timer = $JumpRequestTimer # 跳跃输入缓冲计时器
@onready var state_machine: Node = $StateMachine
@onready var stats: stats = $stats
@onready var invincible_timer: Timer = $invincibleTimer
@onready var headcheker: RayCast2D = $Graphics/Headcheker
@onready var footchecker: RayCast2D = $Graphics/footchecker
@export var can_combo:= false
# 状态机变量
var current_state: State = State.IDLE # 初始状态设为闲置
var is_attack_finished := false
func _on_animation_finished(anim_name: String) -> void:
if anim_name in ["attack_1", "attack_2", "attack_3"]: # 确保动画名称与实际匹配
is_attack_finished = true
# 处理跳跃输入(按下/松开)
func _unhandled_input(event: InputEvent) -> void:
# 按下跳跃键时,启动跳跃请求计时器(缓冲输入)
if event.is_action_pressed("jump"):
jump_request_timer.start()
# 松开跳跃键时,停止计时器并实现短跳逻辑
if event.is_action_released("jump"):
jump_request_timer.stop()
# 若仍在上升阶段,减半上升速度(缩短跳跃高度)
if velocity.y < JUMP_VELOCITY / 2:
velocity.y = JUMP_VELOCITY / 2
if event.is_action_pressed("attack") and can_combo:
is_combo_requested = true
# 物理帧主循环:驱动状态机和移动逻辑
#func _physics_process(delta: float) -> void:
## 1. 计算下一状态
#var next_state = get_next_state(current_state)
## 2. 状态变化时执行过渡逻辑(播放动画、修改状态变量等)
#if next_state != current_state:
#transition_state(current_state, next_state)
#current_state = next_state
## 3. 执行当前状态的物理逻辑(移动、重力等)
#tick_physics(current_state, delta)
# 各状态的物理逻辑处理
# 各状态的物理逻辑处理
func tick_physics(state: State, delta: float) -> void:
if invincible_timer.time_left > 0:
graphics.modulate.a = sin(Time.get_ticks_msec()) *0.5+0.5
else:
graphics.modulate.a = 1
match state:
State.IDLE:
move(default_gravity, delta)
State.RUNNING:
move(default_gravity, delta)
State.JUMP:
# 传递重力参数,首次跳跃重力为0
move(0.0 if is_first_tick else default_gravity, delta)
State.FALL:
move(default_gravity, delta)
State.LANDING:
stand(default_gravity, delta)
State.WALL_SLIDING:
move(default_gravity /30, delta)
# 滑墙时实时更新朝向(根据墙壁法线反向)
update_wall_slide_facing() # 替换原有的scale.x设置
State.WALL_JUMP:
if state_machine.state_time < 0.1:
stand(0.0 if is_first_tick else default_gravity, delta)
graphics.scale.x = get_wall_normal().x
else:
move(default_gravity, delta)
State.ATTACK_1, State.ATTACK_2, State.ATTACK_3:
stand(default_gravity,delta)
State.HURT,State.DYING:
stand(default_gravity,delta)
# 攻击未结束时保持当前攻击状态,无需返回任何值
is_first_tick = false
# 新增:更新滑墙时的朝向(核心逻辑)
func update_wall_slide_facing() -> void:
var wall_normal = get_wall_normal()
# 朝向与墙壁法线相反(面向墙壁):右墙(法线x=1)→ 向左(scale.x=-1);左墙(法线x=-1)→ 向右(scale.x=1)
graphics.scale.x = -wall_normal.x
# 修正:move函数接受重力参数
func move(gravity_value: float, delta: float) -> void:
var direction := Input.get_axis("move_left", "move_right")
var acceleration := FLOOR_ACCELERATION if is_on_floor() else AIR_ACCELERATION
velocity.x = move_toward(velocity.x, direction * RUN_SPEED, acceleration * delta)
# 应用传入的重力值adadd
velocity.y += gravity_value * delta
if not is_zero_approx(direction):
graphics.scale.x = -1 if direction < 0 else +1
move_and_slide()
# stand 函数:处理角色的站立逻辑
func stand(gravity: float, delta: float) -> void:
var acceleration := FLOOR_ACCELERATION if is_on_floor() else AIR_ACCELERATION
velocity.x = move_toward(velocity.x, 0.0, acceleration * delta)
velocity.y += gravity * delta
move_and_slide()
func die() -> void:
get_tree().reload_current_scene()
func can_wall_slide() -> bool:
return is_on_wall() and headcheker.is_colliding() and footchecker.is_colliding()
# 状态切换逻辑:根据当前状态计算下一状态
func get_next_state(state: State) -> int:
if stats.health==0:
return StateMachine.KEEP_CURRENT if state == State.DYING else State.DYING
if pending_damage:
return State.HURT
# 判断是否可跳跃
var can_jump := is_on_floor() or coyote_timer.time_left > 0
var should_jump := can_jump and jump_request_timer.time_left > 0
if should_jump:
return State.JUMP
if state in GROUND_STATES and not is_on_floor():
return State.FALL
var direction := Input.get_axis("move_left", "move_right")
var is_still := is_zero_approx(direction) and is_zero_approx(velocity.x)
match state:
State.IDLE:
if Input.is_action_just_pressed("attack"):
return State.ATTACK_1
if not is_still:
return State.RUNNING
State.RUNNING:
if Input.is_action_just_pressed("attack"):
return State.ATTACK_1
if is_still:
return State.IDLE
State.JUMP:
if velocity.y >= 0:
return State.FALL
State.FALL:
if is_on_floor():
return State.LANDING if is_still else State.RUNNING # 移除外层 if is_still 判断
if can_wall_slide():
return State.WALL_SLIDING
State.WALL_SLIDING:
if jump_request_timer.time_left > 0:
return State.WALL_JUMP
if is_on_floor():
return State.IDLE
if not is_on_wall():
return State.FALL
State.LANDING:
if not is_still:
return State.RUNNING
if not animation_player.is_playing():
return State.IDLE
State.WALL_JUMP:
if can_wall_slide() and not is_first_tick:
return State.WALL_SLIDING
if velocity.y >= 0:
return State.FALL
State.ATTACK_1:
if not animation_player.is_playing():
return State.ATTACK_2 if is_combo_requested else State.IDLE
State.ATTACK_2:
if not animation_player.is_playing():
return State.ATTACK_3 if is_combo_requested else State.IDLE
State.ATTACK_3:
if not animation_player.is_playing():
return State.IDLE
State.HURT,State.DYING:
if not animation_player.is_playing():
return State.IDLE
#if state in [State.JUMP, State.FALL]:
#if can_wall_slide():
#return State.WALL_SLIDING
#
return StateMachine.KEEP_CURRENT
# 状态过渡逻辑:切换状态时执行动画和参数调整
#var physics_frame_count: int = 0
func transition_state(from: State, to: State) -> void:
#is_first_tick = true
#
#if to == State.WALL_JUMP:
#var wall_normal = get_wall_normal()
#velocity.x = wall_normal.x * WALL_JUMP_VELOCITY.x
#velocity.y = WALL_JUMP_VELOCITY.y
if from not in GROUND_STATES and to in GROUND_STATES:
coyote_timer.stop()
match to:
State.IDLE:
animation_player.play("idle")
State.RUNNING:
animation_player.play("running")
State.LANDING:
animation_player.play("landing")
State.JUMP:
animation_player.play("jump")
velocity.y = JUMP_VELOCITY
coyote_timer.stop()
jump_request_timer.stop()
State.WALL_SLIDING:
animation_player.play("wall_sliding")
update_wall_slide_facing()
velocity.y = 0 # 进入滑墙状态时立即更新朝向
State.FALL:
animation_player.play("fall")
if from in GROUND_STATES:
coyote_timer.start()
State.WALL_JUMP:
animation_player.play("jump")
velocity = WALL_JUMP_VELOCITY
velocity.x *= get_wall_normal().x
jump_request_timer.stop()
State.ATTACK_1:
animation_player.play("attack_1")
is_combo_requested = false
State.ATTACK_2:
animation_player.play("attack_2")
is_combo_requested = false
State.ATTACK_3:
animation_player.play("attack_3")
is_combo_requested = false
State.HURT:
animation_player.play("die")
# 从角色当前生命值中减去待处理的伤害量,实现生命值扣减
stats.health -= pending_damage.amount
# 计算伤害源指向自身的方向向量(用于确定击退方向)
var dir:= pending_damage.source.global_position.direction_to(global_position)
# 根据方向向量和击退力度常量计算角色的击退速度
velocity = dir * KNOCKBACK_AMOUNT
pending_damage = null
invincible_timer.start()
State.DYING:
animation_player.play("hurt")
invincible_timer.stop()
is_first_tick=true
func _on_hurtbox_hurt(hitbox: Hitbox) -> void:
if invincible_timer.time_left > 0:
return
pending_damage = Damage.new()
pending_damage.amount=1
pending_damage.source=hitbox.owner
class_name StateMachine
extends Node
const KEEP_CURRENT := -1 # 全局常量:表示“保持当前状态”
# 当前状态(与业务枚举强绑定,如 Player.State)
var current_state: int = -1:
set(v):
owner.transition_state(current_state, v) # 触发状态过渡逻辑
current_state = v
state_time = 0 # 重置状态持续时间
var state_time: float# 记录当前状态持续的物理时间
func _ready() -> void:
await owner.ready # 等待宿主节点(如 Player)初始化完成
current_state = 0 # 从业务枚举中获取初始状态
func _physics_process(delta: float) -> void:
# 步骤1:循环计算状态,直到稳定
while true:
var next := owner.get_next_state(current_state) as int # 调用宿主的状态计算逻辑
if next == KEEP_CURRENT:
break # 状态不变或保持当前,退出循环
current_state = next# 状态变化时更新
# 步骤2:状态稳定后,执行物理逻辑
owner.tick_physics(current_state, delta)
state_time += delta # 累加当前状态的持续时间
优化我的player代码使其完全适配状态机
最新发布