diff --git a/Icons/BehaviorBranch.svg b/Icons/BehaviorBranch.svg
new file mode 100644
index 0000000..b48c2a4
--- /dev/null
+++ b/Icons/BehaviorBranch.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/Icons/BehaviorBranch.svg.import b/Icons/BehaviorBranch.svg.import
new file mode 100644
index 0000000..38bb271
--- /dev/null
+++ b/Icons/BehaviorBranch.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://whc152vj4786"
+path="res://.godot/imported/BehaviorBranch.svg-2479d9ece5118d365ff6adf5a4a78745.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/BehaviorBranch.svg"
+dest_files=["res://.godot/imported/BehaviorBranch.svg-2479d9ece5118d365ff6adf5a4a78745.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/BehaviorSequence.svg b/Icons/BehaviorSequence.svg
new file mode 100644
index 0000000..1dc484e
--- /dev/null
+++ b/Icons/BehaviorSequence.svg
@@ -0,0 +1,99 @@
+
+
diff --git a/Icons/BehaviorSequence.svg.import b/Icons/BehaviorSequence.svg.import
new file mode 100644
index 0000000..71d4c80
--- /dev/null
+++ b/Icons/BehaviorSequence.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c0bg8q1qhta8d"
+path="res://.godot/imported/BehaviorSequence.svg-e83c81b196d7953a5989711a8e3762a9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/BehaviorSequence.svg"
+dest_files=["res://.godot/imported/BehaviorSequence.svg-e83c81b196d7953a5989711a8e3762a9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/Brain.svg b/Icons/Brain.svg
new file mode 100644
index 0000000..cc17ee6
--- /dev/null
+++ b/Icons/Brain.svg
@@ -0,0 +1,77 @@
+
+
diff --git a/Icons/Brain.svg.import b/Icons/Brain.svg.import
new file mode 100644
index 0000000..c405a3a
--- /dev/null
+++ b/Icons/Brain.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ddywe4nqkppim"
+path="res://.godot/imported/Brain.svg-22061c8f9edbd3ff520fcf62d8a1f665.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/Brain.svg"
+dest_files=["res://.godot/imported/Brain.svg-22061c8f9edbd3ff520fcf62d8a1f665.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/BrainControl.svg b/Icons/BrainControl.svg
new file mode 100644
index 0000000..8d7ac86
--- /dev/null
+++ b/Icons/BrainControl.svg
@@ -0,0 +1,51 @@
+
+
diff --git a/Icons/BrainControl.svg.import b/Icons/BrainControl.svg.import
new file mode 100644
index 0000000..4701ae7
--- /dev/null
+++ b/Icons/BrainControl.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c37yfx3ryvoxu"
+path="res://.godot/imported/BrainControl.svg-0da1cfbf5e1e82c6dfa220b6df44195a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/BrainControl.svg"
+dest_files=["res://.godot/imported/BrainControl.svg-0da1cfbf5e1e82c6dfa220b6df44195a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/Receptor.svg b/Icons/Receptor.svg
new file mode 100644
index 0000000..cf43587
--- /dev/null
+++ b/Icons/Receptor.svg
@@ -0,0 +1,64 @@
+
+
diff --git a/Icons/Receptor.svg.import b/Icons/Receptor.svg.import
new file mode 100644
index 0000000..1bf59c9
--- /dev/null
+++ b/Icons/Receptor.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://jusswlaofkgc"
+path="res://.godot/imported/Receptor.svg-3c1cccf5229247e268d12db0af1e1abb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/Receptor.svg"
+dest_files=["res://.godot/imported/Receptor.svg-3c1cccf5229247e268d12db0af1e1abb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Icons/ReceptorBranch.svg b/Icons/ReceptorBranch.svg
new file mode 100644
index 0000000..b8562de
--- /dev/null
+++ b/Icons/ReceptorBranch.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/Icons/ReceptorBranch.svg.import b/Icons/ReceptorBranch.svg.import
new file mode 100644
index 0000000..f9feb77
--- /dev/null
+++ b/Icons/ReceptorBranch.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c6wwqj7hdrmpa"
+path="res://.godot/imported/ReceptorBranch.svg-bb59728d991a1a6154c3682d9e33cce7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/ReceptorBranch.svg"
+dest_files=["res://.godot/imported/ReceptorBranch.svg-bb59728d991a1a6154c3682d9e33cce7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/Runtime/Actions/Node3D/LookAt.cs b/Runtime/Actions/Node3D/LookAt.cs
index 5824a14..7958f77 100644
--- a/Runtime/Actions/Node3D/LookAt.cs
+++ b/Runtime/Actions/Node3D/LookAt.cs
@@ -18,7 +18,9 @@ namespace Rokojori
protected override void _OnTrigger()
{
- if ( lookFrom == null || lookTarget == null )
+ if ( lookFrom == null || lookTarget == null ||
+ ! IsInstanceValid( lookFrom ) || ! IsInstanceValid( lookTarget )
+ )
{
return;
}
diff --git a/Runtime/Actions/Node3D/SelectNode3D.cs b/Runtime/Actions/Node3D/SelectNode3D.cs
new file mode 100644
index 0000000..a09072a
--- /dev/null
+++ b/Runtime/Actions/Node3D/SelectNode3D.cs
@@ -0,0 +1,141 @@
+
+using Godot;
+using System.Collections.Generic;
+using System.Reflection;
+
+using Rokojori.Extensions;
+namespace Rokojori
+{
+ [GlobalClass, Tool ]
+ public partial class SelectNode3D : Action
+ {
+ [Export]
+ public Selector selector;
+
+ bool _enteredTree = false;
+
+ public override void _EnterTree()
+ {
+ base._EnterTree();
+ _enteredTree = true;
+ }
+
+ Node _target;
+
+ [Export]
+ public Node target
+ {
+ get => _target;
+ set
+ {
+ if ( _target == value )
+ {
+ return;
+ }
+
+ _target = value;
+
+ // Deserialization sets exported properties before _EnterTree fires, so this
+ // only triggers for genuine interactive edits in the editor, never for a scene
+ // being loaded (regardless of property order) or for runtime reassignment.
+ if ( _enteredTree && Engine.IsEditorHint() )
+ {
+ targetMember = "";
+
+#if TOOLS
+ NotifyPropertyListChanged();
+#endif
+ }
+ }
+ }
+
+ [Export]
+ public string targetMember;
+
+
+
+ protected override void _OnTrigger()
+ {
+ if ( target == null )
+ {
+ return;
+ }
+
+ Node3D node3D = null;
+
+ if ( selector == null )
+ {
+ ReflectionHelper.SetValue( target, targetMember, node3D );
+ this.LogInfo( "Set Null:", HierarchyName.Of( target ), targetMember );
+ return;
+ }
+
+ var selectedNode = Unique.Get().Select( selector );
+
+
+ if ( selectedNode is Selectable )
+ {
+ var go = selectedNode.GetParent();
+
+ if ( go != null )
+ {
+ node3D = go.body;
+ }
+ }
+
+ ReflectionHelper.SetValue( target, targetMember, node3D );
+
+ }
+
+#if TOOLS
+
+ public override void _ValidateProperty( Godot.Collections.Dictionary property )
+ {
+ if ( property[ "name" ].AsString() != nameof( targetMember ) )
+ {
+ return;
+ }
+
+ var members = GetTargetNode3DMemberNames();
+
+ property[ "hint" ] = (long) PropertyHint.Enum;
+ property[ "hint_string" ] = members.Count == 0
+ ? "(Invalid Target)"
+ : "None," + string.Join( ",", members );
+ }
+
+ List GetTargetNode3DMemberNames()
+ {
+ var names = new List();
+
+ if ( target == null )
+ {
+ return names;
+ }
+
+ var infos = ReflectionHelper.GetDataMemberInfos( target, BindingFlags.Public | BindingFlags.Instance );
+
+ foreach ( var info in infos )
+ {
+ if ( ! info.IsDefined( typeof( ExportAttribute ) ) )
+ {
+ continue;
+ }
+
+ var memberType = info.GetMemberType();
+
+ if ( memberType == null || ! memberType.IsAssignableFrom( typeof( Node3D ) ) )
+ {
+ continue;
+ }
+
+ names.Add( info.Name );
+ }
+
+ return names;
+ }
+
+#endif
+
+ }
+}
\ No newline at end of file
diff --git a/Runtime/Actions/Node3D/SelectNode3D.cs.uid b/Runtime/Actions/Node3D/SelectNode3D.cs.uid
new file mode 100644
index 0000000..cd38eeb
--- /dev/null
+++ b/Runtime/Actions/Node3D/SelectNode3D.cs.uid
@@ -0,0 +1 @@
+uid://76pepn06ofq5
diff --git a/Runtime/Actions/Node3D/SetNode3D.cs b/Runtime/Actions/Node3D/SetNode3D.cs
index d551d85..6196649 100644
--- a/Runtime/Actions/Node3D/SetNode3D.cs
+++ b/Runtime/Actions/Node3D/SetNode3D.cs
@@ -30,10 +30,13 @@ namespace Rokojori
return;
}
- var value = ReflectionHelper.GetValue( source, sourceMember );
+ var value = sourceMember == null || sourceMember == "" ? (Node3D) source :
+ ReflectionHelper.GetValue( source, sourceMember );
ReflectionHelper.SetValue( target, targetMember, value );
}
+
+
}
}
\ No newline at end of file
diff --git a/Runtime/Actions/Values/NumberValue.cs b/Runtime/Actions/Values/NumberValue.cs
index 1a9835c..962c81a 100644
--- a/Runtime/Actions/Values/NumberValue.cs
+++ b/Runtime/Actions/Values/NumberValue.cs
@@ -23,7 +23,7 @@ public partial class NumberValue : Node
return GetType().Name + " { value = " + _value._DDD() + ", frozen = " + _frozen + " }";
}
- void _OnValueChange( double v )
+ protected virtual void _OnValueChange( double v )
{
if ( _frozen )
{
diff --git a/Runtime/Actions/Visual/TweenFloatShaderProperty.cs b/Runtime/Actions/Visual/TweenFloatShaderProperty.cs
index 7e3e05a..9ce737a 100644
--- a/Runtime/Actions/Visual/TweenFloatShaderProperty.cs
+++ b/Runtime/Actions/Visual/TweenFloatShaderProperty.cs
@@ -13,6 +13,9 @@ namespace Rokojori
[Export]
public Material material;
+ [Export]
+ public GeometryInstance3D instanceTarget;
+
[Export]
public FloatPropertyName propertyName;
@@ -58,12 +61,47 @@ namespace Rokojori
public bool interruptCurrent = true;
protected override void _OnTrigger()
+ {
+ if ( material != null && ! IsInstanceValid( material ) )
+ {
+ material = null;
+ }
+
+ if ( instanceTarget != null && ! IsInstanceValid( instanceTarget ) )
+ {
+ instanceTarget = null;
+ }
+
+ if ( propertyName == null )
+ {
+ return;
+ }
+
+ // this.LogInfo( "TRIGGERED" );
+
+ if ( instanceTarget != null )
+ {
+ // this.LogInfo( "INSTANCE" );
+ TweenInstanceUniform();
+ return;
+ }
+
+ if ( material != null )
+ {
+ // this.LogInfo( "MATERIAL" );
+ TweenMaterial();
+ return;
+ }
+
+
+ }
+
+ void TweenMaterial()
{
if ( ! interruptCurrent && _actionID != -1 )
{
return;
}
-
if ( _actionID != -1 )
{
@@ -72,30 +110,30 @@ namespace Rokojori
if ( stopAnimation )
{
-
+
AnimationManager.StartAnimation( this, material, propertyName );
if ( setStopValue )
{
propertyName.Set( material, endValue );
}
-
+
AnimationManager.EndAnimation( this, material, propertyName );
-
+
return;
}
_actionID = DispatchStart();
-
+
var startValue = propertyName.Get( material );
-
+
if ( forceStartValue && Engine.IsEditorHint() )
- {
+ {
startValue = forcedStartValue;
}
@@ -104,15 +142,15 @@ namespace Rokojori
startValue = animationStartValue;
}
-
-
+
+
AnimationManager.StartAnimation( this, material, propertyName );
// this.LogInfo( "Get Float Tween", startValue );
_timeID = TimelineManager.ScheduleSpanWith( duration,
( span, type )=>
- {
+ {
// this.LogInfo( "Update Float Tween", startValue );
if ( span.id != _timeID )
@@ -138,12 +176,12 @@ namespace Rokojori
propertyName.Set( material, value );
// ReflectionHelper.SetValue( material, propertyName , value );
-
- if ( type == TimelineSpanUpdateType.End )
- {
- // this.LogInfo( "End Float Tween", endValue );
- // target._Set( targetMember, endValue );
+ if ( type == TimelineSpanUpdateType.End )
+ {
+ // this.LogInfo( "End Float Tween", endValue );
+
+ // target._Set( targetMember, endValue );
propertyName.Set( material, endValue );
AnimationManager.EndAnimation( this, material, propertyName );
@@ -156,5 +194,105 @@ namespace Rokojori
).id;
}
+ void TweenInstanceUniform()
+ {
+ if ( ! interruptCurrent && _actionID != -1 )
+ {
+ return;
+ }
+
+ if ( _actionID != -1 )
+ {
+ CancelAction( _actionID );
+ }
+
+ if ( stopAnimation )
+ {
+
+ AnimationManager.StartAnimation( this, instanceTarget, propertyName.propertyName );
+
+ if ( setStopValue )
+ {
+ propertyName.SetInstance( instanceTarget, endValue );
+ }
+
+ AnimationManager.EndAnimation( this, instanceTarget, propertyName.propertyName );
+
+
+ return;
+ }
+
+
+ _actionID = DispatchStart();
+
+ var startValue = propertyName.GetInstance( instanceTarget );
+
+
+
+
+ if ( forceStartValue && Engine.IsEditorHint() )
+ {
+ startValue = forcedStartValue;
+ }
+
+ if ( animateStartValue )
+ {
+ startValue = animationStartValue;
+ }
+
+
+
+ AnimationManager.StartAnimation( this, instanceTarget, propertyName.propertyName );
+
+ // this.LogInfo( "Get Float Tween", startValue );
+
+ _timeID = TimelineManager.ScheduleSpanWith( duration,
+ ( span, type )=>
+ {
+
+ // this.LogInfo( "Update Float Tween", startValue );
+ if ( span.id != _timeID )
+ {
+ return;
+ }
+
+ if ( ! AnimationManager.IsAnimating( this, instanceTarget, propertyName.propertyName ) )
+ {
+ return;
+ }
+
+ var phase = span.phase;
+
+ if ( curve != null )
+ {
+ phase = curve.Sample( phase );
+ }
+
+ var value = Mathf.Lerp( startValue, endValue, phase );
+
+
+
+ // this.LogInfo( "Updating Float Tween", "phase:", phase, "value:", value, instanceTarget );
+
+ propertyName.SetInstance( instanceTarget, value );
+ // ReflectionHelper.SetValue( material, propertyName , value );
+
+ if ( type == TimelineSpanUpdateType.End )
+ {
+ // this.LogInfo( "End Float Tween", endValue );
+
+ // target._Set( targetMember, endValue );
+
+ propertyName.SetInstance( instanceTarget, endValue );
+ AnimationManager.EndAnimation( this, instanceTarget, propertyName.propertyName );
+ DispatchEnd( _actionID );
+ _actionID = -1;
+ _timeID = -1;
+ }
+ },
+ this
+ ).id;
+ }
+
}
}
\ No newline at end of file
diff --git a/Runtime/Actions/Visual/TweenMaterial.cs b/Runtime/Actions/Visual/TweenMaterial.cs
index addb0d9..a73dc5f 100644
--- a/Runtime/Actions/Visual/TweenMaterial.cs
+++ b/Runtime/Actions/Visual/TweenMaterial.cs
@@ -89,6 +89,11 @@ namespace Rokojori
var fromMaterial = MaterialSurfaceContainer.From( target, this.index ).GetMaterialInSlot( slot );
+ // Registers ownership of the *slot assignment itself* (as opposed to the color/float
+ // properties on the material), so a second TweenMaterial targeting the same slot takes
+ // over cleanly instead of both tweens fighting to set the end material.
+ AnimationManager.StartAnimation( this, target, GetSlotKey() );
+
SetAssignedMaterial( fromMaterial );
var tl = TimelineManager.Ensure( timeLine );
@@ -180,25 +185,32 @@ namespace Rokojori
AnimationManager.EndAnimation( this, _assignedMaterial, f.propertyName );
}
- if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )
+ // Only write the end material (and release ownership) if no later TweenMaterial
+ // has since taken over this slot - otherwise this would stomp on it after the fact.
+ if ( AnimationManager.IsAnimating( this, target, GetSlotKey() ) )
{
- _assignedMaterial = null;
- var msc = MaterialSurfaceContainer.From( target, index );
- msc.SetMaterialInSlot( slot, toMaterial );
-
- var prop = new ColorPropertyName();
- prop.propertyName = "albedo_color";
-
- var info = new Color();
-
- if ( toMaterial is BaseMaterial3D bm )
+ if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )
{
- info = bm.AlbedoColor;
+ _assignedMaterial = null;
+ var msc = MaterialSurfaceContainer.From( target, index );
+ msc.SetMaterialInSlot( slot, toMaterial );
+
+ var prop = new ColorPropertyName();
+ prop.propertyName = "albedo_color";
+
+ var info = new Color();
+
+ if ( toMaterial is BaseMaterial3D bm )
+ {
+ info = bm.AlbedoColor;
+ }
+
+ // this.LogInfo( "Setting:", slot, toMaterial, info, prop.Get( toMaterial ) );
}
- // this.LogInfo( "Setting:", slot, toMaterial, info, prop.Get( toMaterial ) );
+ AnimationManager.EndAnimation( this, target, GetSlotKey() );
}
-
+
DispatchEnd( sequenceID );
}
},
@@ -208,6 +220,11 @@ namespace Rokojori
}
+ string GetSlotKey()
+ {
+ return $"MaterialSlot:{index}:{slot}";
+ }
+
void SetAssignedMaterial( Material material )
{
if ( MaterialAssignmentMode.Temporary_Duplicate_For_Each_Tween == assignmentMode )
diff --git a/Runtime/Animation/Shake/Shake.cs b/Runtime/Animation/Shake/Shake.cs
index adbcd89..28d3a1d 100644
--- a/Runtime/Animation/Shake/Shake.cs
+++ b/Runtime/Animation/Shake/Shake.cs
@@ -118,6 +118,11 @@ namespace Rokojori
for ( int i = 0; i < targets.Length; i++ )
{
+ if ( ! IsInstanceValid( targets[ i ] ) )
+ {
+ continue;
+ }
+
var o = _targetValues[ i ];
o.Set( targets[ i ], shakeEffect.globalPosition, shakeEffect.globalRotation );
@@ -146,6 +151,11 @@ namespace Rokojori
for ( int i = 0; i < targets.Length; i++ )
{
+ if ( ! IsInstanceValid( targets[ i ] ) )
+ {
+ continue;
+ }
+
var combined = offsetData.Clone();
var o = _targetValues[ i ];
diff --git a/Runtime/Behavior/Brain.cs b/Runtime/Behavior/Brain.cs
deleted file mode 100644
index 36e89c2..0000000
--- a/Runtime/Behavior/Brain.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-using Godot;
-
-using Rokojori.Extensions;
-namespace Rokojori;
-
-[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
-public partial class Brain:Action
-{
- [Export]
- public Receptor[] receptors;
-
- [Export]
- public Node behaviorRoot;
-
- [Export]
- public BehaviorSequence activeSequence;
-
- bool _paused = false;
-
- public bool paused => _paused;
-
- public void SetPaused( bool value )
- {
- _paused = value;
- }
-
- protected override void _OnTrigger()
- {
- if ( _paused )
- {
- return;
- }
-
- if ( activeSequence != null && activeSequence.isExclusive && activeSequence.isRunning )
- {
- return;
- }
-
- var resolved = Resolve();
-
- if ( resolved == null )
- {
- return;
- }
-
- if ( resolved == activeSequence )
- {
- if ( activeSequence.reinitialize )
- {
- activeSequence.CancelAllRunning();
- activeSequence.Trigger();
- }
-
- return;
- }
-
- var previous = activeSequence;
- activeSequence = resolved;
-
- if ( previous != null )
- {
- previous.CancelAllRunning();
- }
-
- activeSequence.Trigger();
- }
-
- BehaviorSequence Resolve()
- {
- if ( behaviorRoot == null )
- {
- return null;
- }
-
- return ResolveIn( behaviorRoot );
- }
-
- BehaviorSequence ResolveIn( Node parent )
- {
- var walker = NodesWalker.Get();
- var numChildren = walker.NumChildren( parent );
-
- for ( int i = 0; i < numChildren; i++ )
- {
- var child = walker.ChildAt( parent, i );
-
- if ( child is BehaviorSequence sequence )
- {
- return sequence;
- }
-
- if ( child is BehaviorBranch branch )
- {
- if ( branch.IsBranchActive( this ) )
- {
- var found = ResolveIn( branch );
-
- if ( found != null )
- {
- return found;
- }
- }
- }
- }
-
- return null;
- }
-}
diff --git a/Runtime/Behavior/BehaviorBranch.cs b/Runtime/Behavior/Core/BehaviorBranch.cs
similarity index 83%
rename from Runtime/Behavior/BehaviorBranch.cs
rename to Runtime/Behavior/Core/BehaviorBranch.cs
index 6b3a335..5fffc64 100644
--- a/Runtime/Behavior/BehaviorBranch.cs
+++ b/Runtime/Behavior/Core/BehaviorBranch.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/BehaviorBranch.svg")]
public abstract partial class BehaviorBranch:Node
{
[Export]
diff --git a/Runtime/Behavior/BehaviorBranch.cs.uid b/Runtime/Behavior/Core/BehaviorBranch.cs.uid
similarity index 100%
rename from Runtime/Behavior/BehaviorBranch.cs.uid
rename to Runtime/Behavior/Core/BehaviorBranch.cs.uid
diff --git a/Runtime/Behavior/BehaviorSequence.cs b/Runtime/Behavior/Core/BehaviorSequence.cs
similarity index 77%
rename from Runtime/Behavior/BehaviorSequence.cs
rename to Runtime/Behavior/Core/BehaviorSequence.cs
index 2b69fd9..7647568 100644
--- a/Runtime/Behavior/BehaviorSequence.cs
+++ b/Runtime/Behavior/Core/BehaviorSequence.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/BehaviorSequence.svg")]
public partial class BehaviorSequence:Sequence
{
[Export]
@@ -29,7 +29,9 @@ public partial class BehaviorSequence:Sequence
void OnDone( SequenceActionFinishedEvent ev )
{
- if ( _brain == null || _brain.activeSequence != this )
+ this.LogInfo( "On Done BS" );
+
+ if ( _brain == null || _brain.activeNode != this )
{
return;
}
diff --git a/Runtime/Behavior/BehaviorSequence.cs.uid b/Runtime/Behavior/Core/BehaviorSequence.cs.uid
similarity index 100%
rename from Runtime/Behavior/BehaviorSequence.cs.uid
rename to Runtime/Behavior/Core/BehaviorSequence.cs.uid
diff --git a/Runtime/Behavior/Core/Brain.cs b/Runtime/Behavior/Core/Brain.cs
new file mode 100644
index 0000000..d021012
--- /dev/null
+++ b/Runtime/Behavior/Core/Brain.cs
@@ -0,0 +1,321 @@
+using Godot;
+
+using System.Collections.Generic;
+
+using Rokojori.Extensions;
+using System.Linq;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Brain.svg")]
+public partial class Brain:Action
+{
+ [Export]
+ public Receptor[] receptors;
+
+ [Export]
+ public Node[] receporContainers;
+
+ [Export]
+ public Node behaviorRoot;
+
+
+ [ExportGroup( "Debugging")]
+ [Export]
+ public Node activeNode;
+
+
+ [Export]
+ public bool printActiveNodeSelection = false;
+
+ [Export]
+ public bool printBranchResult = false;
+
+ // Non-BehaviorSequence nodes (plain Actions) don't carry this state themselves,
+ // so these stand in for it. Kept as protected statics in case that needs tuning later.
+ protected static bool defaultIsExclusive = false;
+ protected static bool defaultIsRunning = false;
+ protected static bool defaultReinitialize = true;
+
+ bool _paused = false;
+
+ public bool paused => _paused;
+
+ public void SetPaused( bool value )
+ {
+ _paused = value;
+ }
+
+ static bool IsExclusive( Node node )
+ {
+ return node is BehaviorSequence sequence ? sequence.isExclusive : defaultIsExclusive;
+ }
+
+ static bool IsRunning( Node node )
+ {
+ return node is BehaviorSequence sequence ? sequence.isRunning : defaultIsRunning;
+ }
+
+ static bool ShouldReinitialize( Node node )
+ {
+ return node is BehaviorSequence sequence ? sequence.reinitialize : defaultReinitialize;
+ }
+
+ static void CancelIfRunning( Node node )
+ {
+ if ( node is BehaviorSequence sequence )
+ {
+ sequence.CancelAllRunning();
+ }
+ }
+
+ public void StartBrain()
+ {
+ SetPaused( false );
+
+ receptors?.ForEachNonNull( r => r.Start() );
+
+ receporContainers?.ForEachNonNull( c => c.ForEach( r => r.Start() ) );
+
+ Trigger();
+ }
+
+ // The node most recently armed with a one-shot onSequenceDone listener, so a late
+ // event from a node the Brain has since moved away from doesn't retrigger it.
+ SequenceAction _listenedSequenceAction;
+
+ // The node a next-frame restart is currently scheduled for, so a plain Action
+ // doesn't accumulate more than one pending restart chain at a time.
+ Node _pendingRestartNode;
+
+ void ActivateNode( Node node )
+ {
+ if ( node is Action action )
+ {
+ action.Trigger();
+ }
+
+ if ( node is BehaviorSequence )
+ {
+ // Manages its own re-trigger/loop via onSequenceDone already (see BehaviorSequence._Ready).
+ return;
+ }
+
+ if ( node is SequenceAction sequenceAction )
+ {
+ _listenedSequenceAction = sequenceAction;
+
+ if ( ! sequenceAction.onSequenceDone.HasAction( OnListenedSequenceActionDone ) )
+ {
+ sequenceAction.onSequenceDone.Once( OnListenedSequenceActionDone );
+ }
+ }
+ else if ( node is Action )
+ {
+ ScheduleRestartNextFrame( node );
+ }
+ }
+
+ void OnListenedSequenceActionDone( SequenceActionFinishedEvent ev )
+ {
+ if ( activeNode != _listenedSequenceAction )
+ {
+ return;
+ }
+
+ Trigger();
+ }
+
+ async void ScheduleRestartNextFrame( Node node )
+ {
+ if ( _pendingRestartNode == node )
+ {
+ return;
+ }
+
+ _pendingRestartNode = node;
+
+ await this.RequestNextFrame();
+
+ if ( _pendingRestartNode == node )
+ {
+ _pendingRestartNode = null;
+ }
+
+ if ( activeNode != node )
+ {
+ return;
+ }
+
+ Trigger();
+ }
+
+ // Dedup flag for the no-match-resolved restart below, mirroring _pendingRestartNode.
+ bool _pendingNullResolveRestart = false;
+
+ async void ScheduleRestartNextFrameOnNullResolve()
+ {
+ if ( _pendingNullResolveRestart )
+ {
+ return;
+ }
+
+ _pendingNullResolveRestart = true;
+
+ await this.RequestNextFrame();
+
+ _pendingNullResolveRestart = false;
+
+ Trigger();
+ }
+
+ List _branchInfo = new List();
+
+ protected override void _OnTrigger()
+ {
+ if ( _paused )
+ {
+ if ( printActiveNodeSelection )
+ {
+ this.LogInfo( "Skipped. Paused" );
+ }
+ return;
+ }
+
+ if ( activeNode != null && IsExclusive( activeNode ) && IsRunning( activeNode ) )
+ {
+ if ( printActiveNodeSelection )
+ {
+ this.LogInfo( "Skipped. Running already exclusively" );
+ }
+ return;
+ }
+
+ if ( printBranchResult && _branchInfo.Count > 0 )
+ {
+ _branchInfo.Clear();
+ }
+
+ var resolved = Resolve();
+
+ if ( printBranchResult && _branchInfo.Count > 0 )
+ {
+ this.LogInfo( _branchInfo.Join( "\n" ) );
+ }
+
+ if ( printActiveNodeSelection )
+ {
+ this.LogInfo( "Active Node:", resolved == null ? "Null" : ( HierarchyName.Of( resolved ) ) );
+ }
+
+ if ( resolved == null )
+ {
+ ScheduleRestartNextFrameOnNullResolve();
+ return;
+ }
+
+ if ( resolved == activeNode )
+ {
+ if ( ShouldReinitialize( activeNode ) )
+ {
+ CancelIfRunning( activeNode );
+ ActivateNode( activeNode );
+ }
+
+ return;
+ }
+
+ var previous = activeNode;
+ activeNode = resolved;
+
+ if ( previous != null )
+ {
+ CancelIfRunning( previous );
+ }
+
+ ActivateNode( activeNode );
+ }
+
+ Node Resolve()
+ {
+ if ( behaviorRoot == null )
+ {
+ return null;
+ }
+
+ return ResolveIn( behaviorRoot );
+ }
+
+ Node ResolveIn( Node parent )
+ {
+ var walker = NodesWalker.Get();
+ var numChildren = walker.NumChildren( parent );
+
+ for ( int i = 0; i < numChildren; i++ )
+ {
+ var child = walker.ChildAt( parent, i );
+
+ if ( child is BehaviorBranch branch && branch.IsBranchActive( this ) )
+ {
+ if ( printBranchResult )
+ {
+ _branchInfo.Add( HierarchyName.Of( child ) );
+ }
+
+ var found = ResolveIn( branch );
+
+ if ( found != null )
+ {
+ return found;
+ }
+
+ }
+
+ if ( child is SceneCondition condition && condition.Evaluate() )
+ {
+ if ( printBranchResult )
+ {
+ _branchInfo.Add( HierarchyName.Of( child ) );
+ }
+
+ var found = ResolveIn( condition );
+
+ if ( found != null )
+ {
+ return found;
+ }
+
+ }
+
+ if ( child is Receptor receptor && receptor.active )
+ {
+ if ( printBranchResult )
+ {
+ _branchInfo.Add( HierarchyName.Of( child ) );
+ }
+
+ var found = ResolveIn( receptor );
+
+ if ( found != null )
+ {
+ return found;
+ }
+
+ }
+
+ if ( child is BehaviorSequence sequence )
+ {
+ return sequence;
+ }
+
+ if ( child is Action action )
+ {
+ return action;
+ }
+
+
+ }
+
+ return null;
+ }
+}
diff --git a/Runtime/Behavior/Brain.cs.uid b/Runtime/Behavior/Core/Brain.cs.uid
similarity index 100%
rename from Runtime/Behavior/Brain.cs.uid
rename to Runtime/Behavior/Core/Brain.cs.uid
diff --git a/Runtime/Behavior/BrainControl.cs b/Runtime/Behavior/Core/BrainControl.cs
similarity index 63%
rename from Runtime/Behavior/BrainControl.cs
rename to Runtime/Behavior/Core/BrainControl.cs
index 2e01ce6..1d70e61 100644
--- a/Runtime/Behavior/BrainControl.cs
+++ b/Runtime/Behavior/Core/BrainControl.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/BrainControl.svg")]
public partial class BrainControl:Action
{
public enum Mode
@@ -31,7 +31,7 @@ public partial class BrainControl:Action
switch ( mode )
{
case Mode.Start:
- StartBrain();
+ brain.StartBrain();
break;
case Mode.Pause:
@@ -48,21 +48,5 @@ public partial class BrainControl:Action
}
}
- void StartBrain()
- {
- brain.SetPaused( false );
-
- if ( brain.receptors != null )
- {
- foreach ( var receptor in brain.receptors )
- {
- if ( receptor != null )
- {
- receptor.Start();
- }
- }
- }
-
- brain.Trigger();
- }
+
}
diff --git a/Runtime/Behavior/BrainControl.cs.uid b/Runtime/Behavior/Core/BrainControl.cs.uid
similarity index 100%
rename from Runtime/Behavior/BrainControl.cs.uid
rename to Runtime/Behavior/Core/BrainControl.cs.uid
diff --git a/Runtime/Behavior/ConditionBranch.cs b/Runtime/Behavior/Core/ConditionBranch.cs
similarity index 79%
rename from Runtime/Behavior/ConditionBranch.cs
rename to Runtime/Behavior/Core/ConditionBranch.cs
index de402ff..59f20fa 100644
--- a/Runtime/Behavior/ConditionBranch.cs
+++ b/Runtime/Behavior/Core/ConditionBranch.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass]
public partial class ConditionBranch:BehaviorBranch
{
[Export]
diff --git a/Runtime/Behavior/ConditionBranch.cs.uid b/Runtime/Behavior/Core/ConditionBranch.cs.uid
similarity index 100%
rename from Runtime/Behavior/ConditionBranch.cs.uid
rename to Runtime/Behavior/Core/ConditionBranch.cs.uid
diff --git a/Runtime/Behavior/ReceptorBranch.cs b/Runtime/Behavior/Core/ReceptorBranch.cs
similarity index 76%
rename from Runtime/Behavior/ReceptorBranch.cs
rename to Runtime/Behavior/Core/ReceptorBranch.cs
index 7f7d457..43d233a 100644
--- a/Runtime/Behavior/ReceptorBranch.cs
+++ b/Runtime/Behavior/Core/ReceptorBranch.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/ReceptorBranch.svg")]
public partial class ReceptorBranch:BehaviorBranch
{
[Export]
diff --git a/Runtime/Behavior/ReceptorBranch.cs.uid b/Runtime/Behavior/Core/ReceptorBranch.cs.uid
similarity index 100%
rename from Runtime/Behavior/ReceptorBranch.cs.uid
rename to Runtime/Behavior/Core/ReceptorBranch.cs.uid
diff --git a/Runtime/Behavior/FollowBehavior.cs b/Runtime/Behavior/FollowBehavior.cs
deleted file mode 100644
index 9d2d102..0000000
--- a/Runtime/Behavior/FollowBehavior.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Godot;
-
-using Rokojori.Extensions;
-namespace Rokojori;
-
-[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
-public partial class FollowBehavior:BehaviorSequence
-{
- [Export]
- public BehaviorMovement movement;
-
- [Export]
- public Node3D target;
-
- protected override void _OnTrigger()
- {
- if ( movement != null )
- {
- movement.Follow( target );
- }
-
- base._OnTrigger();
- }
-}
diff --git a/Runtime/Behavior/BehaviorMovement.cs b/Runtime/Behavior/Movements/BehaviorMovement.cs
similarity index 83%
rename from Runtime/Behavior/BehaviorMovement.cs
rename to Runtime/Behavior/Movements/BehaviorMovement.cs
index 4e1de7e..d1ad9ac 100644
--- a/Runtime/Behavior/BehaviorMovement.cs
+++ b/Runtime/Behavior/Movements/BehaviorMovement.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass]
public abstract partial class BehaviorMovement:Node
{
public abstract void GoToPosition( Vector3 worldPosition );
diff --git a/Runtime/Behavior/BehaviorMovement.cs.uid b/Runtime/Behavior/Movements/BehaviorMovement.cs.uid
similarity index 100%
rename from Runtime/Behavior/BehaviorMovement.cs.uid
rename to Runtime/Behavior/Movements/BehaviorMovement.cs.uid
diff --git a/Runtime/Behavior/CharacterBodyBehaviorMovement.cs b/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs
similarity index 96%
rename from Runtime/Behavior/CharacterBodyBehaviorMovement.cs
rename to Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs
index e3653f0..ddbe14b 100644
--- a/Runtime/Behavior/CharacterBodyBehaviorMovement.cs
+++ b/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs
@@ -4,7 +4,7 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass]
public partial class CharacterBodyBehaviorMovement:BehaviorMovement
{
[Export]
diff --git a/Runtime/Behavior/CharacterBodyBehaviorMovement.cs.uid b/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs.uid
similarity index 100%
rename from Runtime/Behavior/CharacterBodyBehaviorMovement.cs.uid
rename to Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs.uid
diff --git a/Runtime/Behavior/Movements/FollowBehavior.cs b/Runtime/Behavior/Movements/FollowBehavior.cs
new file mode 100644
index 0000000..f6ae51a
--- /dev/null
+++ b/Runtime/Behavior/Movements/FollowBehavior.cs
@@ -0,0 +1,33 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class FollowBehavior:Action
+{
+ [Export]
+ public BehaviorMovement movement;
+
+ [Export]
+ public Node3D target;
+
+ protected override void _OnTrigger()
+ {
+ if ( movement == null || target == null )
+ {
+ return;
+ }
+
+ // var id = DispatchStart();
+
+ movement.Follow( target );
+
+ // await this.RequestNextFrame();
+
+ // this.LogInfo( "Done" );
+
+ // DispatchEnd( id );
+ }
+}
diff --git a/Runtime/Behavior/FollowBehavior.cs.uid b/Runtime/Behavior/Movements/FollowBehavior.cs.uid
similarity index 100%
rename from Runtime/Behavior/FollowBehavior.cs.uid
rename to Runtime/Behavior/Movements/FollowBehavior.cs.uid
diff --git a/Runtime/Behavior/Receptor.cs b/Runtime/Behavior/Receptors/Receptor.cs
similarity index 73%
rename from Runtime/Behavior/Receptor.cs
rename to Runtime/Behavior/Receptors/Receptor.cs
index 13ed2b8..3e09562 100644
--- a/Runtime/Behavior/Receptor.cs
+++ b/Runtime/Behavior/Receptors/Receptor.cs
@@ -4,25 +4,29 @@ using Rokojori.Extensions;
namespace Rokojori;
[Tool]
-[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Action.svg")]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Receptor.svg")]
public partial class Receptor:Action
{
[Export]
public SceneCondition sceneCondition;
[Export( PropertyHint.Range, "0.1,100")]
- public float receiptionTriggerCoverage = 75f;
+ public float receptionTriggerCoverage = 75f;
[Export( PropertyHint.Range, "10,2000")]
- public float receiptionWindowDurationMS = 500f;
+ public float receptionWindowDurationMS = 500f;
[Export( PropertyHint.Range, "10,1000")]
public float tickRateMS = 100f;
[Export]
public Timeline timeline;
+
+ [ExportGroup("Debugging")]
+ [Export]
+ public float fillState = 0;
- int resultSize => Mathf.CeilToInt( receiptionWindowDurationMS / tickRateMS );
+ int resultSize => Mathf.CeilToInt( receptionWindowDurationMS / tickRateMS );
RingBuffer _resultsBuffer;
@@ -70,8 +74,11 @@ public partial class Receptor:Action
void Sample()
{
+ // this.LogInfo( "Receptor Update" );
+
if ( sceneCondition == null )
{
+ // this.LogInfo( "No condition" );
return;
}
@@ -84,12 +91,15 @@ public partial class Receptor:Action
if ( _resultsBuffer.count != resultSize )
{
+ // this.LogInfo( "No activation" );
_activated = false;
return;
}
var numActive = _resultsBuffer.CountItems( b => b );
- _activated = numActive / (float) resultSize >= receiptionTriggerCoverage / 100f;
+ fillState = numActive / (float) resultSize;
+
+ _activated = fillState >= receptionTriggerCoverage / 100f;
}
}
diff --git a/Runtime/Behavior/Receptor.cs.uid b/Runtime/Behavior/Receptors/Receptor.cs.uid
similarity index 100%
rename from Runtime/Behavior/Receptor.cs.uid
rename to Runtime/Behavior/Receptors/Receptor.cs.uid
diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/SetThirdPersonCameraLockTarget.cs b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/SetThirdPersonCameraLockTarget.cs
deleted file mode 100644
index 5f4fb06..0000000
--- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/SetThirdPersonCameraLockTarget.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-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;
- }
- }
-}
diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/SetThirdPersonCameraLockTarget.cs.uid b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/SetThirdPersonCameraLockTarget.cs.uid
deleted file mode 100644
index 12fb438..0000000
--- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/SetThirdPersonCameraLockTarget.cs.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://pesdcfjnpi30
diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs
index 6a01446..49e8fef 100644
--- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs
+++ b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs
@@ -17,7 +17,7 @@ namespace Rokojori
public Node3D target;
[Export]
- public Node3D lockTarget;
+ public CameraLocker cameraLocker;
[Export]
public float yaw;
@@ -68,12 +68,6 @@ namespace Rokojori
return;
}
- if ( lockTarget != null && ! IsInstanceValid( lockTarget ) )
- {
- lockTarget = null;
- }
-
-
// var tl = TimeLineManager.Ensure( timeLine );
// var delta = tl.delta;
var delta = (float) gameDelta;
@@ -81,19 +75,14 @@ namespace Rokojori
var targetPosition = Smoothing.Apply( settings.targetFollowSmoothing, target.GlobalPosition, delta );
- var hasLockTarget = lockTarget != null;
+ var lockTargetNode = cameraLocker != null && cameraLocker.isLocked && IsInstanceValid( cameraLocker.lockTarget ) ? cameraLocker.lockTarget.pointerPosition : null;
+ var hasLockTarget = lockTargetNode != null;
var lockTargetPosition = _lastLockTargetPosition;
if ( hasLockTarget )
{
- lockTargetPosition = Smoothing.Apply( settings.lockTargetFollowSmoothing, lockTarget.GlobalPosition, delta );
+ lockTargetPosition = Smoothing.Apply( settings.lockTargetFollowSmoothing, lockTargetNode.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
diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs
index c8e93a0..44fc73d 100644
--- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs
+++ b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs
@@ -81,9 +81,6 @@ namespace Rokojori
[Export]
public Smoothing lockBlendSmoothing = new FrameSmoothing();
- [Export]
- public float lockReleaseDistance = 0f;
-
[Export]
public float lockMinPitch = -10;
diff --git a/Runtime/Conditions/HasCondition.cs b/Runtime/Conditions/HasCondition.cs
new file mode 100644
index 0000000..45792f2
--- /dev/null
+++ b/Runtime/Conditions/HasCondition.cs
@@ -0,0 +1,205 @@
+
+using System.Collections.Generic;
+using System.Reflection;
+using Godot;
+
+
+using Rokojori.Extensions;
+namespace Rokojori
+{
+ [Tool]
+ [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Condition.svg")]
+ public partial class HasCondition: SceneCondition
+ {
+ bool _enteredTree = false;
+
+ public override void _EnterTree()
+ {
+ base._EnterTree();
+ _enteredTree = true;
+ }
+
+ void OnTargetAssignmentChanged()
+ {
+ // Deserialization sets exported properties before _EnterTree fires, so this
+ // only triggers for genuine interactive edits in the editor, never for a scene
+ // being loaded (regardless of property order) or for runtime reassignment.
+ if ( ! _enteredTree || ! Engine.IsEditorHint() )
+ {
+ return;
+ }
+
+ targetName = "";
+
+#if TOOLS
+ NotifyPropertyListChanged();
+#endif
+ }
+
+ GodotObject _target;
+
+ [Export]
+ public GodotObject target
+ {
+ get => _target;
+ set
+ {
+ if ( _target == value )
+ {
+ return;
+ }
+
+ _target = value;
+ OnTargetAssignmentChanged();
+ }
+ }
+
+ Node _targetNode;
+
+ [Export]
+ public Node targetNode
+ {
+ get => _targetNode;
+ set
+ {
+ if ( _targetNode == value )
+ {
+ return;
+ }
+
+ _targetNode = value;
+ OnTargetAssignmentChanged();
+ }
+ }
+
+ [Export]
+ public string targetName;
+
+ GodotObject GetEffectiveTarget()
+ {
+ return target != null ? target : (GodotObject) targetNode;
+ }
+
+ public override bool Evaluate()
+ {
+ var effectiveTarget = GetEffectiveTarget();
+
+ if ( effectiveTarget == null || string.IsNullOrEmpty( targetName ) )
+ {
+ return false;
+ }
+
+ var value = ReflectionHelper.GetDataMemberValue