Inventory management focused game written in Godot / C#
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
4.0 KiB

public partial class PickupController : Node3D, IInitalizable<Player>
{
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 void Initialize(Player player)
{
// TODO: Find a better way to find the world.
// For now we just use the parent of the `Player` object.
_world = player.GetParent<Node3D>();
}
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"];
}
}