You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
3.8 KiB
119 lines
3.8 KiB
extends Node3D |
|
|
|
@export var camera : Camera3D |
|
@export var pickup_distance := 2.0 |
|
|
|
@onready var world := find_parent("World") as Node3D |
|
|
|
var current_item : Item |
|
var is_current_item_held := false |
|
var placement_preview : MeshInstance3D |
|
# TODO: Support holding multiple items. |
|
# TODO: Allow rotation of the item while held. |
|
|
|
|
|
func _ready() -> void: |
|
pass |
|
|
|
|
|
func _input(event: InputEvent) -> void: |
|
ensure_current_item_valid() |
|
if !current_item: return |
|
|
|
if event.is_action_pressed("interact_pickup"): |
|
if !is_current_item_held: |
|
is_current_item_held = true |
|
|
|
# Create a clone of the item's mesh and use it as a placement preview. |
|
placement_preview = current_item.get_node("MeshInstance3D").duplicate(0) as MeshInstance3D |
|
placement_preview.name = "PlacementPreview" |
|
placement_preview.layers = RenderLayer.OUTLINE |
|
placement_preview.top_level = true |
|
add_child(placement_preview) |
|
|
|
# Parent item to the pickup controller. |
|
var prev_rot := current_item.global_rotation |
|
current_item.get_parent().remove_child(current_item) |
|
add_child(current_item) |
|
current_item.mesh.layers &= ~RenderLayer.OUTLINE |
|
current_item.position = Vector3.ZERO |
|
current_item.global_rotation = prev_rot |
|
current_item.freeze = true |
|
|
|
get_viewport().set_input_as_handled() |
|
|
|
elif event.is_action_pressed("interact_place"): |
|
if is_current_item_held: |
|
is_current_item_held = false |
|
|
|
# Parent item back to the world. |
|
remove_child(current_item) |
|
world.add_child(current_item) |
|
|
|
# Set item's transform to where the placement preview is. |
|
# TODO: If placement preview is not valid, don't allow placing the item. |
|
current_item.global_transform = placement_preview.global_transform |
|
current_item.freeze = false |
|
|
|
placement_preview.queue_free() |
|
placement_preview = null |
|
|
|
get_viewport().set_input_as_handled() |
|
|
|
|
|
func _process(_delta: float) -> void: |
|
pass |
|
|
|
|
|
func _physics_process(_delta: float) -> void: |
|
ensure_current_item_valid() |
|
|
|
if is_current_item_held: |
|
# Cast a ray but exlude the current item from being hit. |
|
var ray_result := ray_to_mouse_cursor([ current_item ]) |
|
if ray_result: |
|
var pos := ray_result.position as Vector3 |
|
var normal := ray_result.normal as Vector3 |
|
|
|
# Snap rotation to nearest axis. |
|
var global_rot = current_item.global_rotation |
|
global_rot.x = snappedf(global_rot.x, TAU / 4) |
|
global_rot.y = snappedf(global_rot.y, TAU / 4) |
|
global_rot.z = snappedf(global_rot.z, TAU / 4) |
|
placement_preview.global_rotation = global_rot |
|
|
|
# Snap the position to the grid. |
|
var half_size := current_item.size * Item.GRID_SIZE / 2 |
|
pos += half_size * (normal * placement_preview.global_transform.basis) |
|
pos = pos.snapped(Item.GRID_SIZE * Vector3.ONE) |
|
placement_preview.global_position = pos |
|
|
|
else: |
|
# Remove the outline from the previously looked-at item. |
|
if current_item: |
|
current_item.mesh.layers &= ~RenderLayer.OUTLINE |
|
|
|
var ray_result := ray_to_mouse_cursor() |
|
# If the ray hit anything and the object hit is an item, set it as current. |
|
if ray_result: |
|
current_item = ray_result.collider as Item |
|
if current_item: |
|
current_item.mesh.layers |= RenderLayer.OUTLINE |
|
|
|
|
|
func ensure_current_item_valid() -> void: |
|
if !current_item: return |
|
if !is_instance_valid(current_item): |
|
current_item = null |
|
is_current_item_held = false |
|
placement_preview.queue_free() |
|
|
|
func ray_to_mouse_cursor(exclude: Array[CollisionObject3D] = []) -> Dictionary: |
|
const COLLIDE_WITH := PhysicsLayer.WORLD | PhysicsLayer.ITEM |
|
var map_to_rid := func(obj: CollisionObject3D) -> RID: return obj.get_rid() |
|
|
|
var mouse := get_viewport().get_mouse_position() |
|
var from := camera.project_ray_origin(mouse) |
|
var to := from + camera.project_ray_normal(mouse) * pickup_distance |
|
var query := PhysicsRayQueryParameters3D.create(from, to, COLLIDE_WITH, exclude.map(map_to_rid)) |
|
return get_world_3d().direct_space_state.intersect_ray(query)
|
|
|