[TUTORIAL] MAD Animations

There are several types of ship animations in Homeworld Remastered. The first type is a stock animation such as a muzzle flash or the ion particles that gather when an ion cannon is about to fire. These animations are called using scripting. The second type of animations are called “MAD” and they are the animations that happen when part of a ship moves, for instance the Mothership door opening when a Destroyer is built. This tutorial explains how to generate “MAD” animations using 3DS Max and HODOR. This tutorial builds on my previous tutorial getting a simple ship in game.

Tools used in this tutorial:

  1. 3ds Max 2014 (some other versions work, Blender is also a possibility)
  2. HODOR
  3. Notepad++ (or another text editor)
6 Likes

+++ STEP 1 - EXAMINING THE EXAMPLE SHIP’S ANIMATION CONTAINERS +++

To start with, we will have a look at the animations in the example Taiidan Interceptor. Import the example ship (http://steamcommunity.com/sharedfiles/filedetails/?id=409273414) DAE file (Tai_Interceptor.dae) into 3DS Max and open up scene explorer. The first thing to note is the “HOLD_ANIM” dummy. This dummy is a container for animation information. It has four child dummies, which all define animations (note: these dummies have no physical or positional significance, but they always seem to be located at the origin).

The four animation dummies have names that define the animations. Note that these dummies themselves are not animated. Their only purpose is to define animations via their names. The naming conventions is:

ANIM: name
ST:   start frame in seconds
EN:   end frame in seconds
LS:   start of looping in seconds (beyond ST) - If this parameter is omitted, it will default to zero
LE:   end of looping in seconds (before EN) - If this parameter is omitted, it will default to zero

If “F” is appended (e.g. STF) the value is given in frames, rather than in seconds. LS(F) and LE(F) are relative to the ST(F) and EN(F) times, so they will be zero most of the time.

For the Taiidan Interceptor we have four animations:

  1. Animation “Wings_Close” starts at 0 seconds, ends at 1.1 seconds, no looping offsets
  2. Animation “Wings_Close_DMG” (DMG = damaged) starts at 2.2 seconds, ends at 3.83333 seconds, no looping offsets
  3. Animation “Wings_Open” starts at 1.13333 seconds, ends at 2.16667 seconds, no looping offsets
  4. Animation “Wings_Open_DMG” (DMG = damaged) starts at 3.86667 seconds, ends at 5.33333 seconds, no looping offsets
1 Like

+++ STEP 2 - EXAMINING THE EXAMPLE SHIP’S JOINT ANIMATIONS +++

To look at the animations themselves, move the slider on the bottom bar along by clicking and dragging it. You will see the “wings” of the Interceptor open and close. Note that the key frame numbers on the bar correspond to the points specified in the animation container dummies. For example, the “Wings_Open” animation begins at frame 34, which is 1.13333 * 30 (@ 30 frames per second). To view the keyframes themselves, click on one of the wing joints. You will see a number of boxes appear on the animation bar - these are the animation key frames (key points in the animation, or the path that the moving parts are following). Note that there are no key frames defined for the mesh of the wings, only the joints. The meshes are children of the joints and their pivots are in the same location and orientation as the joints. This is important.

image

1 Like

+++ STEP 3 - EXAMINING THE EXAMPLE SHIP’S SCRIPTING +++

The MAD animations are not defined solely by the data in the HOD file. There is a script file called the “.madstate” file. Here is part of tai_interceptor.madstate:

TAI_INTERCEPTOR_Launched_OnSet = function(ship)
    startTime = 0
    if(isAnimRunning(ship, "Wings_Close") ~= 0) then
        startTime = getAnimLength(ship, "Wings_Close") - getTime(ship, "Wings_Close")
        stopAnim(ship, "Wings_Close")
        endEffect(ship, "Wings_Close")
    end
    startAnim(ship, "Wings_Open")
    startEffect(ship, "Wings_Open")
    setTime(ship,"Wings_Open",startTime)
    setPauseTime(ship, "Wings_Open", 1000)
end

The script simply calls the animation “Wings_Open” (this name must match the animation in the containers). The if loop prevents the animation “Wings_Open” from playing until the animation “Wings_Close” has finished. Launched_OnSet is called when the ship launches. The following options are available (from http://hw2wiki.net/wiki.hw2.info/ScopeMadstate.html):

States:
Normal Called when a ship is created.
Open Called when a ship is in its open or “deployed” state.
Closed Called when ships are no longer open or “deployed”.
CodeRed Called when a ship tries to fire its weapons.
CodeGreen Called when a ship is finished firing its weapons (there’s a delay before this is called)
ResourceStart The ship is about to start resourcing, this is called when it starts to get in position.
ResourceDo The ship is in latch position and harvesting.
ResourceEnd The ship has launched from the resource and is about to head back.
RepairStart The ship is about to get in position to repair something.
RepairDo The ship is in position and is starting to repair.
RepairEnd The ship has finished repairs.
DockPathOpen The animation linked dock path (set in the hod file) has been booked. The docking / launching ship will wait for this state to be set.
DockPathClosed The animation linked dock path is now free.
Launched The ship is fully launched.
Docked The ship is on the final approach for docking.
DefenseFieldActivate The defense field is trying to activate, the game logic will not start until this state is set.
DefenseFieldDeActivate The defense field is no longer active.
CloakFieldActivate Cloaking is trying to activate.
CloakFieldDeactivate Cloaking has stopped.
HyperspaceGateActivate Hyperspace gate is in position and trying to link with its pair.
HyperspaceGateDeActivate Hyperspace gate has delinked.
DoingFlightManeuver The ship is performing some kind of flight maneuver.
CaptureActive The ship has started to capture the target.
CaptureInActive The ship is not capturing anything.
NIS00 Called by the NIS (or custom code - can be useful for non-standard animations, i.e. anything not covered above).
NIS01 Called by the NIS (or custom code - can be useful for non-standard animations, i.e. anything not covered above).
NIS02 Called by the NIS (or custom code - can be useful for non-standard animations, i.e. anything not covered above).

Events (Information taken from HW2_Madstate.pdf):
OnSet Called when the specific state is set by the game code.
OnPause Called when the animation associated with the state pauses.
OnEnd Called when the animation associated with the state ends.

2 Likes

+++ STEP 4 - DEFINING AN ANIMATION +++

To define your own animation using 3DS Max, first set up your ship as described above, with the main part of the ship as usual and the moving parts as separate meshes. The moving parts must have joints defined, with the pivot of each mesh/joint pair in the same location and orientation. I recommend testing your ship in game before starting any animation, to make sure everything is in the right place. It can be a real pain to correct joint locations or rotations after you have started animating… Here are the pivots and hierarchy for the Taiidan Republic Interceptor, whose wings will be animated:

Secondly, the joints must be animated. To do this:

  1. Click on the key in the bottom right
  2. Move the slider to a new key frame
  3. Move/rotate the joint to its new position
  4. Click on the key again

Key frames can moved around by clicking and dragging.

Next, the HOLD_ANIM and animation containers are required. These are defined as explained above:

1 Like

+++ STEP 5 - DEFINING A MADSTATE FILE +++

When you export your DAE file and run HODOR, a HOD file will be generated as usual. An addition file called a .MAD file will also be generated. To get the game to recognise and play your animations, you will also need a .madstate file. Here is the mine (trp_interceptor.madstate):

TRP_INTERCEPTOR_Launched_OnSet = function(ship)
    startTime = 0
    if(isAnimRunning(ship, "Wings_Close") ~= 0) then
        startTime = getAnimLength(ship, "Wings_Close") - getTime(ship, "Wings_Close")
        stopAnim(ship, "Wings_Close")
        endEffect(ship, "Wings_Close")
    end
    startAnim(ship, "Wings_Open")
    startEffect(ship, "Wings_Open")
    setTime(ship,"Wings_Open",startTime)
    setPauseTime(ship, "Wings_Open", 1000)
end

TRP_INTERCEPTOR_Docked_OnSet = function(ship)
    startTime = 0
    if(isAnimRunning(ship, "Wings_Open") ~= 0) then
        startTime = getAnimLength(ship, "Wings_Open") - getTime(ship, "Wings_Open")
        stopAnim(ship, "Wings_Open")
        endEffect(ship, "Wings_Open")
    end
    startAnim(ship, "Wings_Close")
    startEffect(ship, "Wings_Close")
    setTime(ship,"Wings_Close",startTime)
    setPauseTime(ship, "Wings_Close", 1000)
end

For this script I have copied the stock Taiidan Interceptor file and modified it, which is usually the best way to construct these .madstate files (unless you really know what you are doing). The two functions play the “Wings_Open” and “Wings_Close” animations, whilst checking that the other animation is not already playing.

The other script that needs to refer to the animations is the .events file. Here are is relevant section of trp_interceptor.events:

In the animation chunk:

        animation6 =
        {
            name = "Wings_Open",
            length = 2,
            loop = 0,
            parent = "",
            minimum = 0,
            maximum = 0,
            markers = {""}
        },
        animation7 =
        {
            name = "Wings_Close",
            length = 2,
            loop = 0,
            parent = "",
            minimum = 0,
            maximum = 0,
            markers = {""}
        },

And in the events chunk:

                Wings_Open = 
                { 
                        { "anim", "Wings_Open", }, 
                        { "animtime", "0", }, 
                        { "marker", "EngineNozzle1", }, 
                        { "fx", "resourcing_dust_spray_full", }, 
                        { "sound", "SP_ELEMENTS/KPR_ATTACKDROID_OPEN", }, 
                        { "fx_scale", "0.4", },       
                },
                Wings_Close = 
                { 
                        { "anim", "Wings_Close", }, 
                        { "animtime", "0", }, 
                        { "marker", "EngineNozzle1", }, 
                        { "fx", "resourcing_dust_spray_full", }, 
                        { "sound", "SP_ELEMENTS/KPR_ATTACKDROID_CLOSE", }, 
                        { "fx_scale", "0.4", },       
                },                

The final result:

image

4 Likes

Finally, some sage advice from @EvilleJedi:

1 Like

Can I thank you now ? :stuck_out_tongue_winking_eye: (I didn’t want to have my message in the middle of your tutorial ^^)

2 Likes

:wink: No worries, I’m finished now!

1 Like

Absolutely fantastic! I’ve needed a hand-holding level tutorial for this since last February :wink:

//EDIT

So I’ve created the warp in and warp out animations and I have HODOR making a MAD file, however in game the animation is sorta skipped. The only thing I’ve not done is “convert all curves to linear”, which makes no sense to me. I hunted for a button that had that on it, but to no avail…

It should be noted that I have animated the root joint (ROOT_LOD[0]), if there’s something weird going on with that particular joint and HODOR that may also be it? @bitvenom, I have a vague recollection of hearing something like that…

1 Like

I also don’t know what that means. I never had any problems with it so I haven’t had to worry about it. I think it might be a bad idea to have an animation on LOD0…

I need to do a door animation or something for another test then, to make sure I have the method down. If I can’t animate the root then the DAE hierarchy of every single warp capable ship in STC will need to be revised as we move the whole ship as part of the warp_out/in anim…

Hello everybody,
I followed your tutorial step by step but unfortunately my animation does not function in games and I do not understand where is the problem.

I send you some of my files.

If anyone could look at them and tell me where is my problem would be really nice.

Un grand merci d’avance.

A big thank you in advance

See you soon

I had a look at your files. I can’t see any problems at first glance. HODOR has produced a MAD file and your .madstate and .events files look fine to me. You have a lot of “_” in your ship names… Is that ok? Calling @EatThePath?

This format seems to use the _ to separate the ship name from the animation type, so I am wondering if it getting confused by the extra _s. But maybe that is wrong.

Uns_VF1_A_CodeRed_OnSet = function(ship)

Other than that, I’m not sure why you start the second animation at 2.0333. What happens if you start it at 3? Sometimes it is wise to have a gap between animations, like the Taiidan Interceptor has (Close = 0-1.3333, Open = 2-3.3333).

EDIT: Also, you might need capitals in the madstate file, like this:

UNS_VF1_A_CodeRed_OnSet = function(ship)

Every ship in HW@ has at least as many underscores in the filename and it’s never caused any problems with hodor.

1 Like

Hello everyone
Thank you for your answers, so if I’ve understand the file structure is good.

the problem may come from names of joint and mesh, I’m going to simplify them and see what happens.

a big thank you.

No, I think the joint names are OK.

My suggestion is to use capital letters for the function name in the madstate file. E.g.

UNS_VF1_A_CodeRed_OnSet = function(ship)

Instead of

Uns_VF1_A_CodeRed_OnSet = function(ship)

I do seem to remember having this problem before.

Ok, i going to try this.
Thanks

HELLO,

my problem is solved thanks to your help, And yes it was the capital letters, I send you a little video of the result

A great Thank for your help.

3 Likes

A little question can i call an animation with the event file.

And how can i do that.

See you soon.