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.
120 lines
3.8 KiB
120 lines
3.8 KiB
2 years ago
|
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)
|