Files

124 lines
4.4 KiB
GDScript

extends CharacterBody2D
# SoundManager resolved from the autoload tree at runtime
func _get_sound() -> Node:
return get_tree().root.get_node_or_null("SoundManager")
# === НАСТРОЙКИ ФИЗИКИ ===
const GRAVITY: float = 980.0
const WALK_SPEED: float = 200.0
const RUN_SPEED: float = 350.0
const JUMP_VELOCITY: float = -450.0
# === ПРОДВИНУТЫЕ МЕХАНИКИ ===
const COYOTE_TIME: float = 0.1 # Секунды после схода с платформы, когда ещё можно прыгнуть
const JUMP_BUFFER: float = 0.15 # Секунды, когда нажатие прыжка "запоминается" до приземления
const JUMP_CUT_MULT: float = 0.4 # Обрезка скорости подъёма при отпускании кнопки прыжка
# Высота, ниже которой лиса считается упавшей в пропасть
const DEATH_Y: float = 900.0
var coyote_timer: float = 0.0
var jump_buffer_timer: float = 0.0
var is_running: bool = false
var jump_cut_done: bool = true # true = обрезка уже применена (или прыжка нет)
# Направление взгляда (для будущего спрайта)
var facing_right: bool = true
# Точка возрождения (задаётся уровнем)
var spawn_point: Vector2 = Vector2.ZERO
# Защита от повторной обработки смерти в одном кадре
var is_dying: bool = false
func _ready() -> void:
spawn_point = position
func _physics_process(delta: float) -> void:
if is_dying:
return
# 1. Гравитация + Coyote Time
# ВАЖНО: maxf (не maxi!) — таймеры дробные, maxi обнулял бы их каждый кадр.
if not is_on_floor():
velocity.y += GRAVITY * delta
coyote_timer = maxf(coyote_timer - delta, 0.0)
else:
coyote_timer = COYOTE_TIME # Сброс при касании земли
# 2. Jump Buffer
if Input.is_action_just_pressed("jump"):
jump_buffer_timer = JUMP_BUFFER
else:
jump_buffer_timer = maxf(jump_buffer_timer - delta, 0.0)
# 3. Выполнение прыжка (буфер + coyote)
if jump_buffer_timer > 0.0 and coyote_timer > 0.0:
velocity.y = JUMP_VELOCITY
jump_buffer_timer = 0.0
coyote_timer = 0.0 # запрет повторного прыжка в воздухе
jump_cut_done = false
# TODO: Звук прыжка + эмиссия частиц
# 4. Variable Jump Height — обрезаем ОДИН раз при отпускании кнопки
if not jump_cut_done and velocity.y < 0.0 and not Input.is_action_pressed("jump"):
velocity.y *= JUMP_CUT_MULT
jump_cut_done = true
# 5. Горизонтальное движение
var direction: float = Input.get_axis("move_left", "move_right")
is_running = Input.is_action_pressed("run")
var current_speed: float = RUN_SPEED if is_running else WALK_SPEED
if direction != 0.0:
velocity.x = direction * current_speed
facing_right = direction > 0.0
# TODO: сменить анимацию на 'run'
else:
velocity.x = move_toward(velocity.x, 0.0, current_speed)
# TODO: сменить анимацию на 'idle'
# 6. Движение и коллизии
var was_rising: bool = velocity.y < 0.0
move_and_slide()
# 6.1 Удар головой снизу — сообщаем блоку
if was_rising:
for i in range(get_slide_collision_count()):
var col := get_slide_collision(i)
# normal указывает ОТ поверхности к телу; при ударе снизу normal.y > 0
if col.get_normal().y > 0.5:
var other := col.get_collider()
if other != null and other.has_method("on_hit_from_below"):
other.on_hit_from_below()
# 7. Падение в пропасть
if position.y > DEATH_Y:
die()
func hit_coin(points: int) -> void:
GameManager.add_score(points)
var sfx := _get_sound()
if sfx != null and sfx.has_method("play"):
sfx.call("play", "coin")
func die() -> void:
if is_dying:
return
is_dying = true
velocity = Vector2.ZERO
var sfx := _get_sound()
if sfx != null and sfx.has_method("play"):
sfx.call("play", "die")
if GameManager.lose_life():
# Возрождение
GameManager.respawn()
position = spawn_point
velocity = Vector2.ZERO
is_dying = false
else:
# Жизни кончились
get_tree().quit()
# TODO: переключение анимаций (idle / run / jump / fall)