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:
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.
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.
#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
STEP 1
need to update files as follows:
in file engine/gui/core/guiTypes.h do:
appx line 50 change:
in public declaration of "class GuiControlProfile : public SimObject"
near line 144 code
that's fine with this file.. now let's take engine/gui/core/guiTypes.cc:
at the very beggining of file the
in void GuiCursor::initPersistFields() change
next... make the "void GuiCursor::render(const Point2I &pos)" look like this:
in "GuiControlProfile::GuiControlProfile(void) change
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
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 controlchange to:
ColorI mCursorColor; ///< Color for the blinking cursor in text fields (for example) [b] StringTableEntry mBitmapBase; //AniCursorMod[/b] Point2I mTextOffset; ///< Text offset for the controlSTEP 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 4in 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 5next... 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 6in "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 luckP.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
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:
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? 
Torque Owner Yannick Lahay
Simple but efficient :)
It will add some animation to the GUI ;)