Game Development Community

dev|Pro Game Development Curriculum

Dev Diary #2 Save/Load system done, next up Object Interaction

by Gareth Fouche · 01/17/2006 (3:22 am) · 9 comments

Well, despite Real Life interfering with my Game Dev again (figures that the one night last week that I had the entire evening to work, would be the night my car would pack as I'm travelling home), I have managed to get my save load system somewhat completed.

I say somewhat, because it doesnt really save much yet. There are no monsters or quests to keep track of yet, so there isn't much to save. What it saves is the current mission, the players transform, and a region "hit counter", much like a websites hit counter. The first two values are general data used to restore the player to the right mission and the correct position/facing within that mission when you load up. The 3rd is saved for each region the player has been in, and just keeps track of how many times the player has visited that region.

Now, its not much at the moment, but the point is that it works nicely. I've got the basics of a persistent game going. Its rather gratifying. I've recenty added the load/save guis, so you can, as expected, save your game to a specific file, then load up that mission later etc. And if I can save one variable, I can save hundreds. To save, for instance, the monsters, I'd just add all the details to the [Region].monsterInfo simGroup, then when the thing saves, bingo, they're all saved out. When I load it up, I can just cycle through the same group to pull out all the monster data. Easy.

The whole "working directory" thing I talked about before is going on in the background as you move from one area to the next. I've changed it from a working directory to a single working file. Basically, it stores all data about your game in it, including all the data for each region. When you enter a region, it loads it up, saves out (overwrites) the current region data, checks to see if the destination region data exists, and if it does loads from that. If the dest data doesn't exist (because you haven't been there before), it calls an InitRegion function, to load up the defaults for the mission. In this case, initialising the hit counter to 1 ;)

I had some issues with getting file deletion to work, specifically when you exec a file, then later try deleting it, then try to save a file with the same name, exec it, delete it, etc etc. I have a link to a bit of code here on the site which goes into freeing the resource from the ResManager etc; I implemented it but couldn't get it to play nice. So instead what I do is never delete the working file, I simply save over it with a empty SimGroup called EmptyWorkingFile. If, when I exec the working file, this SimGroup exists, I know the working file was, well, empty, and I need to create a new one.

I would, ideally, like to come back to this, so that I can allow players to delete save games (currently you can only make new ones or overwrite old ones), but after struggling for a few hours to get it going and to fully understand the resource system, I realised that I was violating the principles of "quick, dirty prototype, as fast as possible". I don't really need that feature right now, have come up with an alternative, and its not even that bad if the final game doesn't allow you to delete saves. *shrug*.

(ASIDE : Oh, one thing I'd like to point out to those trying to use SimGroups as a save/load system, after you save over a file, before you exec it, make sure to compile the file (compile(%filename);)!!!! Before I did that I would have problems, I'd save then exec it but sometimes get the old pre-save version of the SimGroup, because it was loading from the old .dso. I think what was happening is that I'd exec it then save it then exec it again so quickly that the file creation time comparison between the .cs and .dso would say that the old .dso was for the current file. So it wouldn't recreate the .dso. Compiling the file after you save it ensures that the .dso is for the current .cs file.)

So anyway, after getting that working well, last night I moved onto integrating the Lighting Kit 1.4 into my project, or rather, adding my changes to the ready-to-go TLK'ed-TGE that you get. Was pretty painless. All the stuff in the TLK is great, but for some reason the thing that made me the most excited was seeing the decal projectors. Not sure how dynamic they are, but the first thing that leapt to my mind is "Oooh, wonder what spell effects I can make with those?" Glowing runes and whatnot ;)

Then, in the hour I had left before slumber claimed me, I worked on the next item in the list, object interaction. I need to be able to interact with my world. Luckily, there is a GG resource to help me get this going quickly :D. Specifically Dave Meyers Object selection code (make sure to use the later, updated version). Object selection is one step away from object interaction, it lets you click on an object and sets this as the "selected" object. It'll be simple to extend this to call something like onInteract(selectedObject), which will do different things depending on what the object is and what state the player is in (talk to NPC, attack monster, pull lever, cast spell at target).

I got the resource in, no problem. Easy to use and follow. However, its not quite what I want. It works in an event driven way. When you click, it calls the server and gets the selected object given back to it. However, in most RPGs, when you move your cursor over something, it changes to indicate how you can interact with it, a little hand for something you can use, a sword for an enemy, etc. To do this, you need to be constantly polling the world for what is "under" the cursor.

So I'm going to modify the code to be executing in the background all the time, and when you press the mouse button it sends the call to onInteract() to the server. Not only that, but I think I am going to move the scene polling to the client side. I don't know how much CPU the "commandToServer" call takes, even given that it will loopback in single player, but why call it every frame? Luckily, I have the RTS kit, which does something similar to what I want. When you pass your cursor over a unit it shows info about that unit and highlights it with a selection circle. Then when you click it sends commands to the server to do something relevant with the object. Bingo. I really must recommend the RTS kit again, even though I am not making a RTS anymore I still find plenty of useful stuff in there to add to my game. The minimap will get its turn soon ;)

#1
01/17/2006 (3:34 am)
Hey Gareth, glad you made progress. It seems we're on the exact same page. I too finished my save/load system and am now starting the integration of TLK 1.4. Really looking forward to trying the decal projector!

Nick
#2
01/17/2006 (6:35 am)
You do know that you can skip the .dso compilation when executing files right? Something like the code below should work fine (it's kinda dodgey though):

// Function to execute and delete a temporary script without creating dsos.
function cleanExec(%filename)
{
    // Store the previous ignore DSOs setting.
    %previous = $Pref::ignoreDSOs;

    // Set the engine to ignore DSOs.
    $Pref::ignoreDSOs = true;

   // Execute the temporary file.
   exec(%filename);

   // Restore the previous ignore DSOs setting.
   $Pref::ignoreDSOs = %previous;

   // Delete the temporary file.
   deleteFile(%filename);
}

// Called via something like:
cleanExec("~/testFile.cs");

The resource I posted ages and ages back for adding a "deleteFile" command to the resource manager should work fine. At least it does for me and I've had code setting up, writing, executing, and then deleting hundreds of files for a pseudo-random level-generation routine.

Edit: Code change. =)
#3
01/17/2006 (6:52 am)
Oh, cool, thanks Daniel. Learn something new every day, hehe.

As to the delete stuff, I have your resource, implemented it, but something was still going wonky, maybe I was just staring at it too long and missing some obvious error. Like I said, I will give it another look, when I get the time, but for the moment that functionality has shifted to low priority. It doesn't take long to put your code in, so theres a good chance I will return to it. ;)


@ Nick : Cool man, good to hear someone else is travelling the same path :)
#4
01/17/2006 (7:13 am)
Gareth,

I've sent you my fileDelete function. Works for me, give it a try. Didn't want to post it here cause it's code and this is public.

Nick
#5
01/17/2006 (7:34 am)
Finally another South African ;p

~neo (joburg)
#6
01/17/2006 (1:40 pm)
Gareth: You don't need to do your ray casting on the server or in script at all. You can just do it in the GUI's onMouseMove method. Look at the editor/world editor for a good example.
#7
01/17/2006 (4:55 pm)
I've probably made changes to the deleteFile code since I posted the resource. If I didn't have so much stuff to do I might take a look at it and check that it works properly. I'm pretty sure I wrote it for TGE 1.1 or 1.2 or something, so it's fairly old...
#8
01/17/2006 (11:01 pm)
Nick and Daniel : Thanks guys, like I said, I'm sure it was something obvious that I missed because I'd been staring at code for too long ;). Will plug in Nicks code and see if it works.

Josh, yeah, thats in fact the way the RTS code does it. However, I am going to do it in something besides the onMouseMove code, reason being (consider this in first person perspective), say I am sitting still, not moving, and a monster runs forward, passing under the crosshair/cursor. The crosshair must change to represent a hostile creature. So it needs to be based on when the world updates, not on when the player moves.

@ Neo : Check out the "any south african torquers" thread, there are actually quite a few of us ;)
#9
01/17/2006 (11:21 pm)
Don't get me started on the RTS starter kit code... =)

The way I handle mouse context/selection in my game is with a fairly stock TS-control-style gui that virtually inherits from ITickable (so that it has set updates via advanceTime, interpolateTick, and processTick).

The gui class has a method called "updateCursor" which performs all the appropriate collision checks once per update (from advanceTime) in addition to whenever a mouse-related callback (onMouseDown, onMouseDragged, etc) is triggered.

It then uses the data from the collision checking (in addition to the control object's current state and whether or not the client itself has anything selected) to determine the final context of the mouse (which cursor is displayed, whether additional details are rendered, whether the selected object is highlighted and so on).

Even if you move the camera or player around -- or if something moves under your cursor, it works fine.