MonoGame (new XNA) Tutorial: MeoMotion Character Animation (Vector Bone and Distortion Animator) Page 3

(page 1, page 2, PAGE 3)

In this final tutorial we'll create an player class which will allow us to add many characters (and many of the same type) of which will run their own animations and be able to face different directions and move at different speeds (their behavior data is independent).
MeoMotorExample

(A)... Right click on the MeoMotor project and click add - then class - then name it MeoPlayer.cs
Now let's add some variables:

class MeoPlayer
{
    public bool PREMULTIPLY_ALPHA = true;
    const float HIDE_ALPHA_UNDER = 0.01f; // minimum alpha (anything lower is clipped from calculations)
    //--------------------------------------------------------------------------------------------------
    public  Vector2 position;         // character's position 
    //public Vector2 veloctiy;        // <-- add later (could use to control play_speed for example)
    public  string  sheet_name;       // name of the original sheet used in the animator (without the .png) [this is just for identification purposes]
    public  int     animation_index;  // index into list of animations (ie: anim[animation_index]) 
    public  float   play_speed;       // animation speed    
    public  bool    stopped, flip, active; 
    public  float   timer;             // for interpolating between keys
    private int     part_count;       
    private int     key1;             // current key
    private int     key2;             // next key        
    private Final[] final;            // holds final animation data
        
    SpriteBatchDistort batch;        
    MeoMotion meo;                    // meo = original instance of MeoMotion manager which loads and contains all the animations

Premultiply Alpha is needed on alpha channel if you set property of png to non-premultplied - but if it is premultiplied you can set premultiply to false. Anything under a certain alpha (1% in this case) is clipped from calculations and drawing. It is possible that objects were set to nearly invisible in original project and user doesn't realize they are doing a bunch of unnecessary calculations and drawing of unused parts.

batch and meo will actually be refering to the original instance of them made in Game1.cs (and are only passed in the constructor)
Since each NPC is a unique instance, we don't need to worry about sharing final - so final will only go from 0 - part_count

// C O N S T R U C T O R 
/// <summary>
/// Creates a new character -- note: character can be changed by changing sheet_name and then setting an animation
/// </summary>
/// <param name="SheetName">used to determine which character on the spritesheet to use (uses the old original sheet name to refer to it)</param>
/// <param name="Position">default position of character</param>
/// <param name="Meo">instance of meo to use</param>
/// <param name="spriteBatchDistort">instance of spriteBatchDistort to use for drawing</param>
public MeoPlayer(string SheetName, Vector2 Position, MeoMotion Meo, SpriteBatchDistort spriteBatchDistort) 
{
    sheet_name = SheetName; 
    position = Position;
    play_speed = 1.0f; part_count = 0; flip = false;
    meo = Meo;        
    final = new Final[(meo.max_parts + 1)]; // create a memory pool big enough to hold any character's render data
    int a = 0; do { final[a] = new Final(); a++; } while (a < (meo.max_parts+1)); // allocate 
    batch = spriteBatchDistort;
}

Here we pass in info about what character it is (SheetName - original project sheet used for the character), some starting position for the character, and the MeoMotion instance and spriteBatchDistort instance to use...
(flip is used to horizontally flip the character in draw)
As you may remember, in Load_TXT, it calculates the maximum potential parts for any character - altho you could probably use SheetName to determine specifically which character it is and subtract end_part from start_part to get the precise number of pieces this NPC would use for final.

(B) We need something to set which animation to play. We'll make one version which can take the animation name and another which simply uses an index passed.

// S E T  A N I M A T I O N 
/// <summary>
/// Set a character animation by name with option to set flip horizontally
/// </summary>        
public void SetAnimation(string AnimationName, bool Flip=false) // note: normally, if change character direction - just change flip = !flip
{
    if (AnimationName == "none") { active = false; return; }    // usually better to just change public var active to false for this
    animation_index = meo.GetIndex(sheet_name, AnimationName);  
    part_count = meo.anim[animation_index].end_part - meo.anim[animation_index].start_part; // make sure we are using the correct part_count
    flip = Flip; active = true; key1 = 0; key2 = 1; timer = 0;
}
public void SetAnimation(int AnimationIndex, bool Flip = false, bool Active=true) // (if we already know the index)
{
    if (!Active) { active = false; return; }            
    part_count = meo.anim[animation_index].end_part - meo.anim[animation_index].start_part; // make sure we are using the correct part_count
    flip = Flip; active = true; key1 = 0; key2 = 1; timer = 0;
}

Here we just use GetIndex in MeoMotion.cs and set part_count to be the difference between end and start. You might even want to have MeoMotion actually store a part count in the animation set to simplify this more. If active as false is passed - it just changes the active status and returns.

(C) Let's make it play or Update (SetAnimation triggers which animation is running and update calculates the animation). If the animation is not active or stopped it should return. Also at the end of the method, it should flip the final vertices of the character if flip is true (note the character is first at origin until drawn at a position).

// U P D A T E 
// Customizable update for character animation. Could have other updates with other behaviors too, or pass in vars to control behavior decisions.. 
//(like point weapon or something)
public void Update(GameTime gameTime) { if ((stopped)||(!active)) return; int a = animation_index; int k1 = key1, k2=key2; int t1 = meo.anim[a].times[k1], t2 = meo.anim[a].times[k2]; //CAPPED 60 FPS TIMELAPSE IN MILLISECONDS (I prefer this way as it simplifies cdcr-vector projections)
timer += 16.6666667f * (play_speed * meo.anim[a].default_speed); //timer += gameTime.ElapsedGameTime.Milliseconds*(play_speed*meo.anim[a].default_speed); // <--- this works too if (timer > t2) // ready to switch keys to interpolate between { key1 = key2; key2++; if (key2 >= meo.anim[a].num_keys) { if (meo.anim[a].looping == false) { key2 = meo.anim[a].num_keys - 1; stopped = true; timer = 0; } // stop the animation else { key1 = 0; key2 = 1; timer = 0; } // loop the animation } k1 = key1; k2 = key2; t1 = meo.anim[a].times[k1]; t2 = meo.anim[a].times[k2]; } if (timer < t1) Console.WriteLine("Error: somehow timer is less than t1 - check code for bugs"); float time_dif = t2 - t1; // total time between both keys if (time_dif <= 0) time_dif = 0.0001f; // prevent unlikely possibility of division by zero float percent = (timer - t1) / time_dif; // what is the percentage (0-1) [for interpolation] Vector2 pos, scale, o1, o2, o3, o4; float rot; float x1, x2, x3, x4; float y1, y2, y3, y4; int i = 0, p; do { final[i].order = meo.anim[a].keys[i, k1].order; if (meo.anim[a].keys[i, k1].active == false) { final[i].hide = true; i++; continue; } // (part not shown - skip - go to do) final[i].hide = false; final[i].part = meo.anim[a].keys[i, k1].part; p = final[i].part; // interpolate the data between the 2 keyframes final[i].alpha = MathHelper.Lerp(meo.anim[a].keys[i, k1].alpha, meo.anim[a].keys[i, k2].alpha, percent); // blend alpha transparency if (final[i].alpha < HIDE_ALPHA_UNDER) { final[i].hide = true; i++; continue; } if (final[i].alpha > 1.0f) final[i].alpha = 1.0f; // precaution pos = Vector2.Lerp(meo.anim[a].keys[i, k1].pos, meo.anim[a].keys[i, k2].pos, percent); // blend position rot = MathHelper.Lerp(meo.anim[a].keys[i, k1].rot, meo.anim[a].keys[i, k2].rot, percent); // blend rotation scale = Vector2.Lerp(meo.anim[a].keys[i, k1].scale, meo.anim[a].keys[i, k2].scale, percent); // blend scale o1 = Vector2.Lerp(meo.anim[a].keys[i, k1].o1, meo.anim[a].keys[i, k2].o1, percent); // blend distortion offsets o2 = Vector2.Lerp(meo.anim[a].keys[i, k1].o2, meo.anim[a].keys[i, k2].o2, percent); o3 = Vector2.Lerp(meo.anim[a].keys[i, k1].o3, meo.anim[a].keys[i, k2].o3, percent); o4 = Vector2.Lerp(meo.anim[a].keys[i, k1].o4, meo.anim[a].keys[i, k2].o4, percent); // calculate the transformed vertices from the above data x1 = meo.parts[p].m1.X * scale.X; y1 = meo.parts[p].m1.Y * scale.Y; // scale part points at origin(0,0) x2 = meo.parts[p].m2.X * scale.X; y2 = meo.parts[p].m2.Y * scale.Y; x3 = meo.parts[p].m3.X * scale.X; y3 = meo.parts[p].m3.Y * scale.Y; x4 = meo.parts[p].m4.X * scale.X; y4 = meo.parts[p].m4.Y * scale.Y; pos += meo.anim[a].default_offset; // adjust postions by default offset property // (HERE YOU COULD ALSO ALTER LIMB ROTATIONS PROGRAMMABLY TO MATCH MOTION OF CHARACTER AS CONTROLLED BY PLAYER
// - like trailing hair or tail - or vector pointing of a weapon)
// IE: Use: meo.parts[p].name to match part and add custom behavior
// - if part has children you will need to add parent end-position to child (and add additional rotation of parent)
if (rot != 0f) { float cos = (float)Math.Cos(rot), sin = (float)Math.Sin(rot); // rotate points around origin and then add the position where they belong final[i].v1.X = pos.X + x1 * cos - y1 * sin; final[i].v1.Y = pos.Y + x1 * sin + y1 * cos; final[i].v2.X = pos.X + x2 * cos - y2 * sin; final[i].v2.Y = pos.Y + x2 * sin + y2 * cos; final[i].v3.X = pos.X + x3 * cos - y3 * sin; final[i].v3.Y = pos.Y + x3 * sin + y3 * cos; final[i].v4.X = pos.X + x4 * cos - y4 * sin; final[i].v4.Y = pos.Y + x4 * sin + y4 * cos; } else { final[i].v1.X = pos.X + x1; final[i].v1.Y = pos.Y + y1; // no rotation, so just put the points in the correct position final[i].v2.X = pos.X + x2; final[i].v2.Y = pos.Y + y2; final[i].v3.X = pos.X + x3; final[i].v3.Y = pos.Y + y3; final[i].v4.X = pos.X + x4; final[i].v4.Y = pos.Y + y4; } final[i].v1 += o1; // add the distortion offsets of the points final[i].v2 += o2; final[i].v3 += o3; final[i].v4 += o4; if (flip) // flip horizontally: { final[i].v1.X = -final[i].v1.X; final[i].v2.X = -final[i].v2.X; final[i].v3.X = -final[i].v3.X; final[i].v4.X = -final[i].v4.X; } i++; } while (i < part_count); }

1__ Gets the keys and times for those keys
2__ Figures out how far in the timeline the animation is based on milliseconds since last frame [ I made it fixed time-step because I prefer this for many reasons I don't want to try to defend here (^-^) - but if you need to sync online or something, by all means, in that case use gameTime - but you will also need to remember to apply that to movements and collision projections ]
3__ When timer has passed t2, it's time to get the next set of keys(and times) to blend between
4__ In loop we record the drawing order of a part, which part is actually shown(because some parts can be swapped during animation), blend the alpha based on percent between key times, and skip and hide anything nearly invisible (to optimize).
5__ We blend the offset position (from player origin), rotation (around pivot), and scale - oh - and vertex offsets...
6__ Model points, M1-M4 are: points of rect around origin positioned so pivot is at 0,0 --- these are scaled and then rotated around 0,0 and then the pos (animated part offset position) is added.
7__ Finally offset positions are added to distort the vertices and horizontal flipping is done if need be.

NOTE: Here it is possible to add extra custom behavior like a switch or delegate to modify how certain parts might rotate in response to character movement - ie: add an extra param to signal that a part of a certain name (ie: "hair") should trail the character. This could also be done with an outside character specific update which modifies Final differently for reponsive rotations (like pointing a weapon) -- note that if doing so, you may need to generate a child list(using parent to determine) for each part - then rotate the parent and add the position offset of the end-point to the child (plus add the additional rotation to the child rotation). Another way would be to have seperate animations for "cape" and "hair" which select animations to blend between based on motion, and attach that animation piece to the rest of the character which animates using a different animation(index).
We're not going to do this here but this is the psuedocode for setting up the center point of rotation taking flip into consideration:
if (!flip) {
pos.x += meo->parts[p].m1.x*scale.x; pos.y += meo->parts[p].m1.y*scale.y; // change position of part relative to origin offset (so rotation works right)
final[i].scale = scale;
pos += meo->anim[a].default_offset; // adjust position of parts by animation's default offset property
final[i].pos = pos; final[i].scale = scale; final[i].rot = rot;
final[i].center.x = meo->parts[p].pivot.x*scale.x; final[i].center.y = meo->parts[p].pivot.y*scale.y; // scale pivot offset too
} else {
pos.x = -pos.x; // flip the position offset
pos.x += -meo->parts[p].m2.x*scale.x;
pos.y += meo->parts[p].m1.y*scale.y; // change position of part relative to origin offset (so rotation works right)
final[i].scale = scale;
pos += meo->anim[a].default_offset; // adjust position of parts by animation's default offset property
final[i].pos = pos; final[i].scale = scale; final[i].rot = -rot;
final[i].center.x = meo->parts[p].m2.x*scale.x; final[i].center.y = meo->parts[p].pivot.y*scale.y;
}

(D) So drawing the character now is pretty simply - we just use the data we calculated above. It's possible to later pass effects to the draw giving each unique NPC/character their own color or effect or draw style...

// D R A W
// Customizable draw for character. Could make other draw overloads or pass in vars to control drawing behavior for specific characters. 
public void Draw(Color color)
{
    if (!active) return; 
    int i = 0, n = 0, p;
    Color col; 
    
    n = 0;
    do
    {
        i = final[n].order; 
        if (final[i].hide) { n++; continue; }       
        p = final[i].part;                          // may switch parts during animation
        color.A = (byte)(final[i].alpha * 255.0f);  // alpha transparency from 0-255
        if (PREMULTIPLY_ALPHA) col = Color.FromNonPremultiplied(color.ToVector4()); // (make sure the properties of the image in content match)
        else col = color;     
		  // draw transformed sprite parts at character's position
batch.DrawTransformedVertices(meo.parts[p].rect, position, final[i].v1, final[i].v2, final[i].v3, final[i].v4, col); n++; } while (n < part_count); }

The correct part to draw in this layer is extracted, and if visible, we calculate the correct alpha adjusted color and send the pre-transformed coordinates to render the animated distorted part.

Also we add this helper method so it's a bit easier to lookup this character's animation index if we want it for customizing character properties more easily:

// GET INDEX
public int GetIndex(string AnimationName)
{
    if (AnimationName == "none") return 0;
    return meo.GetIndex(sheet_name, AnimationName);
}

(E) So that's it for MeoPlayer.cs. Let's make some changes to Game1.cs to test it out...

You can use arrows to move characters horizontally (they will flip automatically) and use Q or W to alter their play speed (ie: 1.0f = normal, 0.5f=half, 2.0f=double) It may be desirable to add a start_flip property to characters and each should have their own.

You'll notice too that it loops through characters and runs their updates independently (as they could potentially all be doing something completely different).

So that's it! Hopefully that was helpful! (^-^)
Note that this is still in early development, however all the tests I've run so far are working.

Here's an example project which contains all of what you learned here (plus water shaders added):

Newest CS (MonoGame) MeoMotion runtime support has a few additional features and can be found >> HERE <<
The following source codes can easily be updated to use these additional features by comparing source code.

Download: MeoMono MonoGame (project was made in VS 2010 - will work in others) // I think I was using shader 5
Download: MeoMotor
XNA 4.0 + ( [basically identical] in VS 2010 - will work in others) // added higher shader version support

Download: MeoDX11 DirectX 11 + (project made in VS 2013 - for 2010 - may need to start a project and add all the files - Note that this also uses DirectXTK - using Shader 5 )

Download: SharpMeo SharpDX (should be similar for SlimDX) - this might be adaptable to Paradox game engine also
[ project made in VS 2013 - using SharpDX toolkit 2.6.3 build ]

SDL2 (pure sdl) animator - Note: fully correct vertex distortions require setting GPU_ONLY to false which is great for cutscenes but for very intense game situations it is best to set characters to GPU_ONLY as true):
Download: MeoSDL SDL2 (project was made in VS 2013 [should work in others] using only SDL )

Download: MeoOpenGL OpenGL version (with SDL2 for window and a few others) [project made in VS 2013 - shader #version 330 ]

MeoMotion Animation Editor

I may add more runtime support and features if there is enough support for this project.

>>> Back to Game Design <<<