class_name FlashEffect extends BaseEffect ## Shader applied to the target sprite during the flash. @export var flash_shader: Shader = preload("res://shaders/flash.gdshader") ## Color of the flash overlay. @export var flash_color: Color = Color.WHITE ## How many flash cycles per second. @export_range(0.0, 60.0) var flash_frequency: float = 10.0 ## Fraction of each cycle the flash is visible (0.0–1.0, i.e. duty cycle). @export_range(0.0, 1.0) var flash_length: float = 0.5 ## How the flash blends with the original sprite. @export_enum("Mix", "Additive", "Screen") var blend_mode: int = 0 ## Additional brightness added to the flash color (0.0 = none, 1.0 = double). @export_range(0.0, 1.0) var brightness_boost: float = 0.0 ## Duration of the fade-in at the start of the effect (seconds). @export_range(0.0, 5.0) var start_ease: float = 0.0 ## Easing curve for the fade-in. @export_enum("Linear", "Smooth", "Ease-In", "Ease-Out", "Ease-In-Out") var start_ease_mode: int = 1 ## Duration of the fade-out at the end of the effect (seconds). @export_range(0.0, 5.0) var end_ease: float = 0.0 ## Easing curve for the fade-out. @export_enum("Linear", "Smooth", "Ease-In", "Ease-Out", "Ease-In-Out") var end_ease_mode: int = 1 var _original_material: Material func start(target: Node) -> void: super.start(target) if target is CanvasItem: _original_material = target.material var flash_material := ShaderMaterial.new() flash_material.shader = flash_shader flash_material.set_shader_parameter("flash_color", flash_color) flash_material.set_shader_parameter("flash_intensity", 0.0) flash_material.set_shader_parameter("blend_mode", blend_mode) flash_material.set_shader_parameter("brightness_boost", brightness_boost) target.material = flash_material func process(delta: float, target: Node) -> void: super.process(delta, target) if target.material is ShaderMaterial: var intensity := _compute_intensity(_elapsed) target.material.set_shader_parameter("flash_intensity", intensity) func stop(target: Node) -> void: if target is CanvasItem: target.material = _original_material ## Compute the combined eased flash intensity for a given elapsed time. ## Returns a value 0.0–1.0 where 1.0 means full flash visibility. func _compute_intensity(elapsed: float) -> float: if duration <= 0.0: return 0.0 # Clamp elapsed to duration to avoid overshoot on the final frame var t: float = min(elapsed, duration) # Overall envelope: fade in during start_ease, hold, fade out during end_ease var envelope: float = 1.0 if t < start_ease and start_ease > 0.0: envelope = _ease_value(t / start_ease, start_ease_mode) elif t > duration - end_ease and end_ease > 0.0: var fade_progress = (t - (duration - end_ease)) / end_ease envelope = 1.0 - _ease_value(fade_progress, end_ease_mode) # Flash pattern: on/off cycling at the given frequency and duty cycle # Each flash pulse also eases in and out for a smoother look var period := 1.0 / flash_frequency if flash_frequency > 0.0 else 1.0 var phase := fmod(t, period) / period # 0.0–1.0 position within the current cycle var pulse: float = 0.0 if phase < flash_length: # Inside the "on" portion of this cycle — ease within the pulse itself var pulse_progress = phase / flash_length if flash_length > 0.0 else 1.0 # Ease in for the first half of the pulse (build up), ease out for the second (fade down) if pulse_progress < 0.5: pulse = _ease_value(pulse_progress * 2.0, 2) # Ease-In (build up) else: pulse = _ease_value((1.0 - pulse_progress) * 2.0, 3) # Ease-Out (fade down) return envelope * pulse ## Apply easing to a 0.0–1.0 normalized value based on the selected mode. func _ease_value(value: float, mode: int) -> float: match mode: 0: return value # Linear 1: return smoothstep(0.0, 1.0, value) # Smooth 2: return ease(value, -2.0) # Ease-In (accelerating) 3: return ease(value, 2.0) # Ease-Out (decelerating) 4: return ease(value, 3.0) # Ease-In-Out (S-curve) return value