RSS

UnrealScript: GameState Save Tutorial

08 Apr

In this tutorial I will explain my method for saving important variables in UnrealScript. All this stuff is pretty straight forward and isn’t all that difficult. I read about it on this page of the UDN but if you would like to see a more practical demonstration then you’ve come to the right place.

Note: this tutorial assumes you have some basic knowledge of how UnrealScript works and also assumes that you have done some programming before. If you need an intro to UnrealScript or intro to Programming tutorial you should look somewhere else as all this might seem confusing at best. This tutorial also assumes you already have a PlayerController class setup.

What we will be using

If you checked out the link above you will notice that UDN mentions three ways to saving data. I obviously opted to use the first approach for a couple of reasons:

  • it works for both UDK and UDK: Mobile
  • it is done in UnrealScript which obviously has a lot of benefits

This approach uses two functions from the Engine class called BasicLoadObject for loading and BasicSaveObject for saving. If you open up the Engine.uc class and look for those two functions you will find this:


/**
* Serializes an object to a file (object pointers to non-always loaded objects are not supported)
*
* @param Obj The object to serialize
* @param Pathname The path to the file to save
* @param bIsSaveGame If TRUE, FILEWRITE_SaveGame will be used to create the file writer
* @param Version A version number to save with the archive, so that we can safely fail when loading old versioned files
*
* @return TRUE if successful
*/
native static final function bool BasicSaveObject(Object Obj, string Pathname, bool bIsSaveGame, int Version);

/**
* Loads an object from a file (saved with the BasicSaveObject function). It should already be
* allocated just like the original object was allocated
*
* @param Obj The object to serialize
* @param Pathname The path to the file to read and create the object from
* @param bIsSaveGame If TRUE, FILEREAD_SaveGame will be used to create the file reader
* @param Version A version number to match with the version saved in the archive, so that we can safely fail when loading old versioned files
*
* @return TRUE if successful
*/
native static final function bool BasicLoadObject(Object Obj, string Pathname, bool bIsSaveGame, int Version);

It is all pretty straight forward – the object “Obj” is saved into “Pathname”, the boolean bIsSaveGame has to be true if you are on the mobile and the final integer number “Version” is used to make sure you load a save for the proper game version. So now what do we do with these functions?

Our attention should focus on Object Obj since it is what will be saved. As the document explains: “… create a single one of these objects to store all of your settings, then save the object” so that should be our first step.

Creating an object to store our

What I opted to do was to put together a class called GameState (extending Object) whose primary purpose would be to store copies of variables I would like to save. Naturally, depending on your game, you might want to construct things differently. But I shall assume that you are writing a simple game that only needs to store variables.


/**
* This class is intended to store and hold all the information we would
* like to save in our game
* @author Daniel G. Haddad
*/

class DH_GameState extends Object;

Let’s say my player controller has the three variables I would like to store:


...
var float highScore;
var float currentLevel;
var float numOfShots;
...

So what I do now is duplicate these variables inside the DH_GameState class so our class will now look something like this:


/**
* This class is intended to store and hold all the information we would
* like to save in our game
* @author Daniel G. Haddad
*/

class DH_GameState extends Object;

var float highScore;
var float currentLevel;
var float numOfShots;

/**
* Here I default the values of the variables.
**/
defaultproperties
{
highScore = -1
currentLevel= -1
numOfShots= -1
}

So now we have our DH_GameState class ready for use.

Now, there are plenty of places you can call the save and load functions but in this tutorial I opted to include the code as part of the PlayerController.

If you already have a PlayerController class then open it up and prepare to add a couple of functions to it.

Our save function:


/**
* This function saves our variables using BasicSaveObject
**/
function savePCVariables()
{
local DH_GameState gameState; //our gameState variable
//Init our gamestate variable - this is important
gameState = new class'DH_GameState';

//Save the variables
gameState.highScore = highScore; //Set the gameState’s highScore to the PC’s highScore
gameState.currentLevel = currentLevel; // etc…
gameState.numOfShots = numOfShots; // etc…

//Call the Engine BasicSaveObject function to save the gameState object
//Version 0: Development
class’Engine’.static.BasicSaveObject(gameState, “GameState.bin”, true, 0);
}

/**
* This function loads our variables using BasicLoadObject
**/
function loadPCVariables()
{
local DH_GameState gameState; //our gameState variable
//Init our gamestate variable – this is important
gameState = new class’DH_GameState’;

//Load the variables gamestate using BasicLoadObject
//Version 0: Development
if(class’Engine’.static.BasicLoadObject(gameState, “GameState.bin”, true, 0)) //Load only if the file actually exists
{
highScore = gameState.highScore; //Set the PC’s highScore to the value we loaded
currentLevel = gameState.currentLevel; // etc…
numOfShots = gameState.numOfShots; // etc…
}
else
{
`Log(“File not found while loading PC’s variables”);
}
}

Now whenever you need to save your variables simply call the savePCVariables() function and whenever you want to load call the loadPCVariables() function.

How they work

Saving:

local DH_GameState gameState; //our gameState variable
//Init our gamestate variable - this is important
gameState = new class'DH_GameState';

We first initialize a DH_GameState object called gameState. We tailored this class to have the variables we want to save so this object will store the variables we would like to save inside it…


//Save the variables
gameState.highScore = highScore; //Set the gameState's highScore to the PC's highScore
gameState.currentLevel = currentLevel; // etc...
gameState.numOfShots = numOfShots; // etc...

…and then we move on to populating it (gameState object) with the values we want to save.


//Call the Engine BasicSaveObject function to save the gameState object
//Version 0: Development
class'Engine'.static.BasicSaveObject(gameState, "GameState.bin", true, 0);

Finally, with everything ready, we call the Engine’s BasicSaveObject function and pass gameState to it as the object.
I chose to save the info in a file called “GameState.bin”.

Loading:


local DH_GameState gameState; //our gameState variable
//Init our gamestate variable - this is important
gameState = new class'DH_GameState';

Just as we did above, we initialize our gameState object that we will use to load the information into.


//Load the variables gamestate using BasicLoadObject
//Version 0: Development
if(class'Engine'.static.BasicLoadObject(gameState, "GameState.bin", true, 0)) //Load only if the file actually exists

We now load the saved information from the same file name we wrote into only this time the function is inside an if statement. This is done to check if the file exists because, as you might have noticed, the function BasicLoadObject returns a boolean that tells you whether or not the load was successful.


{
highScore = gameState.highScore; //Set the PC's highScore to the value we loaded
currentLevel = gameState.currentLevel; // etc...
numOfShots = gameState.numOfShots; // etc...
}

Now we put into the PC’s variables the values we just loaded into the gameState object.


else
{
`Log("File not found while loading PC's variables");
}
}

And finally we print out some message in case the load fails. I assume that the error was because the file was not found.

So there you have it. Now you have two functions that will save and load the important variables you specified. If you want to add more variables all you need to do is:

  1. Add the variables inside the DH_GameState class
  2. Go over your save function and add the code needed to set the gameState variable value before saving
  3. Go over your load function and add the code needed to set the value of the PC’s variable based on the loaded gameState value

Now I’ll later write up a tutorial that creates a couple of Kismet Nodes that when activate call these two functions. This can be very handy as it will allow you to save and load important variables at key points during your levels. But that’s all for now.

Important Notes

  • Calling BasicSaveObject on a file that already exists will overwrite it
  • The default location for the files will be under your: UDK\UDK-20XX-XX\Binaries\WinXX folder
  • This function will create directories when requested and it will also use relative directory commands like: ..\..\MySaveDirectory\MySaveGame.USX (Solid Snake)

I’ve made a game!

For the past year I’ve formed a small game studio called Cwerki Studios and we’ve been hard at work on our debut game, Min: A Space Adventure! Min built using UDK and was approved by Apple and will become available worldwide on June 18th! Check out our launch trailer!

Click here to download Min for Free from the App Store!

Make sure you like our video and share it with your friends!

It doesn’t end there, Min would not have been possible without the help and support of our community, we have huge, huge plans for Min but more importantly we want to include our community in this future. Head over to our Facebook page and like us to take part in polls and discussions we’ll be orchestrating to decide on Min’s future.

Advertisements
 
17 Comments

Posted by on 08/04/2011 in Tutorials, UnrealScript

 

Tags: , , , , , , , ,

17 responses to “UnrealScript: GameState Save Tutorial

  1. Gauji

    08/05/2011 at 5:03 AM

    Whatsup dude, i like that tutorial. I was trying implement that with my own kismet node but i keep getting a sintax error saying that im missing an expression.

    class SeqAct_LandShiftMobileCam extends SequenceAction;

    var() float MaxGroundDistVar;

    event Activated()

    {
    local LandshiftMobilePC LandVar;

    LandVar == LandshiftMobilePC(PlayerTick());
    LandVar.MaxGroundDist(MaxGroundDistVar);

    }

    defaultproperties

    {
    ObjName=”OrbitalCamera”
    ObjCategory=”Landshift”

    //VariableLinks(1)=(ExpectedType=class’SeqVar_Vector’,LinkDesc=”CurrentCameraLocation”,PropertyName=CurrentCameraLocation)
    //VariableLinks(2)=(ExpectedType=class’SeqVar_Vector’,LinkDesc=”CurrentFocusLocation”,PropertyName=CurrentFocusLocation)
    //VariableLinks(3)=(ExpectedType=class’SeqVar_Float’,LinkDesc=”RotationFactor”,PropertyName=RotationFactor)
    //VariableLinks(4)=(ExpectedType=class’SeqVar_Float’,LinkDesc=”MinGroundDist”,PropertyName=MinGroundDist)
    VariableLinks(0)=(ExpectedType=class’SeqVar_Float’,LinkDesc=”MaxGroundDist”,PropertyName=MaxGroundDist)
    //VariableLinks(5)=(ExpectedType=class’SeqVar_Float’,LinkDesc=”TapToMoveSmoothFactor”,PropertyName=TapToMoveSmoothFactor)
    }

    the error that i get is under “LandVar == LandshiftMobilePC(PlayerTick());”

    Error, bad or missing expression after ‘==’ : ‘LandshiftMobilePC

    do u have any idea what might be wrong ?

    -g

    thx for the tut btw

     
  2. danhaddad

    08/05/2011 at 12:13 PM

    Hello,

    I’m glad you like it.

    LandVar == LandshiftMobilePC(PlayerTick())

    “==” is used for comparison so that is one problem, but I think there might be others.

    LandVar == LandshiftMobilePC(PlayerTick());

    What are you trying to do here?

     
  3. Gauji

    08/05/2011 at 8:47 PM

    Yeah i fixed the comparison thing

    PlayerTick, contains the everything about the Variable im trying to alter, i have tried a few different things without success i think the problem lies with the LandShiftMobilePC class

    class LandshiftMobilePC extends MobilePC;

    // The camera's distance from the focus location
    var float CamDist;

    // Multiply the player input so the camera doesn't move too fast
    var float RotationFactor;

    // Cache the player's view rotation
    var rotator VRot;

    //Limit the Camera's up-down movement.
    var float MinGroundDist;
    var float MaxGroundDist;

    // Current camera and focus locations.
    var vector CurrentCameraLocation, CurrentFocusLocation;

    // Tapped location we're trying to move toward.
    var vector DesiredFocusLocation;

    // Don't move toward the new location too quickly.
    var float TapToMoveSmoothFactor;

    simulated function PostBeginPlay()
    {
    super.PostBeginPlay();

    // Cache the camera's distance from the focus point.
    CamDist = VSize(CurrentCameraLocation - CurrentFocusLocation);
    }

    function SetupZones()
    {
    super.SetupZones();

    //When the FreeLookZone is tapped, call TapToMoveTap.
    if(FreeLookZone != none)
    FreeLookZone.OnTapDelegate = TapToMoveTap;
    }

    function bool TapToMoveTap(MobileInputZone Zone, EZoneTouchEvent EventType, Vector2D TouchLoc)
    {
    local vector2D ViewportSize;
    local Vector2D RelLocation;
    local vector Origin, Direction, Destination;
    local vector HitLoc, HitNorm;
    local Actor HitActor;

    //Get the size of the screen.
    LocalPlayer(Player).ViewportClient.GetViewportSize(ViewportSize);

    //Get the percentage location of the tap.
    RelLocation.X = TouchLoc.X / ViewportSize.X;
    RelLocation.Y = TouchLoc.Y / ViewportSize.Y;

    //Turn the 2D screen coordinates into 3D world coordinates.
    LocalPlayer(Player).Deproject(RelLocation, Origin, Direction);

    //Make the end of the trace far away along the view rotation.
    Destination = Origin + (Direction * 10240);

    //Trace to see if anything was hit.
    foreach Pawn.TraceActors(class'Actor', HitActor, HitLoc, HitNorm, Destination, Origin)
    {
    if (HitActor.bBlockActors || HitActor.bWorldGeometry)
    break;
    }

    //If something IS hit, ser the focus location to the hit location.
    if (HitActor != none)
    DesiredFocusLocation = HitLoc;

    return true;
    }

    function PlayerTick(float DeltaTime)
    {
    local vector NewCamLoc, ZTest;
    local vector NewFocusLoc;
    local float CamHeight;

    Super.PlayerTick(DeltaTime);

    //Get the camera's current location.
    NewFocusLoc = CurrentCameraLocation;

    //Smoothly move towards the desired focus location.
    NewFocusLoc += (DesiredFocusLocation - NewFocusLoc) * TapToMoveSmoothFactor;

    //Get the camera's current location.
    NewCamLoc = CurrentCameraLocation + (NewFocusLoc - CurrentFocusLocation);

    //Move the camera left or right based on player input.
    //RotationFactor slows down the camera movement.
    ZTest -= RotationFactor * (PlayerInput.aMouseX * vect(0,0.5,0) >> VRot);

    //Move the camera up or down based on player input.
    //RotationFactor slows down the camera movement.
    ZTest -= RotationFactor * (PlayerInput.aMouseY * vect(0,0,0.5) >> VRot);

    // Add the current camera location.
    ZTest += NewCamLoc;

    //Get the camera's height above the focus location.
    CamHeight = Ztest.Z - NewFocusLoc.Z;

    //Test if we're too high up or too low, if so only apply left-right movement.
    if(CamHeight MaxGroundDist)
    NewCamLoc -= RotationFactor * (PlayerInput.aMouseX * vect(0,0.5,0) >> VRot);
    else
    NewCamLoc = ZTest;

    //Move the camera out to the same distance as before.
    NewCamLoc = CamDist * normal(NewCamLoc - NewFocusLoc);

    //Move the camera's center to the focus location.
    NewCamLoc += NewFocusLoc;

    //Save the camera location.
    CurrentCameraLocation = NewCamLoc;

    //Save the focus lcoation.
    CurrentFocusLocation = NewFocusLoc;
    }

    public function MaximumGroundDistance(SeqAct_LandshiftMobileCam action)
    {
    MaxGroundDist(action.Amount);
    }

    simulated event GetPlayerViewPoint(out vector POVLocation, out Rotator POVRotation)
    {
    super.GetPlayerViewPoint(POVLocation, POVRotation);

    //Set the camera's location and rotation.
    POVLocation = CurrentCameraLocation;
    POVRotation = rotator(CurrentFocusLocation - CurrentCameraLocation);

    //Save our current view rotation.
    VRot = POVRotation;
    }

    defaultproperties
    {

    CurrentCameraLocation=(X=256,Z=256)
    CurrentFocusLocation=(X=0,Z=0)
    RotationFactor=0.01
    MinGroundDist=128.0
    MaxGroundDist=350.0
    TapToMoveSmoothFactor=0.1
    }

    im just trying to alter the MaxGroundDist variable within kismet

    im very new to this stuff so im just trying to implement code from others together while im gathering understanding of US

     
  4. Fox

    31/05/2011 at 11:31 PM

    Thank you so much for this tutorial. I’m awful at scripting but am having to learn on the fly to create the game I want to create, and this was exactly what I needed to help me learn how to store variables for long term use.

    Initially, every time I closed my GUI (having successfully changed my stats as intended), loading the GUI again would simply reset everything to default. I knew the problem was my class being reinitialized every time I loaded the .swf file, but without global variables I was clueless as to solving the issue.

    Cue your excellent post. Not only does my system now work but as an added bonus I even understand why! Thanks again 😀

     
  5. Paweł Kamiński

    18/10/2011 at 3:47 AM

    Man! You made my day. BasicLoadObject was exacly what I was looking for. Damn I almost got in plans to do DLL to save files. Tested, works, thank you very much.

     
    • danhaddad

      18/10/2011 at 3:58 AM

      You are welcome, this is only the beginning though, in a real save system you would use things in a better way. I plan to touch-up on all this, just as soon as I have time to spare.

       
  6. Paddz

    12/11/2011 at 2:03 AM

    Thank you very much for this, Daniel! You really helped a great deal with that little tutorial!

     
    • danhaddad

      12/11/2011 at 2:07 AM

      My pleasure Paddz!

      Again, that tutorial was meant as an intro – when you do it in your game you should do things a little differently – I’ll expand on this once Min is well into development!

      I’m very glad this helped! Best of luck.

       
  7. Juan

    24/11/2011 at 4:15 PM

    Amazing. Thnx alot!

     
  8. g3n3fyp

    29/11/2011 at 9:01 AM

    Thank you so much for this! I’ve been looking for a save/load tutorial. haven’t tried it out yet but still wanted to give thanks.

    just a side qns, does it work for udk july 2011 build?

    Thank again

     
    • danhaddad

      29/11/2011 at 6:17 PM

      Glad this helped, again, that tutorial is only an intro but should get you started.

      It should work with that UDK version also.

      Enjoy

       
  9. Arman Pakan

    26/12/2011 at 8:11 PM

    thanks man..
    you help me a lot..
    I hope, you teach us how we make a kismet node for using in during game..
    because i dont know yet how can i open a specific map by reading string name of map from the file
    and i use that in exec open function in player controller class 😦

     
  10. Mc

    19/06/2012 at 11:49 PM

    Simply love it! It’s so easy to understand. It was one of most useful things I’ve learned sacrificing 2 minutes of mine time 🙂 Big thanks!

     
  11. Phil B

    26/06/2013 at 8:20 AM

    Hey, very cool! Great writeup, and fun little game, looks like 🙂

    Seems like a neat game for kids… what about making a PC release of it as well?

     

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: