Game Development Community

TorqueX Newbie Diary #6

by David Everhart · 01/28/2008 (9:29 am) · 6 comments

Quite a bit has been done on Arillian since the last update. The main advances have been:

1) Introduction of another animation
2) Revamp of the project setup
3) Revamp of the animation system
4) Animation change based on mouse

Introduction of another animation

In order to test out animation transition, and the action that causes it, I needed another animation. I already had one for a human male in an idle mode with no weapons facing south. I went to my trusty poser 7 and used the same mode, but this time, rendered it southwest. One thing to note, I do not include shadows, since I will be implementing shadows runtime based on light sources.

Revamp of the project setup

I restructured my project by moving out the peices to logical projects. I now have the following projects in my solution:

1) DataAccess - Persistence layer, provides access to a persistance engine (in my case, Sql Server 2005 Express)
2) AnimationManagement - This project handles all the animation creation and management
3) Common - Anything that is common to all the projects, or needs to be access globally.
4) ArillianGameEngine - This project handles all the core Arillian classes, such as Player, ItemManager, etc
5) Game - The basic project that the startertemplates use, it is still used as the starting point

Revamp of the animation system

I was unhappy with the file naming system I had originally used. Although Diablo II used a similar format for naming, it felt hackish to me. I went ahead and implemented a data model in sql server 2005 express to persist the animation information, as well as all game related information (such as login , accounts, saved games, configuration, etc). This allowed me to run stored procedures (that take around 5- 10 milliseconds) to grab animation layer information. Since my AnimationLayers have IDs now, My file system now has folders related to the ID.

Fo example, The root folder to the animationlayers is constant (C:\Projects\Arillian\Game\data\Images\AnimationLayers). Inside that folder are folders called 1, 2, 3, 4, etc. They correspond to the AnimationLayerID. Inside those folders are the frames for that animation layer. If you recall, an AnimationLayer is just one layer for an animation configuration. The animation configuration for any player is always 4 animation layers (head, torso, lefthand, righthand).

My Player object has one animated sprite, which I was hoping to just swap in and out the animation datas. Unfortunately, I do not think TorqueX was really designed for that. So I implemented a solution around it. I still have the animated sprite definition in my Player object, but now I copyto() a new animated sprite, attach the animation, and then run it from there. I maintain the object id of the current sprite in the TorqueObjectDatabase, so I can unregister it, and then register the new one, and set the current id. Here is my Player.UpdateAnimation() function so far:

public void UpdateAnimation()
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("UpdateAnimation::Start Time is " + startTime.ToLongTimeString());
            // Get the Current Camera
            T2DSceneCamera currentCamera = (T2DSceneCamera)TorqueObjectDatabase.Instance.FindObject("Camera");

            // Get the AnimationConfiguration
            AnimationConfiguration configuration = GetAnimationConfiguration();

            // Make sure we have 4, if we dont, then the AnimationConfiguration is not valid
            if (configuration.AnimationLayers.Count == 4)
            {
                // Get the T2dAnimationData
                T2DAnimationData animationData = AnimationManager.Instance.CreateT2DAnimationData(configuration, ImageFormat.Png);

                // Check to see if the current sprice has an ID, default is -1
                if (currentSpriteId > -1)
                {
                    // Unmount the Camera
                    currentCamera.Dismount();
                    // Unregister it
                    TorqueObjectDatabase.Instance.Unregister(TorqueObjectDatabase.Instance.FindObject((uint)currentSpriteId));

                }
                // Create a new copy based off our _sprite definition
                T2DAnimatedSprite spriteCopy = new T2DAnimatedSprite();
                _sprite.CopyTo(spriteCopy);

                // Set the new animation data
                spriteCopy.AnimationData = animationData;

                // Setup our Sprite
                TorqueObjectDatabase.Instance.Register(spriteCopy);
                currentSpriteId = Convert.ToInt32(spriteCopy.ObjectId);
                currentCamera.Mount(spriteCopy, "", true);

                // Play the new animation
                spriteCopy.PlayAnimation();
            }

            DateTime endTime = DateTime.Now;
            Console.WriteLine("UpdateAnimation::End Time is " + endTime.ToLongTimeString());
            TimeSpan timeToRun = endTime.Subtract(startTime);
            Console.WriteLine("UpdateAnimation::Time to run is " + timeToRun.TotalMilliseconds.ToString());
        }



When the code runs, this is how long it takes to compress 60 frames (4 animation layers * 15 frames each), draw a shadow on each cell of the final image (I do this runtime, as opposed to baking it in the image), render it to T2DAnimationData, and then attach it to the newly copied animated sprite:

UpdateAnimation::Start Time is 3:01:17 AM
UpdateAnimation::End Time is 3:01:17 AM
UpdateAnimation::Time to run is 234.375


Now, when I run this, I cache the animation layer in a Dictionary. The int is the animation layer id, and the image is the animation layer image. When the animation is cached, it will use the image from there, instead of rerendering the 15 frames. Here is a cached pull:

UpdateAnimation::Start Time is 3:01:19 AM
UpdateAnimation::End Time is 3:01:19 AM
UpdateAnimation::Time to run is 78.125

There is no noticeable lag in animation switching. Bear in mind, the time to run is in milliseconds. I am happy with the speed of it, but do not know yet how it will perform when a lot of things are moving on the screen. The below code is the check to see if it is cached, and if is not, to cache it.

Image imageToMerge;
                    if (AnimationManager.Instance.AnimationLayerImages.ContainsKey(newAnimationLayer.ID))
                    {
                        // Load from cache
                        AnimationManager.Instance.AnimationLayerImages.TryGetValue(newAnimationLayer.ID, out imageToMerge);
                    }
                    else
                    {
                        // Load from file
                        imageToMerge = CreateAnimationImageStrip(newAnimationLayer);
                        // Store it for future use if it isnt already cached
                        AnimationManager.Instance.AnimationLayerImages.Add(newAnimationLayer.ID, imageToMerge);
                    }

Animation change based on mouse click position

I first wanted to use a button to test my animation change, but realized I would have to figure out the new 1.5 GUI setup. Instead , I decided to implement the mouse functionality. I have some guidelines on what I want the mouse to do in regards to the player. They are:

1) If the Shift button is held down, mouse clicking anywhere on the screen will have the player face that direction
2) If the shift button is not held down, and they are within a distance of 50, then perform the walk animation and move the player in that direction
3) If the shift button is not held down, and they are over a distance of 50, then perform the run animation and move the player in that direction

I implemented ITickOBject on my custom player class, and in the process tick, handle the mouse. I have not integrated the distance or shift peices yet, but have implemented the change direction based on angle of mouse click or drag.Code is below:

public void ProcessTick(Move move, float dt)
        {
            // Get the current Mouse State
            MouseState state = Mouse.GetState();
            if (state.LeftButton == ButtonState.Pressed) // Means button was pressed
            {
                // Get the Mouse coordinates
                Vector2 mouseCoordinates = Utility.ConvertScreenToWorld(new Vector2((float)state.X, (float)state.Y), Configuration.Instance.ScreenResolution);
                // Figure out what the angle is in relation to the player
                float angle = T2DVectorUtil.AngleFromTarget(mouseCoordinates, Player.Instance.Sprite.Position);
                // Convert it to an enumeration we can use 
                Enumeration.Direction newDirection = Utility.ConvertAngleToDirection(angle);
                // Check to see if we need to do anything
                if (newDirection != Player.Instance.Direction)
                {
                    //Console.WriteLine(newDirection.ToString());
                    float distance = Vector2.Distance(mouseCoordinates, Player.Instance.Sprite.Position);
                    Player.Instance.Direction = newDirection ;
                    Player.Instance.UpdateAnimation();
                }

            }
        }
This allows me to capture even mouse drag events, so you can literally mousedrag a circle around the player, and it will only fire if the angle is between certain ranges. You can also click, and it will do a check as well (Thanks to John Kanalkis for the idea, and Josh Thomas for the screen to world conversion function.).

Summary

Here are two screenshots of the south idle and southwest idle animations:

Facing South:
img101.imageshack.us/img101/1583/facingsouthls3.jpgFacing SouthWest:
img101.imageshack.us/img101/3058/facingsouthwestvc4.jpg
Its been a long and fun road so far, a lot more to go. With Poser 7, I am finally feeling comfortable in it, and can whip out renderings pretty easy. I am still learning the animation aspects of it though. The world is comng along, thanks to WorldCreator 2.5 as well. My next steps are to render out the 8 direction for the idle, walking, and running animations. I also want to get it so that you can move the player around like in Diablo. I am close to it now, the mechanics are there, I just need to flush it out. With the inclusion of a database, I now have a persistence layer I am very familiar with, so that should speed things up as well. Until the next update!

#1
01/28/2008 (4:27 pm)
Pretty interesting read. Makes me want to learn C#. :) It seems likea beautiful language.
#2
01/28/2008 (5:06 pm)
Hehe, I loved C# the first time I saw it! In college it was all C/C++/Assembler. Then did some VB6 contracts (never ever again *vomits* ). When C# came out and about in 2001 (at least that is when I was learning it), it reminded me so much of C++ I had to switch to it :)
#3
01/29/2008 (5:51 am)
This is a great write-up, David. I really enjoy reading through your developer diaries. You've outlined an impressive solution to the animation switching problem. Just wait until you start playing with the Torque X 3D animation... with a whole different set of issues. ;)

John K.
#4
01/29/2008 (6:43 am)
Thanks John! You have been a great help to me, I am not sure I would have gotten this far this fast without your help on the forums! I figure doing it in 2D will be great introduction to the world of game design and development concepts. Once I am more comfortable in the world of 3d modeling, I will most likely do Arillian in 3d (looking at mythos has me wild with inspiration in terms of graphical aspect, not necessarily game mechanics). I am sure once I cross the 3d bridge, it will be a wild ride indeed!
#5
01/30/2008 (5:18 am)
Happy to help... That's very smart of you to start off with 2D and then working up to 3D. I see so many projects diving right into the more complex 3D world and then giving up. There's a lot of great lessons to be learned with building 2D games. But I also really like the classic Diablo look you have going for Arillian. I'm always up for some play testing if you're looking for a tester.

John K.
#6
01/30/2008 (4:11 pm)
Roger that John! That will be a ways off , but I am slowly getting there :)