using Godot; using System.Collections.Generic; namespace Rokojori { [Tool] [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Flash.svg") ] public partial class FlashVFX:SequenceAction, Animator { [Export] public FlashPreset preset; [Export] public Node[] targets; [Export] public bool includeChildren = false; public enum MaterialAssignementMode { Replace_Per_Target, Add_For_Each_Material } [ExportGroup("Material Assignment")] [Export] public MaterialAssignementMode assignementMode = MaterialAssignementMode.Add_For_Each_Material; [Export] public Node3D positionOffset; public void OnAnimatorStart(){} public void OnAnimatorEnd(){} public void OnAnimatorCancel(){} Dictionary _running = new Dictionary(); protected override void _OnTrigger() { if ( assignementMode == MaterialAssignementMode.Replace_Per_Target ) { _OnTrigger_ReplacePerTarget(); } if ( assignementMode == MaterialAssignementMode.Add_For_Each_Material ) { _OnTrigger_AddForEachMaterial(); } } protected void _OnTrigger_ReplacePerTarget() { var id = DispatchStart(); var duration = preset.duration == null ? new SecondsDuration() : preset.duration; var tl = TimeLineManager.Ensure( duration.timeLine ); var OVERLAY = GeometryInstance3D.PropertyName.MaterialOverlay; var runner = new FlashVFXRunner(); runner.flashVFX = this; runner.targets = VFXTools.GetGeometryInstance3Ds( targets, includeChildren ); var gradient = preset.colorGradient.GetGradient(); var gradientRepeat = preset.colorGradient.GetRepeat(); var gradientRepeatMode = preset.colorGradient.GetRepeatMode(); var opacityCurve = preset.opacityCurve; if ( runner.targets.Count == 0 ) { DispatchEnd( id ); return; } runner.materials = runner.targets.Map( gi => { return preset.flashType.CreateAndInitializeMaterial( runner, gi, gradient.Sample( 0 ) ); } ); _running[ id ] = runner; runner.AddLight(); for ( int i = 0; i < runner.targets.Count; i++ ) { var t = runner.targets[ i ]; AnimationManager.StartAnimation( this, t, OVERLAY ); t.MaterialOverlay = runner.materials[ i ]; runner.assignedMaterials[ t ] = runner.materials[ i ]; } TimeLineManager.ScheduleSpanIn( tl, 0, duration.GetDurationInSeconds(), ( span, type )=> { var timeNow = tl.position; var phase = span.phase; var color = gradient.SampleRepeated( phase * gradientRepeat, gradientRepeatMode ); var opacity = opacityCurve.Sample( phase ); runner.UpdateLight( phase, color, opacity ); for ( int i = 0; i < runner.targets.Count; i++ ) { var gi = runner.targets[ i ]; if ( ! AnimationManager.IsAnimating( this, gi, OVERLAY ) ) { continue; } var material = runner.materials[ i ]; preset.flashType.ApplyFlashType( runner, span.phase, color, opacity, gi, material ); } if ( type == TimeLineSpanUpdateType.End ) { for ( int i = 0; i < runner.targets.Count; i++ ) { var gi = runner.targets[ i ]; if ( gi.MaterialOverlay == runner.assignedMaterials[ gi ] ) { gi.MaterialOverlay = null; } AnimationManager.EndAnimation( this, gi, OVERLAY, runner.materials[ i ] ); } runner.RemoveLight(); DispatchEnd( id ); } }, this ); } protected void _OnTrigger_AddForEachMaterial() { var id = DispatchStart(); var duration = preset.duration == null ? new SecondsDuration() : preset.duration; var tl = TimeLineManager.Ensure( duration.timeLine ); var OVERLAY = GeometryInstance3D.PropertyName.MaterialOverlay; var runner = new FlashVFXRunner(); runner.flashVFX = this; runner.targets = VFXTools.GetGeometryInstance3Ds( targets, includeChildren ); if ( runner.targets.Count == 0 ) { DispatchEnd( id ); return; } runner.AddLight(); var gradient = preset.colorGradient.GetGradient(); var gradientRepeat = preset.colorGradient.GetRepeat(); var gradientRepeatMode = preset.colorGradient.GetRepeatMode(); var opacityCurve = preset.opacityCurve; Material nullMaterial = null; for ( int i = 0; i < runner.targets.Count; i++ ) { var t = runner.targets[ i ]; if ( t.MaterialOverlay == null ) { if ( nullMaterial == null ) { nullMaterial = preset.flashType.CreateAndInitializeMaterial( runner, null, gradient.Sample( 0 ) ); runner.materials.Add( nullMaterial ); runner.assignedMaterials[ t ] = nullMaterial; } } else { var original = t.MaterialOverlay; Material extended = null; if ( ! runner.mappedMaterials.ContainsKey( original ) ) { var mapped = preset.flashType.CreateAndInitializeMaterial( runner, null, gradient.Sample( 0 ) ); extended = t.MaterialOverlay.Duplicate() as Material; Materials.AddToNextPass( extended, mapped ); runner.materials.Add( extended ); runner.mappedMaterials[ original ] = extended; } else { extended = runner.mappedMaterials[ original ]; } runner.originalMaterials[ t ] = original; runner.assignedMaterials[ t ] = extended; t.MaterialOverlay = extended; } } _running[ id ] = runner; TimeLineManager.ScheduleSpanIn( tl, 0, duration.GetDurationInSeconds(), ( span, type )=> { var timeNow = tl.position; var phase = span.phase; var color = gradient.SampleRepeated( phase * gradientRepeat, gradientRepeatMode ); var opacity = opacityCurve.Sample( phase ); runner.UpdateLight( phase, color, opacity ); for ( int i = 0; i < runner.materials.Count; i++ ) { var material = runner.materials[ i ]; preset.flashType.ApplyFlashType( runner, span.phase, color, opacity, null, material ); } if ( type == TimeLineSpanUpdateType.End ) { for ( int i = 0; i < runner.targets.Count; i++ ) { var gi = runner.targets[ i ]; var material = runner.assignedMaterials[ gi ]; if ( gi.MaterialOverlay == material ) { gi.MaterialOverlay = runner.originalMaterials.ContainsKey( gi ) ? runner.originalMaterials[ gi ] : null; } } DispatchEnd( id ); runner.RemoveLight(); } }, this ); } } public class FlashVFXRunner { public FlashVFX flashVFX; public FlashPreset preset => flashVFX.preset; public List targets = []; public List materials = []; public OmniLight3D light; public Dictionary originalMaterials = new Dictionary(); public Dictionary assignedMaterials = new Dictionary(); public Dictionary mappedMaterials = new Dictionary(); public Vector3 GetFlashPosition( FlashPreset.PositionMode mode, Vector3 offset ) { if ( FlashPreset.PositionMode.Position_Offset == mode ) { return flashVFX.positionOffset.GlobalPosition + offset; } else if ( FlashPreset.PositionMode.Origin_Of_First == mode ) { return targets[ 0 ].GlobalPosition + offset; } else if ( FlashPreset.PositionMode.Center_Of_All == mode ) { return Node3DExtensions.GetCenter( targets ) + offset; } else { return offset; } } public void AddLight() { if ( ! preset.lightEnabled ) { return; } light = flashVFX.CreateChild(); light.LightColor = new Color( 0, 0, 0 ); light.GlobalPosition = GetFlashPosition( preset.lightPosition, preset.lightPositionOffset ); light.OmniRange = preset.lightRange; light.OmniAttenuation = preset.lightAttenutation; light.LightEnergy = preset.lightEnergy; light.ShadowEnabled = preset.lightShadowCasting; } public void UpdateLight( float phase, Color color, float opacity ) { if ( light == null ) { return; } light.LightColor = color * opacity; } public void RemoveLight() { if ( light == null ) { return; } light.DeleteSelf(); } } }