Custom Spell System
APG internship — Godot 4 — GDScript / C#
view on github →Overview
Built during my internship at Adventure Party Games. This is a visual spell design prototype built in Godot 4. The goal was to let designers construct spells entirely through a node graph editor without writing any new code for each spell — spells are compiled into data at edit time and cast as 3D projectiles at runtime.
The editor is accessed in-game with TAB. Spells are fired with the left mouse button.
The system is composed of seven node categories that can be combined freely.
Node Types
| element | Assigns color and identity to the spell: Fire, Ice, Lightning, or Arcane. |
| shape | Defines the hitbox geometry: Orb, Beam, AOE, Cone, Wall, Explode, Projectile, or Gravity Projectile. |
| path | Controls movement behavior: Line of Sight, Homing, Boomerang, Curve, Zig-Zag, or Upwards. |
| casting | Determines firing pattern: Burst, Continuous, Charge Up, and self-targeting variants of each. |
| modifier | Adjusts numeric parameters such as damage, speed, and size. |
| effect | Applies buffs or debuffs to the caster or target. |
| trigger | Activates child spells on events: On Hit, On Kill, On End, or On Timer — enabling chained and compound spells, e.g. a projectile that explodes into homing orbs on impact. |
| spell_ref | Embeds a previously saved spell by reference, allowing complex spells to be reused as components inside other spells. |
Implementation
When the designer saves a spell, the graph is compiled into a flat sequential array of dictionaries — one dict per node, in traversal order. We read the array and dispatch behavior entirely from data. This is what allows new spells to be authored without touching any C# or GDScript.
Trigger nodes are the exception: at compile time, _split_at_triggers() walks
the array, detects trigger entries, and splices each trigger’s child spell in-place as
a nested sub-array. The runtime re-enters this sub-array on the trigger event, which is what
gives chained spells (e.g. projectile → on-hit → homing orbs) their recursive
structure without any special-casing in the cast loop. SpellFactory also
recursively inlines any spell_ref entries before assembling the object, so
references to saved spells are fully transparent to the cast loop.
Spell casting is not tied to mouse input directly. Pressing the button starts the wand
animation; the animation hits a keyframe that emits a spell_cast() signal,
which the spell system receives to actually spawn the projectile. This keeps cast timing
locked to the animation rather than raw input.
The project is written in GDScript, with the only C# parts being the external PlayerController Library and its physics.
↑ back to top