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.