class_name PlayerArcade extends CharacterBody3D # Arcade player: same FPS controller as the demo, plus HP and a melee # attack on left-click. Emits hp_changed and died signals; arcade_scene # listens and updates the HUD / restart logic. signal hp_changed(current: int, maximum: int) signal died signal attacked(hit_point: Vector3, hit_body: Node) @export var speed := 6.0 @export var run_multiplier := 1.8 @export var jump_velocity := 6.0 @export var gravity := 22.0 @export var mouse_sensitivity := 0.002 @export var max_hp := 20 @export var melee_range := 1.6 @export var melee_damage := 6 @export var melee_cooldown := 0.35 @onready var camera: Camera3D = $Camera3D var hp := 20 var _pitch := 0.0 var _captured := false var _melee_timer := 0.0 var _alive := true func _ready() -> void: hp = max_hp hp_changed.emit(hp, max_hp) _capture() func _capture() -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED _captured = true func _release() -> void: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _captured = false func _unhandled_input(event: InputEvent) -> void: if not _alive: return if event is InputEventMouseMotion and _captured: var m := event as InputEventMouseMotion rotation.y -= m.relative.x * mouse_sensitivity _pitch -= m.relative.y * mouse_sensitivity _pitch = clamp(_pitch, deg_to_rad(-85.0), deg_to_rad(85.0)) camera.rotation.x = _pitch elif event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE: _release() elif event is InputEventMouseButton and event.pressed: if not _captured: _capture() elif event.button_index == MOUSE_BUTTON_LEFT: _try_attack() func _physics_process(delta: float) -> void: if not _alive: return if _melee_timer > 0.0: _melee_timer -= delta if not is_on_floor(): velocity.y -= gravity * delta var input := Vector2.ZERO if Input.is_key_pressed(KEY_W): input.y -= 1.0 if Input.is_key_pressed(KEY_S): input.y += 1.0 if Input.is_key_pressed(KEY_A): input.x -= 1.0 if Input.is_key_pressed(KEY_D): input.x += 1.0 input = input.normalized() var s := speed if Input.is_key_pressed(KEY_SHIFT): s *= run_multiplier var forward := -transform.basis.z var right := transform.basis.x var horiz := (forward * -input.y + right * input.x) * s velocity.x = horiz.x velocity.z = horiz.z if is_on_floor() and Input.is_key_pressed(KEY_SPACE): velocity.y = jump_velocity move_and_slide() # Deal damage to any enemy within melee_range in front of the camera. func _try_attack() -> void: if _melee_timer > 0.0: return _melee_timer = melee_cooldown var origin := camera.global_position var dir := -camera.global_transform.basis.z var space := get_world_3d().direct_space_state var query := PhysicsRayQueryParameters3D.create(origin, origin + dir * melee_range) query.exclude = [self] var hit := space.intersect_ray(query) if hit.is_empty(): return var body := hit.get("collider") as Node if body and body.has_method("take_damage"): body.take_damage(melee_damage, self) attacked.emit(hit.get("position", Vector3.ZERO), body) # Called by enemies. func take_damage(amount: int, _source: Node) -> void: if not _alive: return hp = max(0, hp - amount) hp_changed.emit(hp, max_hp) if hp <= 0: _alive = false died.emit()