From 839b0bca39bd67c1ad4ef46fb5a8600f4b1f6466 Mon Sep 17 00:00:00 2001 From: Josef Date: Tue, 22 Jul 2025 16:08:22 +0200 Subject: [PATCH] Vignette, CoolDowns, Pause, NodeState --- Runtime/Actions/Audio/PlayMusic.cs | 36 ++++++ Runtime/Actions/Audio/PlayMusic.cs.uid | 1 + Runtime/Actions/Conditional/CoolDown.cs | 39 ++++++ Runtime/Actions/Conditional/CoolDown.cs.uid | 1 + Runtime/Actions/Node/SetNodeStateSpecific.cs | 22 ++++ .../Actions/Node/SetNodeStateSpecific.cs.uid | 1 + Runtime/Actions/Node3D/MoveTo.cs | 66 ++++++++++ Runtime/Actions/Node3D/MoveTo.cs.uid | 1 + Runtime/Actions/Node3D/OnCollision.cs | 5 +- Runtime/Actions/Node3D/PlayParticles.cs | 89 +++++++++++++- Runtime/Actions/RemoveNode.cs | 10 +- Runtime/Actions/SetPauseState.cs | 59 +++++++++ Runtime/Actions/SetPauseState.cs.uid | 1 + Runtime/Actions/Solo.cs | 33 +++++ Runtime/Actions/Solo.cs.uid | 1 + .../Flash/Presets/Red Hit - Flash.tres | 4 +- .../Animation/Transform/TransformTarget.cs | 4 +- Runtime/Events/Signals.cs | 47 ++++++++ Runtime/Events/Signals.cs.uid | 1 + Runtime/Godot/NodeState.cs | 113 ++++++++++++++++-- Runtime/Godot/NodeStateConfiguration.cs | 34 ++++++ Runtime/Godot/NodeStateConfiguration.cs.uid | 1 + Runtime/Godot/Nodes.cs | 14 ++- .../Conditions/CharacterIsJumping.cs | 26 ++++ .../Conditions/CharacterIsJumping.cs.uid | 1 + .../Interactions/CharacterController/Jump.cs | 30 +++-- Runtime/Math/Math3D.cs | 10 ++ .../Assets/Grass/Windy Grass Shader.gdshader | 32 +++-- Runtime/Procedural/Mesh/MeshCombiner.cs | 21 ++-- Runtime/Procedural/Mesh/MeshExtractor.cs | 38 ++++++ Runtime/Procedural/Mesh/MeshGeometry.cs | 49 ++++++-- .../GreyScale/GrayScaleShader.glsl | 2 +- .../Vignette/VignetteEffect.cs | 61 ++++++++++ .../Vignette/VignetteEffect.cs.uid | 1 + .../Vignette/VignetteShader.glsl | 41 +++++++ .../Vignette/VignetteShader.glsl.import | 14 +++ Runtime/Rendering/Objects/RDPushConstants.cs | 1 + .../Default-Input-Icons-Library.tres | 4 +- Runtime/Sensors/OnSensor.cs | 34 ++++-- Runtime/UI/UI.cs | 5 + 40 files changed, 876 insertions(+), 77 deletions(-) create mode 100644 Runtime/Actions/Audio/PlayMusic.cs create mode 100644 Runtime/Actions/Audio/PlayMusic.cs.uid create mode 100644 Runtime/Actions/Conditional/CoolDown.cs create mode 100644 Runtime/Actions/Conditional/CoolDown.cs.uid create mode 100644 Runtime/Actions/Node/SetNodeStateSpecific.cs create mode 100644 Runtime/Actions/Node/SetNodeStateSpecific.cs.uid create mode 100644 Runtime/Actions/Node3D/MoveTo.cs create mode 100644 Runtime/Actions/Node3D/MoveTo.cs.uid create mode 100644 Runtime/Actions/SetPauseState.cs create mode 100644 Runtime/Actions/SetPauseState.cs.uid create mode 100644 Runtime/Actions/Solo.cs create mode 100644 Runtime/Actions/Solo.cs.uid create mode 100644 Runtime/Events/Signals.cs create mode 100644 Runtime/Events/Signals.cs.uid create mode 100644 Runtime/Godot/NodeStateConfiguration.cs create mode 100644 Runtime/Godot/NodeStateConfiguration.cs.uid create mode 100644 Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs create mode 100644 Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs.uid create mode 100644 Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs create mode 100644 Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs.uid create mode 100644 Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl create mode 100644 Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl.import diff --git a/Runtime/Actions/Audio/PlayMusic.cs b/Runtime/Actions/Audio/PlayMusic.cs new file mode 100644 index 0000000..0e76e0a --- /dev/null +++ b/Runtime/Actions/Audio/PlayMusic.cs @@ -0,0 +1,36 @@ + +using Godot; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool][GlobalClass] + public partial class PlayMusic:Action + { + [Export] + public AudioStreamPlayer music; + + [Export] + public bool stopSiblingPlayers = false; + + protected override void _OnTrigger() + { + if ( stopSiblingPlayers ) + { + GetParent().ForEachDirectChild( + ( p )=> + { + if ( p == music ) + { + return; + } + + p.Stop(); + } + ); + } + + music.Play(); + } + } +} \ No newline at end of file diff --git a/Runtime/Actions/Audio/PlayMusic.cs.uid b/Runtime/Actions/Audio/PlayMusic.cs.uid new file mode 100644 index 0000000..bbb159e --- /dev/null +++ b/Runtime/Actions/Audio/PlayMusic.cs.uid @@ -0,0 +1 @@ +uid://cyicertlwo4m0 diff --git a/Runtime/Actions/Conditional/CoolDown.cs b/Runtime/Actions/Conditional/CoolDown.cs new file mode 100644 index 0000000..c178370 --- /dev/null +++ b/Runtime/Actions/Conditional/CoolDown.cs @@ -0,0 +1,39 @@ + +using Godot; +using System.Collections.Generic; + + +namespace Rokojori +{ + [Tool] + [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/ConditionalAction.svg")] + public partial class CoolDown : Action + { + [Export] + public Action action; + + [Export] + public Duration coolDownDuration; + + bool _isCoolingDown = false; + protected override void _OnTrigger() + { + if ( _isCoolingDown ) + { + return; + } + + _isCoolingDown = coolDownDuration != null; + Trigger( action ); + + if ( coolDownDuration != null ) + { + TimeLineManager.ScheduleEventIn( + coolDownDuration.timeLine, coolDownDuration.GetDurationInSeconds(), + ev => _isCoolingDown = false + ); + } + + } + } +} \ No newline at end of file diff --git a/Runtime/Actions/Conditional/CoolDown.cs.uid b/Runtime/Actions/Conditional/CoolDown.cs.uid new file mode 100644 index 0000000..656c42f --- /dev/null +++ b/Runtime/Actions/Conditional/CoolDown.cs.uid @@ -0,0 +1 @@ +uid://cbk1j230s8i68 diff --git a/Runtime/Actions/Node/SetNodeStateSpecific.cs b/Runtime/Actions/Node/SetNodeStateSpecific.cs new file mode 100644 index 0000000..3cf9fce --- /dev/null +++ b/Runtime/Actions/Node/SetNodeStateSpecific.cs @@ -0,0 +1,22 @@ + +using Godot; + + +namespace Rokojori +{ + [Tool][GlobalClass ] + public partial class SetNodeStateSpecific : Action + { + [Export] + public Node target; + + [Export] + public NodeStateConfiguration configuration; + + protected override void _OnTrigger() + { + NodeState.Configure( target, configuration ); + } + } + +} \ No newline at end of file diff --git a/Runtime/Actions/Node/SetNodeStateSpecific.cs.uid b/Runtime/Actions/Node/SetNodeStateSpecific.cs.uid new file mode 100644 index 0000000..5c6d428 --- /dev/null +++ b/Runtime/Actions/Node/SetNodeStateSpecific.cs.uid @@ -0,0 +1 @@ +uid://cko3krhs7jgc4 diff --git a/Runtime/Actions/Node3D/MoveTo.cs b/Runtime/Actions/Node3D/MoveTo.cs new file mode 100644 index 0000000..df2203e --- /dev/null +++ b/Runtime/Actions/Node3D/MoveTo.cs @@ -0,0 +1,66 @@ +using Godot; + + +namespace Rokojori +{ + [Tool][GlobalClass ] + public partial class MoveTo : SequenceAction, Animator + { + [Export] + public Node3D target; + + [Export] + public Node3D goal; + + [Export] + public Duration duration; + + int _actionID = -1; + int _currentSpanAnimationID = -1; + + public void OnAnimatorStart(){} + public void OnAnimatorEnd(){} + public void OnAnimatorCancel(){} + + protected override void _OnTrigger() + { + if ( _actionID != -1 ) + { + CancelAction( _actionID ); + } + + var sequenceID = DispatchStart(); + _actionID = sequenceID; + + AnimationManager.StartAnimation( this, target, AnimationMember.Position ); + + var startPosition = target.GlobalPosition; + + _currentSpanAnimationID = TimeLineManager.ScheduleSpanIn( duration.timeLine, 0, duration.GetDurationInSeconds(), + ( TimeLineSpan span, TimeLineSpanUpdateType type )=> + { + if ( span.id != _currentSpanAnimationID ) + { + return; + } + + if ( ! AnimationManager.IsAnimating( this, target, AnimationMember.Position ) ) + { + return; + } + + target.GlobalPosition = startPosition.Lerp( goal.GlobalPosition, span.phase );; + + + if ( TimeLineSpanUpdateType.End == type ) + { + AnimationManager.EndAnimation( this, target, AnimationMember.Position ); + + DispatchEnd( sequenceID ); + return; + } + } + ).id; + } + } +} \ No newline at end of file diff --git a/Runtime/Actions/Node3D/MoveTo.cs.uid b/Runtime/Actions/Node3D/MoveTo.cs.uid new file mode 100644 index 0000000..b995049 --- /dev/null +++ b/Runtime/Actions/Node3D/MoveTo.cs.uid @@ -0,0 +1 @@ +uid://b0abujtgcfvlh diff --git a/Runtime/Actions/Node3D/OnCollision.cs b/Runtime/Actions/Node3D/OnCollision.cs index 43a69ac..4e853fc 100644 --- a/Runtime/Actions/Node3D/OnCollision.cs +++ b/Runtime/Actions/Node3D/OnCollision.cs @@ -37,10 +37,9 @@ namespace Rokojori return _nodes; } - public void OnNodeStateChanged( bool processEnabled, bool inputEnabled, bool physicsEnabled, bool signalsEnabled, - Node.ProcessModeEnum processMode, bool visible ) + public void OnNodeStateChanged() { - if ( ! processEnabled || ! physicsEnabled || Node.ProcessModeEnum.Disabled == processMode ) + if ( ! IsProcessing() || ! IsPhysicsProcessing() || Node.ProcessModeEnum.Disabled == this.ProcessMode ) { this.LogInfo( "Clearing nodes" ); _nodes.Clear(); diff --git a/Runtime/Actions/Node3D/PlayParticles.cs b/Runtime/Actions/Node3D/PlayParticles.cs index 9f4a46b..465bab2 100644 --- a/Runtime/Actions/Node3D/PlayParticles.cs +++ b/Runtime/Actions/Node3D/PlayParticles.cs @@ -1,5 +1,6 @@ using Godot; +using System.Collections.Generic; namespace Rokojori { @@ -9,9 +10,95 @@ namespace Rokojori [Export] public GpuParticles3D particles3D; + [Export] + public bool usePooling = false; + + [Export] + public float godotFinishedReporingBugOffset = 0.2f; + + [Export] + public TimeLine timeLine; + + List _pool = new List(); + List _emitting = new List(); + + protected override void _OnTrigger() { - particles3D.Restart(); + if ( ! usePooling ) + { + particles3D.Restart(); + return; + } + + if ( _emitting.Count == 0 ) + { + _emitting.Add( false ); + } + + var p = GetFreeParticles(); + + if ( p == null ) + { + p = DuplicateParticles(); + } + + this.LogInfo( "Using Particles", p ); + + + + var index = IndexOf( p ); + + _emitting[ index ] = true; + + p.OnFinishedOnce( + () => + { + if ( godotFinishedReporingBugOffset <= 0 ) + { + _emitting[ index ] = false; + return; + } + + TimeLineManager.ScheduleEventIn( + timeLine, + godotFinishedReporingBugOffset, + t => _emitting[ index ] = false + ); + } + ); + p.Restart(); + + } + + public int IndexOf( GpuParticles3D p ) + { + return p == particles3D ? 0 : _pool.IndexOf( p ) + 1; + } + + public bool IsEmitting( GpuParticles3D p ) + { + return _emitting[ IndexOf( p ) ]; + } + + GpuParticles3D GetFreeParticles() + { + if ( ! IsEmitting( particles3D ) ) + { + return particles3D; + } + + return _pool.Find( p => ! p.Emitting ); + } + + GpuParticles3D DuplicateParticles() + { + this.LogInfo( "Duplicating Particles" ); + var duplicate = particles3D.Duplicate() as GpuParticles3D; + _pool.Add( duplicate ); + particles3D.GetParent().AddChild( duplicate ); + _emitting.Add( false ); + return duplicate; } } } \ No newline at end of file diff --git a/Runtime/Actions/RemoveNode.cs b/Runtime/Actions/RemoveNode.cs index acd0fc6..82624c8 100644 --- a/Runtime/Actions/RemoveNode.cs +++ b/Runtime/Actions/RemoveNode.cs @@ -10,12 +10,18 @@ namespace Rokojori [Export] public Node target; + [Export] + public bool queue = true; + protected override void _OnTrigger() { - if ( target != null ) + if ( target == null ) { - target.SelfDestroy(); + return; } + + target.SelfDestroy( queue ); + } } } \ No newline at end of file diff --git a/Runtime/Actions/SetPauseState.cs b/Runtime/Actions/SetPauseState.cs new file mode 100644 index 0000000..8d71dc6 --- /dev/null +++ b/Runtime/Actions/SetPauseState.cs @@ -0,0 +1,59 @@ + +using Godot; + + +namespace Rokojori +{ + [Tool][GlobalClass ] + public partial class SetPauseState : Action + { + public enum Mode + { + Pause, + Resume, + Toggle + } + + [Export] + public Mode mode; + + [Export] + public Action onPausing; + + [Export] + public Action onResuming; + + [Export] + public bool onlyTriggerOnChange = true; + + protected override void _OnTrigger() + { + + var currentState = GetTree().Paused; + + var nextState = Mode.Toggle == mode ? ! currentState : Mode.Pause == mode ? false : true; + + this.LogInfo( "SetPauseState", currentState, ">>", nextState ); + + if ( currentState == nextState && onlyTriggerOnChange ) + { + return; + } + + + this.LogInfo( "Setting Pause State", nextState ); + GetTree().Paused = nextState; + + if ( nextState ) + { + Action.Trigger( onPausing ); + } + else + { + Action.Trigger( onResuming ); + } + + } + } + +} \ No newline at end of file diff --git a/Runtime/Actions/SetPauseState.cs.uid b/Runtime/Actions/SetPauseState.cs.uid new file mode 100644 index 0000000..21ef959 --- /dev/null +++ b/Runtime/Actions/SetPauseState.cs.uid @@ -0,0 +1 @@ +uid://bwah32ket1t43 diff --git a/Runtime/Actions/Solo.cs b/Runtime/Actions/Solo.cs new file mode 100644 index 0000000..c7b2acf --- /dev/null +++ b/Runtime/Actions/Solo.cs @@ -0,0 +1,33 @@ + +using Godot; + + +namespace Rokojori +{ + [Tool][GlobalClass ] + public partial class Solo:Action + { + [Export] + public Node soloNode; + + [Export] + public NodeStateConfiguration soloConfiguration; + + [Export] + public NodeStateConfiguration muteConifiguration; + + + protected override void _OnTrigger() + { + var parent = soloNode.GetParent(); + + parent.ForEachDirectChild( + n => + { + var configuration = soloNode == n ? soloConfiguration : muteConifiguration; + NodeState.Configure( n, configuration ); + } + ); + } + } +} \ No newline at end of file diff --git a/Runtime/Actions/Solo.cs.uid b/Runtime/Actions/Solo.cs.uid new file mode 100644 index 0000000..54d4eeb --- /dev/null +++ b/Runtime/Actions/Solo.cs.uid @@ -0,0 +1 @@ +uid://dyl10mv4eg2h diff --git a/Runtime/Animation/Flash/Presets/Red Hit - Flash.tres b/Runtime/Animation/Flash/Presets/Red Hit - Flash.tres index 9d2f016..8269328 100644 --- a/Runtime/Animation/Flash/Presets/Red Hit - Flash.tres +++ b/Runtime/Animation/Flash/Presets/Red Hit - Flash.tres @@ -7,8 +7,8 @@ [sub_resource type="Resource" id="Resource_54hj8"] script = ExtResource("1_nmdum") -color = Color(1, 0, 0, 1) -colorMultiply = 1.0 +color = Color(0.859799, 0.542989, 0.696896, 1) +colorMultiply = 4.0 rgbMultiply = 1.0 alphaMultiply = 1.0 diff --git a/Runtime/Animation/Transform/TransformTarget.cs b/Runtime/Animation/Transform/TransformTarget.cs index be0834a..baaef71 100644 --- a/Runtime/Animation/Transform/TransformTarget.cs +++ b/Runtime/Animation/Transform/TransformTarget.cs @@ -91,7 +91,7 @@ namespace Rokojori else if ( TransformTarget.Global_Rotation == transformTarget ) { var rotation = target.GlobalRotation; - target.GlobalRotation = value; + target.GlobalRotation = value * MathX.DegreesToRadians; // RJLog.Log( "GlobalRotation = ", rotation, ">>", value, target.GlobalRotation ); } else if ( TransformTarget.Local_Position == transformTarget ) @@ -101,7 +101,7 @@ namespace Rokojori else if ( TransformTarget.Local_Rotation == transformTarget ) { var rotation = target.Rotation; - target.Rotation = value; + target.Rotation = value * MathX.DegreesToRadians; // RJLog.Log( "Rotation = ", rotation, ">>", value, target.Rotation ); } else if ( TransformTarget.Local_Scale == transformTarget ) diff --git a/Runtime/Events/Signals.cs b/Runtime/Events/Signals.cs new file mode 100644 index 0000000..f62849d --- /dev/null +++ b/Runtime/Events/Signals.cs @@ -0,0 +1,47 @@ + +using System.Collections.Generic; + +using Godot; + +namespace Rokojori +{ + public static class Signals + { + public static void OnFinished( this GpuParticles3D particles3D, System.Action action, bool once = false ) + { + if ( once ) + { + _OnSignalOnce( particles3D, GpuParticles3D.SignalName.Finished, action ); + } + else + { + _OnSignal( particles3D, GpuParticles3D.SignalName.Finished, action ); + } + } + + public static void OnFinishedOnce( this GpuParticles3D particles3D, System.Action action ) + { + OnFinished( particles3D, action, true ); + } + + public static void _OnSignal( Node node, string name, System.Action action ) + { + node.Connect( name, Callable.From( action ) ); + } + + public static void _OnSignalOnce( Node node, string name, System.Action action ) + { + Callable? onceAction = null; + + onceAction = Callable.From( + ()=> + { + action(); + node.Disconnect( name, (Callable) onceAction ); + } + ); + + node.Connect( name, (Callable)onceAction ); + } + } +} \ No newline at end of file diff --git a/Runtime/Events/Signals.cs.uid b/Runtime/Events/Signals.cs.uid new file mode 100644 index 0000000..7539278 --- /dev/null +++ b/Runtime/Events/Signals.cs.uid @@ -0,0 +1 @@ +uid://d1q6ycp82ol24 diff --git a/Runtime/Godot/NodeState.cs b/Runtime/Godot/NodeState.cs index 590335e..ceacf42 100644 --- a/Runtime/Godot/NodeState.cs +++ b/Runtime/Godot/NodeState.cs @@ -7,13 +7,107 @@ namespace Rokojori { public interface iNodeState { - public void OnNodeStateChanged( bool processEnabled, bool inputEnabled, bool physicsEnabled, bool signalsEnabled, - Node.ProcessModeEnum processMode, bool visible ); + public void OnNodeStateChanged(); } public static class NodeState { + public static bool IsVisible( this Node n ) + { + if ( n is Node3D ) + { + return ( n as Node3D ).Visible; + } + + if ( n is Node2D ) + { + return ( n as Node2D ).Visible; + } + + if ( n is CanvasItem ) + { + return ( n as CanvasItem ).Visible; + } + + return false; + } + + public static void Configure( Node target, NodeStateConfiguration configuration ) + { + Configure( target, + configuration.processEnabled, configuration.inputEnabled, configuration.physicsEnabled, + configuration.signalsEnabled, configuration.visible, configuration.setProcessMode, configuration.processMode + ); + } + + public static void SetNodeState( this Node target, NodeStateConfiguration configuration ) + { + Configure( target, configuration ); + } + + public static void Configure( + Node target, + Trillean processEnabled, Trillean inputEnabled,Trillean physicsEnabled, + Trillean signalsEnabled,Trillean visible, bool setProcessMode, Node.ProcessModeEnum processMode + ) + { + if ( Trillean.Any != processEnabled ) + { + target.SetProcess( TrilleanLogic.ToBool( processEnabled ) ); + } + + if ( Trillean.Any != inputEnabled ) + { + target.SetProcessInput( TrilleanLogic.ToBool( inputEnabled ) ); + } + + if ( Trillean.Any != physicsEnabled ) + { + target.SetPhysicsProcess( TrilleanLogic.ToBool( physicsEnabled ) ); + } + + if ( Trillean.Any != signalsEnabled ) + { + target.SetBlockSignals( ! TrilleanLogic.ToBool( signalsEnabled ) ); + } + + if ( setProcessMode ) + { + target.ProcessMode = processMode; + } + + + if ( Trillean.Any != visible ) + { + var n = target; + + var visibleFlag = TrilleanLogic.ToBool( visible ); + + if ( n is Node3D ) + { + ( n as Node3D ).Visible = visibleFlag; + } + + if ( n is Node2D ) + { + ( n as Node2D ).Visible = visibleFlag; + } + + if ( n is CanvasItem ) + { + ( n as CanvasItem ).Visible = visibleFlag; + } + } + + + if ( target is iNodeState ins ) + { + ins.OnNodeStateChanged(); + } + } + + public static void Configure( Node n, bool processEnabled, bool inputEnabled, bool physicsEnabled, bool signalsEnabled, Node.ProcessModeEnum processMode, bool visible ) { @@ -29,13 +123,6 @@ namespace Rokojori n.ProcessMode = processMode; - if ( n is iNodeState ins ) - { - ins.OnNodeStateChanged( processEnabled, inputEnabled, physicsEnabled, signalsEnabled, - processMode, visible ); - } - - if ( n is Node3D ) { ( n as Node3D ).Visible = visible; @@ -49,8 +136,12 @@ namespace Rokojori if ( n is CanvasItem ) { ( n as CanvasItem ).Visible = visible; - } - + } + + if ( n is iNodeState ins ) + { + ins.OnNodeStateChanged(); + } } public static void Set( Node n, bool enabled ) diff --git a/Runtime/Godot/NodeStateConfiguration.cs b/Runtime/Godot/NodeStateConfiguration.cs new file mode 100644 index 0000000..65af486 --- /dev/null +++ b/Runtime/Godot/NodeStateConfiguration.cs @@ -0,0 +1,34 @@ +using Godot; + +using System.Collections.Generic; +using System; + +namespace Rokojori +{ + [Tool,GlobalClass] + public partial class NodeStateConfiguration:Resource + { + [Export] + public Trillean processEnabled = Trillean.Any; + + [Export] + public Trillean inputEnabled = Trillean.Any; + + [Export] + public Trillean physicsEnabled = Trillean.Any; + + [Export] + public Trillean signalsEnabled = Trillean.Any; + + [Export] + public Trillean visible = Trillean.Any; + + + [Export] + public bool setProcessMode = false; + + [Export] + public Node.ProcessModeEnum processMode; + } +} + \ No newline at end of file diff --git a/Runtime/Godot/NodeStateConfiguration.cs.uid b/Runtime/Godot/NodeStateConfiguration.cs.uid new file mode 100644 index 0000000..12878b0 --- /dev/null +++ b/Runtime/Godot/NodeStateConfiguration.cs.uid @@ -0,0 +1 @@ +uid://dcerk2bxdjmxr diff --git a/Runtime/Godot/Nodes.cs b/Runtime/Godot/Nodes.cs index 7cb044f..61f8b62 100644 --- a/Runtime/Godot/Nodes.cs +++ b/Runtime/Godot/Nodes.cs @@ -538,7 +538,7 @@ namespace Rokojori } } - public static void SelfDestroy( this Node node ) + public static void SelfDestroy( this Node node, bool queue = true ) { var parent = node.GetParent(); @@ -547,7 +547,17 @@ namespace Rokojori parent.RemoveChild( node ); } - node.QueueFree(); + node.LogInfo( "Freeing queued:", queue, node); + + if ( queue ) + { + node.QueueFree(); + } + else + { + node.Free(); + } + } diff --git a/Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs b/Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs new file mode 100644 index 0000000..a07899f --- /dev/null +++ b/Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs @@ -0,0 +1,26 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using Godot.Collections; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class CharacterIsJumping:SceneCondition + { + [Export] + public Jump jump; + + public override bool Evaluate() + { + if ( jump == null ) + { + return false; + } + + + return jump.IsJumping(); + } + } +} \ No newline at end of file diff --git a/Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs.uid b/Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs.uid new file mode 100644 index 0000000..936af5d --- /dev/null +++ b/Runtime/Interactions/CharacterController/Conditions/CharacterIsJumping.cs.uid @@ -0,0 +1 @@ +uid://5yoyxyejk4ga diff --git a/Runtime/Interactions/CharacterController/Jump.cs b/Runtime/Interactions/CharacterController/Jump.cs index 9779a5e..0b12442 100644 --- a/Runtime/Interactions/CharacterController/Jump.cs +++ b/Runtime/Interactions/CharacterController/Jump.cs @@ -12,6 +12,9 @@ namespace Rokojori [Export] public Sensor button; + [Export] + public Action onJump; + [ExportGroup( "Jump Impulse")] [Export] @@ -39,31 +42,41 @@ namespace Rokojori bool canJump = false; - bool jumping = false; + bool jumpPressing = false; bool needsToRelease = false; + bool inAir = false; + public bool IsJumping() + { + return inAir; + } protected override void _OnTrigger() { if ( body.IsOnFloor() ) { - if ( jumping ) + if ( inAir ) { - jumping = false; + inAir = false; + } + + if ( jumpPressing ) + { + jumpPressing = false; needsToRelease = true; } } - if ( ! ( jumping || body.IsOnFloor() ) ) + if ( ! ( jumpPressing || body.IsOnFloor() ) ) { return; } if ( ! button.isActive ) { - jumping = false; + jumpPressing = false; needsToRelease = false; return; } @@ -73,11 +86,14 @@ namespace Rokojori return; } - if ( ! jumping ) + if ( ! jumpPressing ) { - jumping = true; + jumpPressing = true; jumpedDuration = 0; + inAir = true; + Trigger( onJump ); + var jumpDirection = Vector3.Up * jumpImpulseStrength; if ( body.Velocity.Length() > velocityTresholdForJumpDirection ) diff --git a/Runtime/Math/Math3D.cs b/Runtime/Math/Math3D.cs index 022afbc..9e0a36b 100644 --- a/Runtime/Math/Math3D.cs +++ b/Runtime/Math/Math3D.cs @@ -505,6 +505,16 @@ namespace Rokojori return new Vector2( v.Y, v.Z ); } + public static Vector3 ZeroY( this Vector3 v ) + { + return new Vector3( v.X, 0, v.Z ); + } + + public static Vector3 SetY( this Vector3 v, float y ) + { + return new Vector3( v.X, y, v.Z ); + } + public static Basis AlignUp( Basis basis, Vector3 upDirection ) { basis.Y = upDirection; diff --git a/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader b/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader index ec0c79e..0d54706 100644 --- a/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader +++ b/Runtime/Procedural/Assets/Grass/Windy Grass Shader.gdshader @@ -51,22 +51,24 @@ uniform vec4 obstacle1 = vec4( 0, 0, 0, 0 ); uniform vec4 obstacle2 = vec4( 0, 0, 0, 0 ); uniform vec4 obstacle3 = vec4( 0, 0, 0, 0 ); uniform vec4 obstacle4 = vec4( 0, 0, 0, 0 ); +uniform float obstacleDeformationPower = 1.0; uniform float obstacleDeformation = 1.0; uniform float obstacleScale = 1.0; uniform float maxDeformation = 0.3; uniform float maxYOffset = 0.1; +uniform float obstacleYScale = 1.0; vec3 deform( vec3 worldPosition, vec4 obstacle ) { - vec3 direction = worldPosition - obstacle.xyz; + vec3 direction = ( worldPosition - obstacle.xyz ) * vec3( 1.0, obstacleYScale, 1.0 ); float d = length( direction ); float size = obstacle.w * obstacleScale; - if ( d = 0.0 ) + if ( d < size && d >= 0.0 ) { float amount = max( 0, ( size - d ) ); - amount = pow( amount, obstacleDeformation ); + amount = pow( amount, obstacleDeformationPower ) * obstacleDeformation; return worldPosition + normalize( direction ) * amount; } @@ -108,10 +110,23 @@ void vertex() albedoColor = mix( albedo.rgb, HSLtoRGB( albedoHSL ), hslVariation.w ); + if ( obstaclesEnabeld ) + { + vec3 originalWorldVertex = worldVertex; + + worldVertex = deform( worldVertex, obstacle1 ); + worldVertex = deform( worldVertex, obstacle2 ); + worldVertex = deform( worldVertex, obstacle3 ); + worldVertex = deform( worldVertex, obstacle4 ); + + worldVertex = limitDeform( originalWorldVertex, worldVertex ); + } + if ( windEnabled ) { + float windAmount = normalizeToRange01( VERTEX.y, windStart, windEnd ); float rawWindAmount = windAmount; windAmount = mix( windAmount, windAmount * windAmount, windWeightCurve ); @@ -120,20 +135,11 @@ void vertex() float strength = texture( windNoise, windUV + windNoiseStrengthOffset ).r * windStrength; vec2 circle = onCircle( angle ) * strength; - vec3 originalWorldVertex = worldVertex; + worldVertex += vec3( circle.x, 0, circle.y ) * windAmount; // VERTEX = worldToLocal( worldVertex + vec3( circle.x, 0, circle.y ) * windAmount, MODEL_MATRIX ); - if ( obstaclesEnabeld ) - { - worldVertex = deform( worldVertex, obstacle1 ); - worldVertex = deform( worldVertex, obstacle2 ); - worldVertex = deform( worldVertex, obstacle3 ); - worldVertex = deform( worldVertex, obstacle4 ); - - worldVertex = limitDeform( originalWorldVertex, worldVertex ); - } VERTEX = worldToLocal( worldVertex, MODEL_MATRIX ); float minY = min( VERTEX.y, 0 ); // VERTEX.y = mix( VERTEX.y, max( 0, VERTEX.y - strength * windAmount), windHeightCompensation * 2.0f ); diff --git a/Runtime/Procedural/Mesh/MeshCombiner.cs b/Runtime/Procedural/Mesh/MeshCombiner.cs index 7123907..3e6f255 100644 --- a/Runtime/Procedural/Mesh/MeshCombiner.cs +++ b/Runtime/Procedural/Mesh/MeshCombiner.cs @@ -30,7 +30,7 @@ namespace Rokojori public Node3D[] sourceNodes = []; [ExportToolButton( "Combine")] - public Callable CombineButton => Callable.From( Combine ); + public Callable CombineButton => Callable.From( ()=>{ Combine(); } ); [ExportGroup( "Mesh")] [Export] @@ -105,18 +105,8 @@ namespace Rokojori { if ( ! _meshGeometryCache.Has( surface.mesh, surface.index ) ) { - // var arrayMesh = surface.mesh as ArrayMesh; - - // if ( arrayMesh == null ) - // { - // arrayMesh = new ArrayMesh(); - // var st = new SurfaceTool(); - // st.Begin( Mesh.PrimitiveType.Triangles ); - // st.CreateFrom( surface.mesh, surface.index ); - // st.Commit( arrayMesh ); - // } - var mg = MeshGeometry.From( surface.mesh, null, surface.index ); + mg.EnsureIndices(); _meshGeometryCache.Set( surface.mesh, surface.index, mg ); this.LogInfo( "Created mesh with triangles:", mg.numTriangles ); @@ -208,9 +198,14 @@ namespace Rokojori async Task CombineMaterials() { + if ( ! combineMaterials ) + { + return; + } + _materials.ForEach( m => - { + { var type = _materialTypeGroups.FindKey( k => k.EqualsTo( m ) ); if ( type == null ) diff --git a/Runtime/Procedural/Mesh/MeshExtractor.cs b/Runtime/Procedural/Mesh/MeshExtractor.cs index 631ebd9..3024fc1 100644 --- a/Runtime/Procedural/Mesh/MeshExtractor.cs +++ b/Runtime/Procedural/Mesh/MeshExtractor.cs @@ -162,6 +162,43 @@ namespace Rokojori { list = list == null ? new List>() : list; + if ( n is CsgShape3D c ) + { + // RJLog.Log( "Extracting:", c, c.IsRootShape() ); + + if ( ! c.IsRootShape() ) + { + return list; + } + + var meshesData = c.GetMeshes(); + + // RJLog.Log( "MeshData:", meshesData ); + // RJLog.Log( "MeshData 0:",meshesData[ 0 ] ); + // RJLog.Log( "MeshData 1:", meshesData[ 1 ] ); + var trsf = (Transform3D) meshesData[ 0 ] * c.Transform; + var mesh = (ArrayMesh) meshesData[ 1 ]; + + var mg = MeshGeometry.From( mesh ); + + + Material material = null; + + if ( c is CsgBox3D bx ){ material = bx.Material; } + if ( c is CsgCylinder3D cy ){ material = cy.Material; } + if ( c is CsgMesh3D ms ){ material = ms.Material; } + if ( c is CsgPolygon3D pl ){ material = pl.Material; } + if ( c is CsgSphere3D sp ){ material = sp.Material; } + if ( c is CsgTorus3D tr ){ material = tr.Material; } + + RJLog.Log( "MeshInfo ", c, mesh, material, "\n", + "tris:", mg.numTriangles + ); + + var surface = new MeshSurface( mesh, material, 0, c ); + + list.Add( new Transformable( surface, trsf ) ); + } if ( n is MeshInstance3D mi ) { @@ -187,6 +224,7 @@ namespace Rokojori } } + if ( n is MultiMeshInstance3D mmi ) { var owner = mmi; diff --git a/Runtime/Procedural/Mesh/MeshGeometry.cs b/Runtime/Procedural/Mesh/MeshGeometry.cs index 922ea73..da1e13e 100644 --- a/Runtime/Procedural/Mesh/MeshGeometry.cs +++ b/Runtime/Procedural/Mesh/MeshGeometry.cs @@ -277,7 +277,7 @@ namespace Rokojori public List customMeshAttributes = new List(); - public int numTriangles => indices.Count / 3; + public int numTriangles => indices.Count == 0 && vertices.Count > 0 ? vertices.Count / 3 : indices.Count / 3; public static MeshGeometry From( MeshInstance3D meshInstance3D ) @@ -285,22 +285,43 @@ namespace Rokojori return From( (ArrayMesh)meshInstance3D.Mesh, meshInstance3D.GlobalTransform ); } + public static MeshGeometry CreateWithSurfaceTool( Mesh mesh, Transform3D? trsf = null, int index = 0 ) + { + var arrayMesh = new ArrayMesh(); + + var st = new SurfaceTool(); + st.Begin( Mesh.PrimitiveType.Triangles ); + st.CreateFrom( mesh, index ); + st.Commit( arrayMesh ); + + return From( arrayMesh, trsf, 0 ); + } + public static MeshGeometry From( Mesh mesh, Transform3D? trsf = null, int index = 0 ) { var arrayMesh = mesh as ArrayMesh; if ( arrayMesh == null ) { - arrayMesh = new ArrayMesh(); - var st = new SurfaceTool(); - st.Begin( Mesh.PrimitiveType.Triangles ); - st.CreateFrom( mesh, index ); - st.Commit( arrayMesh ); + return CreateWithSurfaceTool( mesh, trsf, index ); } return From( arrayMesh, trsf, 0 ); } + public void EnsureIndices() + { + if ( indices == null || indices.Count == 0 ) + { + indices = new List(); + + for ( int i = 0; i < vertices.Count; i++ ) + { + indices.Add( i ); + } + } + } + public static MeshGeometry From( ArrayMesh mesh, Transform3D? trsf = null, int index = 0 ) { @@ -315,6 +336,8 @@ namespace Rokojori var vertices = arrays[ (int) Mesh.ArrayType.Vertex ]; + // RJLog.Log( "Verts:", (( Vector3[] )vertices ).Length ); + if ( Variant.Type.Nil != vertices.VariantType ) { mg.vertices = new List( vertices.AsVector3Array() ); @@ -1532,7 +1555,7 @@ namespace Rokojori var surfaceArray = new Godot.Collections.Array(); surfaceArray.Resize( (int) Mesh.ArrayType.Max ); - var flags = Mesh.ArrayFormat.FormatVertex | Mesh.ArrayFormat.FormatIndex; + var flags = Mesh.ArrayFormat.FormatVertex; if ( vertices != null && vertices.Count != 0 ) { @@ -1568,6 +1591,18 @@ namespace Rokojori surfaceArray[ (int) Mesh.ArrayType.Index ] = indices.ToArray(); flags |= Mesh.ArrayFormat.FormatIndex; } + else + { + var indexArray = new int[ vertices.Count ]; + + for ( int i = 0; i < indexArray.Length; i++ ) + { + indexArray[ i ] = i; + } + + surfaceArray[ (int) Mesh.ArrayType.Index ] = indexArray; + flags |= Mesh.ArrayFormat.FormatIndex; + } customMeshAttributes.ForEach( c => diff --git a/Runtime/Rendering/Compositor/CompositorEffects/GreyScale/GrayScaleShader.glsl b/Runtime/Rendering/Compositor/CompositorEffects/GreyScale/GrayScaleShader.glsl index 08b8c38..d7af795 100644 --- a/Runtime/Rendering/Compositor/CompositorEffects/GreyScale/GrayScaleShader.glsl +++ b/Runtime/Rendering/Compositor/CompositorEffects/GreyScale/GrayScaleShader.glsl @@ -29,7 +29,7 @@ void main() float gray = color.r * 0.2125 + color.g * 0.7154 + color.b * 0.0721; - color.rgb = vec3( gray ); + color.rgb = vec3( gray * 0.5); imageStore( color_image, uv, color ); } \ No newline at end of file diff --git a/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs new file mode 100644 index 0000000..c3e190c --- /dev/null +++ b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs @@ -0,0 +1,61 @@ + +using Godot; +using Godot.Collections; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class VignetteEffect:SingleShaderCompositorEffect + { + public static readonly string shaderPath = Path( "Vignette/VignetteShader.glsl" ); + + [Export( PropertyHint.Range, "0,1")] + public float amount = 1.0f; + + [Export( PropertyHint.Range, "0.1,2")] + public float radius = 0.5f; + + [Export] + public float power = 1.0f; + + [Export] + public float offset = 1.0f; + + [Export] + public Color colorTop; + + [Export] + public Color colorBottom; + + + protected override void OnConfigure() + { + EffectCallbackType = EffectCallbackTypeEnum.PostTransparent; + _shaderPath = shaderPath; + _groupSize = 8; + } + + protected override void SetConstants() + { + constants.Set( + amount, + radius, + power, + offset, + (Vector2) context.internalSize + ); + } + + protected override void RenderView() + { + context.AssignScreenColorTexture(); + + context.pushConstants = constants; + + context.ProcessComputeProgram(); + + } + } +} \ No newline at end of file diff --git a/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs.uid b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs.uid new file mode 100644 index 0000000..80231b1 --- /dev/null +++ b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteEffect.cs.uid @@ -0,0 +1 @@ +uid://bgiluy6xgcvb0 diff --git a/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl new file mode 100644 index 0000000..94ed5b0 --- /dev/null +++ b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl @@ -0,0 +1,41 @@ +#[compute] +#version 450 + +layout( local_size_x = 16, local_size_y = 16, local_size_z = 1 ) in; + +layout( rgba16f, set = 0, binding = 0) +uniform image2D color_image; + +layout(push_constant, std430) +uniform Params +{ + float amount; + float radius; + float power; + float offset; + vec2 raster_size; + +} params; + + +void main() +{ + ivec2 uv = ivec2( gl_GlobalInvocationID.xy ); + ivec2 size = ivec2( params.raster_size ); + + if ( uv.x >= size.x || uv.y >= size.y ) + { + return; + } + + vec4 color = imageLoad( color_image, uv ); + + float d = length( uv - size/2 ) / length( size/2 * params.radius ); + d = pow( d, params.power ); + float amount = clamp( 1.0 - d + params.offset, 0.0, 1.0 ); + + + color.rgb = mix( color.rgb, vec3( 0.01, 0.01, 0.02 ), ( 1.0 - amount ) * params.amount ); + + imageStore( color_image, uv, color ); +} \ No newline at end of file diff --git a/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl.import b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl.import new file mode 100644 index 0000000..2884630 --- /dev/null +++ b/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl.import @@ -0,0 +1,14 @@ +[remap] + +importer="glsl" +type="RDShaderFile" +uid="uid://bnkadrfrp7jbc" +path="res://.godot/imported/VignetteShader.glsl-97e3dee05027b0474da3bfd8cf9341fa.res" + +[deps] + +source_file="res://addons/rokojori_action_library/Runtime/Rendering/Compositor/CompositorEffects/Vignette/VignetteShader.glsl" +dest_files=["res://.godot/imported/VignetteShader.glsl-97e3dee05027b0474da3bfd8cf9341fa.res"] + +[params] + diff --git a/Runtime/Rendering/Objects/RDPushConstants.cs b/Runtime/Rendering/Objects/RDPushConstants.cs index 2a17f18..342ce8c 100644 --- a/Runtime/Rendering/Objects/RDPushConstants.cs +++ b/Runtime/Rendering/Objects/RDPushConstants.cs @@ -23,6 +23,7 @@ namespace Rokojori public int size => _floatIndex + _intIndex; + public void Set( params object[] objects ) { Reset(); diff --git a/Runtime/Sensors/Default-Sensors/Default-Input-Icons-Library.tres b/Runtime/Sensors/Default-Sensors/Default-Input-Icons-Library.tres index 4942e11..31888ea 100644 --- a/Runtime/Sensors/Default-Sensors/Default-Input-Icons-Library.tres +++ b/Runtime/Sensors/Default-Sensors/Default-Input-Icons-Library.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="InputIconsLibrary" load_steps=74 format=3 uid="uid://dq52vhnqr5m6"] +[gd_resource type="Resource" script_class="InputIconsLibrary" load_steps=73 format=3 uid="uid://dq52vhnqr5m6"] [ext_resource type="Script" uid="uid://c3dpplc2slwd5" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/Definitions/DefaultInputIconDefinition.cs" id="1_64knt"] [ext_resource type="Script" uid="uid://bx1cm2837cuuc" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/InputIconsLibrary.cs" id="1_urlfx"] @@ -28,7 +28,6 @@ [ext_resource type="Texture2D" uid="uid://dxwnbh3a3fy8w" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/Graphics/GamePad-Left-Shoulder-Button.svg" id="13_omll7"] [ext_resource type="Texture2D" uid="uid://1pfyxi7wifn8" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/Graphics/Keyboard-SpaceKey.svg" id="14_6vom6"] [ext_resource type="Texture2D" uid="uid://bgi8cbw57gka0" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/Graphics/GamePad-MainButton.svg" id="14_n68qo"] -[ext_resource type="SystemFont" uid="uid://s1pi67tbxu24" path="res://3rdPerson/Inputs/Jost-Font.tres" id="15_co1yv"] [ext_resource type="Texture2D" uid="uid://cb8ldiej8234h" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/Graphics/GamePad-Axis-Background.svg" id="18_amimm"] [ext_resource type="Texture2D" uid="uid://ddfvjmi7sva6f" path="res://addons/rokojori_action_library/Runtime/Sensors/InputIcons/Graphics/GamePad-Axis-Pressed.svg" id="19_7x4g0"] [ext_resource type="Script" uid="uid://bvj322mokkq63" path="res://addons/rokojori_action_library/Runtime/Localization/LocaleText.cs" id="20_55eoq"] @@ -340,7 +339,6 @@ borderBottom = 0.0 [resource] script = ExtResource("1_urlfx") -font = ExtResource("15_co1yv") fontSize = SubResource("Resource_40bf3") iconHeightInEm = 1.5 mouseInputIcon = SubResource("Resource_34cgw") diff --git a/Runtime/Sensors/OnSensor.cs b/Runtime/Sensors/OnSensor.cs index fb56b7c..e641d9b 100644 --- a/Runtime/Sensors/OnSensor.cs +++ b/Runtime/Sensors/OnSensor.cs @@ -7,7 +7,7 @@ namespace Rokojori [Tool][GlobalClass, Icon("res://addons/rokojori_action_library/Icons/OnEvent.svg") ] public partial class OnSensor: Node { - [Export] + [Export] public Sensor sensor; [Export] @@ -19,30 +19,44 @@ namespace Rokojori [Export] public Action onEnd; + [ExportGroup("Condition")] + [Export] + public Condition condition; + + [Export] + public SceneCondition sceneCondition; + public override void _Process( double delta) { - var active = sensor.isActive; - var wasActive = sensor.wasActive; - - if ( ! active && ! wasActive ) + if ( sensor == null ) { return; } - var started = ! wasActive && active; - var ended = wasActive && ! active; + if ( condition != null && ! condition.Evaluate() ) + { + return; + } - if ( started ) + if ( sceneCondition != null && ! sceneCondition.Evaluate() ) + { + return; + } + + + + + if ( sensor.isDown ) { Action.Trigger( onStart ); } - if ( active ) + if ( sensor.isHold ) { Action.Trigger( onActive ); } - if ( ended ) + if ( sensor.isUp ) { Action.Trigger( onActive ); } diff --git a/Runtime/UI/UI.cs b/Runtime/UI/UI.cs index 6f690ce..5c14292 100644 --- a/Runtime/UI/UI.cs +++ b/Runtime/UI/UI.cs @@ -245,8 +245,13 @@ namespace Rokojori return; } + var oldFontSize = X_computedFontSizePixels; X_computedFontSizePixels = UINumber.Compute( this, settings.fontSize ) * fontZoom; + if ( oldFontSize != X_computedFontSizePixels ) + { + _fontSizeFlag = true; + } if ( Theme != null ) {