Save system - Demo and discussion inside

This is following from the thread on creating stories vs games in CS (thread) and a lot of comments about the need for a save system to make more ‘game-like’ CS games viable. I have lapsed into a state of insanity and spent my evening coding a demo for a save system.

This is something I already had in mind for my own games and I figured I may as well procrastinate from writing actual prose to build and share this instead.

What does the save system do?

  • Autosaves the game. In the demo it does it before each choice, but it can also be used to make specific checkpoints (e.g. at the end of chapters) - you can make more than one autosave to checkpoint various parts of the game
  • Players can manually make their own saves. Currently there are two save slots which are overwritable and you can load either save slot.
  • Players can load the autosave or either save slot at any point
  • Players can name their saves so they can find the right one to load

The save/load system demo - https://dashingdon.com/go/8162

How does it work? How easy is to use? Will it work for my game?

  • The basic principle is that every important facet of the game is saved in a variable. For example, we might have ‘GoldAmount = 100’. The code then copies that value to a ‘save’ variable, for example ‘SaveGoldAmount_1 = 100’. For the second save slot, it copies it to ‘SaveGoldAmount_2’.
  • To load, we simply reverse the process and set GoldAmount to the value of our save variable.
  • There’s a bit of control flow here and there to make sure you’re recording the right thing at the right time and the correct saves/loads occur - but it’s nothing groundbreaking

I’d say it’s pretty straight forward to adapt to your own game once you have parsed through how it works. The hardest bit is ensuring the control flow is correct and that you’re saving where you’re supposed to, that the correct values are passed to the correct variables and so on.

This design should work for almost any game that I can think of (right now in my insane state).
The game saves all the required variables to define the ‘state’ of the game (obviously you will need to make sure you add all the necessary ones) and also the scene the player was on.
When they load the game, it will put them right back on that scene with all the variables set to as they were at that time - it will be as if they went back in time.

CS actually works really well for this system as it is itself a state-based language. Where you are in any game is entirely determined by the value of variables, the only movement you do is between scenes and labels and the load system can easily place you back where you need to be.

Is this useful for CS games? What would make it better?
I dunno. Discuss!

9 Likes

The idea is nice, but there’s a visible downtime while saving, and there isn’t that many variables in your demo. I think it would be impracticable as is.

There’s also a problem that the number of variables is multiplied for each slot. It is not scalable. For a real game with hundreds of variables I think it would be crazy.

Have you thought about building a parser and serializing the variables? I don’t know how performant that would be. I’ve started doing something like that, but haven’t completed.

2 Likes

Is the downtime not just due to Dashingdon rather than the number of variables? It runs just fine on my computer and I’d be very surprised if setting a handful of variables caused a noticeable slowdown when run on a device locally using its memory/processor.

Of course, hundreds of variables would be a different story. But you don’t have to save every single variable for a save, there’ll be many variables which are fixed for the game (names, gender, pronouns, etc). You can also segment your game so that saves are only valid for a certain period of time (e.g. a chapter or two), that way you only need to save the variables of relevance to that chapter. I suspect there’s some not too cumbersome methods of reducing the workload for the save system.

But yeah, I wouldn’t recommend using this for anything more than one or two save slots in reality, if nothing else because there’s not really any need. I’d imagine that the majority of games that want a save system will want an autosave/checkpoint feature and a single save slot.

The game runs in the browser, not on the server if I’m not mistaken. So maybe it’s my browser? :joy: Anyway, I played it on my phone (6GB), on chrome, if it helps.

I may be wrong, but while the game runs on the user’s computer (browser), it occasionally needs to fetch files/data from DD (esp gosub_scenes and goto_scenes), including the (non-CS) save slot data, which is stored on DD, not the browser.

So there could be a delay due to things on DD’s end, especially if waiting to download/write the save data from/to DD.

In fact, I’m pretty sure the order goes like this:

  1. Initialization - Browser downloads and executes CS “app”
  2. Browser loads variables from startup.txt and stores them locally
  3. Browser rewrites variables locally along the way as user proceeds through script
  4. Browser occasionally has to load new scenes/images/sounds from DD/host
  5. User saves are written to DD and NEW scripts are executed (outside of standard CS package)
  6. A user may recall (download) save from DD, again using NEW scripts

So there is a bit of traffic between DD/host and the browser, depending on what’s going on

2 Likes

I’m pretty sure the saves are stored in cache on the browser. If you clean your cache all saves disappear.

I think the only traffic is when fetching new files.

2 Likes

I just tried it out again on DD and it’s saving and loading pretty much immediately. Without doing a massive load of testing, it’s hard to say how well/poorly it would scale with increasing numbers of variables.

I was thinking of how you can mitigate this in the event that you do have loads of variables in your game.
You will typically have three types of variables:

  • Static (name, gender, pronoun, etc.)
  • Dynamic and always needed (relationship trackers, core stats, etc. Anything you’re using throughout msot/all of the game(
  • Dynamic and time limited (flags for making certain choices, such as meeting a character, going to a specific place, etc.)

You don’t need to save the static ones as they never change.
You will always need to save the first type of dynamic ones
You can use a variable to track the ‘stage’ of the game and then an *if in the save/load to only save and load the relevant dynamic variables from the third group that are relevant at that point in the game.

The real test is to stick it in a game and see if it works. But for now, it’s here for people to use, or not use, as they wish.

1 Like

First of all, thanks for implementing the save system :smiley: :+1:

In addition to the obvious use case of going back and trying another option, I find the autosave feature specially useful because sometimes I want to re-read the previous page but there is no button to do it. Will it be a standard feature of Choicescript? I mean, if I download the latest version of choicescript, and I write a story, will it allow saving games?

Regarding the plain save, I’ve always wanted to download the variables in a file, which would be quite useful in games that promise a second part but do not allow saving the game yet. I would like to be able to store it somewhere until the second part is out and the first part allows saving the game. Will it be possible with this saving system?

This is not a standard feature of Choicescript and would have to be manually coded in by the author. It isn’t especially complicated to do, but will be cumbersome for more complex games (read: those with lots of variables) and will depend largely on what the purpose of the save system would be.

An autosave is the hardest and most resource intensive type to implement as you have to save variables after every choice/screen.

I think the use case that is the least intensive and likely to be the most useful/applicable across different games is to create checkpoints before major scenes or chapters and allow players to restart the game from these.

The principle sitting under the save system can be used to output the variables (kind of) from the game, but it requires action from the player. This is entirely theoretical at this point, but you could print the variable values on the screen, separated by commas and then paste this into a file - generating a CSV file. This can then be read into an established table structure (e.g on Excel), assigning the values to the correct variable in a spreadsheet.

You can even then re-import those values into a game to load someone elses save game, but you have to import the values directly in the code. There may a clever way to enter the string into an *input_text and have CS parse it to the correct variables, but it sounds messy.

The concept of “going back to the last page” has been debated quite intensely on this forum for at least a decade.

The long and short of it is that most people (including the founder of CoG) are against it, mostly because it’s seen as reducing the “impact” of making choices.

3 Likes

Absolutely, I suspect that on balance the ability to just ‘go back’ and re-do every choice would diminish the overall quality of the game as it’d be so easy for a player to do and suddenly the story loses its tension and impact. Interestingly, I suspect that having to go back to the start of the game to replay it and make different choices is actually a big factor in making any one playthrough more meaningful.

Whilst my demo had an autosave on every screen, that was purely to show the functionality. I woud imagine that a much healthier design would be to use it as an automatic checkpoint system for chapters/prior to key events.

One of the main ‘issues’ people have raised about harder games which have hard loss conditions which end the game prematurely is that they have to go back and replay from the beginning. This design easily allows the player to go back to the last checkpoint and resume playing and shouldn’t be too cumbersome in terms of variables.

1 Like

The idea is interesting. A way to do in a scalable way would be to make copies of this.stats using JavaScript, with all usual implications of using JavaScript.

2 Likes

I don’t know anything about JS, so would have to leave anything like that to greater minds. And I know using JS adds complications for publishing a game - in theory, this CS only design does make save/loads practical for the typical CS game.

I suspect that the majority of games that would want something like this would achieve a lot of the desired functionality with a single autosave serving as a checkpoint (or perhaps a few checkpoints). That ought not to tax the system too much as you’d only be interacting with the save and load infrequently.

If I ever get to that stage with a suitable game, I’ll definitely try it out and we can see what kind of performance hit it entails.

There is ChoiceScript Saving Plugin (Update: Sept. 2019), which is supported by DD.


@sinnie I think JimD has been using something very similar to this system in his ZE games for a while now. May be worth a cross reference to see if there are any notable pros/cons across the two?


@cup_half_empty I’ve found a lot of heavy CS stuff slow on my phone too. I don’t know if that’s simply down to phone CPUs or if there is more at play here. @choicehacker 's racing simulator for e.g. isn’t very responsive on my mid-range android phone.

FWIW this seems to run fine on my phone though. Can’t see any noticeable slowdown in the linked demo.

6 Likes

I’ve been one of the people voicing a need for a save system, but after tinkering with CS, I wonder if the easiest way to deal with this is, at the end of every chapter, simply have a choice to proceed or restart that particular chapter. All it takes is a simple goto for each option (goto next chapte, goto start scene). It’s not ideal, I suppose, but it could easily be played off as a deja vu in the story (“So I woke up and went about my day that was eerily similar to my dreams…”) and will take care of any misunderstandings of choice meanings between author and player in a way where the player can go back and fix it for that chapter.

3 Likes

I presume that the code for ZE (or parts of it) isn’t available anywhere and I would instead need to grab a copy of the game to check out the system? Other than in terms of UI/presentation, I can’t imagine there’s much different (unless Jim found a super elegant way to manage the variables), as the fundamental process of save/load is in the mass assignment of variables.

I’ve not played with your plug-in, I presume it is replicating the built-in CS save that is part of published games? And that it is tracking your current state (for closing and re-opening the app)? I know your plug-in has save slots, but that isn’t present in published games, is it?

That’s fundamentally what this demo is/does. Depending on the complexity of the game, you can’t just use a *goto_scene to jump back to the start of a chapter as any number of variables might have shifted over the course of that chapter. For example, if you start chapter 3 with 100 gold, and over the course of it accumulate 200 more, if you simply go back to the start of chapter 3, you will now have 300 gold.

Similarly, you can’t necessarily just hard reset the variables, because many of them depend on the player’s choice. For example, a player could start chapter 3 with anywhere between 0 and 200 gold, when you want to reset back to the start of chapter 3, how much gold do you set the variable to?

With my method, at the start of chapter 3, you use the ‘autosave’ feature to take a copy of every relevant variable. Then when you want to reset back, you pull all the values out of those copies and the game literally resets the state back to exactly as it was.

This can be used to checkpoint chapter starts or in the event of MC dying/failing something. You’re right then the simplest implementation is then to offer the ability to replay the chapter, which then loads the autosave.

1 Like

Yeah, I tried it again and it seemed fine.

I think @Sinnie is looking for a solution which does not involve Javascript. If not, than @choicehacker’s solution or @CJW’s plug-in seem the most straightforward way to go about it.

Anyway, in videogames, save systems serialize variables and persist them as save objects. Usually.

So, I believe that the best scalable way would be to build routine to serialize/deserialize the variables into a string. Of course, you’d pay the price in the parsing step.

1 Like

I didn’t clock what you meant when you suggested it earlier in the thread, but I think what you’re suggesting is this:

*create SaveString_1
*create Gold 100
*create Name "Bob"
*create Gender "Male"

*set SaveString_1 (Gold & "," & Name & "," & Gender)

You then have many versions of SaveString_X and just pass the string to the relevant variable number dependent on which save slot you’re using at the time.

I was thinking about that and I think the save works fine (other than it being a really long string of code).
I figure you can probably put it in a loop:

*set SaveSlotNumber 2

*label SaveLoopStart
*set SaveVariable "NextVariable"
*set SaveString[SaveSlotNumber] ("," & {NextVariable})
*goto SaveLoopStart

But I wasn’t sure on the best way to iterate through all the variables you need.
One way is to give all the variables a standardised name, such as: Variable_1, Variable_2, etc.
Then we can iterate through them using Variable[NextAttributeNumber] and that should work fine. But, you lose all the business logic in your variable naming and will forever be trying to remeber what variable_24 is.

You could use multiple *if to generate a lookup:

*if NextAttributeNumber = 1
    *set NextAttribute "Gold"
*if NextAttributeNumber = 2
    *set NextAttribute "Name"

This lets you iterate through the list in a loop, but requires more code. This is the middle ground between the big long *set command and the simple loop. Unless I’m missing something to make it simpler to iterate through the list, then I suspect this is the best way to produce a single save string.

Having said that, I think loading from that string would be a nightmare. Again, provided I’m not missing something, I think its impossible in CS to do it accurately? The only method I can think of is something like this:

*set Location (SaveString_1 #12) & (SaveString_1 #13) & (SaveString_1 #14)...

The issue here being, what if your variable values aren’t of fixed length?

Fundamentally I’d still wonder whether a CS save system needs to be that scalable to fit most use cases, and if it did, it might just be easier to replicate the variables a few times and use my existing method. There shouldn’t be any additional slow down between having 1 save slot and 5, because you’re only interacting with one slot at a time. That would be except for startup and you having to create 5 copies of all the variables in the code.

1 Like

You definitely want to use loops for that, and you should parse the string instead of figuring out the length of the variable.

I think the biggest pain would be to figure out a way to register variables to be serialized, so the routine knows it. I think the only solution would be to hardcode it, or parse another string with the name of the variables, like in @Twiger_Fluff’s list implementation.

The deserialization routine would read the stream of characters looking for the separator character, in your case a comma. I would personally make this logic a little more robust to avoid splitting at the wrong place, for example if the value of a saved variable is a string which itself contains the separator character.

Like I said, it’s cumber some to build this system, but once in place it would be easier to use.

2 Likes

That looks like it would induce a nice headache trying to figure out!
I think that there is unlikely to be a demand for so many save slots that figuring out parsing a string like that would be worth the implementation effort.

I also notice that Zombie Exodus:Safe Haven lets you load saves from Zombie Exodus - so evidently it is possible to port a CoG save from one game to another.

Is there an easy way to look at the code of a published game?

1 Like