138 lines
4.5 KiB
GDScript
138 lines
4.5 KiB
GDScript
@tool
|
|
extends Node3D
|
|
class_name GridCharacterController
|
|
|
|
@export var grid_manager_path: NodePath
|
|
@export var camera_path: NodePath
|
|
@export var start_cell: Vector2i = Vector2i.ZERO
|
|
@export_range(0.1, 20.0, 0.1) var move_speed: float = 4.0
|
|
@export_range(0.0, 2.0, 0.01) var height_offset: float = 0.3
|
|
@export var home_marker_scene: PackedScene
|
|
|
|
var _grid_manager: GridPlaneManager = null
|
|
var _camera: Camera3D = null
|
|
var _current_cell: Vector2i = Vector2i(-1, -1)
|
|
var _target_position: Vector3
|
|
var _is_moving: bool = false
|
|
var _home_instance: Node3D = null
|
|
|
|
func _ready() -> void:
|
|
set_physics_process(true)
|
|
set_process_unhandled_input(true)
|
|
_resolve_references()
|
|
if _grid_manager:
|
|
_grid_manager.grid_layout_changed.connect(_on_grid_layout_changed)
|
|
_move_to_cell(start_cell, true)
|
|
_spawn_home()
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if not _is_moving:
|
|
return
|
|
var direction: Vector3 = _target_position - global_transform.origin
|
|
var distance: float = direction.length()
|
|
if distance <= 0.01:
|
|
global_position = _target_position
|
|
_is_moving = false
|
|
return
|
|
var step: float = move_speed * delta
|
|
var move_vector: Vector3 = direction.normalized() * min(step, distance)
|
|
global_position += move_vector
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if Engine.is_editor_hint():
|
|
return
|
|
if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
|
|
_move_to_mouse_click()
|
|
|
|
func _move_to_mouse_click() -> void:
|
|
if not _grid_manager or not _camera:
|
|
return
|
|
var viewport: Viewport = get_viewport()
|
|
if viewport == null:
|
|
return
|
|
var mouse_position: Vector2 = viewport.get_mouse_position()
|
|
var target_cell: Vector2i = _pick_cell(mouse_position)
|
|
if target_cell.x < 0 or target_cell.y < 0:
|
|
return
|
|
_move_to_cell(target_cell)
|
|
|
|
func _move_to_cell(cell: Vector2i, instant: bool = false) -> void:
|
|
if not _grid_manager or not _grid_manager.has_cell(cell):
|
|
return
|
|
_current_cell = cell
|
|
var local_center: Vector3 = _grid_manager.cell_to_world(cell)
|
|
var world_center: Vector3 = _grid_manager.to_global(local_center)
|
|
_target_position = world_center + Vector3.UP * height_offset
|
|
if instant:
|
|
global_position = _target_position
|
|
_is_moving = false
|
|
else:
|
|
_is_moving = true
|
|
|
|
func _pick_cell(mouse_position: Vector2) -> Vector2i:
|
|
var ray_origin: Vector3 = _camera.project_ray_origin(mouse_position)
|
|
var ray_direction: Vector3 = _camera.project_ray_normal(mouse_position)
|
|
var plane_normal: Vector3 = _grid_manager.global_transform.basis.y.normalized()
|
|
var plane_point: Vector3 = _grid_manager.global_transform.origin
|
|
var denominator: float = plane_normal.dot(ray_direction)
|
|
if abs(denominator) < 0.0001:
|
|
return Vector2i(-1, -1)
|
|
var t: float = plane_normal.dot(plane_point - ray_origin) / denominator
|
|
if t <= 0.0:
|
|
return Vector2i(-1, -1)
|
|
var world_point: Vector3 = ray_origin + ray_direction * t
|
|
return _grid_manager.world_to_cell(world_point, true)
|
|
|
|
func _on_grid_layout_changed(_cells: Dictionary) -> void:
|
|
if not _grid_manager:
|
|
return
|
|
if not _grid_manager.has_cell(_current_cell):
|
|
_current_cell = Vector2i(
|
|
clamp(_current_cell.x, 0, _grid_manager.columns - 1),
|
|
clamp(_current_cell.y, 0, _grid_manager.rows - 1)
|
|
)
|
|
_move_to_cell(_current_cell, true)
|
|
_update_home_position()
|
|
|
|
func _resolve_references() -> void:
|
|
if grid_manager_path != NodePath():
|
|
_grid_manager = get_node_or_null(grid_manager_path) as GridPlaneManager
|
|
else:
|
|
_grid_manager = get_parent() as GridPlaneManager
|
|
if camera_path != NodePath():
|
|
_camera = get_node_or_null(camera_path) as Camera3D
|
|
if not _home_instance and home_marker_scene:
|
|
_spawn_home()
|
|
|
|
func _spawn_home() -> void:
|
|
if not home_marker_scene or not _grid_manager:
|
|
return
|
|
if _home_instance and is_instance_valid(_home_instance):
|
|
_home_instance.queue_free()
|
|
var new_home := home_marker_scene.instantiate()
|
|
if not new_home:
|
|
return
|
|
if not (new_home is Node3D):
|
|
new_home.queue_free()
|
|
return
|
|
_home_instance = new_home
|
|
var parent := get_parent()
|
|
if parent:
|
|
call_deferred("_add_home_to_parent", parent)
|
|
|
|
func _add_home_to_parent(parent: Node) -> void:
|
|
if not parent or not _home_instance:
|
|
return
|
|
if _home_instance.get_parent() == parent:
|
|
_update_home_position()
|
|
return
|
|
parent.add_child(_home_instance)
|
|
_update_home_position()
|
|
|
|
func _update_home_position() -> void:
|
|
if not _home_instance or not _grid_manager or not _grid_manager.has_cell(start_cell):
|
|
return
|
|
var local_center := _grid_manager.cell_to_world(start_cell)
|
|
var world_center := _grid_manager.to_global(local_center)
|
|
_home_instance.global_position = world_center + Vector3.UP * 0.01
|