godot-walkabout/addons/walkabout/characters/player_character.gd
2025-08-06 10:54:13 -06:00

98 lines
2.8 KiB
GDScript

@tool
@icon("player_character.svg")
class_name WBPlayerCharacter
extends WBCharacter
## A character controlled directly by the player.
## Whether the character will react to player input.
@export var controllable: bool = true
## Time to wait after turning before the character will start moving.
@export var turn_walk_time: float = 0.25
## Collision mask for interactive objects.
## If a collision is detected, [method interact] will be called on the target object if it exists.
@export_flags_2d_physics var interact_mask: int:
set(value):
interact_mask = value
_interact_raycast.collision_mask = interact_mask
@export_group("Input Actions", "input_")
@export var input_left: StringName = &"ui_left"
@export var input_right: StringName = &"ui_right"
@export var input_up: StringName = &"ui_up"
@export var input_down: StringName = &"ui_down"
@export var input_interact: StringName = &"ui_accept"
@export var input_run: StringName = &"ui_cancel"
var _interact_raycast: RayCast2D
var _turn_cooldown: float = 0.0
func _init() -> void:
super._init()
move_finished.connect(_check_move_input)
_interact_raycast = RayCast2D.new()
_interact_raycast.enabled = false
_interact_raycast.collide_with_areas = true
_interact_raycast.hit_from_inside = true
_interact_raycast.add_exception(self)
add_child(_interact_raycast)
func _physics_process(delta: float) -> void:
if Engine.is_editor_hint():
return
_turn_cooldown -= delta
running = Input.is_action_pressed(input_run)
if not moving:
_check_move_input(true)
super._physics_process(delta)
## Enables player control of the character.
func enable_control() -> void:
controllable = true
## Disables player control of the character.
func disable_control() -> void:
controllable = false
func _check_move_input(check_turn_cooldown: bool = false) -> void:
if not controllable:
return
if Input.is_action_pressed(input_left):
_try_move(Dir.LEFT, check_turn_cooldown)
elif Input.is_action_pressed(input_right):
_try_move(Dir.RIGHT, check_turn_cooldown)
elif Input.is_action_pressed(input_up):
_try_move(Dir.UP, check_turn_cooldown)
elif Input.is_action_pressed(input_down):
_try_move(Dir.DOWN, check_turn_cooldown)
elif Input.is_action_just_pressed(input_interact):
_try_interact()
func _try_move(dir: Dir, check_turn_cooldown: bool) -> void:
if facing != dir:
_turn_cooldown = turn_walk_time
if running:
_turn_cooldown *= 0.5
facing = dir
if check_turn_cooldown and _turn_cooldown >= 0.0:
return
start_move(dir)
func _try_interact() -> void:
_interact_raycast.target_position = DIR_VECTORS[facing] * tile_size
_interact_raycast.force_raycast_update()
if _interact_raycast.is_colliding():
var interactive := _interact_raycast.get_collider()
if interactive.has_method(&"interact"):
interactive.interact()