I've bounced around several threads to try and understand how to save game data but it's all kind of scattered, so I'm looking for something more concrete, or an example.
I've read section on memory in the manual and see the cstore() peek() and poke() stuff...but it's just it's just not clicking. I think it's because it's talked about in bytes and addresses, and that's foreign to me. But it seems like saving data between sessions is a do-able thing...I hope...?
I have two things I'm trying to do with save data...
- Save a high score
- Save a boolean to denote whether the player has unlocked a character
I understand that data won't persist between platforms, which is fine. I just want the data to be able to used between game sessions on the same device. So the first time I play the game and get a score, it's saved. I turn off the device/cart. Then I turn it back on and my high score is there.
Any guidance or code snippets are appreciated. This might be a good idea for a Fanzine article too (unless it's already covered and I skimmed over it).
You can use dset and dget to write and read lua values directly or you can peek and poke the addresses. With dset/dget, each value takes four bytes, as with peek and poke you can set and read single byte values. Eight bits make up one byte, so you can store up to eight booleans into one byte by using bor.
You can save that kind of data to the "persistent" area (addresses 0x5e00 to 0x5f00) using poke (that way you have access to 256 individual bytes, that is 8-bit numbers).
You can also use dset to store 32-bit numbers in that same memory space.
Imagine you want to save the level the player is in, and you have less than 256 levels in your game. If your number is stored in your variable "levelnum", you can use poke(0x5e00, levelnum) to store that number in the first byte of the persistent area as an 8-bit number.
If you wanted to store a big number or a number with decimal places, you need to save it as a 32-bit number, which takes four times as much space, and you'd need to use dset(0, yournumber).
dset/dget notation is more comfortable, but peek and poke gives you more control over the space you take up (and you can use them to save binary data such as user-made images). If you have less than 64 numbers you want to save, you can use dset and dget and not worry.
Remember these functions work with the same space in memory:
poke(0x5e00, a) ---- dset(0, abcd) poke(0x5e01, b) - | poke(0x5e02, c) - | poke(0x5e03, d) - V poke(0x5e04, e) ---- dset(1, efgh) poke(0x5e05, f) - | poke(0x5e06, g) - | poke(0x5e07, h) - V (...)
So with dset at slot 0 you use up the four bytes from address 0x5e00 to 0x5e03. With dset at slot 1 you occupy addresses from 0x5e04 to 0x5e07, etc. In the same manner, you can read one byte at a time with peek, or 4 bytes at a time with dget.
Okay, cool, thanks...I think I got it.
So lets say the player has a high score of 2500 points. That's a "big" number since it's over 256, right? So that means I'd have to use dset()
To store that number:
To retrieve that number:
Is that correct?
But then lets say I'd want to store player initials (ABC) along with the high score. I would just do dset(1,playerName) and the computer knows to put that into slot 4 of the memory and not conflict with the high score? Or do I have to account for that?
That makes sense. The only thing I have keep track of then is the size of each value so I know how many dset() slots I have available.
I'll give this a try later. Thanks for the help and snippet.
Well that's a bummer. Break out the decoder ring, I guess.
And I just reread things again and it finally clicked that the dset() ONLY writes in 4-slot chunks. So I had a number like 12345, I'd have to dset(0,1234) and dset(1,5)...and then concat them back together on the way out.
But general best practice would be to store simple numbers or even 1/0 boolean type things and just translate that with the code when dget() is used.
@morningtoast be careful though, your last assumption is not correct!
I simplified my explanation, let's use the correct terminology so that we don't get confused. Don't think these "slots" are digits, they're not! They are bytes. In decimal you cannot really tell easily how much a number takes up by looking at the number of digits. If you look at the number in hexadecimal base though, it's easier.
dset writes 4 bytes at a time, that means that if you store any number from -32767 to 32767, it's going to take up 4 bytes (decimals are allowed).
dset(0,1234) occupies the first 4 bytes with that number (padding it out with zeroes), but so does dset(0, 5). 1234 in 32-bit (4-byte) hex would look like this: 0x000004D2*, while 5 in 32-bit hex would be: 0x00000005.
poke writes 1 byte at a time, meaning anything from 0 to 255 (note this time it's unsigned while with dset was signed, also decimals are NOT allowed.)
poke(0,144) occupies just the one byte. 144 looks like this in hex: 0x90. In 8-bit (1-byte) hex, 5 looks like this: 0x05.
Open up PICO-8 and in the console play with poke and print(peek) as well as dset and print(dget) until you get a grasp of this.
memory address poke(0x5e00, 5) dset(0,5) 0x5e00 5 0 0x5e01 * 0 0x5e02 * 5 0x5e03 * 0 memory address poke(0x5e00, 144) dset(0,144) 0x5e00 144 0 0x5e01 * 0 0x5e02 * 144 0x5e03 * 0 memory address poke(0x5e00, 266) dset(0,266) 0x5e00 10 0 0x5e01 ! 0 0x5e02 ! 10 0x5e03 ! 1 * = unmodified. ! = this number is too big for "poke". Only the remainder of 266-256 is stored, the rest is lost. This is because the number 266 (0x10A) is chopped off at 1 byte, which is 10 (0x0A).
* actually in memory it would look different depending on your processor, but I think endianness is not relevant to this explanation or to PICO-8 for that matter.
I tested this layout by dset-ing numbers and then peek-ing byte by byte. As you can see, the memory layout looks weird byte by byte when using dset (because of how decimals are stored), so make sure to read the numbers you store with dset with dget, so that they get interpreted right!
[Please log in to post a comment]