rokojori_action_library/Runtime/Interactions/LockOn/LockOnPointer.cs

151 lines
3.4 KiB
C#
Raw Normal View History

2026-10-06 18:50:28 +00:00
using Godot;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori
{
public enum LockOnCycleMode
{
None,
ClosestCycle,
Directional
}
// Only tracks the current candidate list and the single selected pointable (sticky - stays on
// the selection until it is cycled away from or stops being a valid candidate). Owns no concept
// of "locked" - that is CameraLocker's job, reading/driving this pointer's pointable.
2026-10-06 18:50:28 +00:00
[Tool]
[GlobalClass]
public partial class LockOnPointer : Pointer
{
public int test = 0;
List<Pointable> _candidates = new List<Pointable>();
2026-10-06 18:50:28 +00:00
public int numCandidates => _candidates.Count;
2026-10-06 18:50:28 +00:00
public Pointable GetCandidate( int index )
{
return _candidates[ index ];
}
2026-10-06 18:50:28 +00:00
public bool IsCandidate( Pointable p )
{
return p != null && _candidates.Contains( p );
}
2026-10-06 18:50:28 +00:00
protected override void _OnTrigger()
{
if ( caster == null )
{
return;
}
2026-10-07 08:29:15 +00:00
if ( pointable != null && ! IsInstanceValid( pointable ) )
{
pointable = null;
}
RefreshCandidates();
2026-10-06 18:50:28 +00:00
if ( pointable == null || ! _candidates.Contains( pointable ) )
2026-10-06 18:50:28 +00:00
{
SetPointable( ClosestCandidate() );
2026-10-06 18:50:28 +00:00
}
}
void RefreshCandidates()
2026-10-06 18:50:28 +00:00
{
_candidates.Clear();
2026-10-06 18:50:28 +00:00
for ( int i = 0; i < caster.NumColliders(); i++ )
{
var p = Nodes.Find<Pointable>( caster.GetCollider( i ) );
if ( p != null && p.enabled )
{
_candidates.Add( p );
2026-10-06 18:50:28 +00:00
}
}
}
Pointable ClosestCandidate()
2026-10-06 18:50:28 +00:00
{
return _candidates.Count == 0 ? null : _candidates[ 0 ];
}
2026-10-06 18:50:28 +00:00
public void CycleNext()
{
if ( _candidates.Count == 0 )
2026-10-06 18:50:28 +00:00
{
return;
}
2026-10-06 18:50:28 +00:00
var currentIndex = _candidates.IndexOf( pointable );
var nextIndex = currentIndex == -1 ? 0 : ( currentIndex + 1 ) % _candidates.Count;
2026-10-06 18:50:28 +00:00
SetPointable( _candidates[ nextIndex ] );
}
2026-10-06 18:50:28 +00:00
public void CyclePrevious()
{
if ( _candidates.Count == 0 )
2026-10-06 18:50:28 +00:00
{
return;
2026-10-06 18:50:28 +00:00
}
var currentIndex = _candidates.IndexOf( pointable );
var previousIndex = currentIndex == -1 ? 0 : ( currentIndex - 1 + _candidates.Count ) % _candidates.Count;
2026-10-06 18:50:28 +00:00
SetPointable( _candidates[ previousIndex ] );
2026-10-06 18:50:28 +00:00
}
public void CycleInScreenSpaceDirection( Vector2 screenSpaceDirection )
2026-10-06 18:50:28 +00:00
{
if ( pointable == null )
{
SetPointable( ClosestCandidate() );
return;
2026-10-06 18:50:28 +00:00
}
var right = caster.GlobalTransform.Basis.X;
var up = caster.GlobalTransform.Basis.Y;
var currentPosition = pointable.GlobalPosition;
Pointable best = null;
var bestScore = float.NegativeInfinity;
for ( int i = 0; i < _candidates.Count; i++ )
2026-10-06 18:50:28 +00:00
{
var candidate = _candidates[ i ];
2026-10-06 18:50:28 +00:00
if ( candidate == pointable )
2026-10-06 18:50:28 +00:00
{
continue;
}
var offset = candidate.GlobalPosition - currentPosition;
var projected = new Vector2( offset.Dot( right ), offset.Dot( up ) );
if ( projected.LengthSquared() < 0.0001f )
{
continue;
}
var score = projected.Normalized().Dot( screenSpaceDirection );
2026-10-06 18:50:28 +00:00
if ( score > bestScore )
{
bestScore = score;
best = candidate;
}
}
if ( best != null )
2026-10-06 18:50:28 +00:00
{
SetPointable( best );
2026-10-06 18:50:28 +00:00
}
}
}
}