Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm soon going to make a game for the iPhone as a school project.

My idea was to make a TLOZ remake, with Tiled Maps.

enter image description here

However, I'm not quite sure how save the data.
In TLOZ you have a lot of sub-quests, treasures, items, and much more.
I want the user to only be able to open a treasure once.

Therefore I have to save this information somehow.
However, I'm not sure how to assign the data to that certain object, for example that treasure that Link has opened.

I thought about making id's for every object, but I'll have multiple maps, and it doesn't seem like the right solution.

Are there any standards for this?

Or does anyone have another idea?

share|improve this question
    
Are you asking how to save it to disk or how to maintain that information in memory? If it's how to save it to disk, just write the data structure you're using in memory to disk. Otherwise see here: gamedev.stackexchange.com/questions/937 –  Byte56 Jan 23 '13 at 17:01
    
I want to save it to the disk, what data structure in memory? You mean a complete copy of all that's in the memory? –  NSAddict Jan 23 '13 at 17:06
2  
Whatever structure you're using to track the quests in memory, not a complete copy of all the memory. Some kind of database or graph structure you're using to check if certain aspects of a quest have been completed? You're not using anything to store that information now? –  Byte56 Jan 23 '13 at 17:09
    
Why does making id's for every object seem wrong? If you're worried about collisions across maps you could make the save game ID equal to the map name + unique id for that map. –  Tetrad Jan 23 '13 at 17:43
7  
You should start with how you're going to organize the data in memory before you worry about saving it to disk. See the question I first posted to get an idea about managing a quest system. I think you may be getting ahead of yourself. Consider all the information you need to store, how it will be accessed and how you'll track progress. Once you have it working in game, then worry about how you'll store it to disk. –  Byte56 Jan 23 '13 at 18:45

5 Answers 5

up vote 6 down vote accepted

I would also go for the ID solution. You should give every object in your whole world (not just on the current map) a unique ID. A 32 Bit int should be sufficient. Further every object can store a state value, also a 32 Bit int value. You can squeeze a lot of information into 32 Bit, e.g. you can make 32 flags out of it to store 32 bool values. Or you can use the higher bits for flags and the lower bits to hold a numeric value. For something as simple as a treasure, you only need to store a bool, where a value of 1 means opened and a value of 0 means closed. The you have a game save object with two methods:

- (void)storeState:(uint32_t)state forObject:(uint32_t)objectID;

- (uint32_t)loadStateForObject:(uint32_t)objectID;

If you call store and there is no state stored for this object ID, the object ID is added to a list among with its state. If there already is a stored state, it is updated to the new state. The load method searches the list for the object ID (for fast access, consider a hashtable, so you don't have to perform a linear search or a sorted array, so you can perform a binary search) and returns the stored state or 0, in case no state has been stored (0 should always represent the default state for all objects).

When you now open a map screen, you initialize all the objects on that screen. The objects all perform a loadState() call to the current game save object to find out, what their initial state should be. For treasures, if it is 0, they display as closed and if it is 1, they display as opened. What is inside the treasure is usually hardcoded in the level data, unless you want to make it random, but if it is random, you don't have to store that in the state either, it will be randomly chosen when the treasure is opened. You may also make it partially random (e.g. the treasure must contain rubies, bombs, etc. and the amount must be at least between X and at most Y). Depends on how (un)predicable you want the game to be. I think the original TLOZ is rather predictable.

If the user now opens a treasure, the treasure updates its state in the game save object, indicating that it has already been opened. So next time the user enters the map screen, the treasure displays opened.

Whenever the game is stored to disk, the game save objects simply writes all known objectIDs and their states in the list to a file. This can be a simple list of two consecutive 32 Bit values like

<id1><state1><id2><state2>...

If a saved game is loaded, the game save object reads this list again and initializes its internal structure with it (the hashtable or sorted array). To indicate the length of the list, you may either store an additional int value in front, that tells the code how the long the list will be or you can terminate the list, e.g. by adding a final entry of objectID 0 to it, where 0 is reserved value (no real object in your game has this ID) and thus indicates the end of the list.

Of course not every aspect of your game may be savable that way, so a save game file may contain more information before or after the object state list. E.g. you may want to store how much rubies/bombs your character currently has, which other special item he posses, and so on. On the other hand, you could store all this to the list as well, using special reserved objectIDs. E.g. real objects start with objectIDs from 1000 upwards. If 0 is reserved, this gives 999 special object IDs. objectID 1 could store the amount of rubies, objectID 2 the amount of bombs, objectID 3 the kind of sword the player owns (in that case different values will indicate different swords; IIRC you could only have one sword at a time in TLOZ).

I admit that this is a very simply approach, but simple is usually good (think of the KISS principle). It should also be no problem to make this whole think very performant. Choosing the right data structure, adding to the list, searching the list and reading/writing the list from/to disk should be very fast, even on the slowest iPhone it should happen in no time. E.g. you can store everything in a NSMutableDictionary, keys are of type NSNumber with the objectIDs and values are NSNumber with the state information. Such a dictionary cannot be stored to disk directly (only dictionaries whose keys are NSString can be stored to disk with a couple of simple function calls), but iterating over a NSDictionary is really trivial. Just add all those 32 Bit values to a NSMutableData and this one can be saved to disk with a single method call. Loading is the other way round, read the whole file into a NSData (single method call again), iterate over the list and keep adding the keys and values to a NSMutableDictionary. Should be piece of cake.

I guess the original save format was somewhat similar to such a list, yet as the NES only had a 8-Bit CPU, I guess the values where only 8 Bit (that's why you could not have more than 255 rubies) and the objectIDs probably 16 Bit (16 Bit doesn't fit into the registers of a 8 Bit CPU, but just like 32 Bit CPUs can handle 64 Bit values, 8 Bit CPUs can handle 16 Bit values, it's just slower as multiple operations are required for everything that only requires a single operation for 8 Bit values).

UPDATE

Since I think this is a cool project, I wrote some sample code to give you starting point, in case you decide to go that route. This would go into SaveGame.h:

#import <Foundation/Foundation.h>

@interface SaveGame : NSObject {
    @private
        NSString * _filePath;
        NSMutableDictionary * _objectStates;
}

@property (readonly) NSString * filePath;

- (id)initWithFilePath:(NSString *)aPath;

- (uint32_t)stateForObjectID:(uint32_t)objID;

- (void)setState:(uint32_t)state forObjectID:(uint32_t)objID;

- (BOOL)saveToFile;

- (BOOL)loadFromFile;

@end


// Special Object IDs
enum {
    HeartContainerCountObjectID = 1,
    // Value is the number of heart containers

    EnergyLevelObjectID = 2,
    // Value is the current energy level as "half heart count", so 1 is half a
    // heart, 2 is one full heart, 3 is one and a half heart, ec.

    RubyCountObjectID = 3,
    // Value is the number of rubies

    BombCountObjectID = 4,
    // Value is the number of bombs

    KeyCountObjectID = 5,
    // Value is the number of keys

    SwordObjectID = 6,
    // Values defined below

    ShieldObjectID = 7,
    // Values defined below

    BoomerangObjectID = 7,
    // Values defined below

    RaftObjectID = 8,
    // Boolean value, 1 player has raft, 0 player hasn't

    StepladderObjcetID = 9,
    // Boolean value, 1 player has stepladder, 0 player hasn't

    // ...and so on...
};


// Values for SwordObjectID
enum {
    WoodenSwordObjectState  = 0, // Default
    WhiteSwordObjectState   = 1,
    MagicalSwordObjectState = 2
};

// Values for ShieldObjectID
enum {
    WoodenShieldObjectState  = 0, // Default
    MagicalShieldObjectState = 1
};

// Values for BoomerangObjectID
enum {
    NoBoomerangObjectState      = 0, // Default
    WoodenBoomerangObjectState  = 1,
    MagicalBoomerangObjectState = 2
};

// ... and so on ...

And this into SaveGame.m:

#import "SaveGame.h"

@implementation SaveGame

@synthesize filePath = _filePath;

- (void)dealloc
{
    [_filePath release];
    [_objectStates release];
    [super dealloc];
}

- (id)initWithFilePath:(NSString *)aPath
{
    if (!aPath) {
        [self release];
        return nil;
    }

    self = [super init];
    if (!self) return nil;

    // aPath might be a NSMutableString, so `retain` is not safe.
    // It's better to copy it. If `aPath` is a `NSString`,
    // `copy` in fact only retains the object instead copying it.
    _filePath = [aPath copy];
    if (!_filePath) {
        [self release];
        return nil;
    }

    _objectStates = [[NSMutableDictionary alloc] init];
    if (!_objectStates) {
        [self release];
        return nil;
    }

    return self;
}

- (uint32_t)stateForObjectID:(uint32_t)objectID
{
    NSNumber * stateNum;

    stateNum = _objectStates[@(objectID)];
    return (uint32_t)[stateNum integerValue];
}

- (void)setState:(uint32_t)state forObjectID:(uint32_t)objID
{
    // 0 is a reserved ID, must not be used
    if (objID == 0) return;

    _objectStates[@(objID)] = @(state);
}

- (BOOL)saveToFile
{
    NSUInteger capacity;
    __block uint32_t * raw;
    NSMutableData * saveData;

    // `raw` must be `__block` as we want to modify it within the block
    // and the modifications should be visible next time the block is called.

    // Two 32-Bit ints per state + end of list terminator
    capacity = sizeof(uint32_t) * (([_objectStates count] * 2) + 1);
    saveData = [NSMutableData dataWithCapacity:capacity];
    if (!saveData) return NO;

    raw = [saveData mutableBytes];
    [_objectStates enumerateKeysAndObjectsUsingBlock:
        ^(NSNumber * key, NSNumber * value, BOOL * stop) {
            *(raw++) = (uint32_t)[key integerValue];
            *(raw++) = (uint32_t)[value integerValue];
        }
    ];
    // Terminate list
    *raw = 0;

    return [saveData writeToFile:_filePath atomically:YES];
}

- (BOOL)loadFromFile
{
    NSUInteger i;
    NSUInteger count;
    NSData * saveData;
    const uint32_t * raw;

    saveData = [NSData dataWithContentsOfFile:_filePath];
    if (!saveData) return NO;

    // Even thouh the list is terminated, make sure we never read
    // beyond the end of the `NSData` memory! Don't rely on the
    // termination, it may be missing, the file may got corrupted, etc.
    // Also the file might have been truncated to 0 bytes, so be careful.
    raw = [saveData bytes];
    count = [saveData length] / 4;
    for (i = 0; i < count; i++) {
        uint32_t state;
        uint32_t objectID;

        objectID = *(raw++);
        // Stop `for` loop if end of list terminator is found
        if (objectID == 0) break;

        state = *(raw++);
        [self setState:state forObjectID:objectID];
    }

    // The list terminator was found way too early.
    // The save file is probably corrupt.
    if (i < count - 1) {
        [_objectStates removeAllObjects];
        return NO;
    }

    // No list terminator was found at all, the `for` loop was
    // terminated by the counter condition.
    // The save file is probably corrupt.
    if (i == count) {
        [_objectStates removeAllObjects];
        return NO;
    }

    return YES;
}

@end

The code is untested, but I verified that it does compile. It doesn't assume you are using ARC (automatic reference counting), but in case you do, the code will become somewhat simpler (all retain/release calls can be removed and dealloc must not call [super dealloc] in that case; copy calls need to persist also with ARC).

Of course, if all the "objects" like the player's sword are also represented by real objects in Obj-C, I would not place the values for the object states (...ObjectState enum values) into the SaveGame header. There is no need why the "whole app" needs to know about them, it's enough if the objects know how to interpret the state numbers. In that case I would place them into the implementation files of these objects, since this knowledge should then be private (encapsulated by the object).

I found a pretty useful web page, that lists all the objects from TLOZ.

Storing the Triforce State in object IDs is also easy. Each piece is represented by a flag (you have 32 flags per int, the Triforce doesn't even consist out of that many pieces).

Storing the dungeon maps seems a bit harder, but that isn't really the case. You know, those overview maps:
in-game dungeon map

You only need to know which rooms the player has already visited and if he has the compass for this dungeon or not. Easy. Each room of of each dungeon has an objectID with a boolean state, that is 1 if the player has visited the room, 0 otherwise. Same goes for the compass, the compass of each dungeon has a boolean state. Same goes for doors in the dungeons (which may have been opened or not) and "crack-walls" (which may have been bombed or not), each one has a unique ID and a boolean state. All information of this game can be stored in this one simple list... it's pretty amazing, don't you think :-D

Here is a very cool web page that has detailed maps of all dungeons of TLOZ, as well as an overview map of the whole overwold. Including information where specific items can be found (including secrets). Also check out the "Interactive Overwold Map", which allows you to navigate a 3 by 3 screens map and show/hide secrets and tips, including the insides of all secret caves. Basically this page contains all the information necessary to make an exact copy of the game, pixel for pixel you like.

The game logic is rather simple and the whole game will tun out to need much less code than one might expect. I guess the most time consuming task is setting up all the maps of the game, defining all the object IDs and so. Doing all this "in code" is probably a rather bad idea. I would think of a format that your code can parse, e.g. XML or JSON or maybe something as simple as structured ASCII text. Each map has a fixed number of "fields", so you could store each map as X by X values, where each value defines the content of the field: a tree, water, a coast line and which coast line, rocks and which rocks, a wall and which wall, an enemy and which enemy and into what direction it is looking (most enemies cannot look at a direction, thus you could treat the looking ones simply as different enemies, there are only 4 directions they can look at after all). Values below a certain value represent static objects, those are always the same when you enter the screen, all other values represents dynamic objects (those that store their state) and the number is split into two parts, one part is directly the object ID of the object, the other part the type of object. E.g. dynamic objects have the highest bit set, the next 7 bits define the object type (allows 127 types, e.g. 1 is a door) and the last 24 bits are directly the object ID (allows up to 16.7 Mio objects, you won't need that many), however keep in mind that values below 1000 are reserved as special values.

Since creating levels is often the most time consuming task, you may want to speed the process up and instead of starting by writing the game code, you start by writing a level editor for your PC/Mac. This seems like a waste of time, since it will take a lot of time, but trust me, it DOES pay off. Most professional games started by writing at least a simple level editor. It makes creating levels a very easy task (drag'n drop an object where you want it to be, give it an object ID if required, done). So not only it speed up the level creation process dramatically in the end, you cannot test your game code unless you have level data for testing it, since untested code is worthless code. And if you only created dummy level in the beginning, an editor allows you to quickly create them and also quickly change them to test certain aspects of your code.

Now go out and have fun young jedi. I wish plenty of luck with this project.

Update 2

If you'd like to use all the original artworks in your project, I found them online. Have a look at this page. It contains all the sprites used in the game. Even the original font. Just thought this might be useful for you.

share|improve this answer
    
Thanks a lot for your answer! Sounds promising! –  NSAddict Jan 23 '13 at 18:10
1  
@NSAddict: I wrote a huge update; it provides some code you may use, links to two very useful pages for making this game and also a couple of tips how to store data that seems more complicated and on storing and creating level data. I hope this information will turn out somewhat valuable for you in the long run :-) –  Mecki Jan 24 '13 at 13:38
1  
You are insane! :D thanks a lot! –  NSAddict Jan 24 '13 at 13:40
    
I'll award you a bounty as soon as i can :D TLOZ ftw! –  NSAddict Jan 24 '13 at 13:45
    
@NSAddict Another small update. I found a web page that contains all the sprites that were used in the original game. So if you really want a one by one copy, no need to start creating your own graphics, its all there ;-) –  Mecki Jan 24 '13 at 18:55

The key phrase you're looking for is game state serialisation.

Game state is what it says. Your game has some sort of structure to keep the current state of the game. In an RPG game, you want to store the list of quests the player is on, how far they are into those quests and what their characters' stats are.

Serialisation is the reversible conversion of a data structure and its contents into a storeable format. You can find serialisers for various languages. The key here is reversible. The serialised program state can be used to sufficiently completely restore a program's state. Just for tasters, here's a Java explanation and a list of Lua implementations.

If your game state is fairly simple, you might want to write your own serialiser for the data. As a trivial example, consider a boring game in which a player is able to move on a 2D plane. They have coordinates x and y. To serialise the game's state (the x and y) you could simply write them onto two lines of a text file. To restore the game state, read the text file and parse the first two lines into numbers that you assign to the variables x and y.

share|improve this answer

You are definitely on the right path, I believe that having an ID for all objects in some sort of database is the way to go. The way you model your database is up to you though, there are multiple ways to achieve what you want each one with pros and cons.

For example, in your database, you could have a list of all "Treasures", it can contain information such as what's inside and whether or not it's been opened. On your map, you could place that Treasure and your character will behave different based on the properties of that Treasure object (opened or not and content).

You could also keep a list of Treasures that had been opened in the past, when the player tries to open the chest, it checks to see if it's in the list of opened treasure to see if it has previously been opened. If not, reward treasure and add it to the list of opened treasure. I prefer the other solution better, but as I said, there are many different alternatives.

Thank you and I hope this helps!

share|improve this answer
    
Thanks for the answer! I'll wait for other answers so I can compare them. –  NSAddict Jan 23 '13 at 17:11

First you have to ask yourself what you need to be saving. For a Zelda style game, what map the player is on, what quest you have completed, and what items you have should be enough. I usually just write that data in some semi readable text form and store it in a file. It usually does not have to be more complicated then that

share|improve this answer

I would create a system where there is a central object, GameStateSerializer, that keeps track of all objects with a certain interface, ISerializable. If a class implements this interface, it will be able to save its persistent data and then read it in again when the object is loaded, thus enabling save games.

The interface will have two functions: one for saving, one for loading. Both functions will have a stream parameter so they can write and read their own data without needing to know where that data is going. The GameStateSerializer is responsible for managing those streams. This decoupling allows 99% of the code to stay the same even if you decide to change to save games stored on the cloud; simply use a network stream instead of a file stream. You could also then apply compression or encryption through the GameStateSerializer.

Additionally, each unique serializable object should have its own unique ID. This way, the GameStateSerializer can map an ID to an object and be able to load the objects in the correct order or at least be able to hand the correct data to the correct object. These IDs can be determined automatically by the game's level editor.

For example, a treasure chest object will have a boolean flag that indicates whether it has been opened, or, like in most Zelda games, a flag to indicate whether or not it has appeared after the player has solved a puzzle. The chest class will inherit from the ISerializable interface and implement the two functions. The Read function will read the two flags from the stream, and the Write function will write the two flags from the stream. Remember to always read the data in the same order that you write it in.

share|improve this answer
    
That's a good Java-world answer, but surely the question is language-agnostic? –  Anko Jan 23 '13 at 21:31
    
The OP is asking about iPhone, so the answer is fitted to his target domain. –  Boreal Jan 23 '13 at 21:57
    
iOS development is not just Java. –  Anko Jan 23 '13 at 22:06
    
Interfaces are not just Java either. –  Boreal Jan 24 '13 at 2:17

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.