Let's have an up-to-date summary of how things work now
, in the latest commits in my repo (which are much newer than the latest experimental posted here) (EDIT: This is no longer true; I updated the experimental). For this, I will be describing the features
in full, not just "what the editor needs to know".
Anything struck-out here is valid in the experimental, but has since been removed in the source code, so don't use it (or if you have a really good use case for it, explain that, so we can see why we should re-add it).
Basic setupAn object must have a primary animation, defined either in the base section of the object
(I encourage doing it this way where possible, for backwards-compatibility) (this method will be considered deprecated and exists for backwards-compatibility purposes) or in a $PRIMARY_ANIMATION section. I will explain how these work later in the post. The primary animation is given special handling to allow defining it in the main section, which means old pieces are forwards-compatible
and new pieces can be made backwards-compatible.
An object may (but does not have to) have one or more secondary animations. These are defined in $ANIMATION sections.
The primary animation's frame updating is not controlled by anything new. Just like the sole animation was in stable-NL, the primary animation is directly controlled by game physics, even when it's as simple as "a looping animation that never does anything else". This is to minimize the risk of breaking anything physics-wise. (Code exists, for future use, to allow an object's primary animation to be replaced at runtime with one of the secondaries; but this is currently not used by anything. This should not be something the user can do arbitrarily, but rather, if a future object type needs this functionality, it exists.)
Secondary animations are controlled entirely by the new animation system, which I will explain the workings of after the explanation of the data.
Defining an animationA $PRIMARY_ANIMATION or $ANIMATION segment can contain the following data. Most of these are valid for either, but some are only valid for secondary animations.
NAME - This defines the suffix of the filename to load the animation from. For example, if the name is "example", on an object called "bob" (ie: the NXMO file is "bob.nxmo"), the animation would be loaded from "bob_example.png". If NAME is blank or absent, no suffix is applied - it would be loaded from "bob.png" (not "bob_.png"). This works the same way, regardless of whether the animation is primary or secondary - the primary can use a suffix, a secondary can use the non-suffixed file!
FRAMES - Frame count of the animation. If absent, this is treated as 1.
COLOR - Defines the color name (from the currently-active theme) to recolor the animation with. Works exactly the same way as, in the old system, a mask with a target of *SELF sould have. If absent (which it will be in 99% of cases - exceptions being the default style one-ways and pickups), no recoloring occurs.
HORIZONTAL_STRIP - Only the presence / absence (not any value) of this keyword is tested. If it's present, the frames are loaded from the PNG file horizontally rather than vertically.
Z_INDEX - Defines the order in which animations are rendered. If absent, it defaults to 1 for the primary animation, and 0 for any other animation.
INITIAL_FRAME - Sets the initial frame of the animation. This is ignored on the primary animation
if the object is of a type where the frame is physics-significant (but not if it isn't). If absent, defaults to 0. "RANDOM" can be used as the value to specify a random initial frame.
OFFSET_X and
OFFSET_Y - Sets the position, relative to the object's in-data-file coordinates, that the animation is rendered at. Default to zero. To be clear: These
are valid for the primary animation!
NINE_SLICE_LEFT,
NINE_SLICE_TOP,
NINE_SLICE_RIGHT and
NINE_SLICE_BOTTOM - Sets the number of pixels on each side to treat as the edge when nine-slicing.
HIDE - Only the presence / absence (not any value) of this keyword is tested for. If it's present, the
default visibility of the animation is "hidden". Ignored on the primary animation.
STATE - Sets the
default animation state of the animation. Ignored on the primary animation. This can be one of: "PLAY", "PAUSE", "STOP", "LOOP_TO_ZERO", "MATCH_PRIMARY_FRAME". If absent (or invalid), the default value is either "
STOP" (if "HIDE" is present - but I am very strongly considering changing this to "PAUSE") "PAUSE" (if "HIDE" is present) or "PLAY" (if "HIDE" is absent).
$TRIGGER - Explained below.
Defining the primary animation in the main body of the NXMO fileInternally, NeoLemmix - if it detects that no $PRIMARY_ANIMATION section exists - it creates one based on these lines in the main segment, then parses it the same way it usually would. Any line that doesn't get created by this translation, just uses its default value. Note that
not all features supported in a $PRIMARY_ANIMATION segment, are supported here. Any I list here with no comments, just get copied verbatim to the generated $PRIMARY_ANIMATION segment.
FRAMES HORIZONTAL_STRIPNINE_SLICE_LEFT,
NINE_SLICE_TOP,
NINE_SLICE_RIGHT and
NINE_SLICE_BOTTOMINITIAL_FRAMEPREVIEW_FRAME - Copied to the generated segment, with the keyword changed to "INITIAL_FRAME".
RANDOM_START_FRAME - If this is present, "INITIAL_FRAME -1" is added to the generated segment.Defining a trigger condition on an animationAny animation except the primary can contain any number of trigger conditions. These are order-sensitive - the later in the file, the higher the priority of it. Trigger conditions can contain the following lines:
CONDITION - The condition on which the trigger is applied. Valid values are "READY", "BUSY", "DISABLED", "DISARMED",
"LEFT", "RIGHT", (in limited-exits/entrances branch only) "EXHAUSTED". Absent or invalid gets treated as an unconditional trigger - ie: it will
always pass.
HIDE and
STATE - These work the same way as in the base $ANIMATION segment.
Processing / displaying animationsThere are three steps in updating and displaying the object, in this order - testing the trigger conditions, updating the frame number, and actually rendering the animation. The first two happen one after another during the game's physics update loop. The third one happens, of course, during object rendering. During each one of these steps, each animation is handled one-by-one.
Special case: The primary animation does not get any trigger tests or frame number updates applied (the frame number is updated by game physics). However, the actual rendering works exactly the same way as any other animation.
Testing trigger conditionsThe trigger condition test starts at the
last trigger in the file, and works backwards. When it finds a trigger where the condition is fulfilled, it sets the animation's current STATE and VISIBLE to whatever is defined in that trigger - and then stops testing. Should it get through all the triggers without any of the conditions being fulfilled (for example, an object with "BUSY" and "DISABLED" conditions, but it's currently idle), the animation's current STATE and VISIBLE are set to whatever is defined in the base $ANIMATION segment.
Here's a reminder on how the conditions work. This is mostly a copy-paste from the above post.
OBJECT TYPE | "READY"
----------------|-----------------------------------
GENERAL RULE | The condition will be true if the object would able to interact with a lemming at this moment
DOM_TRAP | True when the trap is idle (but not disabled)
DOM_TELEPORT | True when the teleporter and its paired receiver (if any) are idle
DOM_RECEIVER | True when the receiver and its paired teleporter (if any) are idle
DOM_PICKUP | True when the skill has not been picked up
DOM_LOCKEXIT | True when the exit is open (not just opening - must be fully open)
DOM_BUTTON | True when the button has not been pressed
DOM_WINDOW | True when the window is open (not just opening - must be fully open)
DOM_TRAPONCE | True when the trap has not yet been triggered (or disabled)
All others | Always true
OBJECT TYPE | "BUSY"
----------------|-----------------------------------
GENERAL RULE | The condition will be true when the object is transitioning between states, or currently in use
DOM_TRAP | True when the trap is mid-kill
DOM_TELEPORT | True when the teleporter, or its paired receiver, are mid-operation
DOM_RECEIVER | True when the receiver, or its paired teleporter, are mid-operation
DOM_LOCKEXIT | True when the exit is in the process of opening
DOM_WINDOW | True when the window is in the process of opening
DOM_TRAPONCE | True when the trap is mid-kill
All others | Always false
OBJECT TYPE | "DISABLED"
----------------|-----------------------------------
GENERAL RULE | The condition will be true when the object is unable to interact with a lemming, either permanently or
| until some external condition is fulfilled.
DOM_TRAP | True if the trap has been disabled (most likely by a disarmer)
DOM_TELEPORT | True if no receiver exists on the level (edge case - might not implement)
DOM_RECEIVER | True if no teleporter exists on the level (edge case- might not implement)
DOM_PICKUP | True if the skill has been picked up
DOM_LOCKEXIT | True while the exit is in a locked state
DOM_BUTTON | True when the button has been pressed
DOM_WINDOW | Always false (? - maybe, "true when no more lemmings are to be released")
DOM_TRAPONCE | True when the trap has been disabled (most likely by a disarmer) or used
All others | Always false
OBJECT TYPE | "DISARMED"
----------------|-----------------------------------
GENERAL RULE | The condition will be true if a Disarmer has deactivated the object. Exists as a separate condition
| from Disabled for the purpose of single-use traps, which may want to differentiate between disarmed
| and used.
DOM_TRAP | True if the trap has been disarmed
DOM_TRAPONCE | True if the trap has been disarmed
All others | Always false
OBJECT TYPE | "LEFT"
----------------|-----------------------------------
GENERAL RULE | True if a direction-sensitive object is currently facing left.
DOM_FLIPPER | True if the splitter will turn the next lemming to the left
DOM_WINDOW | True if the window releases lemmings facing left
All others | Always false
OBJECT TYPE | "RIGHT"
----------------|-----------------------------------
GENERAL RULE | True if a direction-sensitive object is currently facing right.
DOM_FLIPPER | True if the splitter will turn the next lemming to the right
DOM_WINDOW | True if the window releases lemmings facing right
All others | Always false
OBJECT TYPE | "EXHAUSTED" (!!! This only exists in the limited-count-entrances-exits branch !!!)
----------------|-----------------------------------
GENERAL RULE | True if a lemming-limited object is used up.
DOM_EXIT | True if the exit has a capacity, and the capacity is used up
DOM_LOCKEXIT | True if the exit has a capacity, and the capacity is used up
DOM_WINDOW | True if the window has a lemming limit (outside of the total number of lemmings in the level overall),
| and it has released all its lemmings.
All others | Always false
How the animation's frame is updatedFrame update depends on the current STATE (as set by the trigger test):
PLAY - The animation will advance by one frame every update cycle. If it reaches the end of the animation, it loops back to the beginning, and continues from there.
PAUSE - The animation remains on the current frame.
STOP - The animation jumps to frame 0. After this, STATE is changed to "PAUSED".
LOOP_TO_ZERO - This works the same way as "PLAY", except that when the animation reaches frame 0, STATE is changed to "PAUSED".
MATCH_PRIMARY_FRAME - The frame number will be set to whatever the primary animation's current frame number is.
What will happen if the primary animation has more frames than this animatino, is undefined!How the animations are renderedThe order in which animations are rendered, is determined firstly by their Z_INDEX value (lower number = rendered first), and where these are equal, by their order in the file. The primary animation is always treated as coming first in the file (regardless of where it actually is in the order), but its placement in the rendering can still be altered via Z_INDEX.
If an object is resizable,
all animations are resized. However, each animation can have independent nine-slicing values. (Some interesting effects can probably be created here, by having one of the edge sizes, equal to the overall size of the animation graphic - then you can create an animation that only appears on one corner, for example. Though I need to test this, to ensure there's no infinite-loops or divide-by-zero errors that result from trying this...)
An animation will be displayed if either:
a) Its visibility is TRUE
or b) Its visibility is FALSE, and its current state isn't "PAUSE" (remember: its current state will NEVER be "STOP" at this point, due to the actions during frame number updating). Why is it this way? Because of the useful combination of visibility=false and state=loop_to_zero.
I think that covers everything. In terms of what could maybe be changed:
"LEFT", "RIGHT": Maybe these are unnessecary. Let's remove them for now, maybe? Adding them back later would be simple enough.
"DISARMED" vs "DISABLED": As mentioned above, I do feel it's important to have some way to distinguish between "disarmed" and "used up" on a single-use trap, which is why I separated these in the first place. But I'm open to acheiving this a different way - as long as distinguishing remains possible.
This does leave a lot open to designers, but (a) I notice that more and more content designers are on board with the idea of "don't try to be deceptive", than ever. These days, deceptive content mostly seems to arise from the occasional creator outside our community, or as technical demonstrations / April Fools pranks, unlike in the past where some creators went out of their way to be deceptive; and (b) I feel the flexibility this allows for in object graphical design is worth it.
I am open to perhaps having a few presets that can be set as one liners, eg "PRESET TRAP_ANIMATE_UNITL_DISABLED", "PRESET TRAP_ANIMATE_IDLE_ONLY", etc, that will automatically set STATE, HIDE and $TRIGGERs as needed for some of the most common use cases. Then, only people who want to produce less-common effects need to worry about the full intracicies of the system.
Regarding less differentiation for the primary animation and getting rid of Z_INDEX (the former of which I feel is necessary if we're going to do the latter) - yeah, we could make this an $ANIMATION section that includes the "PRIMARY" keyword. The only thing here is we need a rule here - if the primary is instead defined in the main section (and I absolutely want to keep this, for many reasons), does it get treated as being first or last in the animation list? I would lean towards "last" here, for the same reason I defaulted it's Z-index to 1 - although not always, we'll usually want to draw the secondary behind the primary in case of overlap.
And to clarify another point I just spotted:
For hatches, left-facing only occurs when flipping the hatch, which (hopefully) flips the secondary animations as well.
Yes - to confirm, secondary anims get flipped / inverted / rotated along with the main one, and yes, their offsets also get moved accordingly. To the user, it would be indistinguishable from rotating / flipping / inverting a single large composite image (even though internally, each animation is reoriented and positioned separately).