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.9 KiB
120 lines
3.9 KiB
11 months ago
|
public partial class PickupController : Node3D
|
||
|
{
|
||
|
public Item CurrentItem { get; private set; }
|
||
|
public bool IsCurrentItemHeld { get; private set; }
|
||
|
MeshInstance3D _placementPreview;
|
||
|
|
||
|
[Export] public Camera3D Camera { get; set; }
|
||
|
[Export] public float PickupDistance { get; set; } = 2.0f;
|
||
|
|
||
|
Node3D _world;
|
||
|
public override void _Ready()
|
||
|
{
|
||
|
// TODO: Find a better way to find the world.
|
||
|
_world = (Node3D)FindParent("Warehouse");
|
||
|
}
|
||
|
|
||
|
public override void _UnhandledInput(InputEvent @event)
|
||
|
{
|
||
|
EnsureCurrentItemValid();
|
||
|
if (CurrentItem == null) return;
|
||
|
|
||
|
if (@event.IsActionPressed("interact_pickup")) {
|
||
|
if (!IsCurrentItemHeld) {
|
||
|
IsCurrentItemHeld = true;
|
||
|
|
||
|
// Parent item to the `PickupController`.
|
||
|
var prevRot = CurrentItem.GlobalRotation;
|
||
|
CurrentItem.GetParent().RemoveChild(CurrentItem);
|
||
|
AddChild(CurrentItem);
|
||
|
CurrentItem.Mesh.Layers &= (uint)~RenderLayer.Outline;
|
||
|
CurrentItem.Position = Vector3.Zero;
|
||
|
CurrentItem.GlobalRotation = prevRot;
|
||
|
CurrentItem.CollisionLayer &= (uint)~PhysicsLayer.Static;
|
||
|
// CurrentItem.Freeze = true;
|
||
|
|
||
|
GetViewport().SetInputAsHandled();
|
||
|
}
|
||
|
} else if (@event.IsActionPressed("interact_place")) {
|
||
|
if (IsCurrentItemHeld) {
|
||
|
IsCurrentItemHeld = false;
|
||
|
|
||
|
// Parent item back to the world.
|
||
|
var prevTransform = CurrentItem.GlobalTransform;
|
||
|
CurrentItem.CollisionLayer |= (uint)PhysicsLayer.Static;
|
||
|
// CurrentItem.Freeze = false;
|
||
|
RemoveChild(CurrentItem);
|
||
|
_world.AddChild(CurrentItem);
|
||
|
CurrentItem.GlobalTransform = prevTransform;
|
||
|
|
||
|
GetViewport().SetInputAsHandled();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void _PhysicsProcess(double delta)
|
||
|
{
|
||
|
EnsureCurrentItemValid();
|
||
|
|
||
|
if (IsCurrentItemHeld) {
|
||
|
if ((RayToMouseCursor() is RayResult ray) && (ray.Collider is Grid)) {
|
||
|
// Snao rotation to nearest axis.
|
||
|
// FIXME: This needs to snap to the
|
||
|
// var globalRot = CurrentItem.GlobalRotation;
|
||
|
// globalRot.X = Snapped(globalRot.X, Tau / 4);
|
||
|
// globalRot.Y = Snapped(globalRot.Y, Tau / 4);
|
||
|
// globalRot.Z = Snapped(globalRot.Z, Tau / 4);
|
||
|
// CurrentItem.GlobalRotation = globalRot;
|
||
|
|
||
|
// Snap the position to the grid.
|
||
|
var halfSize = (Vector3)CurrentItem.Size * Grid.StepSize / 2;
|
||
|
var pos = ray.Position + halfSize * (ray.Normal * CurrentItem.GlobalTransform.Basis);
|
||
|
pos = pos.Snapped(Grid.StepSize * Vector3.One); // FIXME: This does global snapping only
|
||
|
CurrentItem.GlobalPosition = pos;
|
||
|
}
|
||
|
} else {
|
||
|
var interactable = RayToMouseCursor()?.Collider;
|
||
|
|
||
|
// Remove the outline from the previously looked-at item.
|
||
|
if (CurrentItem != null) CurrentItem.Mesh.Layers &= (uint)~RenderLayer.Outline;
|
||
|
// If the ray hits anything and the object hit is an item, set it as current.
|
||
|
CurrentItem = interactable as Item;
|
||
|
// Add the outline to the currently looked-at item.
|
||
|
if (CurrentItem != null) CurrentItem.Mesh.Layers |= (uint)RenderLayer.Outline;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnsureCurrentItemValid()
|
||
|
{
|
||
|
if (CurrentItem == null) return;
|
||
|
if (!IsInstanceValid(CurrentItem)) {
|
||
|
CurrentItem = null;
|
||
|
IsCurrentItemHeld = false;
|
||
|
_placementPreview.QueueFree();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RayResult RayToMouseCursor()
|
||
|
{
|
||
|
var mouse = GetViewport().GetMousePosition();
|
||
|
var from = Camera.ProjectRayOrigin(mouse);
|
||
|
var to = from + Camera.ProjectRayNormal(mouse) * PickupDistance;
|
||
|
|
||
|
var query = PhysicsRayQueryParameters3D.Create(from, to);
|
||
|
query.CollisionMask = (uint)PhysicsLayer.Interactable;
|
||
|
query.CollideWithAreas = true;
|
||
|
// Exclude the `CurrentItem` from collision checking if it's being held.
|
||
|
query.Exclude = IsCurrentItemHeld ? [ CurrentItem.GetRid() ] : [];
|
||
|
|
||
|
var result = GetWorld3D().DirectSpaceState.IntersectRay(query);
|
||
|
return (result.Count > 0) ? new(result) : null;
|
||
|
}
|
||
|
|
||
|
class RayResult(Dictionary dict)
|
||
|
{
|
||
|
public CollisionObject3D Collider { get; } = dict["collider"].As<CollisionObject3D>();
|
||
|
public Vector3 Position { get; } = (Vector3)dict["position"];
|
||
|
public Vector3 Normal { get; } = (Vector3)dict["normal"];
|
||
|
}
|
||
|
}
|