Imagine for a second that you made a database for great big evil Bank of America and part of it needed to transfer money between accounts. You write this code:
x = moneyAmount (10, 'USD') // x represent TEN U.S. DOLLARS
@p
if (account1.hasAtLeast (x)) then
@p
account1.subtractAmount (x)
@p
// NO MANS LAND
@p
account2.addAmount (x)
@p
else
@p
beep ()
@p
end
Look at that weird line, "NO MANS LAND". What happens if the database CRASHES (a bug or a cosmic ray) on that line? The customer is out TEN U.S. DOLLARS! Dollars are serious business. And let's not comment on Bank of America's software writing abilities.
Fig 1. If this transaction fails at any point, we end up without our penny but ALSO WITHOUT DRACULA. Egads.
What you want is something that will either completely fail, or completely go through. That is called an atomic operation. You accomplish atomicity through something called transactions. There are different ways to do this depending on what system you are using, but generally it involves an "open transaction" command, followed by a bunch of operations (e.g., taking money out of some accounts, putting money in others) and then a "close transaction".
@p
When you wrap something in a transaction this way, you give it atomicity. Real useful!
Fig 2. Transactions to the rescue! They can enclose any number of strange interactions, as is clearly demonstrated. With this, we shall always have our penny or our dracula.
You know what I do not like? I do not like games that let you save everywhere. It shifts the thought process of the player away from engaging with the game world to managing their save states, making the whole thing more artificial.
@p
I favour a zelda-style saving scheme, where basically your progess is always saved but there are certain sequences of tasks (traverse dungeon, defeat boss) that have to be done all at once.
@p
You could almost view these sequences of tasks as gameplay transactions.
Which brings us to my topic, finally. Suppose you have a bunch of gameplay variables, stored in a big array let's call it "Session". Session defines, for instance, how many coins the player has. When the player buys something, you give her an item and take some coins; the item is also stored in Session.
@p
What often happens, however, is you create a certain sequence for the player. For instance: "enter this room, notice the statue of Goldair, it's magnetic field rips your sword away, and now you must do a complete boss battle with only bombs and your unicorn horn."
@p
Here, we are taking the player's sword away, maybe a bit contrived but all things considered a pretty reasonable design choice (remember the cave of the Dark Elf from FF2?)
@p
However, the player then has to beat the boss! What if the player does not beat the boss? What if... the player... DIES? Depending on what you do with Session after they die, they might ressurrect at the start of the dungeon, WITHOUT their sword. That is probably not what you want.
Here is what you do. You need to envision the entire boss sequence, starting with when the player notices the statue of Goldair, as a transaction. In your scripting code, you do this:
Session.lock ()
@p
... code for noticin the statue of Goldair
@p
... code for stealing the sword
@p
... code for spawning the boss
@p
... code for waiting until said boss is defeated
@p
... code for returning the player's sword
@p
Session.unlock ()
Session.lock should do two things:
@p
1) It should save the player's current progress and write to disc. If they had 57 coins, the bronze armour, and so on, before the boss battle, they should respawn with those same items, even if some of them were gotten somewhere IN the cave.
@p
2) It should set a flag that means the save game will not be written to disc. If some other code, somewhere (e.g., menu code, suspend event, whatever) hits Session.save, the game will actually NOT be saved.
@p
Session.unlock should save the game to disc and unset the flag
@p
There are variations on this as well, depending on the specifics of how your save system works (e.g., do you always allow the player to NOT save if they so choose, or do you just auto-save?)
Using this methodology, you can simultaneously be nice and mean to the player, which is pretty much your whole job description as game designer.
@p
You can be nice because you're giving the player a miniature space (the transaction) in which to freely experiment. They can use all their bombs and elixers and if they die, no biggie! They can re-try the boss battle with their same state.
@p
You can be mean because it lets you have comparatively free reign to abuse the player temporarily. You can fairly make a boss that requires the player to drink elixers, because at the end of it they will have traded those elixers for defeating the boss. Elixers dranken don't count unless they win, but then after they win, they do. Perfect!
Older games use this kind of transactional system by only providing limited save points. This certainly frees you up as a designer but in my mind this is too punishing, especially for the modern age. Save anywhere doesn't have this problem, but goes too far for my liking, at least when it is in the sense of your whole state being saved.
@p
Instead I prefer a save system based on transactions, where the player has to do certain things all at once but the save system is otherwise quite forgiving.
@p
Note that you can also transaction-lock only SOME variables. For instance, one thing I often do is build transactions for only the player's position. So when they enter a dungeon, any money they collect IS auto-saved but their position is not saved until they return to the overworld somewhere; if they die they have to start back at the dungeon entrance (Zelda games introduced the idea of a dungeon lobby, for just this purpose, which I think is still somewhat underutilized today.)
@p
You get the picture! Hopefully, framing certain design decisions in terms of transactions, both literally (e.g., in your Session class) and figuratively (e.g., in gameplay sequences) can be a helpful tool.
2011-06-10