Juicee

June 10, 2026 · View on GitHub

Complete reference for every class in the Juicee framework. For effect params, see effects-reference.md. For the singleton API, see singleton-api.md.


JuiceeEffect

class_name JuiceeEffect extends Resource — Base class for all effects.

Every file in addons/juicee/effects/ that ends with _effect.gd extends this class. It handles the full apply lifecycle: cooldown gate → chance roll → pre-delay → intensity multiplication → accessibility gate → _apply() → signals.

Exported properties (inherited by every effect)

PropertyTypeDefaultDescription
chancefloat1.0Probability (0–1) this effect fires. 1.0 = always, 0.5 = 50% chance.
delayfloat0.0Pre-delay in seconds before _apply() runs. Uses real-time timer.
intensity_minfloat1.0Lower bound of the per-play intensity multiplier.
intensity_maxfloat1.0Upper bound of the per-play intensity multiplier. Both 1.0 = no randomization.
cooldownfloat0.0Minimum seconds between apply() calls on this resource.
graph_positionVector2Vector2.ZEROBlock position in the visual graph editor. Don't edit manually.

Signals

SignalArgsWhen
started_apply() is about to run (after delay and gates)
finished_apply() returned normally
stoppedstop() was called during a run
delay_startedseconds: floatThe pre-delay timer started (used by graph debug bar)

Public methods

func apply(context: Node, params: Dictionary = {}) -> void

Entry point. Runs the full pipeline: cooldown → chance → delay → intensity → accessibility → _apply(). Do not override. Pass runtime params like {"hit_direction": Vector2.LEFT}.

func stop() -> void

Cancels the current run immediately. Kills all _tracked tweens. Sets _cancelled = true so manual loops bail out. Bumps the generation counter so pending delays abort without reaching _apply().

func is_playing() -> bool

true between started and finished/stopped.

func cooldown_remaining() -> float

Seconds until the cooldown expires. 0.0 if ready.

Virtual methods (override in subclasses)

func _apply(context: Node, intensity_mult: float) -> void

The only method you override. intensity_mult is randf_range(intensity_min, intensity_max) scaled by JuiceeAccessibility.intensity_scale. If the accessibility layer blocks this effect, apply() never calls _apply().

func get_display_name() -> String          # "My Cool" — defaults to parsed script name
func get_category_name() -> String         # "Camera", "Screen", etc. — graph popup grouping
func get_category_color() -> Color         # colored titlebar stripe on graph blocks
func get_icon_path() -> String             # SVG icon path for graph block titlebar
func get_description() -> String           # tooltip in graph popup
func get_accessibility_tag() -> int        # one of JuiceeAccessibility.TAG_*

Protected methods (use in subclasses)

func _track(tween: Tween) -> Tween

Register a tween for cleanup. Every create_tween() call must be wrapped. Otherwise stop() can't kill it.

var tween := _track(target.create_tween())
func _tween_curved(tween, target, prop_name, from_value, to_value, duration, curve) -> Tweener

Curve-aware property tween. If curve is null, falls back to tween_property (caller sets set_trans/set_ease). If curve is set, samples it per-frame — gives designers custom easing shapes without touching code.

func _spawn_screen_shader_overlay(context, layer_name, z=128) -> Array  # [CanvasLayer, ColorRect]
func _spawn_screen_solid_overlay(context, layer_name, z=128) -> Array   # [CanvasLayer, ColorRect]
func _sweep_overlay_layers(context, layer_name) -> void

Screen overlay helpers. _spawn_screen_shader_overlay creates a BackBufferCopy + ColorRect pair — necessary for effects that read SCREEN_TEXTURE in their shader. _spawn_screen_solid_overlay creates just a ColorRect (for tints, bars, flash overlays). _sweep_overlay_layers cleans up any stale layers from a previous run (call at the top of every screen effect).

Static variables

static var accessibility: JuiceeAccessibility

Set by Juicee autoload in _ready(). All effects read this automatically — zero per-effect code needed.

Internal variables (read-only in subclasses)

var _runtime_params: Dictionary   # params passed to apply()
var _cancelled: bool              # set by stop() — check in manual loops
var _gen: int                     # generation counter — bumped by apply() and stop()

JuiceeSequence

class_name JuiceeSequence extends Resource — Ordered container of effects.

The persistence unit. Created inline by the Inspector UI, exported by the graph editor, or built in code. Everything serializes to .tres.

Exported properties

PropertyTypeDefaultDescription
effectsArray[JuiceeEffect][]The effect list. Add via Inspector "+ Add Effect" or graph editor.
parallelboolfalsetrue = all effects fire simultaneously. false = sequential (each awaits the previous).
stagger_delayfloat0.0When parallel=true, seconds between each effect's start. 0 = all at once.
graph_connectionsPackedStringArray[]Graph editor wire data. Auto-managed. Format: "from_id:from_port:to_id:to_port".

Methods

func play(context: Node, params: Dictionary = {}) -> void

Plays the sequence. awaitable — returns when all effects finish (or are stopped/cancelled). params is forwarded to every effect's _apply() via _runtime_params.

func stop() -> void

Cancels every in-flight effect and terminates the sequence loop.

func is_playing() -> bool
func pause() -> void       # hold at the seam between effects
func resume() -> void      # resume a paused sequence
func is_paused() -> bool

Pause semantics: the currently running effect finishes naturally. The sequence then waits at the inter-effect gap until resume() is called. There is no mid-effect pause.

Signals

SignalArgsWhen
startedplay() called and execution begins
finishedAll effects completed normally
stoppedstop() was called
effect_startedeffect: JuiceeEffectJust before an effect's _apply() runs
effect_finishedeffect: JuiceeEffectAfter an effect's _apply() returns

JuiceePlayer

class_name JuiceePlayer extends Node — Scene node that owns and fires a JuiceeSequence.

Exported properties

PropertyTypeDefaultDescription
sequenceJuiceeSequencenullThe sequence to play. Build inline or load a .tres.
auto_playboolfalseCall play() automatically when the node enters the tree.
target_pathNodePath""Override context node. Empty = use parent.
trigger_sourceNodePath""Node that emits the auto-trigger signal.
trigger_signalStringName""Signal name on trigger_source — auto-fires play() when received. Accepts any arity.
cooldownfloat0.0Minimum seconds between play() calls.
queue_during_cooldownboolfalseQueue one pending play() during cooldown instead of dropping it.

Methods

func play(params: Dictionary = {}) -> void
func stop() -> void
func is_on_cooldown() -> bool
func cooldown_remaining() -> float

Signals

SignalWhen
startedplay() passed cooldown and started the sequence
finishedSequence completed normally
blocked_by_cooldownplay() was called during cooldown

Signal trigger

Set trigger_source + trigger_signal to wire effects without code. Example: an Area2D with body_entered — when a body enters, play() fires automatically.


JuiceeStateStack

class_name JuiceeStateStack extends RefCounted — Concurrent-safe property restore.

Static class — all methods are static. No instance needed.

Problem it solves

Without this, two simultaneous shakes both capture cam.offset. The first to finish restores to the mid-shake snapshot of the second, leaving the camera stuck. With the stack, only the first capture stores the TRUE original; all subsequent captures get the same original back and increment a ref count. The property is only restored when the LAST effect releases it.

Methods

static func capture(target: Object, property: String) -> Variant

Captures target[property] if not already captured, increments ref count, returns the original value. Property can be a sub-path: "modulate:a", "scale:x".

static func release(target: Object, property: String) -> void

Decrements ref count. At zero, restores original and removes the entry. Safe to call on a freed target — stale entries are pruned automatically.

static func active_count() -> int   # debug: number of currently held entries
static func reset() -> void         # force-clear (editor reloads, test resets)

Usage pattern

var original: Vector2 = JuiceeStateStack.capture(target, "position")
# ... modify target.position freely ...
JuiceeStateStack.release(target, "position")

JuiceeAccessibility

class_name JuiceeAccessibility extends RefCounted — Global motion/flash control.

Accessed via Juicee.accessibility (the autoload singleton). Set flags from your game's settings screen.

Properties

PropertyTypeDefaultDescription
reduced_motionboolfalseHalves ALL effect intensities. Game stays alive but calmer.
no_flashboolfalseSilences TAG_FLASH effects (Flash, Strobe, AmbientFlash, ScreenTint).
no_screenshakeboolfalseSilences TAG_SCREENSHAKE effects (Shake, Recoil, etc.).
no_chromaticboolfalseSilences TAG_CHROMATIC effects (Chromatic, Glitch, ColorGrade).
intensity_scalefloat1.0Master multiplier (0–1). Applied after reduced_motion.

Signal

signal changed   # emitted when any flag changes — connect to HUD icons/tooltips

Methods

func to_dict() -> Dictionary    # serialize to your save system
func from_dict(d: Dictionary)   # restore from save

Tag constants (used in get_accessibility_tag())

JuiceeAccessibility.TAG_NONE        # 0 — always plays at full intensity (audio, time, flow)
JuiceeAccessibility.TAG_FLASH       # 1 — Flash, StrobeLight, AmbientFlash
JuiceeAccessibility.TAG_SCREENSHAKE # 2 — Shake, Shake3D, DirectionalShake, Recoil
JuiceeAccessibility.TAG_CHROMATIC   # 3 — Chromatic, Glitch, ColorGrade
JuiceeAccessibility.TAG_MOTION      # 4 — Blur, Pixelate, Zoom (scaled by reduced_motion)

Integration

# In your settings screen:
Juicee.accessibility.reduced_motion = settings.reduced_motion
Juicee.accessibility.no_flash       = settings.no_flash
Juicee.accessibility.no_screenshake = settings.no_screenshake

# Save/load:
save_data["accessibility"] = Juicee.accessibility.to_dict()
Juicee.accessibility.from_dict(save_data["accessibility"])

JuiceeBeatClock

class_name JuiceeBeatClock extends Node — Accumulator-based BPM beat emitter.

Add to your scene tree as a regular node. Point JuiceeBeatSyncEffect.clock_path or JuiceeZoomPulseEffect.clock_path at it for musically tight beat sync.

Exported properties

PropertyTypeDefaultDescription
bpmfloat120.0Beats per minute (20–300).
auto_startboolfalseStart the clock when _ready() fires.

Signal

signal beat(beat_number: int)

Fires on every beat. beat_number is 1-indexed and increments monotonically — use beat_number % N == 0 to trigger every N beats.

Methods

func start() -> void
func stop() -> void
func reset() -> void               # resets beat_number and accumulator to 0, keeps running
func get_beat_phase() -> float     # 0.0–1.0 position within the current beat interval
func get_beat_number() -> int      # current beat counter (0 = not started)

Example

@onready var clock: JuiceeBeatClock = $BeatClock

func _ready() -> void:
    clock.bpm = 128.0
    clock.beat.connect(_on_beat)
    clock.start()

func _on_beat(n: int) -> void:
    if n % 4 == 0:  # every bar (4 beats)
        Juicee.zoom_pulse(self, 128.0, 0.12, 4.0)

JuiceeGraphPlayer

class_name JuiceeGraphPlayer extends Node — Runtime graph executor.

You don't need to use this directly. The Juicee autoload and JuiceePlayer both delegate to it internally when playing a graph resource. Useful if you want to drive a graph purely from code.

static func play(resource: JuiceeGraphResource, context: Node) -> void

Walks the graph from its Trigger node, executing effects and honoring flow control (Split, Loop, Random, Condition). awaitable.

Graph execution semantics

Node typeBehavior
triggerEntry point — execution starts here
effectCalls effect.apply(context) and awaits it
splitAll outputs fire concurrently (no await on any)
loopRuns the connected chain count times sequentially
randomPicks one output weighted by properties.weights
conditionEvaluates properties.expression via Expression; port 0 = true, port 1 = false

JuiceeGraphResource

class_name JuiceeGraphResource extends Resource — Serialized graph.

Saved as .tres. Contains an array of JuiceeGraphNodeData and connection metadata. You rarely interact with this directly — the graph editor and player manage it.

Methods

func find_trigger() -> JuiceeGraphNodeData     # returns the single Trigger node
func get_next(id: String) -> Array             # returns ordered list of next nodes

JuiceeGraphNodeData

class_name JuiceeGraphNodeData extends Resource — Single node in a graph.

Properties

PropertyTypeDescription
idStringUnique node ID (UUID-style)
typeString"trigger", "effect", "split", "loop", "random", "condition"
effectJuiceeEffectThe effect instance (only for type == "effect")
propertiesDictionaryFlow-control params (count, expression, weights, port_count)
graph_positionVector2Position in graph editor canvas