il y a 7 ans
This will explain you how to use the gamebuino save format, the gb.save interface. First, this will explain the simple usage and after that the advanced, more customizable usage with config-gamebuino.h
The savefile is structured in blocks. A block can hold a number or a buffer. By default the savefile has 64 blocks.
Handling numbers is very easily done:
gb.save.set(0, 42); // saves 42 in block 0 gb.save.set(1, -1337); // saves -1337 in block 1 int i = gb.save.get(0); // fetches block 0, which is 42 int j = gb.save.get(1); // fetches block 0, which is -1337 gb.save.del(0); // deletes block 0
Numbers default to zero (configurable, see advanced usage)
Now, instead of storing a number in a block, you can also store a blob. By default the size of a blob is 32 bytes.
Well, what is a blob? A blob can be literetally anything! You can store strings, buffers, objects or just about anything you'd want to store.
gb.save.set(2, "Foxes are awesome!"); // stores the string "Foxes are awesome!" in block 2 CustomObject obj; gb.save.set(3, obj); // stores obj void* buffer = malloc(10); gb.save.set(4, buffer, 10); // stores a buffer of length 10
if these functions return true setting was possible, if they return false then something went wrong.
Now, how do you fetch those blobs?
char string[32]; gb.save.get(2, string); gb.display.print(string); // prints block 2 as string, so "Foxes are awesome!" CustomObject obj; gb.save.get(3, obj); // fetches the custom object void* buf = malloc(10); gb.save.get(4, buf, 10); // fetches up to 10 bytes from block 4
if these blocks don't exist yet the buffer is filled with zeros (configurable, see advanced usage)
Please note that after setting it is enforced wether the block is an int or a blob. For example:
// this will error gb.save.set(0, 42); gb.save.set(0, "blah"); // error, block is an int!! // this will also error gb.save.set(0, 42); char name[10]; gb.save.get(0, name); // error, block is an int! // this will work, though gb.save.set(0, 42); gb.save.del(0); // we delete the block gb.save.set(0, "Foxies!");
You can easily store floats / signed ints etc. as ints by casting them:
gb.save.set(0, (int32_t)3.14159); float pi = (float)gb.save.get(0); gb.save.set(1, (int32_t)true); bool stuff = (bool)gb.save.get(1);
The basic idea of advanced usage is to pre-define which block is an int and which one is a blob and to allow custom defaults. For this we need to create an array of the type SaveDefault, passing that then on to gb.save.config:
const SaveDefault savefileDefaults[] = { { /* data */ }, { /* data */ }, }; void setup() { gb.begin(); // set the save defaults gb.save.config(savefileDefaults); }
Now, there are multiple different SaveDefault data things possible. The first argument is always the block number to configure, the second one which type that block must be (SAVETYPE_INT or SAVETYPE_BLOB). After that, it depends on if you use int or blob:
SAVETYPE_INT
As third argument you have to set the default value. Fourth one is always zero. So these all are valid SaveDefault data things:
{ 0, SAVETYPE_INT, 9999, 0 } { 1, SAVETYPE_INT, -42, 0 }
SAVETYPE_BLOB
Blobs must have a third and optionally a fourth argument. With only three arguments the third one is the max length of the blob (fourth one must be zero again), with four arguments the third one is the default and the third one the max length. For the default data, you see below how that needs to be set.
The length of a blob can only be up to ~2^32 bytes*!
So these are valid blob constructors:
{ 2, SAVETYPE_BLOB, 20, 0 } { 3, SAVETYPE_BLOB, {.ptr="Fox"}, 20 }
Ok, that was a lot to take in. So here is an example taking full use of all the features:
const SaveDefault savefileDefaults[] = { { SAVE_PLAYERNAME, SAVETYPE_BLOB, {.ptr="Link"}, 20 }, { SAVE_HEARTS, SAVETYPE_INT, 3, 0 }, { SAVE_SOMEBUFFER, SAVETYPE_BLOB, 25, 0 }, }; void setup() { gb.begin(); gb.save.config(savefileDefaults); } // somewhere later in the code char name[20]; gb.save.get(SAVE_PLAYERNAME, name); gb.display.print(name); // prints "Link" int hearts = gb.save.get(SAVE_HEARTS); // returns 3
I sure hope this helped you to understand how to use the save API to its full extend!
*: While a blob can be up to ~2^32 bytes large, the total payload may not exceed 2^32-1 bytes. Payload is all blob sizes added together. A blob is always four bytes larger than you set it, because it is prefixed with its size.
NEW il y a 6 ans
Please note that after setting it is enforced weather the block is an int or a blob.
*whether
NEW il y a 6 ans
Out of curiosity since games typically won't be sharing save data what is the advantage of using this format vs just defining and using your own custom save format?
NEW il y a 6 ans
Well, with your own save format you'd have to think yourself about how to save things etc. This API provides abstraction to the point that you can just
gb.save.set(0, someObject);
and all the hard lifting is done by the library
NEW il y a 5 ans
Hello, thank you for these explanations.
I'm totally new using c++ and I have a little question :
I don't understand this syntax : {.ptr="Link"}
Thank you again.
EDIT :
Another couple of questions while I'm trying to make this work on my stuff :) :
I want to save the last unlocked level, so I set the default value like this :
#define SAVE_LAST_UNLOCKED_LEVEL 0 const SaveDefault savefileDefaults[] = { {SAVE_LAST_UNLOCKED_LEVEL, SAVETYPE_INT, 1, 0} };
The default value is 1 so if no level has been unlocked yet, only the number 1 is available.
This works well so far, but I have a problem when I try to overwrite the value I stored there.
My setup function looks like this :
void setup() { gb.begin(); gb.save.config(savefileDefaults); currentLevel = gb.save.get(SAVE_LAST_UNLOCKED_LEVEL); lastUnlockedLevel = gb.save.get(SAVE_LAST_UNLOCKED_LEVEL); chosenLevel = currentLevel; displayMenu(); }
And later in the program, when a level is cleared, I call this function :
void updateSavedLevel() { if (currentLevel > lastUnlockedLevel) { gb.save.set(SAVE_LAST_UNLOCKED_LEVEL, currentLevel); } lastUnlockedLevel = gb.save.get(SAVE_LAST_UNLOCKED_LEVEL); }
But that SAVE_LAST_UNLOCKED_LEVEL remains always 1, so the levels don't unlocked themselves in the menu...
Did I miss something ? Is it possible to write over that default value ?
If not, what should I do ?
Thanks.
Sorunome
il y a 5 ans
I don't understand this syntax :
{.ptr="Link"}
If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.
Another couple of questions while I'm trying to make this work on my stuff :) : [...]
are you using lastUnlockedLevel
to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel)
to verify that variables contents?
NEW il y a 5 ans
I don't understand this syntax :
{.ptr="Link"}
If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.
Another couple of questions while I'm trying to make this work on my stuff :) : [...]
are you using lastUnlockedLevel
to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel)
to verify that variables contents?
Codnpix
il y a 5 ans
If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.
Thank you for the link !
are you using lastUnlockedLevel to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel) to verify that variables contents?
Yes this is the variable I use to update the menu. I did try a print of my variable, and even directly of the gb.save.get(), but nothing moved...
But I finally succeeded by just using the basic operations save.get() and save.set(), and no default values array... It was not really necessary in that precise case but it was the occasion to understand how to use it.
NEW il y a 5 ans
If you are rather new to C++, you shouldn't worry too much about the syntax, just that you can use it this way. If, however, you want to look more into it, then look into struct and unioin initilization.
Thank you for the link !
are you using lastUnlockedLevel to base the menu output off of? did you try to gb.display.print(lastUnlockedLevel) to verify that variables contents?
Yes this is the variable I use to update the menu. I did try a print of my variable, and even directly of the gb.save.get(), but nothing moved...
But I finally succeeded by just using the basic operations save.get() and save.set(), and no default values array... It was not really necessary in that precise case but it was the occasion to understand how to use it.