Saving Games

Blitz3D Forums/Blitz3D Programming/Saving Games

cash(Posted 2005) [#1]
Okay

I have some quite large levels I am working on. I am using entities, types and various other variables. Is there a way to save the level as is and reload.

Examples would be nice.

Any help appreciated.


Thanks in advance


fall_x(Posted 2005) [#2]
You'll have to loop trough all your entities (with types or arrays) and save all their positions and other data to a file with the write functions (writeline/writestring/writeint/etc). There is no built-in way of doing this automatically.


_PJ_(Posted 2005) [#3]
First, make sure you know what criteria and details/parameters about your objects in the world need to be saved.

perhaps your world is popuated with trees. You may need to know

Tree Size
Tree No. of Branches
Tree Colour
Tree Leaf Shape
Tree Location

Once you have all this data, as fall_x suggests, you should store this data in an array or a Type.

For instance

Type Tree
Field Size
Field Branches
Field Color$
Field Leaf$
Field Loc_X
Field Loc_Y
Field Loc_Z
End Type

When it comes to Saving, Im not 100% on this, because I havent used the file commands for a while, but the general gist is:

[code]

Filename=OPenfile$("Saved_Folder\Savedgame.blah")

For Saver.Tree = Each Tree
WriteLine(Filename)=Saver\Size
WriteLine(Filename)=Saver\Branches
WriteLine(Filename)=Saver\Colour$
WriteLine(Filename)=Saver\Leaf
WriteLine(Filename)=Saver\Loc_X
WriteLine(Filename)=Saver\Loc_Y
WriteLine(Filename)=Saver\Loc_Z

Next

CloseFile(Filename)

[/codebox]


So at any time, levels can then be loaded in with the similar instructions (again, sorry if the filehandling syntax isnt right!):




WolRon(Posted 2005) [#4]
WriteLine and ReadLine would probably be better suited to be something like WriteInt/ReadInt or WriteFloat/ReadFloat#.
That would also create a smaller file size.

A problem with Malices code is that it can only load in trees. You would benefit from doing something like this:

Filename=WriteFile("Saved_Folder\Savedgame.blah") 
For Saver.Rock = Each Rock
  WriteInt(Filename, 1)
  WriteInt(Filename, Saver\Type) 
  WriteInt(Filename, Saver\Size) 
  WriteString(Filename, Saver\Colour$) 
  WriteFloat(Filename, Saver\Loc_X) 
  WriteFloat(Filename, Saver\Loc_Y) 
  WriteFloat(Filename, Saver\Loc_Z) 
Next
For Saver.Tree = Each Tree 
  WriteInt(Filename, 2)
  WriteInt(Filename, Saver\Size) 
  WriteInt(Filename, Saver\Branches) 
  WriteString(Filename, Saver\Colour$) 
  WriteInt(Filename, Saver\Leaf) 
  WriteFloat(Filename, Saver\Loc_X) 
  WriteFloat(Filename, Saver\Loc_Y) 
  WriteFloat(Filename, Saver\Loc_Z) 
Next
;etc.
CloseFile(Filename)
Where the first WriteInt is saving the type of object being saved such as 1=rock, 2=tree, 3=house, 4=lightpost, etc. This way, you know how to load it later.

And then to load it you would:
Filename=ReadFile("Saved_Folder\Savedgame.blah")

Repeat
  objecttype = ReadInt(Filename)

  Select objecttype
    Case 1
      ;load in rock
    Case 2
      Loader.Tree = New Tree
      Loader\Size=ReadInt(Filename)
      Loader\Branches=ReadInt(Filename)
      Loader\Colour$=ReadString$(Filename)
      Loader\Leaf=ReadInt(Filename)
      Loader\Loc_X=ReadFloat#(Filename)
      Loader\Loc_Y=ReadFloat#(Filename)
      Loader\Loc_Z=ReadFloat#(Filename)
    Case 3
      ;load in house
    Case 4
      ;load in lightpost
    Case Default
      RunTimeError "Unknown object encountered"
  End Select
Until Eof (Filename)


I can't help outdoing someone elses code... ;)


_PJ_(Posted 2005) [#5]
Heh I was just doing the basics, and I didnt have much time, but Im happy in the knowledge I was on the right track ;)


Danny(Posted 2005) [#6]
It is a monster job if you have a large and complex game. And not easy to saved and restore your game EXACTLY as it was - especially when using tokamak for example...
What I wouldn't give for a ReadType/WriteType function or command...
for t.tree = each tree
 WriteType t
next
And a simple routine to load it back again like:
While not eof(x)
 t.tree = new tree
 ReadType t
wend 
Or even better, just:
WriteType Each Tree
WriteType Each House
WriteType Each Everything
*sigh*


jfk EO-11110(Posted 2005) [#7]
Save-Game is a thing that should be implemented from beginning on. It's very hard to add it to a game that has already a certain size.

BTW Cash, did you get my mail?


fall_x(Posted 2005) [#8]
Save-Game is a thing that should be implemented from beginning on. It's very hard to add it to a game that has already a certain size.
I agree. That, and making sure you have some code that deletes current entities and types from memory, for instance before loading your game or before switching levels you will need to clear the current level, all enemies, power-ups, ... It's a real pain to look trough your code for anything you could have missed that could cause a memory leak.


Danny(Posted 2005) [#9]
All absolutely true.
But if you work on a big game for let's say a year, I would have to change and update my loading & saving routines nearly every other day or so.

Too bad there isn't a way where you can just 'save the chunk of memory' wich has all that in there....
Coredump()


_PJ_(Posted 2005) [#10]
Efficient programming would mean that only certain flags would need to be recorded. These flags, when re-loaded, would describe the exact conditions apparent in the world, so the state of enemies, the weather, the number of raindrops left to fall etc. could all be calculated from the flags' status.


BlackJumper(Posted 2005) [#11]
Half of the job could be done for you using Str$(type) which will create a printout of all fields in a type. You would then only need to create a loader for each type in your game and read the values back in from a file.

Some people advocate storing everything in types anyway, so this might not be a bad idea.

Your loader would have to parse the type again... see the example below:
;------------------------------------------------------------------
;----   demo of saving and reloading game data using Str$()    ----
;----             Blackjumper  - Jan 2005                      ----
;------------------------------------------------------------------

Type test
	Field x
	Field y
	Field name$
End Type

For count = 1 To 4
	n.test = New test
	n\x = Rand(10)
	n\y = Rand(10)+100
	n\name$ = Chr(Rand(26)+65) +Chr(Rand(26)+65) +Chr(Rand(26)+65)
Next


fileout = WriteFile("C:\storedgame.txt")
For n.test = Each test
	Print Str$(n)
	WriteString (fileout, Str$(n))
Next
CloseFile( fileout ) 

Print "game data written to file... press any key to continue"
Print
WaitKey

Print "deleting all instances of type 'test'..."
For n.test = Each test
	Delete n
Next
Print "printing all type information...
Print "________________________________"
For n = Each test
	Print Str$(n)
Next
Print "--------------------------------"
WaitKey
Print


Print "... now reading from disk..."
filein = ReadFile("C:\storedgame.txt") 
While Not Eof(filein)
Read1$ = ReadString$( filein ) 
RestoreTestInfo(Read1$)
Wend

Print "printing all reloaded type information...
Print "------------------------"
For n.test = Each test
	Print Str$(n)
Next
Print "------------------------"

WaitKey
End

Function RestoreTestInfo( SavedString$ )
	Print "Read from file --> " + SavedString$
	SavedString$ = Mid$( SavedString$, 2, Len(SavedString$)-2) ; remove end square brackets
	
	firstcomma = Instr(SavedString$, ",")
	firstvalue% = Left$(SavedString$, firstcomma-1)   ; convert first value (up to comma) to an int
	
	SavedString$ = Mid$( SavedString$, firstcomma+1, Len(SavedString$)-firstcomma+1)  ; eat up to 1st comma

	firstcomma = Instr(SavedString$, ",")
	secondvalue% = Left$(SavedString$, firstcomma-1)  ; convert up to new first comma to another int
	
	SavedString$ = Mid$( SavedString$, firstcomma+1, Len(SavedString$)-firstcomma+1)  ; eat up to new 1st comma
	ThirdString$ = Mid$( SavedString$, 2, Len(SavedString$)-2)                        ; remove quotes from string

	reloaded.test = New test			; make a new type
	reloaded\x = firstvalue%			; and assign the
	reloaded\y = secondvalue%			; reloaded values
	reloaded\name$ = ThirdString$		; to the fields
	
End Function


You could investigate add's value parser from the Code Archives to deal with floats, etc...

http://www.blitzbasic.com/codearcs/codearcs.php?code=161


Danny(Posted 2005) [#12]
Super cool - thanks for that BlackJumper, I didn't know Str$() did that !!! This is a BIG help!

cheers,
Danny


_PJ_(Posted 2005) [#13]
Hmm me too! That helps A LOT!!! Thanks BJ


BlackJumper(Posted 2005) [#14]
OK, I guess I will stick this in the Code Archives then.


TeaVirus(Posted 2005) [#15]
I was thinking about implementing a system where any changes to the objects in my game would be written to a log. Then, to save the level I just need to save the log. When I reload the level, I load it as it was designed and replay the saved log. Each object in the level has a unique ID which should make this pretty easy to implement. Things like new objects being created would be logged as well.


_PJ_(Posted 2005) [#16]
That's why using Types is so much easier, because unlike arrays, you can add/subtract to types without having to predetermine any specific dimensions.


angel martinez(Posted 2005) [#17]
I think it has not to be so complicated.
You can do like in console games like Zelda (GameCube), in that game only the player status (many variables, easy to save and load) are saved, and it only stores the keys,items,objects,etc you have got in the game but the scenery is always the same.
I donīt know if I have explain it well but I think that this system is much easier.


Storm8191(Posted 2005) [#18]
I sort of agree with both sides on this, you need to have save-game capabilities early in a project, but it's also painstaking when you have to change data within the saved files every so often.

What I've found to work best is to have file versions. They don't have to necessarily be huge changes between versions, but just have some way to keep track of them. I used this a whole lot in my Cubix 3D map editor, and it worked very well. Just keep a single integer telling you which version this file is, and keep your old routines around to handle those older versions. You might have to fill in the blanks of older files to catch up with new versions.

Then making changes to your files is only a matter of copying your loading & saving code for a new version, and making the necessary changes. Then you have a better file format, while still being able to load older saved games (and maps, etc.).

As far as figuring out exactly what to save, and how to load it again, you'll have to figure that one out on your own. Just figure out what all data you need, and try to reproduce the rest. Good luck.


PowerPC603(Posted 2005) [#19]
I wanted to post a question about the Str$-command, about what would happen if there was a pointer to another type inside that type, but I tried it myself first.
And the result was interesting:

Type test
	Field x
	Field t2.test2
End Type

Type test2
	Field y
End Type

t.test = New test
t\t2 = New test2

t\x = Rand(1, 100)
t\t2\y = Rand(200, 500)

Print t\x
Print t\t2\y
Print
Print Str$(t)

WaitKey()


This actually printed:
36
425

[36,[425]]


Blitz sees that field "t2" inside "t" is actually a pointer to another type, and therefore it prints the contents of that sub-type in an extra pair of brackets.
Didn't know this could be done.


Danny(Posted 2005) [#20]
Unfortunately it DOES NOT support arrays within types!!
If you have something like

FIELD things[5]

Then STR$() will print it as [???]