Game Development Community

GuiTimerCtrl - Render Based, Not Time Based

by Dave Calabrese · 07/29/2009 (6:29 pm) · 2 comments

NOTE: This was written for Torque2D for iPhone, so it may need some modifications to work in other engine variations.

First off, let me say that this control will probably not be for everyone. I wrote this to be pretty specific to what I was doing, which was needing a control that could count down from a pre-determined number, to 0, then call an event. This control could use some solid polish and cleanup, but I'm neck-deep in an end-project crunch and wanted to share this while I was thinking about it.

This is a new GuiTimerCtrl object, based on the existing Timer and Clock resources and forum posts found around the site. The big difference with mine is that it does not rely on actual system time - it instead relies on rendering time. I created it this way because I found numerous inaccuracies and issues in relying on the system time. With no time to debug that, I had to go another route. So again, this control will be great for some people, and pointless for others.

If I get the chance to come back to this - or if anyone wants to take a shot at modifying it - I'd like to add console commands for some of the functionality (like activating pausing), and make this control work with both render-time and system-time.

Anyway... here ya go. Hopefully it'll work for you... once "Insane Crunch (tm)" has ended, I'll try and stop back and review this to make sure it works as well in a public setting as it did in my project!

GuiTimerCtrl.cpp
//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "console/consoleTypes.h"
#include "console/console.h"
#include "core/color.h"
#include "gui/controls/GuiTimerCtrl.h"
#include "dgl/dgl.h"
#include "i18n/lang.h"
#include "platform/platform.h"

// -----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GuiTimerCtrl);

GuiTimerCtrl::GuiTimerCtrl()
{
	//default fonts
	mInitialText = StringTable->insert("");
	mInitialTextID = StringTable->insert("");
	mText[0] = '';
	mMaxStrLen = GuiTimerCtrl::MAX_STRING_LENGTH;
	
	mReversed = false;
	mTimePaused = true;
	mTimerExpired = false;
}

void GuiTimerCtrl::initPersistFields()
{
	Parent::initPersistFields();
	addField( "reversed", TypeBool,          Offset( mReversed, GuiTimerCtrl ) );
	addField( "paused", TypeBool,            Offset( mTimePaused, GuiTimerCtrl ) );
	addField( "timerExpired", TypeBool,      Offset( mTimerExpired, GuiTimerCtrl ) );
	addField( "useTargetTime", TypeBool,     Offset( mUseTargetTime, GuiTimerCtrl ) );
	addField( "targetTime", TypeS32,         Offset( mTargetTime, GuiTimerCtrl ) );
	addField( "currentTime", TypeS32,        Offset( mCurrTime, GuiTimerCtrl ) );
}

// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //

bool GuiTimerCtrl::onAdd()
{
	if(!Parent::onAdd())
		return false;
	dStrncpy(mText, (UTF8*)mInitialText, MAX_STRING_LENGTH);
	mText[MAX_STRING_LENGTH] = '';
	return true;
}

void GuiTimerCtrl::inspectPostApply()
{
	Parent::inspectPostApply();
}

bool GuiTimerCtrl::onWake()
{
	if ( !Parent::onWake() )
		return false;
	
	mFont = mProfile->mFont;
	AssertFatal(mFont, "GuiTimerCtrl::onWake: invalid font in profile" );
	
	if ( mConsoleVariable[0] )
	{
		const char *txt = Con::getVariable( mConsoleVariable );
		if ( txt )
		{
			if ( dStrlen( txt ) > mMaxStrLen )
			{
				char* buf = new char[mMaxStrLen + 1];
				dStrncpy( buf, txt, mMaxStrLen );
				buf[mMaxStrLen] = 0;
				setScriptValue( buf );
				delete [] buf;
			}
			else
				setScriptValue( txt );
		}
	}
	
	//resize
	if ( mProfile->mAutoSizeWidth )
	{
		if ( mProfile->mAutoSizeHeight )
			resize( mBounds.point, Point2I( mFont->getStrWidth((const UTF8 *) mText ), mFont->getHeight() + 4 ) );
		else
			resize( mBounds.point, Point2I( mFont->getStrWidth((const UTF8 *) mText ), mBounds.extent.y ) );
	}
	else if ( mProfile->mAutoSizeHeight )
		resize( mBounds.point, Point2I( mBounds.extent.x, mFont->getHeight() + 4 ) );
	
	return true;
}

void GuiTimerCtrl::onSleep()
{
	Parent::onSleep();
	mFont = NULL;
}

//------------------------------------------------------------------------------
void GuiTimerCtrl::onRender(Point2I offset, const RectI &updateRect)
{	
	//Get the current time from the control. We always do this regardless of our next action.
	char buf[256]; 
	S32 time;
	time = mCurrTime;
	
	if(!mTimePaused)
	{
		//We've got our time... but what to do with it? Add or subtract from it...
		if(mReversed)
		{
			time = mCeil(time -= 1);
			
			//We can't have a negative time. That would require us to be using fluxCapictor.h, which is missing from this project.
			if(time < 0)
				time = 0;
		}
		else
			time = mCeil(time += 1);
		
		
		mCurrTime = time;
	}

	
	if(time <= 0)
	{
		//If the time is 0, then there is nothing else to do. We can hard-code this value.
		dSprintf(buf,sizeof(buf), "00:00"); 
	}
	else
	{
		//This time is stored in MS. Let's convert it into something a bit more manageable... seconds!
		// We're not worried about the milliseconds for this timer, so just get the ceiling value.
		time = mCeil(time / 1000);
		
		//Now let's figure out what that is in actual, human-readable time.
		S32 secs = time % 60;  
		S32 mins = (time % 3600) / 60;  
		S32 hours = time / 3600;  
		
		
		//Now let's display it to the user.
		if(mTimerExpired) //Timer has been expired - just write out the last time stored.
		{
			dSprintf(buf,sizeof(buf), "%f", time); 
		}
		else
		{
			dSprintf(buf,sizeof(buf), "%02d:%02d",mins,secs);  
		}
	}
	
	//If we had a target time, watch for that here.
	if(mUseTargetTime && !mTimePaused && !mTimerExpired)
	{
		if(mReversed)
		{
			if(mCurrTime <= mTargetTime)
			{
				Con::executef(1,"stageTimeExpired");
				mTimerExpired = true;
			}
		}
		else
		{
			if(mCurrTime >= mTargetTime)
			{
				Con::executef(1,"stageTimeExpired");
				mTimerExpired = true;
			}
		}
	}
	
	// Center the text  
	offset.x += (mBounds.extent.x - mProfile->mFont->getStrWidth(buf)) / 2;  
	offset.y += (mBounds.extent.y - mProfile->mFont->getHeight()) / 2;   
	dglDrawText(mProfile->mFont, offset, buf);  
	dglClearBitmapModulation();  

	dglSetBitmapModulation( mProfile->mFontColor );
	
	//render the child controls
	renderChildControls(offset, updateRect);
}



// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- //
// EOF //


GuiTimerCtrl.h
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#ifndef _GuiTimerCtrl_H_
#define _GuiTimerCtrl_H_

#ifndef _GFONT_H_
#include "dgl/gNewFont.h"
#endif
#ifndef _GUITYPES_H_
#include "gui/core/guiTypes.h"
#endif
#ifndef _GUICONTROL_H_
#include "gui/core/guiControl.h"
#endif

class GuiTimerCtrl : public GuiControl
	{
	private:
		typedef GuiControl Parent;
		
	public:
		enum Constants { MAX_STRING_LENGTH = 1024 };
		
		
	protected:
		StringTableEntry mInitialText;
		StringTableEntry mInitialTextID;
		UTF8 mText[MAX_STRING_LENGTH + 1];
		S32 mMaxStrLen;   // max string len, must be less then or equal to 255
		Resource<GFont> mFont;
		
	public:
		
		//creation methods
		DECLARE_CONOBJECT(GuiTimerCtrl);
		GuiTimerCtrl();
		static void initPersistFields();
		
		//Parental methods
		bool onAdd();
		virtual bool onWake();
		virtual void onSleep();
			
		void inspectPostApply();
		//rendering methods
		void onRender(Point2I offset, const RectI &updateRect);
		
		bool mReversed;
		bool mTimePaused;
		bool mTimerExpired;
		bool mUseTargetTime;
		
		S32 mCurrTime;
		S32 mTargetTime;
	};

#endif //_GUI_TEXT_CONTROL_H_

#1
08/09/2009 (12:46 pm)
There's a couple quirks with this in 1.5.2 (and my compiler VS2005 Pro). Here are the changes I made to get it to work:

In the .h file, the original asks for:
#include "dgl/gNewFont.h"

In 1.5.1+ this file has been changed to gFont.h. Thus you'll need to change it to:
#include "dgl/gFont.h"

The latter issue (two occurrences) might be compiler specific. I was getting this error in the .cpp file:
13>c:torque\tge_1_5_2\engine\gui\controls\guitimerctrl.cpp(22) : error C2137: empty character constant

To resolve this, I changed these two lines from having '' to '\0':
Line 22: mText[0] = '\0';
Line 48: mText[MAX_STRING_LENGTH] = '\0';

With these changes, I was able to get the control to work just fine.
#2
08/09/2009 (12:48 pm)
Great addition! I actually wrote this for Torque2D for iPhone, so I'm not surprised that it needed a tweak to work in another engine. I added a note above so people know what it was written for. :)