Logic Event Examples¶
Logic events add behaviour to animations, effectively replacing the classic reference behaviour: play this sound at frame 2, set this velocity at frame 4, spawn this projectile at frame 9, branch when landing, branch when the player enters a command.
Use events when the behaviour follows a fixed timeline. Use Lua when the move needs target searches, damage scaling, cooldowns, or conditional state machines.
The examples below show the saved JSON so you can see exactly what each event stores. You build all of it through the Events section of the Inspector — see Logic Events for the interface.
Runtime Rules That Matter¶
- Exact-frame events run only when
current_animation_frameequals theirframe. - Windowed events use
afterFrameandbeforeFrame; if both are0, the event is active during the entirety of the animation. - Events run before an animation's Lua script.
- Animation IDs in the engine are doubled internally, e.g. animation 7 has the ID 14 (or 0xD in hexadecimal).
VelocityEventandAccelerationEventtake character facing direction into account: positive means moving forward, negative means moving backward.- In velocity and acceleration events, positive Y means upward in editor terms; Internally, the engine stores them with the inverse value, i.e. upward impulse as negative, downwards as positive.
VelocityEventandAccelerationEventcan enable or disable each affected axis. Disabled axis input boxes are ignored, which lets you set one component to zero while preserving the other components.InputVelocityEvent,InputAccelerationEvent,JumpToAnimationOnInputButton, andJumpToAnimationOnInputComboonly act on players.JumpToAnimationOnHitonly acts on players and triggers whenhitPauseTimer == 1.GrabEventneedsattackedCharacterIdto already point to a valid victim, usually because a hitbox connected earlier in the animation.
Jump Startup With Events¶
This event stack starts an airborne move by setting upward velocity, then applies gravity. It is useful for a jump attack or special that should not need Lua.
[
{
"$type": "Sor2NewEra.Gameplay.VelocityEvent, Sor2NewEra",
"frame": 0,
"velocity": { "x": 90, "y": 360, "z": 0 }
},
{
"$type": "Sor2NewEra.Gameplay.AccelerationEvent, Sor2NewEra",
"frame": 1,
"acceleration": { "x": 0, "y": -18, "z": 0 }
},
{
"$type": "Sor2NewEra.Gameplay.JumpToAnimationOnLanding, Sor2NewEra",
"initialFrame": 4,
"animationId": 0,
"animationFrame": 0
}
]
Field notes:
velocity.xmoves forward because the event multiplies it by facing direction.velocity.yis written as positive upward in the event, then converted to negative internal vertical impulse.acceleration.yis negative here because after conversion it becomes downward gravity.JumpToAnimationOnLandingwaits untilimpulse.y > 0, the frame is at leastinitialFrame, and height has reached the ground.
Aerial Spinning Kick¶
This is an event-only version of a forward aerial spinning special. It starts with a lift, moves forward, becomes briefly invincible, spawns sound, then returns to standing on landing.
[
{
"$type": "Sor2NewEra.Gameplay.SoundEvent, Sor2NewEra",
"frame": 0,
"packName": "My Pack",
"soundName": "spin-start",
"channel": 2
},
{
"$type": "Sor2NewEra.Gameplay.InvincibilityEvent, Sor2NewEra",
"frame": 0,
"invincible": true
},
{
"$type": "Sor2NewEra.Gameplay.VelocityEvent, Sor2NewEra",
"frame": 0,
"velocity": { "x": 180, "y": 250, "z": 0 }
},
{
"$type": "Sor2NewEra.Gameplay.AccelerationEvent, Sor2NewEra",
"frame": 1,
"acceleration": { "x": -4, "y": -12, "z": 0 }
},
{
"$type": "Sor2NewEra.Gameplay.InputVelocityEvent, Sor2NewEra",
"afterFrame": 2,
"beforeFrame": 18,
"strength": { "x": 12, "y": 6 }
},
{
"$type": "Sor2NewEra.Gameplay.SoundEvent, Sor2NewEra",
"frame": 8,
"packName": "My Pack",
"soundName": "spin-loop",
"channel": 2
},
{
"$type": "Sor2NewEra.Gameplay.InvincibilityEvent, Sor2NewEra",
"frame": 20,
"invincible": false
},
{
"$type": "Sor2NewEra.Gameplay.JumpToAnimationOnLanding, Sor2NewEra",
"initialFrame": 10,
"animationId": 0,
"animationFrame": 0
}
]
Why this works:
- The first velocity starts the arc.
- Acceleration gradually removes forward speed and pulls the character down.
InputVelocityEventadds held input tovelocity, not impulse. That makes it a direct per-frame nudge during the active window.- The landing event clamps height to ground and queues the standing animation.
- If this move also needs complex hit-pause rebound, a Lua script should handle the rebound and return
falseduring the custom physics frames.
Projectile Attack¶
A projectile attack usually needs two animations: the character's firing animation and the projectile item's active animation.
Character firing animation:
[
{
"$type": "Sor2NewEra.Gameplay.SoundEvent, Sor2NewEra",
"frame": 6,
"packName": "My Pack",
"soundName": "projectile-release",
"channel": 2
},
{
"$type": "Sor2NewEra.Gameplay.SpawnItemEvent, Sor2NewEra",
"frame": 7,
"packName": "My Pack",
"entityName": "Energy Projectile",
"animationId": 0,
"mode": -1,
"lifetime": 80,
"positionOffset": { "x": 1100, "y": 0 },
"velocity": { "x": 520, "y": 0 },
"isProjectile": true,
"damage": 8,
"hitPause": 4,
"hitSoundName": "projectile-hit",
"hitSoundPack": "My Pack",
"hitSoundChannel": 0
}
]
Projectile impact animation:
[
{
"$type": "Sor2NewEra.Gameplay.SoundEvent, Sor2NewEra",
"frame": 0,
"packName": "My Pack",
"soundName": "projectile-impact",
"channel": 0
},
{
"$type": "Sor2NewEra.Gameplay.DestroyEvent, Sor2NewEra",
"frame": 5
}
]
Field notes:
SpawnItemEvent.positionOffsetis converted by the engine and applied relative to the source convention. Test positive and negative values in-game because spawn helpers intentionally negate the stored offsets before calling the original item spawner.animationIdis the projectile's editor animation ID.isProjectileenables projectile hit metadata: damage, hit pause, and hit sound.- If the projectile needs custom movement, attach a Lua script to the projectile's active animation and let the spawn event handle only creation.
Input Combo Special¶
Use JumpToAnimationOnInputCombo for command moves. This example watches for a quarter-turn-style chain during a normal animation and jumps to a special animation.
[
{
"$type": "Sor2NewEra.Gameplay.JumpToAnimationOnInputCombo, Sor2NewEra",
"afterFrame": 0,
"beforeFrame": 12,
"input": "v>b",
"animationId": 42
}
]
Field notes:
inputis parsed by the player input chain parser.- The event checks the player's input history, not just the current frame.
- When the chain matches, the event sets the target animation and returns
falseinternally, stopping later event evaluation for that controller path.
Button Cancel Window¶
This event lets an attack cancel into another animation only during frames 9 through 16.
[
{
"$type": "Sor2NewEra.Gameplay.JumpToAnimationOnInputButton, Sor2NewEra",
"afterFrame": 9,
"beforeFrame": 16,
"input": 16,
"animationId": 18
}
]
input is the numeric InputButtons value. Common values are 16 for B (attack), 32 for C (jump), 64 for A (special), 65536 for X (back attack), 131072 for Y (pick-up), and 1048576 for Z (super move). Use this for simple cancels. Use Lua when the cancel depends on hit_pause_timer, attacked_character_id, or custom_value_*.
Hit-Confirm Branch¶
This branch changes animation when the player's hitbox connects during a window. Internally it checks hitPauseTimer == 1, so it reacts to the first hit-pause frame.
[
{
"$type": "Sor2NewEra.Gameplay.JumpToAnimationOnHit, Sor2NewEra",
"afterFrame": 5,
"beforeFrame": 18,
"animationId": 27
}
]
Use this when the follow-up should happen only after contact. If the move must distinguish blocked hits, projectiles, wall hits, or different victims, use Lua.
Hitbox Into Grab¶
Use GrabEvent when a hitbox should convert into a grab. The hitbox must have already set attackedCharacterId.
[
{
"$type": "Sor2NewEra.Gameplay.GrabEvent, Sor2NewEra",
"frame": 6
},
{
"$type": "Sor2NewEra.Gameplay.JumpToAnimationEvent, Sor2NewEra",
"frame": 7,
"animationId": 4,
"animationFrame": 0
}
]
Authoring notes:
- Place the hitbox on the same frame or an earlier frame than the
GrabEvent. - Let the animation recover naturally may the hit grab miss.
- For scripted throws, events can start the grab, then a Lua grab controller can own victim positioning.
Teleport Or Step Offset¶
PositionOffsetEvent changes real position immediately and marks the object as teleported for the animation system.
[
{
"$type": "Sor2NewEra.Gameplay.PositionOffsetEvent, Sor2NewEra",
"frame": 3,
"offset": { "x": 24, "y": 0, "z": -8 }
}
]
Field notes:
offset.xfollows facing direction.offset.zmoves along the belt-scroller depth axis.offset.yis added to vertical velocity, not height position directly.- Use this for short steps, snaps, and throw alignment. Use Lua if the offset depends on target position.
Health Cost Or Healing¶
HealthEvent adds or removes to health. Use a negative value for a health cost.
[
{
"$type": "Sor2NewEra.Gameplay.HealthEvent, Sor2NewEra",
"frame": 0,
"health": -8
}
]
Common scenario is for health cost during a custom special move. For moves that are not classic specials and should fail if health is too low, use Lua. This event itself always applies the value, ideal for use in classic special moves.
Custom Value Marker¶
Use SetCustomValueEvent when events need to communicate with Lua scripts or global hooks.
[
{
"$type": "Sor2NewEra.Gameplay.SetCustomValueEvent, Sor2NewEra",
"frame": 0,
"valueId": 1,
"value": 3
}
]
Example use: set custom_value_1 to 3 at the start of a move, then have a shared Lua air-control script choose a movement profile from that value.
Choosing Events Or Lua¶
Use events when the answer is fixed by frame number:
- play sound
- start movement
- spawn item
- toggle invincibility
- jump on landing
- branch on simple input
Use Lua when the answer depends on runtime state:
- target distance
- current health or meter
- victim existence
- hitbox result with custom recoil
- weapon type
- multiple hits with cooldowns
- grab victim alignment
- enemy AI phase
Event-driven moves and Lua scripts can be combined. A good pattern is events for the predictable timeline and Lua for the exceptional branches.