Game Development Community

dev|Pro Game Development Curriculum

Animated cursor

by Jeff "Ddraig Goch" Parry · 02/20/2005 (10:41 pm) · 6 comments

Download Code File

I am working on an RTS game and I needed some fancy cursors to indicate selectable objects, out-of-context objects, etc... I searched all over the forums for an animated cursor control but could not find one. So I just added animation capability to the current guiCursor.

Movie (2 Mb)

All you need to do is replace guiTypes.cc and guiTypes.h in engine\gui, recompile and now you can make your cursor a bit more lively. (Note: Always make a backup of the original files before replacing them...you have been warned.)

Cool thing is that your current cursor definitions continue to work as normal. To animate your cursor you need to do the following:

Add three new fields to the cursor definition:

isAnimated indicates if the cursor is animated or not. (default is false)
frames how many frames in the animation sequence.
fps how many frames per second (speed). Note: max is 1000, but in reality best you will get is around 100 because it takes about 10 milliseconds for the code to cycle. 3050 is a good fps to shoot for.

Of course you need a sequence of bitmaps for the animation. Save your bitmaps with a suffix of "_n", where "n" is the frame number. For example:

arrow_1.png
arrow_2.png
arrow_3.png


I borrowed the same method that is used in guiBitmapButtonCtrl. In the definition you simply give the path to the bitmap. In your cursor definition you leave off the suffix, for example:

bitmapName = "./arrow";

The code looks at the number of frames and loops from 1 thru number of frames, appending an underscore and frame number to the bitmapName and then draws that frame.

So pulling it all together, your cursor definition for a 3 frame animated arrow cursor would look like this:

new GuiCursor(ArrowCursor)
{
   hotSpot = "1 1";
   bitmapName = "./arrow";
   isAnimated = true;
   frames = 3;
   fps = 50;
};

Just in case the code link doesn't work. Here is the code.
I included 12 color-changing arrow bitmaps for you to play with.

The nice thing about using bitmaps is that you don't need a special editor for .cur or .ani files, should be ok on any platform, and bitmaps for each frame can be different sizes, (though you will need to be careful to keep the hot spot in the right place.)

I have found no appreciable impact to frame rate.

#1
02/21/2005 (5:18 am)
Very nice !!

Simple but efficient :)
It will add some animation to the GUI ;)
#2
02/21/2005 (11:12 am)
Thank you for this one too!
#3
02/23/2005 (8:23 am)
Great Job! I will use this! Thanks
#4
11/24/2005 (9:36 pm)
"HOWTO" for TGE 1.4:
STEP 1
need to update files as follows:
in file engine/gui/core/guiTypes.h do:
appx line 50 change:
Point2I mExtent;
   TextureHandle mTextureHandle;
to:
Point2I mExtent;
[b]   //AniCursorMod--
   U32 mLastUpdate;
   U32 mFrames;
   U32 mFps;
   U32 mCounter;
   bool mIsAnimated;
   //--AniCursorMod[/b]
   TextureHandle mTextureHandle;
STEP 2
in public declaration of "class GuiControlProfile : public SimObject"
near line 144 code
ColorI mCursorColor;                            ///< Color for the blinking cursor in text fields (for example)

   Point2I mTextOffset;                            ///< Text offset for the control
change to:
ColorI mCursorColor;                            ///< Color for the blinking cursor in text fields (for example)
[b]   StringTableEntry mBitmapBase;                   //AniCursorMod[/b]

   Point2I mTextOffset;                            ///< Text offset for the control
STEP 3
that's fine with this file.. now let's take engine/gui/core/guiTypes.cc:
at the very beggining of file the
GuiCursor::GuiCursor()
{
   mHotSpot.set(0,0);
   mRenderOffset.set(0.0f,0.0f);
   mExtent.set(1,1);
}
should look like:
GuiCursor::GuiCursor()
{
   mHotSpot.set(0,0);
   mRenderOffset.set(0.0f,0.0f);
   mExtent.set(1,1);
[b]   // AniCursorMod--
   // Initialize new animated cursor fields.
   mLastUpdate = 0;
   mFrames = 1;
   mFps = 1000;
   mIsAnimated = false;
   mCounter = 1;
   // --AniCursorMod[/b]
}
STEP 4
in void GuiCursor::initPersistFields() change
addField("bitmapName",  TypeFilename,  Offset(mBitmapName, GuiCursor));
}
to:
addField("bitmapName",  TypeFilename,  Offset(mBitmapName, GuiCursor));
[b]   //AniCursorMod--
   // New persistent fields for
   // animated cursors, set in script
   // in cursor profiles.
   // Animated or not?
   addField("isAnimated",  TypeBool,  Offset(mIsAnimated, GuiCursor));
   // Number of frames in animation
   addField("frames",  TypeS32,  Offset(mFrames, GuiCursor));
   // Frames per second.
   addField("fps",  TypeS32,  Offset(mFps, GuiCursor));
   //--AniCursorMod[/b]
}
STEP 5
next... make the "void GuiCursor::render(const Point2I &pos)" look like this:
void GuiCursor::render(const Point2I &pos)
{
   // Get current milliseconds.
   U32 now = Platform::getRealMilliseconds();
   // How long since last update?
   U32 elapsed = now - mLastUpdate;
   // Calculate milliseconds per frame
   U32 mUpdateTicks = 1000 / mFps; // in case mFps changes.
   if (mIsAnimated) // Is this an animated cursor?
   {
      if (elapsed > mUpdateTicks) // Time for a new frame?
      {
         char buffer[1024]; // Set up a buffer.
         // Add counter as suffix to bitmap name.
         dSprintf(buffer, sizeof(buffer), "%s_%i", mBitmapName, mCounter);
         // Set texture handle to new bitmap.
         mTextureHandle = TextureHandle(buffer, BitmapTexture);
         // Reset last update so we can calc elapsed time.
         mLastUpdate = Platform::getRealMilliseconds();
         mCounter++; // Increment the counter.
         if (mCounter > mFrames) // If we finished all frames
         {
         mCounter = 1; // Reset counter to 1.
         }
      }
   } // Not animated cursor so
   else
   { // Set texture handle to original bitmap.
      if (!mTextureHandle && mBitmapName && mBitmapName[0])
      {
         mTextureHandle = TextureHandle(mBitmapName, BitmapTexture);
      }
   }
   if(!mTextureHandle)							// Oops no bitmap.
   {
      Con::errorf("Failed to load bitmap (%s)",mBitmapName);
      return;
   }
   // Get width and height of bitmap.
   mExtent.set(mTextureHandle.getWidth(), mTextureHandle.getHeight());

   // Render the cursor centered according to dimensions of texture
   S32 texWidth = mTextureHandle.getWidth();
   S32 texHeight = mTextureHandle.getWidth();

   Point2I renderPos = pos;
   renderPos.x -= ( texWidth  * mRenderOffset.x );
   renderPos.y -= ( texHeight * mRenderOffset.y );
   
   dglClearBitmapModulation();
   dglDrawBitmap(mTextureHandle, renderPos);
}
STEP 6
in "GuiControlProfile::GuiControlProfile(void) change
// default bitmap
      mBitmapName    = def->mBitmapName;
      mTextOffset    = def->mTextOffset;
to:
// default bitmap
      mBitmapName    = def->mBitmapName;
[b]      mBitmapBase    = def->mBitmapBase;[/b]
      mTextOffset    = def->mTextOffset;
That's all... rebuild your Torque and follow Jeff's instruction how to use this ;) good luck

P.S. btw, it would be nice if someone will find a way to use .ANI cursors in TGE/TSE... it's much better to use 1 file instead of 10 files of sequence
#5
11/30/2005 (1:50 pm)
here is a quick-fix for latest HEAD/release_0_3_0 of TSE.
Do all steps in the same way (as for TGE1.4) except two things:
file is called guiTypes.cpp, not guiTypes.cc and
STEP 5
make the "void GuiCursor::render(const Point2I &pos)" look like this:
void GuiCursor::render(const Point2I &pos)
{
   //AniCursorMod---
   U32 now = Platform::getRealMilliseconds(); // How long since last update?
   U32 elapsed = now - mLastUpdate; // Calculate milliseconds per frame
   U32 mUpdateTicks = 1000 / mFps; // in case mFps changes.
   if (mIsAnimated) // Is this an animated cursor?
   {
      if (elapsed > mUpdateTicks) // Time for a new frame?
         {
         char buffer[1024]; // Set up a buffer.
         // Add counter as suffix to bitmap name.
         dSprintf(buffer, sizeof(buffer), "%s_%i", mBitmapName, mCounter);
         // Set texture to new bitmap.
         mTextureObject.set(buffer, &GFXGuiCursorProfile);
         // Reset last update so we can calc elapsed time.
         mLastUpdate = Platform::getRealMilliseconds();
         mCounter++; // Increment the counter.
         if (mCounter > mFrames) // If we finished all frames
         {
            mCounter = 1; // Reset counter to 1.
         }
      }
   } // Not animated cursor so
   else
   { // Set texture handle to original bitmap.
      if (!mTextureObject && mBitmapName && mBitmapName[0])
      {
         mTextureObject.set( mBitmapName, &GFXGuiCursorProfile);
         if(!mTextureObject)
            return;
      }
   }
   mExtent.set(mTextureObject->getWidth(), mTextureObject->getHeight());
   GFX->clearBitmapModulation();
   GFX->drawBitmap(mTextureObject, pos);
}
#6
02/17/2008 (6:23 pm)
IS there any way to make this resource use a sprite instead of several images?