trench/character_body_3d.gd
saarsena@gmail.com c2bb3893a9 initial commit
2026-04-22 10:19:57 -04:00

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))