I'm a doctor, not a mechanic

Legacy:Playing Mesh Animations

From Unreal Wiki, The Unreal Engine Documentation Site
Jump to: navigation, search

I tried for nearly a month to figure out how to do this and finally came to the conclusion that there is no one tutorial out there that tells you how to get an Animated Static Mesh into the Unreal game. All of the information was scattered in bits and pieces here and there. I decided that instead of squirreling the information away and becoming highly in demand in the field of Unreal gaming development, like a sucker, I would just give it away on the Wiki, so other young punks can take advantage of all my heartache and hard work.

Musicalglass: The final tutorial you see here has been modified several times over. I came here with a roughly working solution based on what info I could find, and a lot of questions. Thanks largely to the contributions of Foxpaw and Chip, it has been synthesized into a new solution than the traditional method of extending a decoration or mover class, etc. thereby eliminating declaring a lot of unnecessary variables with each new actor.

Solid Snake: Well, your kind of missing the point of static meshes if your trying to animate them as such. The keyword is static. They don't change. Once you start animating things your changing the mesh as such...


This tutorial deals specifically with getting an Animated SkeletalMesh to actually play in the game Unreal. I will also show you how to attach a trigger, and use a few different trigger conditions.

As of this writing, in the current Editor (UT2003) it is impossible to get an animation into and have it play in the game. That is, not without getting your feet wet with Unreal Script.

Prerequisites[edit]

First we will assume a few things:

  1. You know what the Animation Browser is, you've seen animations play in the window and can't figure out how to actually get one into your Map
  2. You've already worked with regular Static Meshes and know how to apply Textures and get those into your Map OK
  3. You are familiar with a 3D application or are trying to learn how to use Maya PLE and have your own animation you would like to get into the game.

We will not assume that you know how to extend Actor Classes or run a compiler from a command prompt or know much of anything about programming for that matter.

Compatible Applications[edit]

Currently the only application which can export mesh animations compatible with Unreal is Milkshape, a little $25 3D application. Milkshape is also useful if you want to export any of the existing models that come with Unreal and convert to another format. Of note, it is also compatible with gMax files, the free version of 3DSMax.

If you're using Max or Maya, you'll need the ActorX plugin.

Users of Lightwave etc. would have to be able to export their models to one of the above formats.

Can I do it for free?: You may have noticed that Maya PLE comes on the discs with Unreal. If you're familiar with Photoshop LE, etc. you're probably thinking that this is some kind of stripped down light version of Maya. Actually it is the complete version of Maya in all repects identical to the full version costing thousands of dollars, except for a few minor things:

  1. You don't get a couple of extra features, such as hair and cloth simulation.
  2. Everything you try to output will have a very visible watermark over it, as well as everything you see in the viewports, preventing you from being able to take a screenshot of your work.

However, everything you export to the game Unreal using the preloaded ActorX plugin will be perfectly usable. If you're considering a career in 3D, whether it be for games or movies or whatever, the industry will just as soon consider hiring someone with work which has a watermark all over it, if it's good modeling or animation.

The Unreal World of Scripting[edit]

OK, ready to get your feet wet with some hardcore coding?

Actually, we're going to let the folks at Epic provide most of our code for us, we just want to tack on a little extra. Traditionally, this has been acomplished by embedding links to all the model data using one of the extensions of the Actor class, such as a Decoration or Mover class. When I looked at the scripts for these methods it seemed that each class had a ton of extra varables associated with it which had nothing to do with the simple task of getting an animation to play. It occurred to me that a new extention to the Actor class specifically for various animation purposes would be useful. I suggested it on this Wiki and, with the help of Foxpaw and Chip have developed just that: an AnimatedActor class.

So we're going to make a very simple script and then compile it.

There are two methods to compile a script;

  1. the fairly simple method of compiling from the Unreal Editor
  2. the slightly more technical method of compiling using ucc make from the DOS command prompt.

Unfortunately since we are declaring some new variables, we must use the second method first. However, in order to avoid having to recompile each time from the command prompt, in keeping with the spirit of Object Oriented Programming we will compile a script which does one thing: display a simple placebo static mesh animation, this will act as a placeholder which you can swap for any animation you want later. Once we have the simple dummy script, we can further extend that into new variations like TriggerAnimatedActor. These simple extensions can be quickly compiled from the Editor.

Animated Actor Script[edit]

First, copy the following script, open up Notepad or your favorite text editor and paste:

class AnimatedActor extends Actor
    placeable;
 
// You can manually add a trigger state here for one time use or 
//  extend this class with a library of various AnimatedActor triggers 
 
var(InitialAnimation) name AnimName; 
var(InitialAnimation) float AnimRate; 
var bool TogglePlay;
 
 defaultproperties 
 {
     bStatic=False 
     bNoDelete=True 
     bStasis=False 
 
     DrawType=DT_Mesh 
     Mesh=SkeletalMesh'myRotatingCube.myCubeMesh' 
 
     AnimName='AnimSeq' 
     AnimRate=1 
     TogglePlay=True 
 }

You will save the file as a text file, only instead of the .txt extension, change it to .uc

Do Save As, find your UT2003 folder on your hard drive and create a new folder in there called AnimatedActor. Inside that create 3 more folders called Classes, Models and Textures. Save as AnimatedActor.uc in YourHardDrive:/UT2003/AnimatedActor/Classes.

We're just about ready to compile, but first we need to create the Animation Package and replace the PackageName.SkeletalMeshName. I could have just as easily typed that in there for you, but I wanted you to see the relationship to the Animation Browser.

Exporting with ActorX[edit]

To begin with for the purpose of this tutorial, we will start with a very simple exported animation. This will be your dummy animation for the purpose of compiling a placeable mesh into your script. Make a quick 256 X 256 Texture in Photoshop or your favorite graphics app, something quick like a gradient or something, don't get all artsy on me, and save it as a .tga file in YourHardDrive:/UT2003/AnimatedActor/Textures. In your 3D application, first make a simple polygon cube, triangulate and texture your model and add a single bone skeleton. Set a keyframe on frame 0, go to frame 4, rotate it 360 degrees and set a keyframe. and you are ready for export. (Remember if your model consists of parts they must first be grouped, N/A here)

Assuming you are using Max or Maya:

In the 3D application's command line, type:

axmain

This brings up the ActorX window and you can export your model as myCubeMesh.PSK in YourHardDrive:/UT2003/AnimatedActor/Models. To export your animation, we'll set the File Name ( myCubeAnim ) the sequence name ( AnimSeq ) and the range ( 1-4 ) Note, we didn't include frame zero, as it is identical to frame 4. This is how to make a perfect looping animation. Click the Digest Animation button, open the animation manager, transfer it from the Animations to the Output Package folder and Save as myCubeAnim.PSA.

Assemble in the Animation Manager[edit]

Now we will combine all the elements of our animation. Import your texture into the Texture Browser and save your texture package. Switch over to the Animation Browser and do File -> Mesh Import. Open your myCubeMesh.PSK model.

Let's call our Animation package RotatingCube. In the Mesh Properties down the right side, expand Skin -> Material and click on (0) Some buttons appear. Click the Use button. If you don't see the Use button you may need to resize the window and everything in the Mesh tab will disappear. To get it back click on one of the other tabs and back again.

Now do File -> Animation Import and get your .PSA file into the same package.

Do Edit -> Link Up Animation and Mesh. Now if you expand Animation in the Mesh properties you should see the name of your animation.

If it says None, you can make it pop in by going to File -> Mesh import a second time, select the name of your file and click Cancel.

Now that you have your texture and animation info associated with your Mesh you can save the animation package in Unreal's default Animation folder. Note in the Animation Browser window just above your model you can see the name of your package and mesh. It should read: RotatingCube.myCubeMesh. Now that we have a working animation and package name, we can use that as a temporary placeholder in our first script. So open up the script you created earlier, replace the PackageName and SkeletalMeshName with your own and save and you're ready to compile.

Compile Your Script[edit]

OK, so far we've only spent something like an hour doing a simple thing like getting an animation into our map (or maybe weeks if it took you a while to locate this tutorial). Whatever! We're almost there. Our script is all ready to place in the game. We just need to compile it so Unreal can read it. Sounds intimidating if you're not a programmer but it's actually quite simple.

First we need to tell the compiler what to compile, so with your text editor, Open UT2003/System/UT2003.ini. Scroll down to about line 350 or so and you see a list of package names to be compiled like this:

EditPackages=Vehicles

Underneath the last one in the list add a new line and insert your own edit package command to the list:

EditPackages=Vehicles

EditPackages=AnimatedActor

Now save the file and go to your command prompt: On Windows, I believe you'll find that in your Start menu under Programs -> Accessories.

( I forget cause I moved mine long ago to the Start menu itself )

In the command prompt enter:

cd ut2003\system (Note: backslash not forward slash)

You should be taken to the Unreal system directory. Enter:

ucc make

Unreal should compile your script if all went well. If not you'll see an error in the command prompt window. If you do get an error and have to do something over, you may have to edit and save the UT2003.ini file again before compiling.

Placing your AnimatedActor[edit]

Open the Actor Class Browser: In the Actor Classes window do File -> Open Package. You should find your newly compiled AnimatedActor.u Open it and expand the Actor hierarchy. You should now see your new AnimatedActor class in the list. Select it. Now you could right click in your map and select AnimatedActor and you should now be able to place your default cube into your map. Now that you have your AnimatedActor with a valid default Animation Package, you can open it up and swap out a different Animation Package by first placing it in the scene, then double click on it to open the AnimatedActor Properties, expand Display and update the SkeletalMesh.

For extensibility, the AnimatedActor itself does not play the animation. In order to add more options to what you can do with the basic Actor you can now add extensions to the extension.

Subclassing from Unreal Editor - Looping Animation Script[edit]

Copy the following script:

class LoopMovie extends AnimatedActor
    placeable;
 
// ========================================
// This bit makes the Animation play automatically at startup
// ========================================
 simulated function PostBeginPlay() 
 
 { 
     LoopAnim( AnimName, AnimRate );
     Super.PostBeginPlay();
 }
// ========================================

In the Actor Class Browser, right click on your AnimatedActor class and select New. Set the package to myLevel and the name to LoopMovie. Select the entire script and paste the new one. In the Script Editor window Do Tools -> Compile Changed.

That's it! Much simpler that the previous compiling method. You should now be able to add your newly compiled script to any map. When you start a new level it will not appear by default in the Actor Class browser. Simply import LoopMovie.u and it will automatically load the parent AnimatedActor class as well. Expand that, and you can have a whole list of subclasses.

Now, create a room or open a map. Select LoopMovie from the Actor window and right click place in your scene. Your newly created default Skeletal Mesh Animation should pop into the scene. Double click on it to open it's properties. You will see in the LoopMovie Properties we have added a new category to Actor called InitialAnimation.

Expand InitialAnimation and verify that the sequence name matches that of your animation (AnimSeq).

You should now be able to play your map and see your movie play automatically. A pretty rapidly spinning cube. Go back to the editor and change the animation rate to a low number like 0.15. You should now have a much slower animation. Note how unreal interpolates the tweening instead of seeing a choppy 4 frame animation.

Here's a few more subclasses of AnimatedActor you can add to your arsenal;

Adding a Trigger[edit]

class TriggerMovieOn extends AnimatedActor
    placeable;
 
// ========================================
// Trigger Animation On - Starts in the off state, then triggers on one time
// To make it on then off, swap the lines that say LoopAnim & StopAnimating
// ========================================
simulated function PostBeginPlay()
{
StopAnimating(  );
     Super.PostBeginPlay();
}
 
function Trigger( actor Other, pawn EventInstigator )
{
     LoopAnim( AnimName, AnimRate );
}
// ========================================

In the Actor Class Browser, expand Trigger and select Trigger and add it to your scene. Open the Trigger properties and set the Event tag to TagName and the Tag to None. Open your animated mesh's properties and set the Tag to None and the Event to TagName.

Trigger On & Off & On...[edit]

Here's a few more variations for trigger behaviour;

class OnOffMovie extends AnimatedActor
    placeable;
 
// ========================================
// Trigger Animation On & Off & On, etc....
// ========================================
// this section deativates the animation until triggered
// can be turned on & off & on, etc.
// once triggered off, it will stop immediately
// to make it start with the movie on, change TogglePlay=true to false in the default properties
 
function Trigger( actor Other, pawn EventInstigator )
{
if (TogglePlay)
{
     LoopAnim( AnimName, AnimRate );
     TogglePlay=False;
}
else
{
     StopAnimating();
     TogglePlay=True;
}
}
// ========================================

Finish Playing First[edit]

class PlayAllMovie extends AnimatedActor
    placeable;
// ========================================
// Trigger Animation on & off - finish playing first
// ========================================
// this section deativates the animation until triggered
// can be turned on & off & on, etc.
// once triggered off, it will complete the animation before stopping
// to make it start with the movie on, change TogglePlay=true to false in the default properties
 
function Trigger( actor Other, pawn EventInstigator )
 
{ 
     if (TogglePlay)
{
     LoopAnim( AnimName, AnimRate );
     TogglePlay=False;
}
else
{ 
     AnimStopLooping(); 
     TogglePlay=True; 
} 
}
// ========================================

Here's a small expansion on the idea, using Touch() to progressively speed up the animation:

//=============================================================================
// Accelerate Animation
//=============================================================================
class AccelAnim extends AnimatedActor
    placeable;
 
event Touch( Actor Other )
{
     AnimRate += 0.1;
     LoopAnim( AnimName, AnimRate );
}

this is very bare-bones but illustrates how the anim rate can be changed by an event.

Under Development - 12/01/03 ~~~ This is a work in progress. If any of you hardcore coders can come up with any more trigger variations or effects, that would be really cool.

Please feel free to contact me at musicalglass@hotmail.com if you find any errors, or if you think there's not enough info in spots or too much which is already covered thoroughly elsewhere on the WIKI or even if you're the one person out there who actually found this info useful.

Related Topics[edit]

Discussion[edit]

Under Development[edit]

I wanted to make a carousel animation which you can turn on and off with a trigger. Once triggered, it will slowly accelerate to a given speed, loop for a bit, then deccelerate. But if triggered a second time, it will go directly to deccelerate mode. Foxpaw has been helping with the development.

Here is the current script:

class EaseInOut extends Actor placeable;
 
var(EaseInOut) name AnimName;
var float AnimRate;
 
var(EaseInOut) float MaxAnimRate;
var(EaseInOut) float AccelerateRate;
var(EaseInOut) float DecelerateRate;
 
auto state Off
{
  simulated function Tick( float Delta )
  {
     AnimRate = FMax(  AnimRate - (Delta * DecelerateRate), 0 );
     LoopAnim( AnimName, AnimRate );
     Global.Tick( Delta );
     if ( AnimRate > 0 )
       Level.Game.Broadcast( None, "I'm off and my animrate is "$AnimRate, 'Say' );
  }
 
simulated function Trigger( actor Other, pawn EventInstigator )
 
  {
     Level.Game.Broadcast( None, "I got triggered!", 'Say' );
 
      GotoState( 'On' );
  }
}
 
state On
{
  simulated function Tick( float Delta )
  {
     AnimRate = FMax( AnimRate + (Delta * AccelerateRate), MaxAnimRate );
     Global.Tick( Delta );
     LoopAnim( AnimName, AnimRate );
     Level.Game.Broadcast( None, "I'm on and my animrate is "$AnimRate, 'Say' );
  }
 
  Begin:
    Sleep(5);
    GotoState( 'Off' );
}
 
defaultproperties
{
     bStatic=False 
     bNoDelete=True 
     bStasis=False
 
     DrawType=DT_Mesh
     Mesh=SkeletalMesh'PackageName.SkeletalMeshName' 
 
     AnimName='AnimSeq'
     AnimRate=0
 
     AccelerateRate=0.5
     DecelerateRate=0.5
     MaxAnimRate=1
}

Musicalglass: I changed MaxRotationRate to MaxAnimRate to get the script to compile. I noticed I was getting multiple trigger commands so I added a delay in the Trigger properties, which I have found useful for on / off scripts like this. I was getting "I got triggered" to the screen OK but no "I'm on..." so I removed if ( Other != None && Other.IsA( 'xPawn' ) ) reasoning that it must have been a part of another script you copied from as there are no xPawns involved here, and it worked perfectly. However, it may zero out on any frame. Once it has come to a complete stop, it snaps to frame 0 to prepare for the next Play command. It would be neccesary to make it so either it will always end on frame 4, or when triggered again will resume playing from the current frame. Also I wanted to add, If Triggered a second time Go To Deccelerate. If I want it to play music while turning that will probably have to go into the script as well.

You were saying that using translation and rotation on a mesh was not a good idea because of the way the video card handles it. Since Movers use this method, would you say then that all movers are problematic and that the best way is always to use bones and animate in an external 3D app? Then using a mesh and scripting to do different effects with PHYS_Rotate etc. would not be a good idea for a scripting tutorial as I would be using a method which is not practical for everyday use, correct?

Also, do you know if there's a way to attach additional objects to the bone of my skeletal mesh, so I can hang additional animated and non anim meshes to my carousel?

I can't seem to get the Touch function to work. I can't find very many examples around. The Hello World tutorial shows you how to write to a log. It should work OK with a Actor class right? It has a default collision radius and everything.

class TouchMovieOn extends AnimatedActor
    placeable;
 
function PostBeginPlay()
{
StopAnimating( );
Super.PostBeginPlay();
}
 
function Touch( actor Other)
{
     LoopAnim( AnimName, AnimRate );
}

I also tried doing my own simple Hello world using the Message broadcast method desribed by Foxpaw for debugging:

class HelloWorld extends Actor
	placeable;
function Touch( actor Other )
 
  {
     Level.Game.Broadcast( None, "I've been touched!", 'Say' );
  }

Am I missing something? Does Broadcast only work in conjuction with another action?

Also I was a bit confused about saving your script in myLevel. So that means that the file is saved with the map. So then each time somebody wants to create a new map with the same class they would reimport it into myLevel again or copy it out of the existing map?

Foxpaw: That should work, maybe the actor isn't getting the Touch() call. A fairly easy way to check if it is broadcasting right would be to stick something in PostBeginPlay() or maybe Tick - functions that are pretty much guaranteed to get called. ANd yes, the myLevel package works as you've described it.

Musicalglass: I was thinking about this problem with the carousel snapping to frame 1 on start. It would seem that what I need is a Pause and Resume feature. As there isn't one supplied I was thinking a possible way to create one would be instead of turning LoopAnim on and off you would freeze and unfreeze the frame rate and LoopAnim would always be true. So you would what, make it so once the frame rate reached zero it would go to sleep mode so it wouldn't eat up ticks, right? Would that work?

Foxpaw: If you are animating the carousel via LoopAnim, I think that should work. However, I wouldn't worry about it getting Ticks, because you can't really prevent it, (It still gets Ticked even while in a latent function like sleep) and also it doesn't really do any harm. If the tick function isn't doing anything, the overhead from the Tick call is very minimal.

Clintonman: I removed the 'Say' argument and the broadcast worked for me. With 'Say' it only showed in the console.

  Level.Game.Broadcast( None, "I've been touched!");