Game Development Community

Startup Splash Screen

by Robert Blanchet Jr. · 06/24/2002 (12:08 pm) · 17 comments

Download Code File

Unfortunately this resource is going to be a bit bare bones.
I had originally spent an hour typing up this resource only
for it to be lost to the internet gods and I lack the motivation
to try and reproduce all of the work that I lost.

Below I'll provide the code and script necessary for you to add
a splash screen fade in/fade out effect to your games startup
sequence. The primary motivation behind this resource was
Phil Carlisle's RWTA startup splash screens and the new Garage
Games startup sequence to the new Tribes 2 patch.

I used Phil's script code as a base for altering the startup gui
sequence and then added in my own source code to produce the
graphics effects.

To begin I want you to load up your trusty Compiler and add two
files to the gui folder of the Torque Workspace, a header file,
and a C++ source file. Open up the header file and add the
following:
#ifndef _GUISPLASHSCREEN_H_
#define _GUISPLASHSCREEN_H_

#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _GUICANVAS_H_
#include "gui/guiCanvas.h"
#endif
#ifndef _GUICONTROL_H_
#include "gui/guiControl.h"
#endif
#ifndef _DGL_H_
#include "dgl/dgl.h"
#endif
#ifndef _GBITMAP_H_
#include "dgl/gBitmap.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif
#ifndef _GCHUNKEDTEXMANAGER_H_
#include "dgl/gChunkedTexManager.h"
#endif

class GuiSplashScreen : public GuiControl
{
private:

	typedef GuiControl Parent;
	void renderRegion(const Point2I &offset, const Point2I &extent);

protected:
	
	// Splash Screen Bitmap Name
	StringTableEntry mLogoName;

	// Currently selected bitmap
	ChunkedTextureHandle mTexHandle;

	// Keep track of time
	F32 mFadeFactor;
	F32 mTime;

	// Keep track of fade state
	bool mFadeIn;
   
public:

	// Creation methods
	DECLARE_CONOBJECT(GuiSplashScreen);

	GuiSplashScreen();
	static void initPersistFields();
	static void consoleInit();

	// Parental methods
	bool onWake();
	void onSleep();

	void onRender(Point2I offset, const RectI &updateRect);
};

#endif // _GUISPLASHSCREEN_H_
There is nothing really special about the header file that we need
to discuss. You'll notice, however, that I'm going to be using
some of the same code from guiChunkedBitmapCtrl. Unfortunately
that control doesn't have an associate header file so I'm forced to
either, make one for it, or re-invent the wheel, since I'm lazy
we are going to re-invent the wheel ;)

Go ahead and save the header file and open up the C++ source file.
There is a lot of code here but I've provided comments just in case
you get confused. If you still can't figure out how something works,
please feel free to ask here in the forums. Now that you have
the C++ source file open, go ahead and add all of the following
code:
#include "gui/guiSplashScreen.h"

IMPLEMENT_CONOBJECT(GuiSplashScreen);

void GuiSplashScreen::initPersistFields()
{
	Parent::initPersistFields();
	addField( "bitmap",        TypeFilename,  Offset( mLogoName, GuiSplashScreen ) );
	addField( "fadeFactor",       TypeF32,		 Offset( mFadeFactor, GuiSplashScreen) );
}

void GuiSplashScreen::consoleInit()
{
	// do nothing
}

GuiSplashScreen::GuiSplashScreen()
{
	// Clear bitmap names
	mLogoName = StringTable->insert("");

	// Clear times
	mTime = 0.0f;

	// Set Fade State
	mFadeIn = true;
}

bool GuiSplashScreen::onWake()
{
	if(!Parent::onWake())
	{
		return false;
	}
	mTexHandle = ChunkedTextureHandle( mLogoName );
	
	// Fades
	mFadeFactor *= 10000.0f;   // Multiply by 10,000 so the fade is produced over a long period of time
	mTime = 0.0f;
	mFadeIn = true;

	return true;
}

void GuiSplashScreen::onSleep()
{
	// Clear everything
	mTexHandle = NULL;
	mFadeFactor = 0.0f;
	mTime = 0.0f;
	mFadeIn = true;

	Parent::onSleep();
}

// Function taken from guiChunkedBitmapCtrl
void GuiSplashScreen::renderRegion(const Point2I &offset, const Point2I &extent)
{
	U32 widthCount = mTexHandle.getTextureCountWidth();
	U32 heightCount = mTexHandle.getTextureCountHeight();
	if(!widthCount || !heightCount)
	{
		return;
	}

	F32 widthScale = F32(extent.x) / F32(mTexHandle.getWidth());
	F32 heightScale = F32(extent.y) / F32(mTexHandle.getHeight());
	dglSetBitmapModulation(ColorF(1,1,1));
	for(U32 i = 0; i < widthCount; i++)
	{
		for(U32 j = 0; j < heightCount; j++)
		{
			TextureHandle t = mTexHandle.getSubTexture(i, j);
			RectI stretchRegion;
			stretchRegion.point.x = (S32)(i * 256 * widthScale  + offset.x);
			stretchRegion.point.y = (S32)(j * 256 * heightScale + offset.y);

			if(i == widthCount - 1)
			{
				stretchRegion.extent.x = extent.x + offset.x - stretchRegion.point.x;
			}
			else
			{
				stretchRegion.extent.x = (S32)((i * 256 + t.getWidth() ) * widthScale  + offset.x - stretchRegion.point.x);
			}

			if(j == heightCount - 1)
			{
				stretchRegion.extent.y = extent.y + offset.y - stretchRegion.point.y;
			}
			else
			{
				stretchRegion.extent.y = (S32)((j * 256 + t.getHeight()) * heightScale + offset.y - stretchRegion.point.y);
			}
			dglDrawBitmapStretch(t, stretchRegion);
		}
	}
}  
 
void GuiSplashScreen::onRender(Point2I offset, const RectI &updateRect)
{
	// Make sure we have a texture handle
	if(mTexHandle)
	{
		if(mFadeIn == true)
		{
			// Fade In Effect
			if(mTime < mFadeFactor)
			{
				// Calculate alpha for the overlayed rectangle over the logo
				F32 fade = 1 - (mTime / mFadeFactor);

				// Render the logo
				renderRegion(offset, mBounds.extent);

				// Render a rectangle the size of the screen over the top of our logo
				dglDrawRectFill(mBounds, ColorF(0, 0, 0, fade));

				// Make sure to update the scene
				setUpdate();

				// Update time
				mTime += F32(Platform::getVirtualMilliseconds());
			}
			else
			{
				// We are done fading in, so lets fade back out
				mFadeIn = false;
				mTime = 0.0f;
			}
		}
		else
		{
			// Fade Out Effect
			if(mTime < mFadeFactor)
			{
				// Render the logo
				renderRegion(offset, mBounds.extent);

				// Render a rectangle the size of the screen over the top of our logo
				dglDrawRectFill(mBounds, ColorF(0, 0, 0, (mTime / mFadeFactor)));

				// Update the scene
				setUpdate();

				// Update time
				mTime += F32(Platform::getVirtualMilliseconds());
			}
			else
			{
				// We are done with both the fade in and fade out effect,
				//  so pop the control off the canvas
				getRoot()->popDialogControl(this);
			}
		}
	}
	else
	{
		// We didn't have a texture handle, so might as well pop the control off the canvas right away
		getRoot()->popDialogControl(this);
	}
}
Again if you need help, just ask :)

Now that we are done with the source code portion, we can go ahead and compile the engine
while we move on to the next section.

The first thing we need to add to the scripts is a GUI script that creates an instance
of our new control and defines its properties. So open up your favorite text editor
and type out the following:
//--- OBJECT WRITE BEGIN ---
new GuiSplashScreen(StartupGui) {
   profile = "GuiDefaultProfile";
   horizSizing = "width";
   vertSizing = "height";
   position = "0 0";
   extent = "640 480";
   minExtent = "8 8";
   visible = "1";
   helpTag = "0";
   bitmap = "./gg_logo";
   fadeFactor = "50"; // value is multiplied by 10,000 internally
   noCursor=1;
   new GuiInputCtrl(StartupInputCtrl) {
      profile = "GuiInputCtrlProfile";
      position = "0 0";
      extent = "10 10";
   };
};
//--- OBJECT WRITE END ---
There are only two things to really discuss here. One, you'll notice that I tell
the gui control that my bitmap is "gg_logo" and that my fadeFactor is 50. Which
is then multiplied by 10,000 in the hardcode. You should recognize these two
properties from our C++ code earlier. The other important thing to notice
is that I define another control within the first control. This control will be
responsible for recognizing input from either the mouse or the keyboard.

Save this script as StartupGui.gui and place it in the torque folder fps/client/ui
It's very important that you make sure it's in that location and that the extension
is *.gui and not *.gui.txt.

Next we need to open up init.cs from the location fps/client/
There are several changes and additions we need to make to this file
so that we can add our new control to the startup sequence of Torque.
Scroll through this file until you find the following line of code:
// Load up the shell GUIs
exec("./ui/mainMenuGui.gui");
And change it to this:
// Load up the shell GUIs
exec("./ui/StartupGui.gui"); // Startup Splash
exec("./ui/mainMenuGui.gui");

Now search further down and find:
// Start up the main menu... this is separated out into a 
// method for easier mod override.
loadMainMenu();
And change it to the following:
// Display our startup splash screen
Canvas.setContent( StartupGui );
Now that we've made sure our splash screen is the first thing to display
on the screen, we need to make sure that the MainMenuGui is loaded after
the fade in/fade out sequence is done, or if there is user input.

Just a bit further down you'll find the following:
function loadMainMenu()
{
   // Startup the client with the Main menu...
   Canvas.setContent( MainMenuGui );
   Canvas.setCursor("DefaultCursor");
}
Add 2 functions before that which read:
function StartupGui::onSleep(%this)
{
   loadMainMenu();
}

function StartupInputCtrl::onInputEvent(%this, %dev, %evt, %make)
{
   if(%make)
   {
      loadMainMenu();
   }
}
onSleep is called whenever the control associated with it is popped off the
GUI Canvas. This can happen through a number of ways. In our case it
happens when our fade in/fade out effect has finished. onInputEvent is
called whenever the user clicks the mouse or hits a key on the keyboard.

Go ahead and save the changes to init.cs
In the zip file I've provided a sample logo splash screen. You may wish to use
this or make your own, but make sure that it is located in fps/client/ui and
that the bitmap field of our StartupGui.gui script is changed to reflect the
new image.

Again, if you have any questions, feel free to ask. I hope you enjoy this resource.

#1
06/24/2002 (12:29 pm)
Very nice, wish you didnt just post code but would have posted more of a concept tutorial with code snippets or something. But still good job.
#2
06/24/2002 (1:59 pm)
This can also be done by simply using a gui control made by Melvyn May which masks the screen to a color, what i did was simplify melv's code a bit so it wont use masks, etc and added it to the canvas as a new set of functions. Same thing, same effect, nice work
#3
06/24/2002 (2:17 pm)
Thanks for the comments. Next time I'll be sure to explain more of what is going on and why it happens that way for tutorials.

You're right Xavier, I could use Melv's mask code to do this but chose not to for a variety of reasons.
#4
06/24/2002 (2:32 pm)
Nice one, and kinda primordial, since every team are eventually going to want their own splashup screen, whether accompanied by the GG/Torque one or not (don't remember if the license is specific about this or not).
Can't imagine what the ressource would have been before you lost it : cool by itself, but in context, very, very nice for someone who had just lost at least an hour of work down the cyber-drain... ;)
Bravo, and thank you
#5
06/25/2002 (2:59 pm)
what are some of those reasons robert?
#6
06/25/2002 (5:08 pm)
To name a few:
1. To learn something from the experience
2. Personal achievement ;p
3. Showing others yet another way to do something
#7
06/27/2002 (9:21 am)
ahh ok, gotcha, :)
thought you found some tech or performance issues
#8
06/29/2002 (8:47 am)
Another usefull addition would be to modify the code so you can fade between gui elements.

Currently when the fading is done, it does a popDialogControl; which immediatly removes the faded screen and causes the maingui to be pushed to the top.

Why not have the fade out actualy fade to the next gui (maingui)?

-Ron
#9
06/29/2002 (12:13 pm)
Ron, the reason behind popping the control off immediately is because this was designed as a specialized class in which its sole lot in life was to show a splash screen and then remove itself.

I don't really understand what you are trying to say in your last question. Currently this control does fade to the MainGUI, it's just not hardcoded.

Unless you mean fading to the MainGUI directly without going to black then I don't really understand your question.
#10
07/10/2002 (5:24 pm)
Robert, I found this resource very useful. I think there might be a prob however: adding this appears to "kill" the show tool. I created two identical apps, one with the splash screen, one without. One with would not open show tool. hmmm. Don't have a clue what might cause this but thought you could get to it quicker than I.
#11
08/15/2003 (4:45 pm)
Does this support multiple splash screen's? for example, a garagegames logo, then my own logo, then start of game. thanks chris
#12
12/09/2003 (6:23 am)
WoW this is a dated resource :) but I have new info to add :)
@Desmond the reason for the nonfunctional show tool is because of these functions in the resource

function loadMainMenu()
{   // Startup the client with the Main menu...   Canvas.setContent( MainMenuGui );   
Canvas.setCursor("DefaultCursor");
}


function StartupGui::onSleep(%this)
{   
loadMainMenu();
}
function StartupInputCtrl::onInputEvent(%this, %dev, %evt, %make)
{   
if(%make)   
{      loadMainMenu();   
}
}

as soon as the Fade effect is done it pops the MainMenu which in turn breaks the show tool. I have fixed this by chaning the loadMainMenu() function as such

function loadMainMenu()
{
        echo("loadMainMenu()");
   // Startup the client with the Main menu..
                if( $currentMod $= "show" )
                {
                        Canvas.setContent( TSShowGui );          
                        Canvas.setCursor("DefaultCursor");
                } else {
                        Canvas.setContent( MainMenuGui );
                        Canvas.setCursor("DefaultCursor");
                }
}

also note that the StartupGui::onSleep() function causes a crash when you use this resource with the latest HEAD. You get an "Control is already sleeping" assert and crash.

-Ron
#13
03/25/2004 (4:03 pm)
I've just updated to the latest release 1.2.0 and I'm getting this Already sleeping error. I don't know enough about the way the gui control system works at the moment to fix it, but if you have a quick solution I'd appreciate it.

Just commenting out the parent::onSleep(); seems to make it crash for me...
of course that could be a different crash caused by something totally unrelated.
#14
07/30/2004 (7:21 am)
We started using Blachet's splash screen resource with Version 1.2.2 of the engine and it worked like a dream. This week we desided that we needed to start using the Torque HEAd and now we get a chrash because the "GuiControl::sleep:Child control is already asleep".

Our programmer is in the process of taking both the GuiControl and GuiSplashScreen code apart to see if I can find out what is happening but so far nothing stands out. Does anyone have a clue as to what is happening here?
#15
07/30/2004 (12:16 pm)
There was a forum discussion re: the sleep function causing issues related to dedicated servers I think (can't remember exactly)....and it was changed in the HEAD.
#16
03/15/2005 (2:56 am)
We are also getting the same error "GuiControl::sleep:Child control is already asleep". Anyone has a solution?
#17
12/05/2005 (11:38 am)
why not use the GuiFadeinBitmapCtrl instead?