TUTORIAL 21 - Scrolling Credits
by Iain
McGinniss
When you have finished you mod, it is often a good idea to leave
the user with a good final sequence which will help them to remember all that
they have played, how much they have enjoyed it, and give a lasting impression
of just how good (or how bad) your mod was. The ordinary exit sequence in the iD
code is a bland affair, just a simple black screen with the "iD Software
is:" screen. The secondary problem for your mod is that you will want to
have you and your team's names in this final sequence, yet still have the iD
software credits there to pay homage to the geniuses who helped create this
beautiful engine for us all to play with.
And so, the best solution for this sort of thing is a
cinema-style scrolling credits sequence. Film producers discovered from a very
early stage that using either switching screens with a few seconds delay in
between, or a more elegant scrolling credits sequence that they could elongate
the final moments of their films for the viewers and also display the full
credits list for the entire project. While this has become wildly out of hand
with movies with people such as the hydration-engineer (coffee maker) and
sanitary technician (cleaner) appearing on the credits, the idea is still sound:
with suitable music the credits can round off a film quite nicely. This also
works quite well for games, and a prime example that most of you will remember
if you were around in the Quake 2 days was action quake, where a scrolling
credits sequence and the theme to the A-Team was added. This, as far as I can
tell, was the first mod that used such a sequence and when I think about it, it
brings back all the memories of the glorious head-shots on my best friends.
Anyway, enough of the background knowledge. To the code!
1. CODING THE SCROLLING CREDITS
For the credits code, we need a versatile piece of code and an easy-to-modify
data set that contains all of our credits in sequential order, from top to
bottom. As this is a menu-based piece of code, it is placed within the UI
project of the source, and for our purposes we will edit the ui_credits.c
file. The thing is though, none of the code that currently exists in the file is
of any real use to us, so into the recycle bin it goes. We will start from
scratch, and work our way up. So, clean out the ui_credits.c file and put in the
following...
// INCLUDE FILES
#include "ui_local.h"
// CONSTANT DEFINITIONS
#define SCROLLSPEED 2.00 // The scrolling speed in pixels per second.
// modify as appropriate for our credits
// #define BACKGROUND_SHADER
// uncomment this to use a background shader, otherwise a solid color
// defined in the vec4_t "color_background" is filled to the screen
// STRUCTURES
typedef struct {
menuframework_s menu;
} creditsmenu_t;
static creditsmenu_t s_credits;
int starttime; // game time at which credits are started
float mvolume; // records the original music volume level, as we will
// modify it for the credits
// change this to change the background colour on credits
vec4_t color_background = {0.00, 0.35, 0.69, 1.00};
// these are just example colours that are used in credits[]
vec4_t color_headertext = {0.53, 0.77, 1.00, 1.00};
vec4_t color_maintext = {1.00, 1.00, 1.00, 1.00};
qhandle_t BackgroundShader; // definition of the background shader pointer
Okay, so I'll explain these variables before we go any further. SCROLLSPEED is a
float that defines the rate at which the words travel up the screen, so by
changing this you can have a fast set of credits (if you have a lot of team
members) or a slow set of credits (if you want to burn your name into the
player's head). BACKGROUND_SHADER is a constant that tells the code whether or
not we want to render a shader (an animated texture) onto the background, or
just a plain color. While a shader can produce very dramatic effects when
combined with good artwork and a solid understanding of blending techniques,
this can be very costly in terms of performance and so if we only need a plain
color, we should just draw as such (filling a solid color into the background
before drawing the credits). The creditsmenu_t structure is just taken from the
old code (so it was not entirely useless). The vec4_t colours are used to
describe the text in our credits sequence as you will see below, and
BackgroundShader is a quake pointer to the shader as cached by the Q3 Engine.
Now, we get to the important stuff:
typedef struct
{
char *string;
int style;
vec4_t *colour;
} cr_line;
cr_line credits[] = { // edit this as necessary for your credits
{ "Scrolling Credits Sequence", UI_CENTER|UI_BIGFONT|UI_PULSE, &color_headertext },
{ "Design by Iain McGinniss", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Special Thanks To:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "ID Software", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "Code 3 Arena", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "The Gamespy Network", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "iD Software is:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Programming:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "John Carmack, John Cash", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Art:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "Adrian Carmack, Kevin Cloud,", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "Paul Steed, Kenneth Scott", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Game Designer:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "Graeme Devine", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Level Design:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "Tim Willits, Christian Antkow", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "Paul Jaquays", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "CEO:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "Todd Hollenshead", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Director of Business Development:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "Katherine Anna Kang", UI_CENTER|UI_SMALLFONT, &color_maintext },
{ "", UI_CENTER|UI_SMALLFONT, &color_blue },
{ "Biz Assist and id mom:", UI_CENTER|UI_SMALLFONT, &color_headertext },
{ "Donna Jackson", UI_CENTER|UI_SMALLFONT, &color_maintext },
{NULL}
};
Okay, let's cover this section. This is the actual definition of the
structure that will contain a single line of text on the credits (cr_line),
which contains the string, the formatting of the characters and the color of the
line (in full RGBA form). This structure is then used to create a full array of
all the lines in the credits sequence (credits[]). If you are at least
reasonably competent with the C language then this kind of structure use should
be routine for you.
For the formatting, we are using the standard iD enumerated constants of the
form UI_*. Here is a full list and what they will do to the text:
UI_LEFT - Align to the left of the
screen. UI_CENTER - Align to the center. UI_RIGHT - Align to the right of the screen. UI_FORMATMASK - Not sure... I'm sure if you search for it
being used in other sections of the UI code it's use will become clear. UI_SMALLFONT - Small font. UI_BIGFONT - Big font. UI_GIANTFONT - Giant font. UI_DROPSHADOW - A drop shadow is created behind the
text. UI_BLINK - The text blinks. UI_INVERSE - The text is inverted? I've not tested this so
I am not entirely sure. UI_PULSE - The text pulses
(an example of this in the standard code is where the mouse pointer is moved
over any menu item, and it pulses).
Now, we will do the actual coding for the credits (the really important
part!):
/*
=================
UI_CreditMenu_Key
=================
*/
static sfxHandle_t UI_CreditMenu_Key( int key ) {
if( key & K_CHAR_FLAG ) {
return 0;
}
// pressing the escape key or clicking the mouse will exit
// we also reset the music volume to the user's original
// choice here, by setting s_musicvolume to the stored var
trap_Cmd_ExecuteText( EXEC_APPEND,
va("s_musicvolume %f; quit\n", mvolume));
return 0;
}
/*
=================
ScrollingCredits_Draw
This is the main drawing function for the credits.
Most of the code is self-explanatory.
=================
*/
static void ScrollingCredits_Draw(void)
{
int x = 320, y, n, ysize = 0, fadetime = 0;
vec4_t fadecolour = { 0.00, 0.00, 0.00, 0.00 };
// ysize is used to determine the entire length
// of the credits in pixels.
// We can then use this in further calculations
if(!ysize) // ysize not calculated, so calculate it dammit!
{
// loop through entire credits array
for(n = 0; n <= sizeof(credits) - 1; n++)
{
// it is a small character
if(credits[n].style & UI_SMALLFONT)
{
// add small character height
ysize += PROP_HEIGHT * PROP_SMALL_SIZE_SCALE;
// it is a big character
}else if(credits[n].style & UI_BIGFONT)
{
// add big character size
ysize += PROP_HEIGHT;
// it is a huge character
}else if(credits[n].style & UI_GIANTFONT)
{
// add giant character size.
ysize += PROP_HEIGHT * (1 / PROP_SMALL_SIZE_SCALE);
}
}
}
// first, fill the background with the specified colour/shader
// we are drawing a shader
#ifdef BACKGROUND_SHADER
UI_DrawHandlePic(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, BackgroundShader);
// we are just filling a color
#else
UI_FillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color_background);
#endif
// let's draw the stuff
// set initial y location
y = 480 - SCROLLSPEED * (float)(uis.realtime - starttime) / 100;
// loop through the entire credits sequence
for(n = 0; n <= sizeof(credits) - 1; n++)
{
// this NULL string marks the end of the credits struct
if(credits[n].string == NULL)
{
if(y < -16) // credits sequence is completely off screen
{
trap_Cmd_ExecuteText( EXEC_APPEND,
va("s_musicvolume %f; quit\n", mvolume));
break; // end of credits
}
break;
}
if( strlen(credits[n].string) == 1) // spacer string, no need to draw
continue;
if( y > -(PROP_HEIGHT * (1 / PROP_SMALL_SIZE_SCALE)))
// the line is within the visible range of the screen
UI_DrawProportionalString(x, y, credits[n].string,
credits[n].style, *credits[n].colour );
// re-adjust y for next line
if(credits[n].style & UI_SMALLFONT)
{
y += PROP_HEIGHT * PROP_SMALL_SIZE_SCALE;
}else if(credits[n].style & UI_BIGFONT)
{
y += PROP_HEIGHT;
}else if(credits[n].style & UI_GIANTFONT)
{
y += PROP_HEIGHT * (1 / PROP_SMALL_SIZE_SCALE);
}
// if y is off the screen, break out of loop
if (y > 480)
break;
}
}
/*
===============
UI_CreditMenu
===============
*/
void UI_CreditMenu( void ) {
memset( &s_credits, 0 ,sizeof(s_credits) );
s_credits.menu.draw = ScrollingCredits_Draw;
s_credits.menu.key = UI_CreditMenu_Key;
s_credits.menu.fullscreen = qtrue;
UI_PushMenu ( &s_credits.menu );
starttime = uis.realtime; // record start time for credits to scroll properly
mvolume = trap_Cvar_VariableValue( "s_musicvolume" );
if(mvolume < 0.5)
trap_Cmd_ExecuteText( EXEC_APPEND, "s_musicvolume 0.5\n" );
trap_Cmd_ExecuteText( EXEC_APPEND, "music music/fla22k_02\n" );
// load the background shader
#ifdef BACKGROUND_SHADER
BackgroundShader =
trap_R_RegisterShaderNoMip("*YOURSHADER_HERE*");
#endif
}
Okay, that's a lot of code to absorb in one go. There are a lot of comments in
there that explain the functionality. I suggest you read it in reverse
order to get the full picture, read through UI_CreditMenu first, then
ScrollingCredits_Draw, then UI_CreditMenu_Key. UI_CreditMenu is the
initialisation section for the code, telling the Quake 3 Engine that we are now
rendering our menu from this section of code, directing it to
ScrollingCredits_Draw for drawing and to UI_CreditMenu_Key for the key handling.
An important bit in UI_CreditMenu is just beyond "UI_PushMenu
(&s_credits.menu)" where the music is initialized, a key part of the goal we
set out for ourselves. mvolume records the initial volume of the music from the
client's configuration, then a decision is made on whether the music is too
quiet or not. If it is, then we set it to a decent level so that the music will
be heard by the client. The line
trap_Cmd_ExecuteText( EXEC_APPEND, "music music/fla22k_02\n" );
is where the command is sent to the client's console to start the music. The
music I have chosen for the credits is one of the standard tracks that are
included with quake 3 arena, fla22k_02. Any of the tracks which are in the music
directory of pak0.pk3 can be used, or even your own if you are not using some
funky compression format (yes, that means no MP3's unfortunately. Harass John
Carmack to add MP3 support to the engine!).
When the credits are finished or the user wants to quit out by pressing a
key, we want to reset the music volume to the user's default so next time they
play, they are not offended by the music blaring out during their mod
experience. This can easily be done by issuing a cvar change command onto the
console, just as if the user had typed it themselves. This is shown in the line
trap_Cmd_ExecuteText( EXEC_APPEND,
va("s_musicvolume %f; quit\n", mvolume));
In both the ScrollingCredits_Draw function and the UI_CreditMenu_Key
function. All it does is type "s_musicvolume <uservolume>;
quit<ENTER KEY>" onto the console, which sets the music volume back
to the default and quits the game. Simple!
In the example code here I have disabled the background shader drawing,
basically because you will need to create your own shader file to render
whatever you want into the background. This is simple enough, just download the
shader documentation from iD software and read through, learn how to use the
blendfunc commands and so on. It's all about creativity, young padawan.
1. BEYOND THUNDERDROME...
So that's it! When you compile this code alongside the rest of the UI project
and load it up, you will have a fully functioning scrolling credits system (ooo!)
which is very easy to modify. To add to the credits, just modify the credits[]
array, adding new lines with the necessary formatting elements, set your colour
and you're laughing. I've already used this code in 2 of my mods, and it works
very well, I've had some nice comments from players about it...
This is not the end however. Homework time! To add to the full cinema feel, you could add
FULL shader support to the code, meaning that animated images and suchlike can
be added to the scrolling section of the code and so on. This can then be
further modified by creating new text formatting modes to fit around graphics
and so on. This would take a lot of work over the existing code, requiring
coordinates and so on to be added to the credits[] array and new code to deal
with this, but in the end you could have a very nice piece of code that you can
be proud of, and hang up on the wall next to your PC GAMER posters (you do have
PC Gamer posters, right?).
Anyway, I hope you have all found this tutorial interesting and useful. To
save all your typing, you can
download the ui_credits.c file for all the code
modifications. Iain
out...
|