Pointer, Particles, Numbers

This commit is contained in:
Rokojori 2026-10-06 20:50:28 +02:00
parent 4bbed6ba6c
commit f472d031a8
34 changed files with 1094 additions and 30 deletions

View File

@ -13,19 +13,38 @@ namespace Rokojori
[Export]
public string message;
[Export]
public Node node;
[Export]
public bool printPath;
protected override void _OnTrigger()
{
if ( printPath )
{
if ( node != null )
{
this.LogInfo( message, node );
}
else
{
this.LogInfo( message );
}
}
else
{
if ( node != null )
{
RJLog.Log( message, node );
}
else
{
RJLog.Log( message );
}
}
}
}
}

View File

@ -13,6 +13,8 @@ namespace Rokojori
[Export]
public Node3D lookTarget;
[Export]
public bool useModelFront = false;
protected override void _OnTrigger()
{
@ -21,7 +23,7 @@ namespace Rokojori
return;
}
lookFrom.LookAt( lookTarget.GlobalPosition );
lookFrom.LookAt( lookTarget.GlobalPosition, Vector3.Up, useModelFront );
}
}
}

View File

@ -0,0 +1,39 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[GlobalClass, Tool ]
public partial class SetNode3D : Action
{
[Export]
public Node source;
[Export]
public string sourceMember;
[Export]
public Node target;
[Export]
public string targetMember;
protected override void _OnTrigger()
{
if ( target == null || source == null )
{
return;
}
var value = ReflectionHelper.GetValue<Node3D>( source, sourceMember );
ReflectionHelper.SetValue<Node3D>( target, targetMember, value );
}
}
}

View File

@ -0,0 +1 @@
uid://dnx8jc8lsfwoi

View File

@ -0,0 +1,72 @@
using Godot;
using System.Collections.Generic;
using System.IO;
using Rokojori.Extensions;
namespace Rokojori;
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
public partial class NumberMath:Action
{
public enum Operator
{
Set,
Add,
Substract,
Multiply,
Divide,
Modulo,
Freeze,
Unfreeze,
Reset
}
[Export]
public NumberValue target;
[Export]
public Operator operation = Operator.Set;
public enum Order
{
Target_Op_Source,
Source_Op_Target
}
[Export]
public Order order = Order.Target_Op_Source;
[Export]
public double sourceValue;
[Export]
public NumberValue sourceReference;
protected override void _OnTrigger()
{
var valueRight = ( sourceReference == null ? 0 : sourceReference.value ) + sourceValue;
var valueLeft = target.value;
if ( order == Order.Source_Op_Target )
{
var buffer = valueLeft;
valueLeft = valueRight;
valueRight = buffer;
}
switch ( operation )
{
case Operator.Set: { target.value = valueRight; } break;
case Operator.Add: { target.value = valueLeft + valueRight; } break;
case Operator.Substract: { target.value = valueLeft - valueRight; } break;
case Operator.Multiply: { target.value = valueLeft * valueRight; } break;
case Operator.Divide: { target.value = valueLeft / valueRight; } break;
case Operator.Modulo: { target.value = valueLeft % valueRight; } break;
case Operator.Freeze: { target.FreezeValue(); } break;
case Operator.Unfreeze: { target.UnfreezeValue(); } break;
case Operator.Reset: { target.ResetValue(); } break;
}
}
}

View File

@ -0,0 +1 @@
uid://b7uiy1j04s3fu

View File

@ -0,0 +1,134 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori;
[Tool][GlobalClass]
public partial class NumberValue : Node
{
protected double _value = 0;
protected bool _frozen;
[Export]
public double value
{
get => _value;
set { _OnValueChange( value ); }
}
public override string ToString()
{
return GetType().Name + " { value = " + _value._DDD() + ", frozen = " + _frozen + " }";
}
void _OnValueChange( double v )
{
if ( _frozen )
{
return;
}
var setterValue = Mathf.Clamp( v, minValue, maxValue );
if ( _value == setterValue )
{
return;
}
var before = _value;
_value = setterValue;
var belowLowTreshold = before < lowTreshold;
var aboveHighTreshold = before > highTreshold;
onChange?.Trigger();
if ( ! belowLowTreshold && _value < lowTreshold )
{
onBelowLowTreshold?.Trigger();
}
if ( ! aboveHighTreshold && _value > highTreshold )
{
onAboveHighTreshold?.Trigger();
}
if ( _value == minValue )
{
onMin?.Trigger();
if ( autoFreezeOnMin )
{
_frozen = true;
}
}
if ( _value == maxValue )
{
onMax?.Trigger();
}
}
public void FreezeValue()
{
this._frozen = true;
}
public void UnfreezeValue()
{
this._frozen = false;
}
public void ResetValue()
{
this._frozen = false;
this._value = resetValue;
}
[Export]
public double resetValue = 100;
[Export]
public double minValue = 0;
[Export]
public double maxValue = 125;
[Export]
public double lowTreshold = 25;
[Export]
public double highTreshold = 75;
[Export]
public bool autoFreezeOnMin = true;
[Export]
public Action onChange;
[Export]
public Action onIncrement;
[Export]
public Action onDecrement;
[Export]
public Action onMin;
[Export]
public Action onMax;
[Export]
public Action onBelowLowTreshold;
[Export]
public Action onAboveHighTreshold;
}

View File

@ -0,0 +1 @@
uid://bg8bavpgdo0r6

View File

@ -0,0 +1,131 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LockOnCamera : VirtualCamera
{
[Export]
public Node3D playerTarget;
[Export]
public LockOnPointer source;
[Export]
public Node3D lockTarget;
[Export]
public LockOnCameraSettings settings;
[Export]
public CharacterController.CharacterUpdateMode updateMode = CharacterController.CharacterUpdateMode.Physics_Process;
[Export]
public bool processInEditor = false;
public bool canProcess => processInEditor || ! Engine.IsEditorHint();
public void SetLockTarget( Node3D target )
{
lockTarget = target;
}
Node3D CurrentLockTarget()
{
if ( source != null && source.pointable != null )
{
return source.pointable;
}
return lockTarget;
}
public override void _Process( double delta )
{
if ( canProcess && CharacterController.CharacterUpdateMode.Process == updateMode )
{
_ProcessCamera( delta );
}
}
public override void _PhysicsProcess( double delta )
{
if ( canProcess && CharacterController.CharacterUpdateMode.Physics_Process == updateMode )
{
_ProcessCamera( delta );
}
}
void _ProcessCamera( double gameDelta )
{
if ( playerTarget == null || settings == null )
{
return;
}
var delta = (float) gameDelta;
var target = CurrentLockTarget();
var playerPosition = Smoothing.Apply( settings.playerFollowSmoothing, playerTarget.GlobalPosition, delta );
Vector3 axisDirection;
Vector3 lookAtPoint;
if ( target != null )
{
var targetPosition = Smoothing.Apply( settings.targetFollowSmoothing, target.GlobalPosition, delta );
var offset = targetPosition - playerPosition;
var flat = new Vector3( offset.X, 0, offset.Z );
axisDirection = flat.LengthSquared() > 0.0001f ? flat.Normalized() : -playerTarget.GlobalTransform.Basis.Z;
lookAtPoint = playerPosition.Lerp( targetPosition, settings.lookAtTargetWeight );
}
else
{
axisDirection = -playerTarget.GlobalTransform.Basis.Z;
lookAtPoint = playerPosition;
}
var side = axisDirection.Cross( Vector3.Up );
var desiredPosition = playerPosition - axisDirection * settings.distance + Vector3.Up * settings.height + side * settings.sideOffset;
cameraPosition = Smoothing.Apply( settings.positionSmoothing, desiredPosition, delta );
lookAtPoint.Y += settings.lookAtHeight;
cameraForward = ( lookAtPoint - cameraPosition ).Normalized();
var basis = Basis.LookingAt( cameraForward, Vector3.Up, true );
cameraRotation = new Quaternion( basis );
FOV = settings.fov;
}
public Vector3 cameraPosition;
public override Vector3 GetCameraPosition()
{
return cameraPosition;
}
public Vector3 cameraForward;
public Quaternion cameraRotation;
public override Quaternion GetCameraRotation()
{
return cameraRotation;
}
[Export]
public float FOV = 60f;
public override float GetCameraFOV()
{
return FOV;
}
}
}

View File

@ -0,0 +1 @@
uid://b5o54ra4cgj2k

View File

@ -0,0 +1,40 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LockOnCameraSettings : Resource
{
[Export]
public float distance = 6f;
[Export]
public float height = 2f;
[Export]
public float sideOffset = 0f;
[ExportGroup( "Look At" )]
[Export(PropertyHint.Range, "0,1")]
public float lookAtTargetWeight = 0.5f;
[Export]
public float lookAtHeight = 0f;
[ExportGroup( "FOV" )]
[Export]
public float fov = 60f;
[ExportGroup( "Smoothing" )]
[Export]
public Smoothing playerFollowSmoothing = new FrameSmoothing();
[Export]
public Smoothing targetFollowSmoothing = new FrameSmoothing();
[Export]
public Smoothing positionSmoothing = new FrameSmoothing();
}
}

View File

@ -0,0 +1 @@
uid://cqa8ktsmoa041

View File

@ -0,0 +1,41 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
public enum ThirdPersonCameraLockMode
{
Lock,
Release
}
[Tool]
[GlobalClass]
public partial class SetThirdPersonCameraLockTarget : Action
{
[Export]
public ThirdPersonCamera camera;
[Export]
public Pointer source;
[Export]
public ThirdPersonCameraLockMode mode = ThirdPersonCameraLockMode.Lock;
protected override void _OnTrigger()
{
if ( camera == null )
{
return;
}
if ( mode == ThirdPersonCameraLockMode.Release )
{
camera.lockTarget = null;
return;
}
camera.lockTarget = source != null && source.pointable != null ? source.pointable.pointerPosition : null;
}
}
}

View File

@ -0,0 +1 @@
uid://pesdcfjnpi30

View File

@ -16,6 +16,9 @@ namespace Rokojori
[Export]
public Node3D target;
[Export]
public Node3D lockTarget;
[Export]
public float yaw;
@ -35,6 +38,8 @@ namespace Rokojori
public bool canProcess => processInEditor || ! Engine.IsEditorHint();
Vector3 _lastLockTargetPosition;
public override void _Process( double delta )
{
if ( canProcess && CharacterController.CharacterUpdateMode.Process == updateMode )
@ -71,6 +76,30 @@ namespace Rokojori
var targetPosition = Smoothing.Apply( settings.targetFollowSmoothing, target.GlobalPosition, delta );
var hasLockTarget = lockTarget != null;
var lockTargetPosition = _lastLockTargetPosition;
if ( hasLockTarget )
{
lockTargetPosition = Smoothing.Apply( settings.lockTargetFollowSmoothing, lockTarget.GlobalPosition, delta );
_lastLockTargetPosition = lockTargetPosition;
if ( settings.lockReleaseDistance > 0f && ( lockTargetPosition - targetPosition ).Length() > settings.lockReleaseDistance )
{
lockTarget = null;
hasLockTarget = false;
}
}
// lockBlend fades 0->1 on engage and 1->0 on release instead of snapping, so the focus
// point, yaw override, pitch clamp and bounding-distance below all ease in/out together -
// lockTargetPosition keeps the last known value through the fade-out tail even after
// lockTarget itself has gone null.
var lockBlend = Smoothing.Apply( settings.lockBlendSmoothing, hasLockTarget ? 1f : 0f, delta );
var isLocked = lockBlend > 0.001f;
var focusPosition = targetPosition.Lerp( ( targetPosition + lockTargetPosition ) / 2f, lockBlend );
var yawAxis = 0f;
var pitchAxis = 0f;
@ -81,7 +110,20 @@ namespace Rokojori
}
// this.LogInfo( "YAW:", yawAxis, "PITCH:", pitchAxis );
if ( isLocked )
{
var lockDirection = lockTargetPosition - targetPosition;
lockDirection.Y = 0;
if ( lockDirection.LengthSquared() > 0.0001f )
{
yaw = Math3D.GlobalYawDegrees( lockDirection );
}
}
else
{
yaw += yawAxis * settings.yawSpeed * data.yawSpeed * delta;
}
if ( data.pitchIsRelative )
@ -94,6 +136,11 @@ namespace Rokojori
pitch = Mathf.Remap( pitchAxis, -1, 1, settings.minPitch, settings.maxPitch );
}
if ( isLocked )
{
pitch = Mathf.Clamp( pitch, settings.lockMinPitch, settings.lockMaxPitch );
}
var appliedYaw = yaw;
var smoothedYaw = Smoothing.ApplyDegrees( settings.yawSmoothing, appliedYaw, delta );
@ -103,16 +150,35 @@ namespace Rokojori
var pitchState = MathX.NormalizeClamped( pitch, settings.minPitch, settings.maxPitch );
var distance = settings.distanceForPitch.Sample( pitchState ) * settings.distanceScale;
var smoothedDistance = Smoothing.Apply( settings.distanceSmoothing, distance, delta );
FOV = settings.fovOffset + ( settings.fovForPitch?.Sample( pitchState ) ?? 0f );
var desiredDistance = settings.distanceForPitch.Sample( pitchState ) * settings.distanceScale;
if ( isLocked )
{
// The camera always looks exactly at focusPosition (cameraForward is -fowardDirection by
// construction below), so fitting a bounding sphere around target+lockTarget in the view
// cone is pitch-independent - unlike pulling back along a single axis, it cannot
// degenerate when the view direction is near-vertical. radius/sin(halfFov) is the
// standard distance that makes a sphere of that radius exactly fill the frame.
var boundingRadius = ( lockTargetPosition - targetPosition ).Length() * 0.5f + settings.lockBoundsPadding;
var halfFovRadians = Mathf.DegToRad( FOV * 0.5f );
if ( boundingRadius > 0f && halfFovRadians > 0.001f )
{
var minDistanceForBounds = boundingRadius / Mathf.Sin( halfFovRadians );
desiredDistance = Mathf.Lerp( desiredDistance, Mathf.Max( desiredDistance, minDistanceForBounds ), lockBlend );
}
}
var smoothedDistance = Smoothing.Apply( settings.distanceSmoothing, desiredDistance, delta );
var fowardDirection = Math3D.YawPitchRotation( smoothedYaw, smoothedPitch ) * Vector3.Forward;
cameraPosition = targetPosition + fowardDirection * smoothedDistance;
cameraPosition = focusPosition + fowardDirection * smoothedDistance;
cameraForward = ( targetPosition - cameraPosition ).Normalized();
cameraForward = ( focusPosition - cameraPosition ).Normalized();
// LookAt( targetPosition, Vector3.Up, true );
var pitchOffset = settings.offset;

View File

@ -73,6 +73,27 @@ namespace Rokojori
public float fovOffset = 60;
[ExportGroup("Lock")]
[Export]
public Smoothing lockTargetFollowSmoothing = new FrameSmoothing();
[Export]
public Smoothing lockBlendSmoothing = new FrameSmoothing();
[Export]
public float lockReleaseDistance = 0f;
[Export]
public float lockMinPitch = -10;
[Export]
public float lockMaxPitch = 45;
[Export]
public float lockBoundsPadding = 0f;
public float GetNormalizedPitch( float pitch )
{
return MathX.Normalize( pitch, minPitch, maxPitch );

View File

@ -51,6 +51,13 @@ namespace Rokojori
[Export]
public CharacterMovementType[] deviceMovementTypes = [];
[ExportGroup( "Manual Movement Type Switch" )]
[Export]
public Sensor movementTypeSwitch;
[Export]
public CharacterMovementType switchedMovementType;
[Export]
public Smoothing onFloorMovementSmoothing = new FrameSmoothing();
@ -119,6 +126,11 @@ namespace Rokojori
currentMovementType = deviceMovementTypes.FindNonNull( d => d.deviceSelector.Selects( activeDevice ) );
if ( movementTypeSwitch != null && movementTypeSwitch.isActive && switchedMovementType != null )
{
currentMovementType = switchedMovementType;
}
if ( currentMovementType == null )
{
// this.LogInfo( "No device found:", activeDevice == null ? "null" : activeDevice.GetType().Name );

View File

@ -0,0 +1,16 @@
using Godot;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class DirectionalCycleInput : Resource
{
[Export]
public Sensor sensor;
[Export]
public Vector2 direction = Vector2.Right;
}
}

View File

@ -0,0 +1 @@
uid://dc6l8lylcqdtv

View File

@ -0,0 +1,289 @@
using Godot;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori
{
public enum LockOnCycleMode
{
None,
ClosestCycle,
Directional
}
[Tool]
[GlobalClass]
public partial class LockOnPointer : Pointer
{
[Export]
public Sensor engageSensor;
[Export]
public bool releaseWhenTargetInvalid = true;
[Export]
public Action onLockEngaged;
[Export]
public Action onLockReleased;
[ExportGroup( "Cycling" )]
[Export]
public LockOnCycleMode cycleMode = LockOnCycleMode.None;
[Export]
public Sensor cycleSensor;
[Export]
public DirectionalCycleInput[] directionalCycleInputs = [];
[Export]
public Node3D closestVisualizer;
[Export]
public Action onClosestFound;
[Export]
public Action onClosestLost;
[Export]
public Action onClosestChanged;
[ExportGroup("Read Only")]
[Export]
public Pointable closestCandidate;
bool _engaged = false;
public bool isEngaged => _engaged;
protected override void _OnTrigger()
{
if ( caster == null )
{
return;
}
UpdateClosestCandidate();
var wasLocked = pointable != null;
if ( visualizer != null && pointable != null )
{
visualizer.CopyGlobalPoseFrom( pointable.pointerPosition );
}
if ( engageSensor != null )
{
_engaged = engageSensor.isActive;
if ( ! _engaged )
{
if ( pointable != null )
{
SetPointable( null );
}
}
else if ( pointable == null )
{
SetPointable( closestCandidate );
}
else if ( releaseWhenTargetInvalid && ! IsStillValidTarget( pointable ) )
{
SetPointable( null );
}
else
{
HandleCycling();
}
}
// Fires off the pointable transition (not the raw engage-sensor edge) so it also covers the
// releaseWhenTargetInvalid auto-release path above, and so pointable already holds its new
// value (or null) by the time a listener - e.g. SetThirdPersonCameraLockTarget - reads it.
var isNowLocked = pointable != null;
if ( isNowLocked != wasLocked )
{
Action.TriggerSafe( isNowLocked ? onLockEngaged : onLockReleased );
}
}
void UpdateClosestCandidate()
{
var next = FindClosestCandidate();
if ( next == closestCandidate )
{
if ( closestVisualizer != null && closestCandidate != null )
{
closestVisualizer.CopyGlobalPoseFrom( closestCandidate.pointerPosition );
}
return;
}
closestCandidate = next;
onClosestChanged?.Trigger();
if ( closestCandidate == null )
{
onClosestLost?.Trigger();
}
else
{
onClosestFound?.Trigger();
}
if ( closestVisualizer != null && closestCandidate != null )
{
closestVisualizer.CopyGlobalPoseFrom( closestCandidate.pointerPosition );
}
}
void HandleCycling()
{
if ( cycleMode == LockOnCycleMode.ClosestCycle )
{
if ( cycleSensor != null && cycleSensor.isDown )
{
SetPointable( FindNextClosestCandidate() );
}
return;
}
if ( cycleMode != LockOnCycleMode.Directional || directionalCycleInputs == null )
{
return;
}
for ( int i = 0; i < directionalCycleInputs.Length; i++ )
{
var input = directionalCycleInputs[ i ];
if ( input == null || input.sensor == null || ! input.sensor.isDown )
{
continue;
}
var next = FindDirectionalCandidate( input.direction );
if ( next != null )
{
SetPointable( next );
}
return;
}
}
Pointable FindClosestCandidate()
{
for ( int i = 0; i < caster.NumColliders(); i++ )
{
var p = Nodes.Find<Pointable>( caster.GetCollider( i ) );
if ( p != null && p.enabled )
{
return p;
}
}
return null;
}
Pointable FindNextClosestCandidate()
{
var currentIndex = -1;
var candidates = new List<Pointable>();
for ( int i = 0; i < caster.NumColliders(); i++ )
{
var p = Nodes.Find<Pointable>( caster.GetCollider( i ) );
if ( p == null || ! p.enabled )
{
continue;
}
if ( p == pointable )
{
currentIndex = candidates.Count;
}
candidates.Add( p );
}
if ( candidates.Count == 0 )
{
return null;
}
if ( currentIndex == -1 )
{
return candidates[ 0 ];
}
return candidates[ ( currentIndex + 1 ) % candidates.Count ];
}
Pointable FindDirectionalCandidate( Vector2 direction )
{
if ( pointable == null )
{
return FindClosestCandidate();
}
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 < caster.NumColliders(); i++ )
{
var candidate = Nodes.Find<Pointable>( caster.GetCollider( i ) );
if ( candidate == null || candidate == pointable || ! candidate.enabled )
{
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( direction );
if ( score > bestScore )
{
bestScore = score;
best = candidate;
}
}
return best;
}
bool IsStillValidTarget( Pointable p )
{
for ( int i = 0; i < caster.NumColliders(); i++ )
{
if ( Nodes.Find<Pointable>( caster.GetCollider( i ) ) == p )
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1 @@
uid://c0oeivnw2q0a5

View File

@ -33,6 +33,11 @@ namespace Rokojori
[Export]
public Action onPointerRemoved;
[Export]
public Node3D pointerPositionOverride;
public Node3D pointerPosition => pointerPositionOverride ?? this;
[ExportGroup("Highlighting")]
[Export]
public HighlightEffect highlightEffect;

View File

@ -17,11 +17,25 @@ namespace Rokojori
[Export]
public HighlightEffect defaultHighlighter;
[Export]
public Node3D visualizer;
[Export]
public Action onPointed;
[Export]
public Action onUnpointed;
[Export]
public Action onPointerChanged;
[ExportGroup("Read Only")]
[Export]
public Pointable pointable;
public readonly EventSlot<Pointable> onPointerChange = new EventSlot<Pointable>();
@ -37,24 +51,35 @@ namespace Rokojori
var currentCollider = caster.NumColliders() == 0 ? null : caster.GetCollider( 0 );
var currentPointable = currentCollider == null ? null : Nodes.Find<Pointable>( currentCollider );
// RJLog.Log( currentCollider, currentPointable );
SetPointable( currentPointable );
if ( currentPointable == pointable )
{
return;
}
protected void SetPointable( Pointable next )
{
if ( next == pointable )
{
if ( visualizer != null && pointable != null )
{
var pointable3D = pointable.pointerPosition;
visualizer.CopyGlobalPoseFrom( pointable3D );
}
return;
}
if ( pointable != null )
{
pointable.UpdatePointerState( this, false );
Highlight( HighlightActionType.End, pointable );
}
pointable = currentPointable;
pointable = next;
if ( pointable != null )
{
@ -62,7 +87,25 @@ namespace Rokojori
Highlight( HighlightActionType.Start, pointable );
}
onPointerChange.DispatchEvent( currentPointable );
onPointerChange.DispatchEvent( pointable );
onPointerChanged?.Trigger();
if ( pointable == null )
{
onUnpointed?.Trigger();
}
else
{
onPointed?.Trigger();
}
if ( visualizer != null && pointable != null )
{
var pointable3D = pointable.pointerPosition;
visualizer.CopyGlobalPoseFrom( pointable3D );
}
if ( printPointables )
{

View File

@ -0,0 +1,63 @@
using Godot;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori
{
public enum ActivationMode
{
Hold,
Toggle
}
[Tool]
[GlobalClass]
public partial class ActivationModeSensor : Sensor
{
[Export]
public Sensor button;
[Export]
public ActivationMode mode = ActivationMode.Hold;
bool _toggledOn = false;
public override bool IsSensor( InputEvent ie )
{
return button != null && button.IsSensor( ie );
}
public void Release()
{
_toggledOn = false;
}
protected override void UpdateValue()
{
if ( button == null )
{
SetBoolValue( false );
return;
}
if ( mode == ActivationMode.Hold )
{
SetFloatValue( button.value );
return;
}
if ( button.isDown )
{
_toggledOn = ! _toggledOn;
}
SetBoolValue( _toggledOn );
}
public override List<InputIcon> GetInputIcons()
{
return button == null ? new List<InputIcon>() : button.GetInputIcons();
}
}
}

View File

@ -0,0 +1 @@
uid://b4eorkinrt8kv

View File

@ -0,0 +1,21 @@
using Godot;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class ReleaseActivationModeSensor : Action
{
[Export]
public ActivationModeSensor activationModeSensor;
protected override void _OnTrigger()
{
activationModeSensor?.Release();
}
}
}

View File

@ -0,0 +1 @@
uid://bmhvyxsurpcql

View File

@ -214,7 +214,7 @@ public partial class TimelineManager:NetworkNode
{
var inManager = timelines.Contains( timeline );
RJLog.Log( "No runner found for:", timeline, "Is in manager:", inManager );
// RJLog.Log( "No runner found for:", timeline, "Is in manager:", inManager );
}
return runner;

View File

@ -11,7 +11,7 @@ colors = PackedColorArray(0.96, 0.9211765, 0.24000007, 1, 1, 0.19607843, 0, 1, 0
[sub_resource type="Resource" id="Resource_w2g18"]
script = ExtResource("1_b51fw")
baseColor = Color(1, 0.19607843, 0, 1)
baseColor = Color(1, 0.5342896, 0, 1)
centerAttraction = 0.0
temperaturePolarBlendRadius = 30.0
startTemperatureShift = 45.0

View File

@ -29,11 +29,11 @@ void fragment()
{
float d = distance( UV, vec2(0.5) );
float a = 1.0 - clamp( d / 0.5, 0.0, 1.0);
float a = 1.0 - clamp( d / ( 0.5 * COLOR.a ), 0.0, 1.0);
ALBEDO = COLOR.rgb;
ALPHA *= a * COLOR.a;
ALPHA *= a;
ALPHA_SCISSOR_THRESHOLD = 0.01;
}

View File

@ -70,7 +70,9 @@ public partial class EmberVFXData:ParticleVFXData
UpdateCoreSettings( preset, particles );
processMaterial.Color = preset.color * preset.colorIntensity;
var color = preset.color.ScaleColor( preset.colorIntensity );
processMaterial.Color = color;
var rampTexture = processMaterial.ColorRamp as GradientTexture1D;

View File

@ -67,6 +67,11 @@ public partial class ParticleVFX:Node3D
_data = data;
}
public void Play()
{
preset.Play( this );
}
}

View File

@ -0,0 +1,32 @@
using Godot;
using System.Collections.Generic;
using Rokojori.Extensions;
namespace Rokojori
{
[Tool][GlobalClass, Icon("res://addons/rokojori_action_library/Icons/PlayParticles.svg")]
public partial class ParticleVFXControl:Action
{
public enum ControlType
{
Play
}
[Export]
public ParticleVFX particleVFX;
[Export]
public ControlType controlType = ControlType.Play;
protected override void _OnTrigger()
{
if ( ControlType.Play == controlType )
{
particleVFX?.Play();
}
}
}
}

View File

@ -0,0 +1 @@
uid://cw6q1qxm35l7l