1
0
Fork 0
mirror of https://github.com/AquariaOSE/Aquaria.git synced 2024-11-29 03:33:48 +00:00

some words about the anim editor, WIP

False.Genesis 2023-10-06 03:39:06 +02:00
parent fc6577a371
commit 615e70c4d7
13 changed files with 353 additions and 2 deletions

@ -1,9 +1,15 @@
The animation editor can be accessed from the main editor menu (when within a mod, press _Tab_ to open the editor), then click on the text at the top. The menu entry is near the bottom.
_Warning: Save your map before you enter the animation editor!_ The particle viewer unloads the current map and you will lose changes if you don't save!
_Warning: Save your map before you enter the animation editor!_ Entering the animation editor unloads the current map and you will lose changes if you don't save!
_Compatibility note_: This article describes the OSE version. Older versions have a severely crippled animation editor that is a pain to use. Don't!
# Getting started with animating
See [animation tutorial](animation-tutorial).
# User interface
The box in the middle displays the currently loaded animation. Only one animation can be loaded at a time.
@ -39,7 +45,7 @@ Most of the buttons have a hotkey assigned but some don't.
* _Warning_: This applies the skin on top of the currently loaded skeleton. This _does_ change textures recorded in the animation file if you save afterwards.
* ___M___ - Selection mode
* The first mode selects the bone that is closest to the mouse (red highlight).
* The second mode selects bones with the keyboard. Press arrow up or down to cycle through bones (blue highlight).
* The second mode selects bones with the keyboard. Press arrow up or down to cycle through bones (blue highlight). There is probably no reason to use this mode, ever.
* ___B___ - Toggle bounding boxes and center points
* ___E___ - Toggle between [[normal edit mode|#normal-mode]] and [[strip edit mode|#strip-edit]].
* Normal mode = grey background
@ -76,6 +82,237 @@ These apply to whichever bone is currently selected. Each function can be combin
# Workflow
TODO
## Normal mode
TODO
## Strip edit mode
TODO
## Grid edit mode (2023 update)
TODO
# Anatomy of an Animation (XML) file
Example:
```xml
<AnimationLayers>
<AnimationLayer />
</AnimationLayers>
<Bones>
<Bone idx="1" gfx="singbulb/bulb" pidx="-1" name="bulb" fh="0" fv="0" gc="1" cr="0" cp="0 0" />
<Bone idx="0" gfx="singbulb/base-0001" pidx="-1" name="base" fh="0" fv="0" gc="1" cr="0" cp="0 0" />
<Bone idx="2" gfx="Particles/glow" pidx="1" name="glow" fh="0" fv="0" gc="0" cr="0" cp="0 0" sz="6 6" rb="0"/>
</Bones>
<Animations>
<Animation name="idle">
<Key e="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 -1 -48 0 0 " />
</Animation>
</Animations>
```
## Animation layers
An animation is made of multiple simultaneously playing _layers_ that can _include_ or _ignore_ certain bones,
eg. you can play one animation that affects the upper body while playing another animation that affects the legs.
At least one animation layer must exist. In most cases you will not need special behavior so you can leave it at the default:
```xml
<AnimationLayers>
<AnimationLayer />
</AnimationLayers>
```
When nothing is specified, an animation layer affects all bones.
Compare this to the animation layers in _naija.xml_:
```xml
<AnimationLayers>
<AnimationLayer />
<AnimationLayer ignore="6 7 8 9 12 13 " />
<AnimationLayer ignore="0 2 3 4 5 6 7 8 9 10 11 12 13 " />
<AnimationLayer name="flourish" />
<AnimationLayer />
<AnimationLayer ignore="0 1 2 4 5 6 7 8 9 10 12 13 " />
<AnimationLayer ignore="6 7 8 9 12 13 " />
<AnimationLayer ignore="0 2 3 4 5 6 7 8 9 10 12 13 15 16 " name="HeadOverride" />
</AnimationLayers>
```
Each anim layer is indexed starting from 0. Higher (ie. further down) animation layers override lower anim layers, ie. the topmost anim layer with an active playing animation wins.
An animation layer that does not actively play an animation is ignored.
This is evaluated for each bone separately.
Note that the animation editor always plays its animation on layer 0, therefore you should always leave it at the default `<AnimationLayer />` and not exclude any bones.
NB: The <b>ANIMLAYER_*</b> constants as defined by [ScriptInterface.cpp](https://github.com/AquariaOSE/Aquaria/blob/master/Aquaria/ScriptInterface.cpp)
refer directly to the animation layers as defined by naija.xml.
It's recommended not to change the anim layers in naija.xml because the game expects them to exist in this order.
To modify the anim layers: Use a text editor! The animation editor does not touch them.
## Bones
The `<Bones>` section has all the bones that take part in an animation.
This section can also not be modified with the animation editor and you'll need a text editor to modify bones.
### Bone attributes (and their defaults)
- __idx__ - Numeric index of the bone. Used by Lua function *entity_getBoneByIdx()*. Must be unique. Does not need to be ordered, and you can leave holes in your numbering, but don't use negative indices.
- __gfx__ ("") - The texture name to use (without file extension, as usual).
- __name__ ("") - Sometimes it's useful to reference bones by name. Leave empty if not needed.
It's certinly easier to name a bone "Head" than to remember the ID for each different skeleton that has a head. Used by Lua function *entity_getBoneByName()*.
- __pidx__ - _idx_ of parent Bone. Set this to attach this bone to a parent, so that when the parent moves, this bone moves with it.
Think of it like your arm (attached to the body), and when you move it around the lower arm follows, and the hand follows the lower arm, etc.
(The skeleton is actually a [Scene graph](https://en.wikipedia.org/wiki/scene_graph)).<br />
Use `parent="-1"` to denote a root bone (that is not attached to a parent). You need at least one root bone in each animation.<br />
The parent bone obviously needs to exist or bad things may happen.
- __fh__ (0) - Set to 1 to flip bone horizontally. It is recommended not to use this and make a flipped version of the texture instead.
- __fv__ (0) - Set to 1 to flip bone vertically. Definitely avoid using this because it doesn't do what you think it should do.
- __gc__ (0) - Set to 1 to generate a collision mask for this bone. Use for bones that should partake in skeletal collision.
<br />(See also: `entity_handleShotCollisionsSkeletal` and the `entity_collideSkeletal*()` family of Lua functions).
- __cr__ (0) - Collide radius. Can be used instead of `gc=` if a simple circle collision is enough.
- __sz__ ("1 1") - Scale factor; __TWO__ values. Affects children. Use the same value for both X and Y direction for a uniform scale.
- __rq__ (1) - Render quad. Set to 0 to not render the bone by default. Can be changed later with `bone_setVisible()`.
- __rbp__ (0) - Render before parent. Normally a bone is drawn after its parent (ie. on top of it). This flag inverts this so the bone is drawn underneath the parent.
- __sel__ (1) - Selectable. Set to 0 to make this bone non-selectable to make sure you don't accidentally grab and move it.
- __pass__ (0) - Layer pass. Higher numbers get drawn more in front regardless of the parent/child hierarchy.
This is an advanced setting, make sure you understand the [draw order](#skeleton-draw-order) first!
- __prt__ ("") - Attaches particle effects to the bone. The particle effects are initially disabled but can be controlled with a bone command.
The string is a list of (slot, name) pairs, ie. `"0 explode 1 fire"` will allocate two slots, IDs 0 and 1 with a particle effect each.
- __strip__ - Enable bone `strip` mode. Two numbers; the first is 1 if vertical, 0 if horizontal; the second is the number of segments. Incompatible with __grid__.
- __grid__ - Enable bone `grid` mode. Two numbers, the size of the grid points in X and Y directions. Each must be >= 2. Incompatible with __strip__.
- __io__ ("0 0") - Two numbers. Sets internal offset of bone (ie. shift away from the center without changing the rotation point).
Similar to __offx__ / __offy__, but does not affect children.
Less commonly used attributes:
- __c__ (1) - Enable collision. Can be set to 0 to explicitly disable collision even if `cr` or `gc` are set.
- __offx__, __offy__ (0) - Offset the bone from its center. This offset affects child bones.
- __rt__ (0) - Repeat texture. Set to 1 to enable repeating texture mode. Can use `quad_setRepeatTexture()` instead.
## Animation section
Each animation has an entry here, of which each is a list of keyframes.
The `<Key e=...` attribute stores the rotation, position and whatever values for all bones in the animation.
You can use `<Key cmd="..."` to issue additional _bone commands_ when that keyframe is reached.
### Bone commands
All bone commands are permanent and once used, change the skeleton for all subsequently played animations for a single entity. Use them carefully and only if really necessary.
Each bone command starts with the bone index it refers to (as set via `<Bone idx=`), followed by a string starting with _"AC"_ that specifies the action.
Additional parameters may follow; depends on the command.
- __bone AC_PRT_LOAD file__ - Change a particle effect slot to a different particle effect. Slot must have been allocated first.
- __bone AC_SND_PLAY file__ - Plays a sound effect. It's not attached to a bone or anything, so the bone ID is useless here.
- __bone AC_FRM_SHOW frameID__ - Change bone texture. Uses the texture of the frame with that ID. Need to have bone frames specified, otherwise this has no effect.
- __bone AC_PRT_START slot__ - Start particle effect in that slot ID.
- __bone AC_PRT_STOP slot__ - Stop particle effect in that slot ID.
- __bone AC_SET_PASS pass__ - Change layer pass of bone.
- __bone AC_RESET_PASS__ - Reset layer pass to the original `<Bone pass=` value
Unknown commands are ignored.
When you use bone commands, make VERY sure they follow the format (__bone cmd [params...]__), because if one thing is read incorrectly it will cause the parser to trip up,
populate the wrong fields, not recognize anything, and subsequently ignore everything.
Note that instead of a single long string:
```xml
<Key cmd="1 AC_FRM_SHOW 3 27 AC_FRM_SHOW 1 7 AC_SET_PASS 1 12 AC_SET_PASS 1 8 AC_SET_PASS 2 9 AC_SET_PASS 2" e="..." />
```
You can break it into multiple lines:
```xml
<Key cmd="
1 AC_FRM_SHOW 3
27 AC_FRM_SHOW 1
7 AC_SET_PASS 1
12 AC_SET_PASS 1
8 AC_SET_PASS 2
9 AC_SET_PASS 2
" e="..." />
```
The parser doesn't care and it's much easier to see the individual commands that way.
## Skeleton draw order
Note that drawing a bone first means it may get overdrawn by other bones drawn after it.
So to have bones appear on top of everything, you want them drawn as late as possible.
The following rules apply, in this order, recursively:
- For each _layer pass_:
- Scan the bone list top to bottom
- For each _root_ bone (ie. those with `pidx="-1"`):
- drawBone(_root_)
And _drawBone(**b**)_ does this:
- Scan the bone list top to bottom:
- If a bone has _**b**_ as parent and `rbp="1"`:
- drawBone(that bone)
- If _**b**_ has `pass=` equal to current _layer pass_:
- Draw _**b**_ to the screen
- Scan the bone list top to bottom:
- If a bone has _**b**_ as parent and `rbp="0"`:
- drawBone(that bone)
If you want your skeleton to be drawn precisely in a specific way you need to take care of the correct bone ordering.
The layer pass depends on the layer that the entity is on. For most layers there is only a single pass with id 0,
but by default LR_ENTITIES goes from passes -2 to 5, inclusive.
You can use the Lua function `setLayerRenderPass()` to change a layer's begin and end pass but that comes at a cost of overall render performance.
Here are some rules of thumb that make the above easier to remember:
- Bones with a greater _layer pass_ are drawn on top of bones with a lower _layer pass_
- Bones towards the end of the list are drawn later.
- Bones with `rbp="1"` are drawn earlier
# Common problems, Q&A
* _Help, I've set `<Bone sz=` and now it's gone_
- Make sure you pass in TWO values, one for X and one for Y directions: `<Bone sz="1.5 1.5" .../>`
- If it's only one value, the other is interpreted as zero, so it's squished infinitely thin and you can't see it.
* _Help, I'm moving one bone around and another unrelated bone follows whenever I release the mouse button?!_<br />
This can happen if two (or more) bones accidentally share the same ID. Check that bone IDs are unique.
To fix this, pick the bone that is less annoying to animate, change its ID to something unique in a text editor, then reload the animation.
You'll need to fix all animations but the _Shift_ and _Shift+Ctrl_ modifiers should help to get this done quickly.
* _Help, i modified the anim XML by hand and now it doesn't load anymore!_<br />
Your edits probably added an error, making the XML invalid. Check that:
- Each `<Tag>` is properly closed with `</Tag>`
- Each single-line tag is closed at the end (`<Bone attributes=... />` - note the trailing __/__)
- Attributes appear only once in a tag (`<Bone idx="0" rq="1" gc="0" rq="0">` is invalid, since `rq` appears twice)
- Attributes must be quoted, `idx=0` is invalid, should be `idx="0"`.
* _Help, i modified the anim XML by hand and now the game crashes when trying to load it?!_
- Check that all bones referenced with `pidx="..."` exist
- `pidx="-1"` for no parent is fine too
- Check that you didn't set any bone's `idx=` and `pidx=` to the same value
- Check that you didn't create a cycle - ie. if you have two bones like this: `<Bone idx="0" pidx="1" ... />` and `<Bone idx="1" pidx="0" ... />`
they try to set one another as parent and that will crash. Always make sure that following all `pidx` links will eventually lead to a bone with `pidx="-1"`.
* _I've set a bone's `pass=` to something other than 0, in the editor it's fine but in-game that bone doesn't show up?_
- Make sure the entity is spawned on LR_ENTITIES. This is the default if you don't move it in the entity script.
- If you really want the entity on another layer, make sure that that layer has the correct pass limits set -- use `setLayerRenderPass()`.
- NB: The animation editor spawns its animation puppets on LR_ENTITIES. If you use non-standard layer pass limits then you'll want to make sure that the pass limits of
LR_ENTITIES are set to cover the full range of render passes any of your animations use, otherwise you won't see those bones that exceed the pass limits.
* _I've set up bones to render across different layer passes and it's working in the editor, but in-game the passes are completely ignored?_
- Make sure that the entity script doesn't call `entity_setRenderPass()` -- if the _entity_ has a render pass set, all bones will use the render pass of the entity they belong to and ignore their own.
This is special-cased in the engine for backwards compatibility reasons, and there's hardly any reason to set a render pass for an entire entity at once. Use bone passes and never set entity passes.

114
animation-tutorial.md Normal file

@ -0,0 +1,114 @@
# Animation tutorial
TODO
# Best practices
## Save often!
This applies to the rest of the editor, too. It's easy to mess up, undo is not always reliable, and in some cases loading an old version is the only sane way to fix a mess.
## Proper centering
If you plan to make an animation file that has more than a small handful of animations,
it's highly recommended you design your graphics in such a way that the rotation points are centered.
It may seem like a small issue but if you don't do this then you will quickly rack up technical debt that will be a real headache to fix the later you decide to fix it.
Therefore try to make ALL your graphics with proper rotation points to avoid the issue altogether.
They didn't do this in the base game so most animations actually exhibit this issue.
To understand the problem, here's an illustration. These are 3 tiles used in the "mia" animation:
![](images/leg1-original.png)
![](images/leg2-original.png)
![](images/arm1-original.png)
The images have a grey box around them for clarity, and the image centerpoint is marked with a pink dot. When rotated, the image rotates around this point.
![](images/mia-original.png)
![](images/mia-original-rot.png)
On the left, the animation is in its original state. When we try to rotate a couple bones, the joints detach because the rotation is around the center, not where we'd expect the joint to be.
This animation frame can be fixed by also adjusting the position of the rotated bones so that the joints again line up properly,
but this is prone to look bad when the animation is playing. The only solution is to add more animation keyframes but this is a lot of work.
The lower leg clearly needs to be rendered before the parent (so that the parent is drawn over it; `rbp="1"` in the XML file), otherwise the blackness around the "knee" would show up and look weird.
Also, there is only one arm tile, so the other arm needs to have `fh="1"` set, which may cause issues.
------
The solution is to pad the graphics with transparency so that the center point is also the point where the joint logically rotates around.
To avoid `fh="1"`, we introduce a second, flipped version of the arm sprite.
![](images/mia-frontleg1-skin.png)
![](images/mia-leg2-centered.png)
![](images/mia-backarm1-skin.png)
![](images/mia-frontarm1-skin.png)
The lower leg has also been changed to look better, and render with `rbp="0"` instead. Naija's legs are very well done to mask the knee transition, use that as a guide in case you design legs.
With this change, the rotation around the joints looks more natural, and no position adjustment is needed:
![](images/mia-better.png)
![](images/mia-better-rot.png)
For a good coomparison, check naija.xml (good) vs. mia.xml or li.xml (bad).
Also note that the head is an exception and doesn't need to be centered on the neck. Just use naija.xml as a reference and it'll be fine.
### Fixing this later
In case you decided to ignore this issue until the point where it can't be ignored anymore, or want to change/extend some existing (bad) animations and properly center them,
there's a way to do this:
First, modify the graphics of all offending bones so that they are properly centered. Load the animation; it will look messed up.
To fix it, pick a pose to use as a reference (usually "idle" is fine), then move all bones back into a good-looking position while holding _Ctrl+Shift_.
Afterwards, go through all animations and all keyframes and make sure they look good. __DON'T hold _Ctrl+Shift_ anymore at this point!!__
Most should be OK and not require touch-ups, but some will still need manual fixing.
Good luck and don't forget to save often!
## Layer passes are global
Try to not use layer passes (ie. anything that is not `pass="0"`) if you can avoid it. If you have some bones rendered in the wrong order,
try to fix it with re-arrangement or changing `rbp=` of some bones if possible.
If you really do need to set a layer pass that is not 0, keep this in mind:
The layer pass applies to ALL entities in that layer, ie. when the layer is drawn, the passes are drawn in order.
That means first the game goes over all entities and all their bones and draws anything that is to be drawn in that pass. Then it repeats that for each pass.
Therefore, don't think about an entity in isolation, think about how it might look when it goes around and mingles with other entities.
If you need eg. the front arm to be in front and its bones have a high pass, it will look fine in isolation.
But when the entity goes behind another entity, then the arm will still be rendered in front because the game draws all passes separately and the arm goes on a higher pass than anything else.
What you'll end up with is a detached looking arm floating in front while the entity it's attached to is covered by something else.
TL;DR Layer passes work best when used in cutscenes and stationary animations, but avoid using this for bone ordering for regular gameplay.
Fun fact: Layer passes were introduced for _The Hug_ (ie. Naija and Li hugging when you leave them alone for a few seconds):
- Naija is always drawn in front of Li.
- To make sure his arm goes over her body when hugging, it's set to a higher pass value while the hug is active, and set back to 0 when the hug ends.
- To see this in action, enter hugging state, then hold _Shift+F_ in developer mode to slow down time, then right-click the pair to break the hug.
The arm immediately goes behind her body because the layer pass is reset.
### Modify layer passes for single animations
If you need layer passes for an animation, cutscene, or other specific things, you can change a bone's pass on the fly, even mid-animation.
Use the __AC_SET_PASS__ and __AC_RESET_PASS__ bone commands for this. If you just need to change the pass for a single animation, put all __AC_SET_PASS__ on the first keyframe.
To make sure the pass settings don't stick and affect other animations played afterwards, add the `resetPassOnEnd` tag to each animation that uses these bone commands:
```xml
<Animation name="special-hugs" resetPassOnEnd="1">
...
</Animation>
```
This way, you don't have to take care of __AC_RESET_PASS__ or extra scripting to reset the pass back to its original value.

BIN
images/arm1-original.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
images/leg1-original.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
images/leg2-original.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

BIN
images/mia-better-rot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
images/mia-better.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
images/mia-original-rot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
images/mia-original.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB