Allar's Blog

Allar's Blog

Beginning Your Game Part 3

IMPORTANT

This tutorial was created with the March build of UDK, as opposed to the February build.

This is a big -MY BAD- on my part.

The rest of the tutorials flow nicely after this. I promise.

Here is what you do:

You download the March build.

You install it.

You set up the config files just like you have been doing in Part 1 and Part 2.

YOU THEN DOWNLOAD THIS .ZIP which has the code finished from Part 3. Don't worry about what you are missing out, if you read through the tutorial you'll get the important stuff.

Open UTGameConfigDefaultGame.ini

Replace the Engine.GameInfo block with the following:

[csharp][Engine.GameInfo]
DefaultGame=UDKGame.UDKGame
DefaultServerGame=UDKGame.UDKGame
PlayerControllerClassName=UDKGame.HTPlayerController
GameDifficulty=+1.0
MaxPlayers=32
DefaultMapPrefixes=(Prefix="HT",GameType="UDKGame.TheHuntGame")[/csharp]

What we did there was move the DefaultMapPrefixes from DefaultProperties to this .ini file, as thats where the March build put it. You will learn how to do stuff like this later on as well.

When you compile, you will get a warning about HTInventoryManager. This will fix itself when you complete step 4.

Then continue to Step 4.

Also, skip Migrating From Feb. To March. since you are now using the March build. <_<

Sorry. These were my first tutorials I've made and I was bound to mess up somewhere. All the future tutorials after this will go smoothly. I will try to redo these first three later when I have some spare time.

Video Version

Subject: Beginning Your Game Part 3
Skill Level: Beginner
Run-Time: 16 Minutes
Author: Michael Allar
Notes: The project I'm working on as I'm creating this documentation requires the classes we've made to be based off higher level classes. These were the changes made.

Written Version

Subject: Beginning Your Game Part 3
Skill Level: Beginner
Author: Michael Allar
Notes: The project I'm working on as I'm creating this documentation requires the classes we've made to be based off higher level classes. These were the changes made.

In order to make these changes, you must be using the February build or later of UDK.

Why Is There A Part 3?

I am the Lead Programmer for a student ran project at The Art Institute of Orange County called The Hunt. It is a standalone game using Unreal Engine 3 being developed with the Unreal Development Kit. I am creating these tutorials as I progress in development of The Hunt and we've recently had a need to base our code off of higher level classes within the engine. Our goal is to stay away from as much UT code as possible without rewriting too much of everything. Instead of extending UTGame, UTPlayerController, and UTPawn, we will be extending GameInfo, UDKPlayerController, and UDKPawn.

UDKGame now extends GameInfo

/*******************************************************************************  
    UDKGame  

    Creation date: 14/01/2010 13:55  
    Copyright (c) 2010, Michael Allar, Epic  

*******************************************************************************/  

class UDKGame extends GameInfo  
    config(UDKGame);  

struct GameTypePrefix  
{  
    var string Prefix;  
    var string GameType;  
};  

var array&lt;GameTypePrefix&gt; DefaultMapPrefixes;  

static event class&lt;GameInfo&gt; SetGameType(string MapName, string Options, string Portal)  
{  
    local string ThisMapPrefix;  
    local int i,pos;  
    local class&lt;GameInfo&gt; NewGameType;  

    if (Left(MapName, 10) ~= &quot;HTFrontEnd&quot;)  
    {  
        return class'UDKGame';  
    }  

    // strip the UEDPIE_ from the filename, if it exists (meaning this is a Play in Editor game)  
    if (Left(MapName, 6) ~= &quot;UEDPIE&quot;)  
    {  
        MapName = Right(MapName, Len(MapName) - 6);  
    }  
    else if ( Left(MapName, 5) ~= &quot;UEDPC&quot; )  
    {  
        MapName = Right(MapName, Len(MapName) - 5);  
    }  
    else if (Left(MapName, 6) ~= &quot;UEDPS3&quot;)  
    {  
        MapName = Right(MapName, Len(MapName) - 6);  
    }  
    else if (Left(MapName, 6) ~= &quot;UED360&quot;)  
    {  
        MapName = Right(MapName, Len(MapName) - 6);  
    }  

    // replace self with appropriate gametype if no game specified  
    pos = InStr(MapName,&quot;-&quot;);  
    ThisMapPrefix = left(MapName,pos);  

    // change game type  
    for ( i=0; i&lt;Default.DefaultMapPrefixes.Length; i++ )  
    {  
        if ( Default.DefaultMapPrefixes[i].Prefix ~= ThisMapPrefix )  
        {  
            NewGameType = class&lt;GameInfo&gt;(DynamicLoadObject(Default.DefaultMapPrefixes[i].GameType,class'Class'));  
            if ( NewGameType != None )  
            {  
                return NewGameType;  
            }  
        }  
    }  

    return class'UDKGame';  
}  

defaultproperties  
{  
    DefaultPawnClass=class'UDKGame.HTPawn'  
    PlayerControllerClass=class'UDKGame.HTPlayerController'  
    DefaultMapPrefixes(0)=("HT&",GameType="UDKGame.TheHuntGame")  
}  
You will see that the main change here is the struct GameTypePrefix, var array<GameTypePrefix> DefaultMapPrefixes;, and the event  SetGameType. Because we are now basing UDKGame of of GameInfo instead of UTGame, we have to restore the map prefix functionality that is present in UTGame but is missing in GameInfo.
[csharp]struct GameTypePrefix { var string Prefix; var string GameType; }; [/csharp]
This declares a struct for a class that holds two strings, Prefix and GameType. A struct is essentially a set of variables in a variable or "structure". Every instance of GameTypePrefix has a Prefix of GameType, which allows us to make an organized array of this data. This may make more sense when we see how it is used.
[csharp]var array<GameTypePrefix> DefaultMapPrefixes;[/csharp]
This declares an array of our struct called DefaultMapPrefixes. An array can be thought of as a collection of objects that you can add, remove, or edit the objects within. DefaultMapPrefixes will hold all of our default map prefixes for our different map names and game types. We will assign these in the default properties block at the end of the class.
[csharp]static event class<GameInfo> SetGameType(string MapName, string Options, string Portal) { local string ThisMapPrefix; local int i,pos; local class<GameInfo> NewGameType; if (Left(MapName, 10) ~= "HTFrontEnd") { return class'UDKGame'; }[/csharp]
This is an event. Events are usually called automatically from other code when an event triggers. In this case, when the engine loads the UDKGame class it calls this event to see if we should set the game type to a different GameInfo class. It is given three strings, MapName, Options, and Portal. The only one we will use in our logic is MapName. We then have three lines of code declaring local variables. local variables are the same as variables we have been using before (var) except they only exist in the function they are declared in (also known as scope). The string ThisMapPrefix will be used to store the map prefix of the currently loaded map or MapName. "i" is used as an iterator for a for loop we will be running later, while pos is an integer that stores the location of a "-" in the map name if one exists (which we will calculate later). NewGameType is a variable of class GameInfo that we will be using to return the new GameInfo class we will switch to.
Our first if statement is checking to see if the first 10 characters from the left of MapName are a case-insensitive match (~=) to "HTFrontEnd", and if so, it will return the class 'UDKGame' causing the GameInfo class we are loading into to be UDKGame. Because we are already in UDKGame, no game type will be switched.
[csharp] // strip the UEDPIE_ from the filename, if it exists (meaning this is a Play in Editor game) if (Left(MapName, 6) ~= "UEDPIE") { MapName = Right(MapName, Len(MapName) - 6); } else if ( Left(MapName, 5) ~= "UEDPC" ) { MapName = Right(MapName, Len(MapName) - 5); } else if (Left(MapName, 6) ~= "UEDPS3") { MapName = Right(MapName, Len(MapName) - 6); } else if (Left(MapName, 6) ~= "UED360") { MapName = Right(MapName, Len(MapName) - 6); }[/csharp]
Just as Epic's comment states, these if statements check to see if our MapName contains a prefix that is added on automatically by the UDK Editor when we use the "Play in Editor" feature. If an editor prefix exists, we reassign MapName to be the the right most characters of the original mapname, ignoring the left-hand editor prefix.
[csharp] // replace self with appropriate gametype if no game specified pos = InStr(MapName,"-"); ThisMapPrefix = left(MapName,pos);[/csharp]
This code searches our MapName for a "-" and stores the position of the first "-" character it finds into our variable "pos". We then assign ThisMapPrefix to the left side of our MapName ending at the "-" character, causing the prefix of our map to only be stored. Example: If the MapName is HT-Hole1, it will first find the "-" character and then store 2 in pos as the "-" is character 2. (The first character of a string is always character zero). We then grab the left of the MapName from the beginning of the string up to the 2nd character (being the "-"), causing ThisMapPrefix to have "HT" assigned to it.
[csharp] // change game type for ( i=0; i<Default.DefaultMapPrefixes.Length; i++ ) { if ( Default.DefaultMapPrefixes[i].Prefix ~= ThisMapPrefix ) { NewGameType = class<GameInfo>(DynamicLoadObject(Default.DefaultMapPrefixes[i].GameType,class'Class')); if ( NewGameType != None ) { return NewGameType; } } }[/csharp]
This is a for loop. A for loop essentially loops a code block while changing a variable every loop until a condition is no longer true. This is where our i variable comes into play (i being short for iterator). We start the loop by making i = 0, and we give the loop a condition of  i<Default.DefaultMapPrefixes.Length. Default.DefaultMapPrefixes refers to our default value of DefaultMapPrefixes which we assign to in the DefaultProperties code block later on in this class. It contains all the map prefixes for our game. Default.DefaultMapPrefixes.Length is the length of the array, or how many prefixes are in DefaultMapPrefixes. Every time this for loop loops, it will continue to loop as long as our iterator is less than the number of prefixes in our array. The "i++" in our for loop tells the loop to increment i by 1 every time it loops.
Our if statement is checking to see if the "ith" object of DefaultMapPrefixes is a case-insensitive (~=) match to our map prefix of our map. Arrays are kind of like strings in that the first object in an array has an 'index' of 0, just like the first character of a string is character 0. To access an object in an array, you suffix the array with square brackets [ ] with the index of the object inside the brackets. In this case it is i, our loop iterator. Every time we loop this code block, i is increased by one, thus moving to the next object in the array. If our array has 1 object in it, that first object has an index of 0, so our loop will loop once. After it loops once, i will be 1 which is not less than the number of objects in our array, which is 1; thus the loop will stop. If we had two objects in our array, the loop will run the code block twice with i being 0 and 1, stopping at 2; which works because the second object of an array has an index of 1 because our first object has an index of 0.
If it is a match, the next line of code is a bit tricky. What we are doing here is loading an object by its asset name which is stored as a string "GameType". Our DefaultMapPrefixes is an array of the GameTypePrefix struct, so every object in the array has two strings, Prefix and GameType. We can access that string by referencing DefaultMapPrefixes[i].GameType where i is the index of the object we are comparing to in our loop. This string is the fully qualified name of a GameInfo class, such as UDKGame.UDKGame or later UDKGame.TheHuntGame. UTGame.UTDeathmatch would also work here. We load it in as a "class" instead of an instance because we are looking for the actual class of the GameInfo we want to switch to, not an instance of it. Because DynamicLoadObject returns a generic Object, we have to do whats called type-casting in order to turn it into a GameInfo class. Type-casting allows us to store an object in a variable thats designed to hold another object if the two objects are compatible. We can store a UDKGame.UDKGame in a GameInfo class because UDKGame derives from GameInfo. We can not however type-cast an Actor into GameInfo because Actor is not a sub-class of GameInfo, but instead is a parent of it. Finally, this newly loaded and type-casted object is stored in NewGameType. Even though if we load a UDKGame.UDKGame class and type cast it as a GameInfo class, it will still remain a UDKGame class with all its unique properties, but it will just be stored as a GameInfo class.
The second if statement checks to see if NewGameType does not equal None, which means that we successfully converted our newly loaded object into a GameInfo class. If for some reason we could not load the object or type-casting failed, NewGameType would not be assigned to anything (None) and therefore would not be a valid thing to return, as we can't change the game mode to "None". If NewGameType was assigned a value, we then return it, which causes the engine to set our new game type to whatever game type matched our prefix that we assigned in DefaultMapPrefixes.
[csharp] return class'UDKGame'; }[/csharp]
This last bit of code will return "UDKGame" if our loop fails to return a new game type, causing us not to switch game modes because we are already in UDKGame. The last bracket closes the SetGameType event.
[csharp]defaultproperties { DefaultPawnClass=class'UDKGame.HTPawn' PlayerControllerClass=class'UDKGame.HTPlayerController' DefaultMapPrefixes(0)=(Prefix="HT",GameType="UDKGame.TheHuntGame") }[/csharp]
This is our defaultproperties box, which is pretty much the same as before. We assign our HT prefix with the GameType UDKGame.TheHuntGame to the first object of DefaultMapPrefixes, so that if our map has a prefix of HT it will load UDKGame.TheHuntGame instead.

{var string Prefix;var string GameType;};

HTPawn now extends UDKPawn

HTPawn now has a defaultproperties block identical to UTPawn, with the UTPawn specific properties removed. It is really long so here is a link to the script instead.

HTPlayerController now extends UDKPlayerController

[csharp]/*************************
HTPlayerController
Creation date: 14/01/2010 14:31
Copyright (c) 2010, Michael Allar
*************************/
class HTPlayerController extends UDKPlayerController
config(UDKGame);[/csharp]

No added functionality here, just inherits from UDKPlayerController instead.

TheHuntGame is a second GameInfo class that extends UDKGame

So that we can use UDKGame as a generic GameInfo class for things like front-ends while having the majority of our game code in a subclass of it.

[csharp]/*************************
TheHuntGame

Creation date: 19/01/2010 22:24  
Copyright (c) 2010, Michael Allar  

*************************/

class TheHuntGame extends UDKGame;

var array< class<Inventory> > DefaultInventory;

event PlayerController Login(string Portal, string Options, const UniqueNetID UniqueID, out string ErrorMessage)
{
local PlayerController PC;
PC = super.Login(Portal, Options, UniqueID, ErrorMessage);
ChangeName(PC, "New Player", true);
return PC;
}

function AddDefaultInventory( pawn PlayerPawn )
{
local int i;

for (i=0; i&lt;DefaultInventory.Length; i++)  
{  
    // Ensure we don't give duplicate items  
    if (PlayerPawn.FindInventoryType( DefaultInventory[i] ) == None)  
    {  
        // Only activate the first weapon  
        PlayerPawn.CreateInventory(DefaultInventory[i], (i &gt; 0));  
    }  
}  

PlayerPawn.AddDefaultInventory();  

}

defaultproperties
{
DefaultPawnClass=class'UDKGame.HTPawn'
PlayerControllerClass=class'UDKGame.HTPlayerController'

ConsolePlayerControllerClass=class'UTGame.UTConsolePlayerController'  

PlayerReplicationInfoClass=class'UTGame.UTPlayerReplicationInfo'  
GameReplicationInfoClass=class'UTGame.UTGameReplicationInfo'  

//DefaultInventory(0)=class'UDKGame.HTWP_LittleBang'  

bRestartLevel=False  
bDelayedStart=False  
bUseSeamlessTravel=true  

}
[/csharp]

Thats all the changes made.

Now you are ready to continue to set up your game!