@ -1,18 +1,25 @@
public partial class PickupController : Node3D
public partial class PickupController : Node3D
{
{
static readonly Color OutlinePickup = Colors . White with { A = 0.75f } ;
static readonly Color OutlineYesPlace = Colors . Green with { A = 0.75f } ;
static readonly Color OutlineNoPlace = Colors . Red with { A = 0.75f } ;
Node3D _ preview ; // Placement preview of the item - a duplicate of its model.
Grid _ grid ; // Grid currently being hovered over.
public Item CurrentItem { get ; private set ; }
public Item CurrentItem { get ; private set ; }
public bool HasItemsHeld = > GetChildCount ( ) > 0 ;
public bool HasItemsHeld = > GetChildCount ( ) > 0 ;
Node3D _ placementPreview ;
[Export] public float PickupDistance { get ; set ; } = 2.0f ;
[Export] public float PickupDistance { get ; set ; } = 2.0f ;
Player _ player ;
Player _ player ;
Node3D _ world ;
Node3D _ world ;
ShaderMaterial _ outlineShaderMaterial ;
public override void _ Ready ( )
public override void _ Ready ( )
{
{
_ player = GetParent < Player > ( ) ;
_ player = GetParent < Player > ( ) ;
// TODO: Find a better way to find the world.
_ world = GetNode < Node3D > ( "/root/Game/Workshop" ) ; // TODO: Find a better way to get the world.
_ world = GetNode < Node3D > ( "/root/Game/Workshop ") ;
_ outlineShaderMaterial = Load < ShaderMaterial > ( "res://assets/shaders/outline_material.tres ") ;
}
}
public override void _ UnhandledInput ( InputEvent @event )
public override void _ UnhandledInput ( InputEvent @event )
@ -24,12 +31,12 @@ public partial class PickupController : Node3D
if ( @event . IsActionPressed ( "interact_pickup" ) ) {
if ( @event . IsActionPressed ( "interact_pickup" ) ) {
if ( ! HasItemsHeld ) {
if ( ! HasItemsHeld ) {
// Create clone of the item's model, use it as placement preview.
// Create clone of the item's model, use it as placement preview.
_ placementP review = ( Node3D ) CurrentItem . Model . Duplicate ( 0 ) ;
_ preview = ( Node3D ) CurrentItem . Model . Duplicate ( 0 ) ;
_ placementP review . Name = "PlacementPreview" ;
_ preview . Name = "PlacementPreview" ;
_ placementP review . TopLevel = true ;
_ preview . TopLevel = true ;
_ placementP review . Visible = false ;
_ preview . Visible = false ;
SetMeshLayerOutline ( _ placementP review , OutlineMode . Exclusive ) ;
SetMeshLayerOutline ( _ preview , OutlineMode . Exclusive ) ;
AddChild ( _ placementP review ) ;
AddChild ( _ preview ) ;
// Parent item to the `PickupController`.
// Parent item to the `PickupController`.
var prevRot = CurrentItem . GlobalRotation ;
var prevRot = CurrentItem . GlobalRotation ;
@ -47,11 +54,12 @@ public partial class PickupController : Node3D
// Parent item back to the world.
// Parent item back to the world.
var prevTransform = CurrentItem . GlobalTransform ;
var prevTransform = CurrentItem . GlobalTransform ;
RemoveChild ( CurrentItem ) ;
RemoveChild ( CurrentItem ) ;
_ world . AddChild ( CurrentItem ) ;
if ( _ placementPreview . Visible ) {
if ( _ preview . Visible & & _ grid ! = null ) {
CurrentItem . GlobalTransform = _ placementPreview . GlobalTransform ;
_ grid . AddChild ( CurrentItem ) ;
CurrentItem . GlobalTransform = _ preview . GlobalTransform ;
} else {
} else {
_ world . AddChild ( CurrentItem ) ;
CurrentItem . GlobalTransform = prevTransform ;
CurrentItem . GlobalTransform = prevTransform ;
CurrentItem . Freeze = false ;
CurrentItem . Freeze = false ;
@ -61,9 +69,13 @@ public partial class PickupController : Node3D
CurrentItem . ApplyImpulse ( direction * 2 ) ;
CurrentItem . ApplyImpulse ( direction * 2 ) ;
}
}
RemoveChild ( _ placementPreview ) ;
// Reset the color of the outline shader material.
_ placementPreview . QueueFree ( ) ;
_ outlineShaderMaterial . SetShaderParameter ( "line_color" , OutlinePickup ) ;
_ placementPreview = null ;
RemoveChild ( _ preview ) ;
_ preview . QueueFree ( ) ;
_ preview = null ;
_ grid = null ;
GetViewport ( ) . SetInputAsHandled ( ) ;
GetViewport ( ) . SetInputAsHandled ( ) ;
}
}
@ -77,25 +89,19 @@ public partial class PickupController : Node3D
if ( HasItemsHeld ) {
if ( HasItemsHeld ) {
if ( ( RayToMouseCursor ( ) is RayResult ray ) & & ( ray . Collider is Grid grid ) ) {
if ( ( RayToMouseCursor ( ) is RayResult ray ) & & ( ray . Collider is Grid grid ) ) {
// Snao rotation to nearest axis.
var transform = CurrentItem . GlobalTransform with { Origin = ray . Position } ;
var localBasis = grid . GlobalBasis . Inverse ( ) * CurrentItem . GlobalBasis ;
transform = grid . Snap ( transform , ray . Normal , CurrentItem ) ;
localBasis = Basis . FromEuler ( localBasis . GetEuler ( ) . Snapped ( Tau / 4 ) ) ;
_ preview . GlobalTransform = transform ;
_ placementPreview . GlobalBasis = grid . GlobalBasis * localBasis ;
var canPlace = grid . CanPlaceAt ( CurrentItem , transform ) ;
// Snap the position to the grid.
var outlineColor = canPlace ? OutlineYesPlace : OutlineNoPlace ;
var halfSize = ( Vector3 ) CurrentItem . Size * Grid . StepSize / 2 ;
_ outlineShaderMaterial . SetShaderParameter ( "line_color" , outlineColor ) ;
var localPos = ray . Position * grid . GlobalTransform ; // Get grid-local ray position.
var localNormal = ray . Normal * grid . GlobalBasis ; // Get grid-local ray normal. (Pointing away from surface hit.)
_ preview . Visible = true ;
var off = localBasis * halfSize . PosMod ( 1.0f ) ; // Calculate an offset for grid snapping.
_ grid = canPlace ? grid : null ;
off [ ( int ) localNormal . Abs ( ) . MaxAxisIndex ( ) ] = 0 ; // Do not include offset in the normal direction.
localPos = off + ( localPos - off ) . Snapped ( Grid . StepSize ) ; // Snap `localPos` to nearest grid value.
var axis = ( localNormal * localBasis ) . Abs ( ) . MaxAxisIndex ( ) ; // Find object-local axis that the normal is pointing towards.
localPos + = halfSize [ ( int ) axis ] * localNormal ; // Offset (push out) object by half its size.
_ placementPreview . GlobalPosition = grid . GlobalTransform * localPos ;
_ placementPreview . Visible = true ;
} else {
} else {
_ placementPreview . Visible = false ;
_ preview . Visible = false ;
_ grid = null ;
}
}
} else {
} else {
var interactable = RayToMouseCursor ( ) ? . Collider ;
var interactable = RayToMouseCursor ( ) ? . Collider ;
@ -114,10 +120,10 @@ public partial class PickupController : Node3D
if ( CurrentItem = = null ) return ;
if ( CurrentItem = = null ) return ;
if ( ! IsInstanceValid ( CurrentItem ) ) {
if ( ! IsInstanceValid ( CurrentItem ) ) {
CurrentItem = null ;
CurrentItem = null ;
if ( _ placementP review ! = null ) {
if ( _ preview ! = null ) {
RemoveChild ( _ placementP review ) ;
RemoveChild ( _ preview ) ;
_ placementP review . QueueFree ( ) ;
_ preview . QueueFree ( ) ;
_ placementP review = null ;
_ preview = null ;
}
}
}
}
}
}