149 lines
4 KiB
GDScript
149 lines
4 KiB
GDScript
extends CharacterBody3D
|
|
|
|
## How fast the player slides between tiles (units per second).
|
|
@export var move_speed: float = 6.0
|
|
## How fast the player rotates 90 degrees (degrees per second).
|
|
@export var turn_speed: float = 360.0
|
|
|
|
# Movement state
|
|
var _is_moving := false
|
|
var _is_turning := false
|
|
|
|
var _move_start := Vector3.ZERO
|
|
var _move_target := Vector3.ZERO
|
|
var _move_progress := 0.0
|
|
|
|
var _turn_start := 0.0
|
|
var _turn_target := 0.0
|
|
var _turn_progress := 0.0
|
|
|
|
|
|
func _ready() -> void:
|
|
position = Grid.snap(position)
|
|
# Snap rotation to nearest 90° so we stay grid-aligned
|
|
rotation.y = roundf(rotation.y / (PI / 2.0)) * (PI / 2.0)
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if _is_moving:
|
|
_process_move(delta)
|
|
elif _is_turning:
|
|
_process_turn(delta)
|
|
else:
|
|
_handle_input()
|
|
|
|
|
|
func _handle_input() -> void:
|
|
var forward := -transform.basis.z
|
|
forward.y = 0.0
|
|
forward = forward.normalized()
|
|
|
|
# Forward / back
|
|
if Input.is_action_just_pressed("move_forward"):
|
|
_try_move(forward)
|
|
elif Input.is_action_just_pressed("move_back"):
|
|
_try_move(-forward)
|
|
# Turn left / right
|
|
elif Input.is_action_just_pressed("turn_left"):
|
|
_start_turn(-1)
|
|
elif Input.is_action_just_pressed("turn_right"):
|
|
_start_turn(1)
|
|
# Interact with entities on this tile
|
|
elif Input.is_action_just_pressed("interact"):
|
|
_try_interact()
|
|
|
|
|
|
func _try_move(direction: Vector3) -> void:
|
|
direction = _snap_direction(direction)
|
|
var target := position + direction * Grid.CELL_SIZE
|
|
|
|
# Raycast to check for walls before moving
|
|
var ray_start := position + Vector3(0, 0.5, 0)
|
|
var ray_end := target + Vector3(0, 0.5, 0)
|
|
|
|
var space := get_world_3d().direct_space_state
|
|
var query := PhysicsRayQueryParameters3D.create(
|
|
ray_start, ray_end, collision_mask
|
|
)
|
|
query.exclude = [get_rid()]
|
|
var result := space.intersect_ray(query)
|
|
|
|
print("--- MOVE ATTEMPT ---")
|
|
print(" from: ", position, " to: ", target)
|
|
print(" direction: ", direction)
|
|
print(" ray from: ", ray_start, " ray to: ", ray_end)
|
|
print(" collision_mask: ", collision_mask)
|
|
if result.is_empty():
|
|
print(" result: CLEAR - moving")
|
|
_move_start = position
|
|
_move_target = target
|
|
_move_progress = 0.0
|
|
_is_moving = true
|
|
else:
|
|
print(" result: BLOCKED")
|
|
print(" hit collider: ", result.collider.name if result.collider else "unknown")
|
|
print(" hit position: ", result.position)
|
|
print(" hit normal: ", result.normal)
|
|
|
|
|
|
func _process_move(delta: float) -> void:
|
|
var distance := _move_start.distance_to(_move_target)
|
|
_move_progress += (move_speed * delta) / distance
|
|
_move_progress = minf(_move_progress, 1.0)
|
|
|
|
position = _move_start.lerp(_move_target, _move_progress)
|
|
|
|
if _move_progress >= 1.0:
|
|
position = _move_target
|
|
_is_moving = false
|
|
_check_step_on()
|
|
|
|
|
|
func _start_turn(direction: int) -> void:
|
|
_turn_start = rotation.y
|
|
_turn_target = _turn_start - direction * (PI / 2.0)
|
|
_turn_progress = 0.0
|
|
_is_turning = true
|
|
|
|
|
|
func _process_turn(delta: float) -> void:
|
|
_turn_progress += (deg_to_rad(turn_speed) * delta) / abs(_turn_target - _turn_start)
|
|
_turn_progress = minf(_turn_progress, 1.0)
|
|
|
|
rotation.y = lerpf(_turn_start, _turn_target, _turn_progress)
|
|
|
|
if _turn_progress >= 1.0:
|
|
rotation.y = _turn_target
|
|
_is_turning = false
|
|
|
|
|
|
func _try_interact() -> void:
|
|
var my_cell := Grid.world_to_cell(position)
|
|
|
|
# Same-cell interactables (statues, etc.)
|
|
for node in get_tree().get_nodes_in_group("interactable"):
|
|
var entity := node as Node3D
|
|
if my_cell == Grid.world_to_cell(entity.global_position):
|
|
entity.interact()
|
|
return
|
|
|
|
# Facing interactables (doors) — detected via their own Area3D
|
|
for node in get_tree().get_nodes_in_group("facing_interactable"):
|
|
if node.try_interact(self):
|
|
return
|
|
|
|
|
|
func _check_step_on() -> void:
|
|
var my_cell := Grid.world_to_cell(position)
|
|
for node in get_tree().get_nodes_in_group("steppable"):
|
|
var entity := node as Node3D
|
|
if my_cell == Grid.world_to_cell(entity.global_position):
|
|
entity.stepped_on()
|
|
|
|
|
|
func _snap_direction(dir: Vector3) -> Vector3:
|
|
if absf(dir.x) >= absf(dir.z):
|
|
return Vector3(signf(dir.x), 0.0, 0.0)
|
|
else:
|
|
return Vector3(0.0, 0.0, signf(dir.z))
|
|
|