diff --git a/Icons/OnStateActive.svg b/Icons/OnStateActive.svg
new file mode 100644
index 0000000..a78c625
--- /dev/null
+++ b/Icons/OnStateActive.svg
@@ -0,0 +1,59 @@
+
+
diff --git a/Icons/OnStateActive.svg.import b/Icons/OnStateActive.svg.import
new file mode 100644
index 0000000..7abf1f5
--- /dev/null
+++ b/Icons/OnStateActive.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pu48jird8x7e"
+path="res://.godot/imported/OnStateActive.svg-44af1d9740b53d2319633872a1d84635.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/OnStateActive.svg"
+dest_files=["res://.godot/imported/OnStateActive.svg-44af1d9740b53d2319633872a1d84635.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/OnStateEntered.svg b/Icons/OnStateEntered.svg
new file mode 100644
index 0000000..a719853
--- /dev/null
+++ b/Icons/OnStateEntered.svg
@@ -0,0 +1,94 @@
+
+
diff --git a/Icons/OnStateEntered.svg.import b/Icons/OnStateEntered.svg.import
new file mode 100644
index 0000000..69d8112
--- /dev/null
+++ b/Icons/OnStateEntered.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://di0gwxfn5v3jm"
+path="res://.godot/imported/OnStateEntered.svg-cc2c47cc15c447c0d4066848fb108e6f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/OnStateEntered.svg"
+dest_files=["res://.godot/imported/OnStateEntered.svg-cc2c47cc15c447c0d4066848fb108e6f.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/OnStateExited.svg b/Icons/OnStateExited.svg
new file mode 100644
index 0000000..16dbcaa
--- /dev/null
+++ b/Icons/OnStateExited.svg
@@ -0,0 +1,61 @@
+
+
diff --git a/Icons/OnStateExited.svg.import b/Icons/OnStateExited.svg.import
new file mode 100644
index 0000000..f37ee4f
--- /dev/null
+++ b/Icons/OnStateExited.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcs52ui6ku110"
+path="res://.godot/imported/OnStateExited.svg-5e9a8abbedeb843cc99c78ab473f3106.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/OnStateExited.svg"
+dest_files=["res://.godot/imported/OnStateExited.svg-5e9a8abbedeb843cc99c78ab473f3106.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/State.svg b/Icons/State.svg
new file mode 100644
index 0000000..5f86cd8
--- /dev/null
+++ b/Icons/State.svg
@@ -0,0 +1,71 @@
+
+
diff --git a/Icons/State.svg.import b/Icons/State.svg.import
new file mode 100644
index 0000000..2d073d4
--- /dev/null
+++ b/Icons/State.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cyii8eavle6jq"
+path="res://.godot/imported/State.svg-152f831ddb60ea6a98f9b0cc3a8104b5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/State.svg"
+dest_files=["res://.godot/imported/State.svg-152f831ddb60ea6a98f9b0cc3a8104b5.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/StateMachine.svg b/Icons/StateMachine.svg
new file mode 100644
index 0000000..839b571
--- /dev/null
+++ b/Icons/StateMachine.svg
@@ -0,0 +1,150 @@
+
+
diff --git a/Icons/StateMachine.svg.import b/Icons/StateMachine.svg.import
new file mode 100644
index 0000000..d8c4f75
--- /dev/null
+++ b/Icons/StateMachine.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bc0rlxsml3vwi"
+path="res://.godot/imported/StateMachine.svg-310f195fb3534c1788d58d28c8ba9b86.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/StateMachine.svg"
+dest_files=["res://.godot/imported/StateMachine.svg-310f195fb3534c1788d58d28c8ba9b86.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/StateTransition.svg b/Icons/StateTransition.svg
new file mode 100644
index 0000000..fa0ea64
--- /dev/null
+++ b/Icons/StateTransition.svg
@@ -0,0 +1,72 @@
+
+
diff --git a/Icons/StateTransition.svg.import b/Icons/StateTransition.svg.import
new file mode 100644
index 0000000..84e85a4
--- /dev/null
+++ b/Icons/StateTransition.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bkubxrl2eugj6"
+path="res://.godot/imported/StateTransition.svg-9e667c2d4f17c05ec9915b7e754fb653.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/rokojori_action_library/Icons/StateTransition.svg"
+dest_files=["res://.godot/imported/StateTransition.svg-9e667c2d4f17c05ec9915b7e754fb653.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/Behavior/Fight/FightManager.cs b/Runtime/Behavior/Fight/FightManager.cs
new file mode 100644
index 0000000..86ae958
--- /dev/null
+++ b/Runtime/Behavior/Fight/FightManager.cs
@@ -0,0 +1,103 @@
+using Godot;
+using System.Collections.Generic;
+using System.Linq;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class FightManager:Node
+{
+ [Export]
+ public int maxEngaged = 3;
+
+ [Export]
+ public int maxAttacking = 1;
+
+ List _slots = new List();
+ Dictionary _lastAttackTime = new Dictionary();
+
+ ulong _cachedFrame = ulong.MaxValue;
+ List _cachedEngaged = new List();
+ List _cachedAttackers = new List();
+
+ public static FightManager Get()
+ {
+ return Unique.Get();
+ }
+
+ public int registeredCount => _slots.Count;
+ public int engagedCount => EngagedSlots().Count;
+ public int attackingCount => AttackingSlots().Count;
+
+ public void Register( FightSlot slot )
+ {
+ if ( slot == null || _slots.Contains( slot ) )
+ {
+ return;
+ }
+
+ _slots.Add( slot );
+ }
+
+ public void Unregister( FightSlot slot )
+ {
+ _slots.Remove( slot );
+ _lastAttackTime.Remove( slot );
+ }
+
+ List EngagedSlots()
+ {
+ RefreshFrameCache();
+ return _cachedEngaged;
+ }
+
+ List AttackingSlots()
+ {
+ RefreshFrameCache();
+ return _cachedAttackers;
+ }
+
+ void RefreshFrameCache()
+ {
+ var frame = Engine.GetPhysicsFrames();
+
+ if ( frame == _cachedFrame )
+ {
+ return;
+ }
+
+ _cachedFrame = frame;
+
+ _cachedEngaged = _slots
+ .OrderByDescending( s => s.priority )
+ .Take( Mathf.Max( 0, maxEngaged ) )
+ .ToList();
+
+ var now = Time.GetTicksMsec() / 1000.0;
+
+ _cachedAttackers = _cachedEngaged
+ .OrderByDescending( s => s.priority )
+ .ThenBy( s => _lastAttackTime.TryGetValue( s, out var t ) ? t : -1 )
+ .Take( Mathf.Max( 0, maxAttacking ) )
+ .ToList();
+
+ _cachedAttackers.ForEach( s => _lastAttackTime[ s ] = now );
+ }
+
+ public bool IsEngaged( FightSlot slot )
+ {
+ return slot != null && EngagedSlots().Contains( slot );
+ }
+
+ public bool HasAttackPermission( FightSlot slot )
+ {
+ return slot != null && AttackingSlots().Contains( slot );
+ }
+
+ public bool IsAnyOtherAttacking( FightSlot self )
+ {
+ return AttackingSlots().Any( s => s != self );
+ }
+}
diff --git a/Runtime/Behavior/Fight/FightManager.cs.uid b/Runtime/Behavior/Fight/FightManager.cs.uid
new file mode 100644
index 0000000..ee8a613
--- /dev/null
+++ b/Runtime/Behavior/Fight/FightManager.cs.uid
@@ -0,0 +1 @@
+uid://cpu8c7kvpg8cf
diff --git a/Runtime/Behavior/Fight/FightSlot.cs b/Runtime/Behavior/Fight/FightSlot.cs
new file mode 100644
index 0000000..5eaa930
--- /dev/null
+++ b/Runtime/Behavior/Fight/FightSlot.cs
@@ -0,0 +1,48 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class FightSlot:Node
+{
+ [Export]
+ public float priority = 0;
+
+ bool _joined = false;
+
+ public void Join()
+ {
+ if ( _joined )
+ {
+ return;
+ }
+
+ var manager = FightManager.Get();
+
+ if ( manager == null )
+ {
+ return;
+ }
+
+ manager.Register( this );
+ _joined = true;
+ }
+
+ public void Leave()
+ {
+ if ( ! _joined )
+ {
+ return;
+ }
+
+ FightManager.Get()?.Unregister( this );
+ _joined = false;
+ }
+
+ public override void _ExitTree()
+ {
+ Leave();
+ }
+}
diff --git a/Runtime/Behavior/Fight/FightSlot.cs.uid b/Runtime/Behavior/Fight/FightSlot.cs.uid
new file mode 100644
index 0000000..3ac0d20
--- /dev/null
+++ b/Runtime/Behavior/Fight/FightSlot.cs.uid
@@ -0,0 +1 @@
+uid://bvfaxecr50ode
diff --git a/Runtime/Behavior/Fight/FightState.cs b/Runtime/Behavior/Fight/FightState.cs
new file mode 100644
index 0000000..1c1008e
--- /dev/null
+++ b/Runtime/Behavior/Fight/FightState.cs
@@ -0,0 +1,13 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class FightState:Node
+{
+ public bool isInCombat => ( FightManager.Get()?.registeredCount ?? 0 ) > 0;
+
+ public int attackerCount => FightManager.Get()?.attackingCount ?? 0;
+}
diff --git a/Runtime/Behavior/Fight/FightState.cs.uid b/Runtime/Behavior/Fight/FightState.cs.uid
new file mode 100644
index 0000000..4708336
--- /dev/null
+++ b/Runtime/Behavior/Fight/FightState.cs.uid
@@ -0,0 +1 @@
+uid://b7ygkdwfomf1c
diff --git a/Runtime/Behavior/Fight/HasAttackPermission.cs b/Runtime/Behavior/Fight/HasAttackPermission.cs
new file mode 100644
index 0000000..2a4a24f
--- /dev/null
+++ b/Runtime/Behavior/Fight/HasAttackPermission.cs
@@ -0,0 +1,17 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class HasAttackPermission:SceneCondition
+{
+ [Export]
+ public FightSlot slot;
+
+ public override bool Evaluate()
+ {
+ return FightManager.Get()?.HasAttackPermission( slot ) ?? false;
+ }
+}
diff --git a/Runtime/Behavior/Fight/HasAttackPermission.cs.uid b/Runtime/Behavior/Fight/HasAttackPermission.cs.uid
new file mode 100644
index 0000000..2c3deb9
--- /dev/null
+++ b/Runtime/Behavior/Fight/HasAttackPermission.cs.uid
@@ -0,0 +1 @@
+uid://f5sb5yvwtbem
diff --git a/Runtime/Behavior/Fight/IsAllyAttacking.cs b/Runtime/Behavior/Fight/IsAllyAttacking.cs
new file mode 100644
index 0000000..6e33d82
--- /dev/null
+++ b/Runtime/Behavior/Fight/IsAllyAttacking.cs
@@ -0,0 +1,17 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class IsAllyAttacking:SceneCondition
+{
+ [Export]
+ public FightSlot slot;
+
+ public override bool Evaluate()
+ {
+ return FightManager.Get()?.IsAnyOtherAttacking( slot ) ?? false;
+ }
+}
diff --git a/Runtime/Behavior/Fight/IsAllyAttacking.cs.uid b/Runtime/Behavior/Fight/IsAllyAttacking.cs.uid
new file mode 100644
index 0000000..70b06e5
--- /dev/null
+++ b/Runtime/Behavior/Fight/IsAllyAttacking.cs.uid
@@ -0,0 +1 @@
+uid://b5gql6d11xntt
diff --git a/Runtime/Behavior/Fight/IsEngagedInFight.cs b/Runtime/Behavior/Fight/IsEngagedInFight.cs
new file mode 100644
index 0000000..5a21028
--- /dev/null
+++ b/Runtime/Behavior/Fight/IsEngagedInFight.cs
@@ -0,0 +1,17 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class IsEngagedInFight:SceneCondition
+{
+ [Export]
+ public FightSlot slot;
+
+ public override bool Evaluate()
+ {
+ return FightManager.Get()?.IsEngaged( slot ) ?? false;
+ }
+}
diff --git a/Runtime/Behavior/Fight/IsEngagedInFight.cs.uid b/Runtime/Behavior/Fight/IsEngagedInFight.cs.uid
new file mode 100644
index 0000000..16056ba
--- /dev/null
+++ b/Runtime/Behavior/Fight/IsEngagedInFight.cs.uid
@@ -0,0 +1 @@
+uid://k8fi0cfr0wl4
diff --git a/Runtime/Behavior/Fight/JoinFight.cs b/Runtime/Behavior/Fight/JoinFight.cs
new file mode 100644
index 0000000..c77760b
--- /dev/null
+++ b/Runtime/Behavior/Fight/JoinFight.cs
@@ -0,0 +1,17 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class JoinFight:Action
+{
+ [Export]
+ public FightSlot slot;
+
+ protected override void _OnTrigger()
+ {
+ slot?.Join();
+ }
+}
diff --git a/Runtime/Behavior/Fight/JoinFight.cs.uid b/Runtime/Behavior/Fight/JoinFight.cs.uid
new file mode 100644
index 0000000..be96a44
--- /dev/null
+++ b/Runtime/Behavior/Fight/JoinFight.cs.uid
@@ -0,0 +1 @@
+uid://4jvpwjxix4ce
diff --git a/Runtime/Behavior/Fight/LeaveFight.cs b/Runtime/Behavior/Fight/LeaveFight.cs
new file mode 100644
index 0000000..2a9217e
--- /dev/null
+++ b/Runtime/Behavior/Fight/LeaveFight.cs
@@ -0,0 +1,17 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class LeaveFight:Action
+{
+ [Export]
+ public FightSlot slot;
+
+ protected override void _OnTrigger()
+ {
+ slot?.Leave();
+ }
+}
diff --git a/Runtime/Behavior/Fight/LeaveFight.cs.uid b/Runtime/Behavior/Fight/LeaveFight.cs.uid
new file mode 100644
index 0000000..868749d
--- /dev/null
+++ b/Runtime/Behavior/Fight/LeaveFight.cs.uid
@@ -0,0 +1 @@
+uid://d08f82rnn7kkr
diff --git a/Runtime/Behavior/Movements/BehaviorMovement.cs b/Runtime/Behavior/Movements/BehaviorMovement.cs
index d1ad9ac..d77a7f3 100644
--- a/Runtime/Behavior/Movements/BehaviorMovement.cs
+++ b/Runtime/Behavior/Movements/BehaviorMovement.cs
@@ -9,8 +9,12 @@ public abstract partial class BehaviorMovement:Node
{
public abstract void GoToPosition( Vector3 worldPosition );
public abstract void LookAtPosition( Vector3 worldPosition );
-
+ public abstract void FleeFromPosition( Vector3 worldPosition );
+
public abstract void GoTo( Node3D node );
public abstract void Follow( Node3D node );
public abstract void LookAt( Node3D node );
+ public abstract void Flee( Node3D node );
+
+ public abstract Vector3 GetGlobalPosition();
}
diff --git a/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs b/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs
index ddbe14b..bd68409 100644
--- a/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs
+++ b/Runtime/Behavior/Movements/CharacterBodyBehaviorMovement.cs
@@ -16,12 +16,33 @@ public partial class CharacterBodyBehaviorMovement:BehaviorMovement
[Export]
public float arrivalDistance = 0.1f;
+ [Export]
+ public float fleeDistance = 10f;
+
+ [ExportGroup("Smoothing")]
+ [Export]
+ public Smoothing rotationSmoothing;
+
+ [Export]
+ public Smoothing velocitySmoothing;
+
+ [Export]
+ public Smoothing accelerationSmoothing;
+
Node3D _followTarget;
Vector3? _targetPosition;
+ Vector3? _fleeFromPosition;
+ bool _fleeing;
+
+ public override Vector3 GetGlobalPosition()
+ {
+ return body?.GlobalPosition ?? Vector3.Zero;
+ }
public override void GoToPosition( Vector3 worldPosition )
{
_followTarget = null;
+ _fleeing = false;
_targetPosition = worldPosition;
}
@@ -40,21 +61,45 @@ public partial class CharacterBodyBehaviorMovement:BehaviorMovement
return;
}
- body.GlobalTransform = new Transform3D( Basis.LookingAt( direction, Vector3.Up ), body.GlobalPosition );
+ var targetRotation = Basis.LookingAt( direction, Vector3.Up ).GetRotationQuaternion();
+ var delta = (float) GetPhysicsProcessDeltaTime();
+ var smoothedRotation = Smoothing.Apply( rotationSmoothing, targetRotation, delta );
+
+ body.GlobalTransform = new Transform3D( new Basis( smoothedRotation ), body.GlobalPosition );
+ }
+
+ public override void FleeFromPosition( Vector3 worldPosition )
+ {
+ _followTarget = null;
+ _targetPosition = null;
+ _fleeing = true;
+ _fleeFromPosition = worldPosition;
}
public override void GoTo( Node3D node )
{
_followTarget = null;
+ _fleeing = false;
_targetPosition = node == null ? null : node.GlobalPosition;
}
public override void Follow( Node3D node )
{
_followTarget = node;
+ _fleeing = false;
_targetPosition = null;
}
+ public override void Flee( Node3D node )
+ {
+ if ( node == null )
+ {
+ return;
+ }
+
+ FleeFromPosition( node.GlobalPosition );
+ }
+
public override void LookAt( Node3D node )
{
if ( node == null )
@@ -72,28 +117,44 @@ public partial class CharacterBodyBehaviorMovement:BehaviorMovement
return;
}
- Vector3? target = _followTarget != null ? _followTarget.GlobalPosition : _targetPosition;
+ Vector3 direction;
+ bool arrived;
- if ( target == null )
+ if ( _fleeing && _fleeFromPosition != null )
{
- return;
+ var away = body.GlobalPosition - _fleeFromPosition.Value;
+ away.Y = 0;
+
+ arrived = away.Length() >= fleeDistance;
+ direction = away.LengthSquared() < 0.0001f ? Vector3.Forward : away.Normalized();
+ }
+ else
+ {
+ Vector3? target = _followTarget != null ? _followTarget.GlobalPosition : _targetPosition;
+
+ if ( target == null )
+ {
+ return;
+ }
+
+ var toTarget = target.Value - body.GlobalPosition;
+ toTarget.Y = 0;
+
+ arrived = toTarget.Length() <= arrivalDistance;
+ direction = arrived ? Vector3.Zero : toTarget.Normalized();
}
- var toTarget = target.Value - body.GlobalPosition;
- toTarget.Y = 0;
+ var desiredVelocity = arrived ? Vector3.Zero : direction * moveSpeed;
- if ( toTarget.Length() <= arrivalDistance )
- {
- body.Velocity = Vector3.Zero;
- body.MoveAndSlide();
- return;
- }
+ var velocityStage = Smoothing.Apply( velocitySmoothing, desiredVelocity, (float) delta );
+ var accelerationStage = Smoothing.Apply( accelerationSmoothing, velocityStage, (float) delta );
- var direction = toTarget.Normalized();
-
- body.Velocity = direction * moveSpeed;
+ body.Velocity = accelerationStage;
body.MoveAndSlide();
- LookAtPosition( body.GlobalPosition + direction );
+ if ( ! arrived && accelerationStage.LengthSquared() > 0.0001f )
+ {
+ LookAtPosition( body.GlobalPosition + accelerationStage.Normalized() );
+ }
}
}
diff --git a/Runtime/Behavior/Movements/FleeBehavior.cs b/Runtime/Behavior/Movements/FleeBehavior.cs
new file mode 100644
index 0000000..1e93b40
--- /dev/null
+++ b/Runtime/Behavior/Movements/FleeBehavior.cs
@@ -0,0 +1,25 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class FleeBehavior:Action
+{
+ [Export]
+ public BehaviorMovement movement;
+
+ [Export]
+ public Node3D target;
+
+ protected override void _OnTrigger()
+ {
+ if ( movement == null || target == null )
+ {
+ return;
+ }
+
+ movement.Flee( target );
+ }
+}
diff --git a/Runtime/Behavior/Movements/FleeBehavior.cs.uid b/Runtime/Behavior/Movements/FleeBehavior.cs.uid
new file mode 100644
index 0000000..1fa8a8a
--- /dev/null
+++ b/Runtime/Behavior/Movements/FleeBehavior.cs.uid
@@ -0,0 +1 @@
+uid://vvewrqnq4msf
diff --git a/Runtime/Behavior/Movements/GoToPointBehavior.cs b/Runtime/Behavior/Movements/GoToPointBehavior.cs
new file mode 100644
index 0000000..c898887
--- /dev/null
+++ b/Runtime/Behavior/Movements/GoToPointBehavior.cs
@@ -0,0 +1,65 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class GoToPointBehavior:SequenceAction
+{
+ [Export]
+ public BehaviorMovement movement;
+
+ [Export]
+ public Node3D target;
+
+ [Export]
+ public float arrivalDistance = 0.2f;
+
+ int _activeId = -1;
+
+ protected override void _OnTrigger()
+ {
+ if ( movement == null || target == null )
+ {
+ return;
+ }
+
+ movement.GoTo( target );
+ _activeId = DispatchStart();
+ }
+
+ public override void _PhysicsProcess( double delta )
+ {
+ if ( _activeId == -1 )
+ {
+ return;
+ }
+
+ if ( target == null )
+ {
+ var cancelledId = _activeId;
+ _activeId = -1;
+ DispatchCancelled( cancelledId );
+ return;
+ }
+
+ var distance = ( movement.GetGlobalPosition() - target.GlobalPosition ).Length();
+
+ if ( distance <= arrivalDistance )
+ {
+ var id = _activeId;
+ _activeId = -1;
+ DispatchEnd( id );
+ }
+ }
+
+ public override void CancelAction( int id )
+ {
+ if ( id == _activeId )
+ {
+ _activeId = -1;
+ DispatchCancelled( id );
+ }
+ }
+}
diff --git a/Runtime/Behavior/Movements/GoToPointBehavior.cs.uid b/Runtime/Behavior/Movements/GoToPointBehavior.cs.uid
new file mode 100644
index 0000000..2f1034d
--- /dev/null
+++ b/Runtime/Behavior/Movements/GoToPointBehavior.cs.uid
@@ -0,0 +1 @@
+uid://c8t8djdwj7sah
diff --git a/Runtime/Behavior/Movements/HideAtSpotBehavior.cs b/Runtime/Behavior/Movements/HideAtSpotBehavior.cs
new file mode 100644
index 0000000..8be129c
--- /dev/null
+++ b/Runtime/Behavior/Movements/HideAtSpotBehavior.cs
@@ -0,0 +1,93 @@
+using System.Linq;
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class HideAtSpotBehavior:SequenceAction
+{
+ [Export]
+ public BehaviorMovement movement;
+
+ [Export]
+ public Node searchRoot;
+
+ [Export]
+ public SelectorFlag[] requiredFlags = [];
+
+ [Export]
+ public float arrivalDistance = 0.2f;
+
+ [ExportGroup("Read Only")]
+ [Export]
+ public HidingSpot chosenSpot;
+
+ int _activeId = -1;
+
+ protected override void _OnTrigger()
+ {
+ if ( movement == null )
+ {
+ return;
+ }
+
+ var root = searchRoot ?? GetTree()?.CurrentScene;
+
+ if ( root == null )
+ {
+ return;
+ }
+
+ var spots = root.GetAll( Matches );
+
+ if ( spots.Count == 0 )
+ {
+ return;
+ }
+
+ var position = movement.GetGlobalPosition();
+
+ chosenSpot = spots.OrderBy( s => s.GlobalPosition.DistanceSquaredTo( position ) ).First();
+
+ movement.GoTo( chosenSpot );
+ _activeId = DispatchStart();
+ }
+
+ bool Matches( HidingSpot spot )
+ {
+ if ( requiredFlags == null || requiredFlags.Length == 0 )
+ {
+ return true;
+ }
+
+ return requiredFlags.All( f => spot.flags.Contains( f ) );
+ }
+
+ public override void _PhysicsProcess( double delta )
+ {
+ if ( _activeId == -1 || chosenSpot == null || movement == null )
+ {
+ return;
+ }
+
+ var distance = ( movement.GetGlobalPosition() - chosenSpot.GlobalPosition ).Length();
+
+ if ( distance <= arrivalDistance )
+ {
+ var id = _activeId;
+ _activeId = -1;
+ DispatchEnd( id );
+ }
+ }
+
+ public override void CancelAction( int id )
+ {
+ if ( id == _activeId )
+ {
+ _activeId = -1;
+ DispatchCancelled( id );
+ }
+ }
+}
diff --git a/Runtime/Behavior/Movements/HideAtSpotBehavior.cs.uid b/Runtime/Behavior/Movements/HideAtSpotBehavior.cs.uid
new file mode 100644
index 0000000..ee720eb
--- /dev/null
+++ b/Runtime/Behavior/Movements/HideAtSpotBehavior.cs.uid
@@ -0,0 +1 @@
+uid://cys665jxtmxi3
diff --git a/Runtime/Behavior/Movements/HidingSpot.cs b/Runtime/Behavior/Movements/HidingSpot.cs
new file mode 100644
index 0000000..e926400
--- /dev/null
+++ b/Runtime/Behavior/Movements/HidingSpot.cs
@@ -0,0 +1,12 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class HidingSpot:Node3D
+{
+ [Export]
+ public SelectorFlag[] flags = [];
+}
diff --git a/Runtime/Behavior/Movements/HidingSpot.cs.uid b/Runtime/Behavior/Movements/HidingSpot.cs.uid
new file mode 100644
index 0000000..a4d6754
--- /dev/null
+++ b/Runtime/Behavior/Movements/HidingSpot.cs.uid
@@ -0,0 +1 @@
+uid://dx60nkvsjsikr
diff --git a/Runtime/Behavior/Movements/MoveOnPath.cs b/Runtime/Behavior/Movements/MoveOnPath.cs
new file mode 100644
index 0000000..2625438
--- /dev/null
+++ b/Runtime/Behavior/Movements/MoveOnPath.cs
@@ -0,0 +1,62 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class MoveOnPath:SequenceAction
+{
+ [Export]
+ public Node3D target;
+
+ [Export]
+ public Spline spline;
+
+ [Export]
+ public float duration = 1f;
+
+ double _elapsed;
+ int _activeId = -1;
+
+ protected override void _OnTrigger()
+ {
+ if ( target == null || spline == null )
+ {
+ return;
+ }
+
+ _elapsed = 0;
+ _activeId = DispatchStart();
+ }
+
+ public override void _PhysicsProcess( double delta )
+ {
+ if ( _activeId == -1 )
+ {
+ return;
+ }
+
+ _elapsed += delta;
+
+ var t = duration <= 0 ? 1f : Mathf.Clamp( (float)( _elapsed / duration ), 0f, 1f );
+
+ target.GlobalPosition = spline.GetCurve().PositionAt( t );
+
+ if ( t >= 1f )
+ {
+ var id = _activeId;
+ _activeId = -1;
+ DispatchEnd( id );
+ }
+ }
+
+ public override void CancelAction( int id )
+ {
+ if ( id == _activeId )
+ {
+ _activeId = -1;
+ DispatchCancelled( id );
+ }
+ }
+}
diff --git a/Runtime/Behavior/Movements/MoveOnPath.cs.uid b/Runtime/Behavior/Movements/MoveOnPath.cs.uid
new file mode 100644
index 0000000..eacda70
--- /dev/null
+++ b/Runtime/Behavior/Movements/MoveOnPath.cs.uid
@@ -0,0 +1 @@
+uid://cvi582q7lkeod
diff --git a/Runtime/Behavior/Movements/NavigationBehaviorMovement.cs b/Runtime/Behavior/Movements/NavigationBehaviorMovement.cs
new file mode 100644
index 0000000..abbbd19
--- /dev/null
+++ b/Runtime/Behavior/Movements/NavigationBehaviorMovement.cs
@@ -0,0 +1,189 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class NavigationBehaviorMovement:BehaviorMovement
+{
+ [Export]
+ public CharacterBody3D body;
+
+ [Export]
+ public NavigationAgent3D agent;
+
+ [Export]
+ public float moveSpeed = 5f;
+
+ [Export]
+ public float fleeDistance = 10f;
+
+ [ExportGroup("Smoothing")]
+ [Export]
+ public Smoothing rotationSmoothing;
+
+ [Export]
+ public Smoothing velocitySmoothing;
+
+ Node3D _followTarget;
+ Vector3? _fleeFromPosition;
+ bool _fleeing;
+
+ public override Vector3 GetGlobalPosition()
+ {
+ return body?.GlobalPosition ?? Vector3.Zero;
+ }
+
+ public override void GoToPosition( Vector3 worldPosition )
+ {
+ _followTarget = null;
+ _fleeing = false;
+ agent?.SetTargetPosition( worldPosition );
+ }
+
+ public override void GoTo( Node3D node )
+ {
+ _followTarget = null;
+ _fleeing = false;
+
+ if ( node == null )
+ {
+ return;
+ }
+
+ agent?.SetTargetPosition( node.GlobalPosition );
+ }
+
+ public override void Follow( Node3D node )
+ {
+ _followTarget = node;
+ _fleeing = false;
+ }
+
+ public override void LookAtPosition( Vector3 worldPosition )
+ {
+ if ( body == null )
+ {
+ return;
+ }
+
+ var direction = worldPosition - body.GlobalPosition;
+ direction.Y = 0;
+
+ if ( direction.LengthSquared() < 0.0001f )
+ {
+ return;
+ }
+
+ var targetRotation = Basis.LookingAt( direction, Vector3.Up ).GetRotationQuaternion();
+ var delta = (float) GetPhysicsProcessDeltaTime();
+ var smoothedRotation = Smoothing.Apply( rotationSmoothing, targetRotation, delta );
+
+ body.GlobalTransform = new Transform3D( new Basis( smoothedRotation ), body.GlobalPosition );
+ }
+
+ public override void LookAt( Node3D node )
+ {
+ if ( node == null )
+ {
+ return;
+ }
+
+ LookAtPosition( node.GlobalPosition );
+ }
+
+ public override void FleeFromPosition( Vector3 worldPosition )
+ {
+ _followTarget = null;
+ _fleeing = true;
+ _fleeFromPosition = worldPosition;
+ }
+
+ public override void Flee( Node3D node )
+ {
+ if ( node == null )
+ {
+ return;
+ }
+
+ FleeFromPosition( node.GlobalPosition );
+ }
+
+ public override void _Ready()
+ {
+ if ( agent != null && agent.AvoidanceEnabled )
+ {
+ agent.VelocityComputed += OnVelocityComputed;
+ }
+ }
+
+ void OnVelocityComputed( Vector3 safeVelocity )
+ {
+ if ( body == null )
+ {
+ return;
+ }
+
+ body.Velocity = safeVelocity;
+ body.MoveAndSlide();
+ }
+
+ public override void _PhysicsProcess( double delta )
+ {
+ if ( body == null || agent == null )
+ {
+ return;
+ }
+
+ if ( _fleeing && _fleeFromPosition != null )
+ {
+ var away = body.GlobalPosition - _fleeFromPosition.Value;
+ away.Y = 0;
+
+ if ( away.Length() >= fleeDistance )
+ {
+ body.Velocity = Vector3.Zero;
+ body.MoveAndSlide();
+ return;
+ }
+
+ var fleeDirection = away.LengthSquared() < 0.0001f ? Vector3.Forward : away.Normalized();
+ agent.SetTargetPosition( body.GlobalPosition + fleeDirection * fleeDistance );
+ }
+ else if ( _followTarget != null )
+ {
+ agent.SetTargetPosition( _followTarget.GlobalPosition );
+ }
+
+ if ( agent.IsNavigationFinished() )
+ {
+ body.Velocity = Vector3.Zero;
+ body.MoveAndSlide();
+ return;
+ }
+
+ var nextPosition = agent.GetNextPathPosition();
+ var toNext = nextPosition - body.GlobalPosition;
+ toNext.Y = 0;
+
+ var desiredVelocity = toNext.LengthSquared() < 0.0001f ? Vector3.Zero : toNext.Normalized() * moveSpeed;
+
+ var finalVelocity = Smoothing.Apply( velocitySmoothing, desiredVelocity, (float) delta );
+
+ if ( agent.AvoidanceEnabled )
+ {
+ agent.Velocity = finalVelocity;
+ }
+ else
+ {
+ body.Velocity = finalVelocity;
+ body.MoveAndSlide();
+ }
+
+ if ( finalVelocity.LengthSquared() > 0.0001f )
+ {
+ LookAtPosition( body.GlobalPosition + finalVelocity.Normalized() );
+ }
+ }
+}
diff --git a/Runtime/Behavior/Movements/NavigationBehaviorMovement.cs.uid b/Runtime/Behavior/Movements/NavigationBehaviorMovement.cs.uid
new file mode 100644
index 0000000..1ab39eb
--- /dev/null
+++ b/Runtime/Behavior/Movements/NavigationBehaviorMovement.cs.uid
@@ -0,0 +1 @@
+uid://b15wtairu8jfd
diff --git a/Runtime/Behavior/Receptors/Receptor.cs b/Runtime/Behavior/Receptors/Receptor.cs
index 3e09562..081cbdb 100644
--- a/Runtime/Behavior/Receptors/Receptor.cs
+++ b/Runtime/Behavior/Receptors/Receptor.cs
@@ -5,101 +5,15 @@ namespace Rokojori;
[Tool]
[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Receptor.svg")]
-public partial class Receptor:Action
+public partial class Receptor:ReceptorBase
{
+
[Export]
public SceneCondition sceneCondition;
-
- [Export( PropertyHint.Range, "0.1,100")]
- public float receptionTriggerCoverage = 75f;
-
- [Export( PropertyHint.Range, "10,2000")]
- 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( receptionWindowDurationMS / tickRateMS );
-
- RingBuffer _resultsBuffer;
-
- bool _activated = false;
-
- public bool activated => _activated;
-
- bool _active = false;
-
- public bool active => _active;
-
- int _eventID = -1;
-
- public void Start()
+ protected override bool _Sample()
{
- if ( _active )
- {
- return;
- }
-
- _active = true;
-
- var duration = tickRateMS / 1000f;
- var tle = TimelineManager.ScheduleLoopEvent( timeline, duration, 0, tle => Sample() );
- _eventID = tle.id;
- }
-
- public void Stop()
- {
- if ( ! _active )
- {
- return;
- }
-
- _active = false;
-
- TimelineManager.RemoveEvent( timeline, _eventID );
- _eventID = -1;
- }
-
- protected override void _OnTrigger()
- {
- Sample();
- }
-
- void Sample()
- {
- // this.LogInfo( "Receptor Update" );
-
- if ( sceneCondition == null )
- {
- // this.LogInfo( "No condition" );
- return;
- }
-
- if ( _resultsBuffer == null || _resultsBuffer.capacity != resultSize )
- {
- _resultsBuffer = new RingBuffer( resultSize );
- }
-
- _resultsBuffer.Add( sceneCondition.Evaluate() );
-
- if ( _resultsBuffer.count != resultSize )
- {
- // this.LogInfo( "No activation" );
- _activated = false;
- return;
- }
-
- var numActive = _resultsBuffer.CountItems( b => b );
- fillState = numActive / (float) resultSize;
-
- _activated = fillState >= receptionTriggerCoverage / 100f;
-
+ return sceneCondition?.Evaluate() ?? false;
}
+
}
diff --git a/Runtime/Behavior/Receptors/ReceptorBase.cs b/Runtime/Behavior/Receptors/ReceptorBase.cs
new file mode 100644
index 0000000..65645ec
--- /dev/null
+++ b/Runtime/Behavior/Receptors/ReceptorBase.cs
@@ -0,0 +1,109 @@
+using Godot;
+using System.Collections.Generic;
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Receptor.svg")]
+public abstract partial class ReceptorBase:Action
+{
+
+ [Export( PropertyHint.Range, "0.1,100")]
+ public float receptionTriggerCoverage = 75f;
+
+ [Export( PropertyHint.Range, "10,2000")]
+ public float receptionWindowDurationMS = 500f;
+
+ [Export( PropertyHint.Range, "10,1000")]
+ public float tickRateMS = 100f;
+
+ [Export]
+ public Timeline timeline;
+
+ [ExportGroup("Debugging")]
+ [Export]
+ public float fillState = 0;
+
+ [Export]
+ public bool printUpdates = false;
+
+ int resultSize => Mathf.CeilToInt( receptionWindowDurationMS / tickRateMS );
+
+ RingBuffer _resultsBuffer;
+
+ bool _activated = false;
+
+ public bool activated => _activated;
+
+ bool _active = false;
+
+ public bool active => _active;
+
+ int _eventID = -1;
+
+ public void Start()
+ {
+ if ( _active )
+ {
+ return;
+ }
+
+ _active = true;
+
+ var duration = tickRateMS / 1000f;
+ var tle = TimelineManager.ScheduleLoopEvent( timeline, duration, 0, tle => ProcessSample() );
+ _eventID = tle.id;
+ }
+
+ public void Stop()
+ {
+ if ( ! _active )
+ {
+ return;
+ }
+
+ _active = false;
+
+ TimelineManager.RemoveEvent( timeline, _eventID );
+ _eventID = -1;
+ }
+
+ protected override void _OnTrigger()
+ {
+ ProcessSample();
+ }
+
+ protected abstract bool _Sample();
+
+ void ProcessSample()
+ {
+ var sample = _Sample();
+
+
+
+ if ( _resultsBuffer == null || _resultsBuffer.capacity != resultSize )
+ {
+ _resultsBuffer = new RingBuffer( resultSize );
+ }
+
+ _resultsBuffer.Add( sample );
+
+ if ( _resultsBuffer.count != resultSize )
+ {
+ // this.LogInfo( "No activation" );
+ _activated = false;
+ return;
+ }
+
+ var numActive = _resultsBuffer.CountItems( b => b );
+ fillState = numActive / (float) resultSize;
+
+
+ _activated = fillState >= receptionTriggerCoverage / 100f;
+
+ if ( printUpdates )
+ {
+ this.LogInfo( fillState, _activated );
+ }
+ }
+}
diff --git a/Runtime/Behavior/Receptors/ReceptorBase.cs.uid b/Runtime/Behavior/Receptors/ReceptorBase.cs.uid
new file mode 100644
index 0000000..7a778e0
--- /dev/null
+++ b/Runtime/Behavior/Receptors/ReceptorBase.cs.uid
@@ -0,0 +1 @@
+uid://dj5ffwbp0v4ee
diff --git a/Runtime/Behavior/Receptors/ReceptorControl.cs b/Runtime/Behavior/Receptors/ReceptorControl.cs
new file mode 100644
index 0000000..313a8f3
--- /dev/null
+++ b/Runtime/Behavior/Receptors/ReceptorControl.cs
@@ -0,0 +1,58 @@
+using Godot;
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/ReceptorControl.svg")]
+public partial class ReceptorControl:Action
+{
+ public enum Mode
+ {
+ Start,
+ Stop
+ }
+
+ [Export]
+ public Receptor[] receptors;
+
+ [Export]
+ public Node[] containers;
+
+
+
+ [Export]
+ public Mode mode;
+
+ protected override void _OnTrigger()
+ {
+ switch ( mode )
+ {
+ case Mode.Start:
+ {
+ ForEachReceptor( r => { r.LogInfo( "Starting" ); r.Start(); } );
+ }
+ break;
+
+ case Mode.Stop:
+ {
+ ForEachReceptor( r => r.Stop() );
+ }
+ break;
+ }
+ }
+
+ void ForEachReceptor( System.Action callback )
+ {
+ receptors?.ForEachNonNull( r => callback( r ) );
+ containers?.ForEachNonNull( c => c.ForEach( r => callback( r ) ) );
+ }
+
+
+}
diff --git a/Runtime/Behavior/Receptors/ReceptorControl.cs.uid b/Runtime/Behavior/Receptors/ReceptorControl.cs.uid
new file mode 100644
index 0000000..98aac46
--- /dev/null
+++ b/Runtime/Behavior/Receptors/ReceptorControl.cs.uid
@@ -0,0 +1 @@
+uid://l66q2ciopfmj
diff --git a/Runtime/Behavior/StateMachine/OnStateActive.cs b/Runtime/Behavior/StateMachine/OnStateActive.cs
new file mode 100644
index 0000000..d90ba96
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/OnStateActive.cs
@@ -0,0 +1,57 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnStateActive.svg")]
+public partial class OnStateActive:Sequence
+{
+
+ [Export]
+ public bool isExclusive = true;
+
+ [Export]
+ public bool reinitialize = false;
+
+ public enum LoopMode
+ {
+ No_Loop,
+ Next_Frame,
+ Direct_Continuation
+ }
+
+ [Export]
+ public LoopMode loopMode = LoopMode.No_Loop;
+
+ public override void _Ready()
+ {
+ base._Ready();
+
+ onSequenceDone.AddAction( OnDone );
+ }
+
+ protected override void _OnTrigger()
+ {
+ // this.LogInfo( "Triggering" );
+ base._OnTrigger();
+ }
+
+ async void OnDone( SequenceActionFinishedEvent ev )
+ {
+ if ( LoopMode.No_Loop == loopMode )
+ {
+ return;
+ }
+
+ if ( LoopMode.Next_Frame == loopMode )
+ {
+ await this.RequestNextFrame();
+ }
+
+
+ // this.LogInfo( "Loop Re-Triggering" );
+ Trigger();
+
+ }
+}
diff --git a/Runtime/Behavior/StateMachine/OnStateActive.cs.uid b/Runtime/Behavior/StateMachine/OnStateActive.cs.uid
new file mode 100644
index 0000000..811c458
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/OnStateActive.cs.uid
@@ -0,0 +1 @@
+uid://dr8k0q7q3760
diff --git a/Runtime/Behavior/StateMachine/OnStateEntered.cs b/Runtime/Behavior/StateMachine/OnStateEntered.cs
new file mode 100644
index 0000000..39edd04
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/OnStateEntered.cs
@@ -0,0 +1,10 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnStateEntered.svg")]
+public partial class OnStateEntered:StateEnterExitAction
+{
+}
diff --git a/Runtime/Behavior/StateMachine/OnStateEntered.cs.uid b/Runtime/Behavior/StateMachine/OnStateEntered.cs.uid
new file mode 100644
index 0000000..762fce9
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/OnStateEntered.cs.uid
@@ -0,0 +1 @@
+uid://duny4jcvytcjs
diff --git a/Runtime/Behavior/StateMachine/OnStateExited.cs b/Runtime/Behavior/StateMachine/OnStateExited.cs
new file mode 100644
index 0000000..cb8a497
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/OnStateExited.cs
@@ -0,0 +1,10 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnStateExited.svg")]
+public partial class OnStateExited:StateEnterExitAction
+{
+}
diff --git a/Runtime/Behavior/StateMachine/OnStateExited.cs.uid b/Runtime/Behavior/StateMachine/OnStateExited.cs.uid
new file mode 100644
index 0000000..5ddb30f
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/OnStateExited.cs.uid
@@ -0,0 +1 @@
+uid://cxv11h345udfg
diff --git a/Runtime/Behavior/StateMachine/State.cs b/Runtime/Behavior/StateMachine/State.cs
new file mode 100644
index 0000000..304a510
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/State.cs
@@ -0,0 +1,10 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/State.svg")]
+public partial class State:Node
+{
+}
diff --git a/Runtime/Behavior/StateMachine/State.cs.uid b/Runtime/Behavior/StateMachine/State.cs.uid
new file mode 100644
index 0000000..b4e6b73
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/State.cs.uid
@@ -0,0 +1 @@
+uid://bf32kfq4h4ws3
diff --git a/Runtime/Behavior/StateMachine/StateEnterExitAction.cs b/Runtime/Behavior/StateMachine/StateEnterExitAction.cs
new file mode 100644
index 0000000..24bc307
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/StateEnterExitAction.cs
@@ -0,0 +1,38 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnEvent.svg")]
+public abstract partial class StateEnterExitAction:Sequence
+{
+ [Export]
+ public SceneCondition sceneCondition;
+
+ [Export]
+ public Condition condition;
+
+ [Export]
+ public State state;
+
+ public bool Matches( State other )
+ {
+ if ( state != null && state != other )
+ {
+ return false;
+ }
+
+ if ( sceneCondition != null && ! sceneCondition.Evaluate() )
+ {
+ return false;
+ }
+
+ if ( condition != null && ! condition.Evaluate() )
+ {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/Runtime/Behavior/StateMachine/StateEnterExitAction.cs.uid b/Runtime/Behavior/StateMachine/StateEnterExitAction.cs.uid
new file mode 100644
index 0000000..c527f35
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/StateEnterExitAction.cs.uid
@@ -0,0 +1 @@
+uid://cs64njpww5p5v
diff --git a/Runtime/Behavior/StateMachine/StateMachine.cs b/Runtime/Behavior/StateMachine/StateMachine.cs
new file mode 100644
index 0000000..86d45b8
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/StateMachine.cs
@@ -0,0 +1,232 @@
+using Godot;
+
+using System.Collections.Generic;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/StateMachine.svg")]
+public partial class StateMachine:SequenceAction
+{
+ [ExportGroup( "Debugging")]
+ [Export]
+ public State currentState;
+
+ [Export]
+ public bool printStateChanges = false;
+
+ [Export]
+ public bool printTransitionChanges = false;
+
+ int _runID = -1;
+
+ List _receptors = new List();
+
+
+ protected override void _OnTrigger()
+ {
+ if ( currentState != null )
+ {
+ // Already running.
+ return;
+ }
+
+ var states = this.GetDirectChildren();
+
+ if ( states.Count == 0 )
+ {
+ return;
+ }
+
+ _runID = DispatchStart();
+
+ StartReceptors( states );
+
+ EnterState( states[ 0 ], null );
+ }
+
+ public override void _Process( double delta )
+ {
+ if ( Engine.IsEditorHint() || currentState == null )
+ {
+ return;
+ }
+
+ CheckTransitions();
+ }
+
+ Dictionary _transitions = new Dictionary();
+
+ void CheckTransitions()
+ {
+
+ var transitions = currentState.GetDirectChildren();
+
+ for ( int i = 0; i < transitions.Count; i++ )
+ {
+ var transition = transitions[ i ];
+ var isTriggered = transition.IsTriggered();
+
+ var before = ! _transitions.ContainsKey( transition ) ? false : _transitions[ transition ];
+
+ if ( before != isTriggered )
+ {
+ _transitions[ transition ] = isTriggered;
+ RJLog.Log( HierarchyName.Of( transition ), isTriggered );
+ }
+
+
+ if ( isTriggered )
+ {
+ DoTransition( transition.targetState );
+ transition.onTransition?.Trigger();
+ return;
+ }
+ }
+ }
+
+ void DoTransition( State target )
+ {
+ var previous = currentState;
+
+ if ( target == previous )
+ {
+ ReinitializeState( previous );
+ return;
+ }
+
+ ExitState( previous, target );
+
+ if ( target == null )
+ {
+ EndStateMachine();
+ return;
+ }
+
+ EnterState( target, previous );
+ }
+
+ void EnterState( State state, State previous )
+ {
+ currentState = state;
+
+ if ( printStateChanges )
+ {
+ this.LogInfo( "Entering State:", HierarchyName.Of( state ) );
+ }
+
+ state.ForEachDirectChild( oe =>
+ {
+ if ( oe.Matches( previous ) )
+ {
+ oe.Trigger();
+ }
+ } );
+
+ state.ForEachDirectChild( oa => oa.Trigger() );
+ }
+
+ void ExitState( State state, State next )
+ {
+ if ( printStateChanges )
+ {
+ this.LogInfo( "Exiting State:", HierarchyName.Of( state ) );
+ }
+
+ state.ForEachDirectChild( oe =>
+ {
+ if ( oe.Matches( next ) )
+ {
+ oe.Trigger();
+ }
+ } );
+
+ state.ForEachDirectChild( oa => oa.CancelAllRunning() );
+ }
+
+ void ReinitializeState( State state )
+ {
+ state.ForEachDirectChild( oe =>
+ {
+ if ( oe.Matches( state ) )
+ {
+ oe.Trigger();
+ }
+ } );
+
+ state.ForEachDirectChild( oe =>
+ {
+ if ( oe.Matches( state ) )
+ {
+ oe.Trigger();
+ }
+ } );
+
+ state.ForEachDirectChild( oa =>
+ {
+ if ( ! oa.reinitialize )
+ {
+ return;
+ }
+
+ if ( oa.isExclusive && oa.isRunning )
+ {
+ return;
+ }
+
+ oa.CancelAllRunning();
+ oa.Trigger();
+ } );
+ }
+
+ void StartReceptors( List states )
+ {
+ _receptors.Clear();
+
+ states.ForEach( s =>
+ s.ForEachDirectChild( t =>
+ {
+ if ( t.receptor != null )
+ {
+ _receptors.Add( t.receptor );
+ }
+ } )
+ );
+
+ _receptors.ForEach( r => r.Start() );
+ }
+
+ void StopReceptors()
+ {
+ _receptors.ForEach( r => r.Stop() );
+ _receptors.Clear();
+ }
+
+ void EndStateMachine()
+ {
+ currentState = null;
+
+ SetProcess( false );
+
+ StopReceptors();
+
+ DispatchEnd( _runID );
+ }
+
+ public override void CancelAction( int id )
+ {
+ if ( id != _runID || currentState == null )
+ {
+ return;
+ }
+
+ currentState = null;
+
+ SetProcess( false );
+
+ StopReceptors();
+
+ DispatchCancelled( id );
+ }
+}
diff --git a/Runtime/Behavior/StateMachine/StateMachine.cs.uid b/Runtime/Behavior/StateMachine/StateMachine.cs.uid
new file mode 100644
index 0000000..3655db2
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/StateMachine.cs.uid
@@ -0,0 +1 @@
+uid://cnv4f0rjl6l0f
diff --git a/Runtime/Behavior/StateMachine/StateTransition.cs b/Runtime/Behavior/StateMachine/StateTransition.cs
new file mode 100644
index 0000000..c2d5018
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/StateTransition.cs
@@ -0,0 +1,62 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass, Icon("res://addons/rokojori_action_library/Icons/StateTransition.svg")]
+public partial class StateTransition:Node
+{
+ [Export]
+ public SceneCondition sceneCondition;
+
+ [Export]
+ public Condition condition;
+
+ [Export]
+ public Receptor receptor;
+
+ // Transitioning to a null targetState ends the owning StateMachine.
+ [Export]
+ public State targetState;
+
+
+ [Export]
+ public Action onTransition;
+
+
+ [ExportToolButton( "Set Reference Name" )]
+ public Callable setReferencedNameButton => Callable.From(
+ ()=>
+ {
+ if ( targetState == null )
+ {
+ this.Name = "▸ (nothing)";
+ }
+ else
+ {
+ this.Name = "▸ " + targetState.Name;
+ }
+ }
+ );
+
+ public bool IsTriggered()
+ {
+ if ( sceneCondition?.Evaluate() ?? false )
+ {
+ return true;
+ }
+
+ if ( condition?.Evaluate() ?? false )
+ {
+ return true;
+ }
+
+ if ( receptor?.activated ?? false )
+ {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/Runtime/Behavior/StateMachine/StateTransition.cs.uid b/Runtime/Behavior/StateMachine/StateTransition.cs.uid
new file mode 100644
index 0000000..84cece8
--- /dev/null
+++ b/Runtime/Behavior/StateMachine/StateTransition.cs.uid
@@ -0,0 +1 @@
+uid://dok5gagvhoo6r
diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs
index 49e8fef..b6ffddd 100644
--- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs
+++ b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCamera.cs
@@ -39,6 +39,8 @@ namespace Rokojori
public bool canProcess => processInEditor || ! Engine.IsEditorHint();
Vector3 _lastLockTargetPosition;
+ float _lockBlend;
+ float _orientationBlend;
public override void _Process( double delta )
{
@@ -88,10 +90,23 @@ namespace Rokojori
// 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 );
+ // lockTarget itself has gone null. Engage and release each get their own Smoothing (and
+ // therefore their own duration), driven off the same persistent _lockBlend value via
+ // ApplyWith so switching which one is active each frame can't desync from the other's
+ // internal state.
+ var lockBlendSmoothing = hasLockTarget ? settings.lockEngageSmoothing : settings.lockReleaseSmoothing;
+ _lockBlend = Smoothing.ApplyWith( lockBlendSmoothing, _lockBlend, hasLockTarget ? 1f : 0f, delta );
+ var lockBlend = _lockBlend;
var isLocked = lockBlend > 0.001f;
+ // Separate blend for yaw/pitch: the lock target's position needs easing since it keeps
+ // moving, but once released there's nothing left to track - yaw/pitch should return to
+ // "the normal camera" on their own (fast) terms instead of riding lockBlend's slower,
+ // framing-tuned duration back down.
+ var orientationBlendSmoothing = hasLockTarget ? settings.lockOrientationEngageSmoothing : settings.lockOrientationReleaseSmoothing;
+ _orientationBlend = Smoothing.ApplyWith( orientationBlendSmoothing, _orientationBlend, hasLockTarget ? 1f : 0f, delta );
+ var orientationBlend = _orientationBlend;
+
var focusPosition = targetPosition.Lerp( ( targetPosition + lockTargetPosition ) / 2f, lockBlend );
var yawAxis = 0f;
@@ -104,21 +119,37 @@ namespace Rokojori
}
// this.LogInfo( "YAW:", yawAxis, "PITCH:", pitchAxis );
- if ( isLocked )
+
+ // Free-look yaw keeps integrating, just scaled down as orientationBlend rises, so it
+ // doesn't freeze the instant a lock starts and resumes smoothly from wherever it actually
+ // is once released - rather than hard-pausing on the isLocked boolean.
+ yaw += yawAxis * settings.yawSpeed * data.yawSpeed * delta * ( 1f - orientationBlend );
+
+ var lockYawTarget = yaw;
+
+ if ( hasLockTarget )
{
var lockDirection = lockTargetPosition - targetPosition;
lockDirection.Y = 0;
if ( lockDirection.LengthSquared() > 0.0001f )
{
- yaw = Math3D.GlobalYawDegrees( lockDirection );
+ lockYawTarget = Math3D.GlobalYawDegrees( lockDirection );
}
}
- else
- {
- yaw += yawAxis * settings.yawSpeed * data.yawSpeed * delta;
- }
+ // Blended by orientationBlend instead of hard-switching on isLocked, so engage eases over
+ // lockOrientationEngageSmoothing's duration while release can return to free-look fast via
+ // lockOrientationReleaseSmoothing, independent of how slow lockBlend (focus/distance) is
+ // still easing out. The normal (faster) yawSmoothing below only adds its usual per-frame
+ // responsiveness on top of that already-eased target.
+ var appliedYaw = MathX.LerpAngleDegrees( yaw, lockYawTarget, orientationBlend );
+
+ // Keep the free-look accumulator synced to whatever is actually being displayed, so it
+ // never goes stale while locked (it barely integrates then) - otherwise releasing the lock
+ // makes lockYawTarget's "no target" fallback snap back to a long-stale pre-lock value
+ // instead of the direction the camera was just actually facing.
+ yaw = appliedYaw;
if ( data.pitchIsRelative )
{
@@ -130,12 +161,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;
+ // Same orientationBlend for the pitch clamp range - avoids a hard clamp snap on
+ // engage/release, and returns to the normal range just as quickly as yaw does on release.
+ var blendedMinPitch = Mathf.Lerp( settings.minPitch, settings.lockMinPitch, orientationBlend );
+ var blendedMaxPitch = Mathf.Lerp( settings.maxPitch, settings.lockMaxPitch, orientationBlend );
+ pitch = Mathf.Clamp( pitch, blendedMinPitch, blendedMaxPitch );
var smoothedYaw = Smoothing.ApplyDegrees( settings.yawSmoothing, appliedYaw, delta );
diff --git a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs
index 44fc73d..7e3c391 100644
--- a/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs
+++ b/Runtime/Cameras/CameraTypes/ThirdPersonCamera/ThirdPersonCameraSettings.cs
@@ -79,7 +79,20 @@ namespace Rokojori
public Smoothing lockTargetFollowSmoothing = new FrameSmoothing();
[Export]
- public Smoothing lockBlendSmoothing = new FrameSmoothing();
+ public Smoothing lockEngageSmoothing = new FrameSmoothing();
+
+ [Export]
+ public Smoothing lockReleaseSmoothing = new FrameSmoothing();
+
+ // Separate from the pair above: those blend the focus position and bounding distance (can
+ // stay slow/cinematic on release). These blend yaw and the pitch clamp range - on release,
+ // orientation should snap back to "the normal camera" fast, independent of how slow the
+ // framing is still easing out.
+ [Export]
+ public Smoothing lockOrientationEngageSmoothing = new FrameSmoothing();
+
+ [Export]
+ public Smoothing lockOrientationReleaseSmoothing = new FrameSmoothing();
[Export]
public float lockMinPitch = -10;
diff --git a/Runtime/Conditions/IsValueBelowThreshold.cs b/Runtime/Conditions/IsValueBelowThreshold.cs
new file mode 100644
index 0000000..e67e03e
--- /dev/null
+++ b/Runtime/Conditions/IsValueBelowThreshold.cs
@@ -0,0 +1,27 @@
+using Godot;
+
+using Rokojori.Extensions;
+namespace Rokojori;
+
+[Tool]
+[GlobalClass]
+public partial class IsValueBelowThreshold:SceneCondition
+{
+ [Export]
+ public NumberValue numberValue;
+
+ [Export]
+ public double threshold = -1;
+
+ public override bool Evaluate()
+ {
+ if ( numberValue == null )
+ {
+ return false;
+ }
+
+ var t = threshold < 0 ? numberValue.lowTreshold : threshold;
+
+ return numberValue.value < t;
+ }
+}
diff --git a/Runtime/Conditions/IsValueBelowThreshold.cs.uid b/Runtime/Conditions/IsValueBelowThreshold.cs.uid
new file mode 100644
index 0000000..6be3a5e
--- /dev/null
+++ b/Runtime/Conditions/IsValueBelowThreshold.cs.uid
@@ -0,0 +1 @@
+uid://dj8npbgn3g3wm
diff --git a/Runtime/Godot/Extensions/Node3DExtensions.cs b/Runtime/Godot/Extensions/Node3DExtensions.cs
index 70d4b02..d1bee73 100644
--- a/Runtime/Godot/Extensions/Node3DExtensions.cs
+++ b/Runtime/Godot/Extensions/Node3DExtensions.cs
@@ -42,6 +42,19 @@ namespace Rokojori
return box.center;
}
+ public static bool CanSee( this Node3D self, Node3D other, float angle = 45f )
+ {
+ var forward = self.GlobalForward();
+ var dir = self.DirectionTowards( other ).Normalized();
+
+ var dot = forward.Dot( dir );
+
+ var threshold = Mathf.Cos( Mathf.DegToRad( angle / 2f ) );
+
+ return dot > threshold;
+ }
+
+
public static Vector3 GetGlobalCenterOfChildren( this Node3D self, bool bounds = false, bool onlyVisible = true )
{
var center = Vector3.Zero;
diff --git a/Runtime/Math/Smoother.cs b/Runtime/Math/Smoother.cs
index 5d15277..c69c960 100644
--- a/Runtime/Math/Smoother.cs
+++ b/Runtime/Math/Smoother.cs
@@ -29,9 +29,9 @@ namespace Rokojori
public float SmoothForDuration( float value, float nextValue, float duration, float delta, float processDelta = MathX.fps120Delta )
{
- var coefficient = MathX.SmoothingCoefficient( duration * 1000f, processDelta );
+ var coefficient = MathX.SmoothingCoefficient( duration * 1000f, 0.1f, processDelta );
return SmoothWithCoefficient( value, nextValue, coefficient, delta, processDelta );
- }
+ }
public float SmoothWithCoefficient( float value, float nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta )
@@ -50,9 +50,9 @@ namespace Rokojori
public Quaternion SmoothForDuration( Quaternion value, Quaternion nextValue, float duration, float delta, float processDelta = MathX.fps120Delta )
{
- var coefficient = MathX.SmoothingCoefficient( duration * 1000f, processDelta );
+ var coefficient = MathX.SmoothingCoefficient( duration * 1000f, 0.1f, processDelta );
return SmoothWithCoefficient( value, nextValue, coefficient, delta, processDelta );
- }
+ }
public Quaternion SmoothWithCoefficient( Quaternion value, Quaternion nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta )
{
@@ -60,7 +60,7 @@ namespace Rokojori
while ( overflowDelta > processDelta )
{
- value = value.Slerp( nextValue, coefficient );
+ value = value.Slerp( nextValue, coefficient ).Normalized();
overflowDelta -= processDelta;
}
@@ -69,9 +69,9 @@ namespace Rokojori
public Vector3 SmoothForDuration( Vector3 value, Vector3 nextValue, float duration, float delta, float processDelta = MathX.fps120Delta )
{
- var coefficient = MathX.SmoothingCoefficient( duration * 1000f, processDelta );
+ var coefficient = MathX.SmoothingCoefficient( duration * 1000f, 0.1f, processDelta );
return SmoothWithCoefficient( value, nextValue, coefficient, delta, processDelta );
- }
+ }
public Vector3 SmoothWithCoefficient( Vector3 value, Vector3 nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta )
{
diff --git a/Runtime/Physics/PhysicsRayCast.cs b/Runtime/Physics/PhysicsRayCast.cs
index 3d32da0..78abc4d 100644
--- a/Runtime/Physics/PhysicsRayCast.cs
+++ b/Runtime/Physics/PhysicsRayCast.cs
@@ -7,7 +7,7 @@ namespace Rokojori
{
public class PhysicsRayCast
{
- public static CollisionData GetFirst( CharacterBody3D body, Vector3 from, Vector3 to )
+ public static CollisionData GetFirst( PhysicsBody3D body, Vector3 from, Vector3 to )
{
var world = body.GetWorld3D();
var rayParameters = new PhysicsRayQueryParameters3D
@@ -25,7 +25,7 @@ namespace Rokojori
return hits.Count == 0 ? null : hits[ 0 ];
}
- public static CollisionData GetFirst( CharacterBody3D body, Vector3 direction )
+ public static CollisionData GetFirst( PhysicsBody3D body, Vector3 direction )
{
var from = body.GlobalPosition;
var to = from + direction;
@@ -33,7 +33,7 @@ namespace Rokojori
return GetFirst( body, from, to );
}
- public static int GetAll( List outputCollisionData, CharacterBody3D body, Vector3 from, Vector3 to )
+ public static int GetAll( List outputCollisionData, PhysicsBody3D body, Vector3 from, Vector3 to )
{
var world = body.GetWorld3D();
var rayParameters = new PhysicsRayQueryParameters3D
@@ -49,7 +49,7 @@ namespace Rokojori
return CollisionData.MultiRayCast( world, rayParameters, outputCollisionData );
}
- public static int GetAll( List outputCollisionData, CharacterBody3D body, Vector3 direction )
+ public static int GetAll( List outputCollisionData, PhysicsBody3D body, Vector3 direction )
{
var from = body.GlobalPosition;
var to = from + direction;