Grid-based game: Serialising position

Started by
19 comments, last by Zipster 6 years, 10 months ago

Oh, interesting!

Well, the issue is, if the player-object wants to move from field 2 to field 3, there will be an animation that transforms the position of the player-object. If a serialisation occurs during this progress, the player will be saved in-between. Even worse: Imagine the playing stopping the game just briefly after saving has been successful, what would happen if the player wants to load this later?

I could of course say, once an object's centre passes the border to a new grid-slot, it will automatically moved to that slot within the vector/list/.. . Therefore, when adding objects from file to field, I would have to calculate, where each centre-point fits in.

This avoids having empty slots in the file, which is ideal, as the average simple calculation is, as you already said, faster than any read/writes. Especially, since empty-objects have to be written to the file and read from it. While calculating allows me to simply map coordinates to slots only during loading, while saving only has to write the actual coordinates.

I really like this approach, thanks a lot for helping me out : )

 

Advertisement
19 hours ago, Angelic Ice said:

Well, the issue is, if the player-object wants to move from field 2 to field 3, there will be an animation that transforms the position of the player-object. If a serialisation occurs during this progress, the player will be saved in-between.

I don't see any issue, tbh. Conceptually, a save+load is a no-op (well, that's how you code it usually at least). That is, after load, you should get the same situation as that you had if you never saved+loaded. I don't see how save affects player movement.

Right now, you have a way to move the player. You may keep the player in one grid, and at the end of a move assign it to the new grid, or you can continuously update its position, and it changes grid when it passes the border, or whatever.

save+load is a no-op, which means you restore to the exact same situation that you had when you pressed "save". If you move the player to the new grid at the end of the move, then the player is logically in the originating grid until the very end of the move. The destination grid is then empty until the moment that the move ends. That is what you want to happen after load too, since "load" acts asif the movement was never interrupted.

The fact that the display suggests that the player is in a different grid position than it logically is, is a display-trick in that case, fooling the user into believing a smooth move (which is fine). This is however following from your decision how you move your player, not from any save or load.

19 hours ago, Angelic Ice said:

Even worse: Imagine the playing stopping the game just briefly after saving has been successful, what would happen if the player wants to load this later?

I have no idea what worries you have here. When you press save, the game-state at that moment is saved, such that you can load it back to the exact same situation later. What happens after a save is not part of the saved game-state, and thus won't happen if you load that state again. It's no different then you're standing next to a gem, you press save, you pick up the gem, and you load the save. The gem is back on the floor and not in your inventory.

19 hours ago, Angelic Ice said:

I could of course say, once an object's centre passes the border to a new grid-slot, it will automatically moved to that slot within the vector/list/.. . Therefore, when adding objects from file to field, I would have to calculate, where each centre-point fits in.

How are you going to restore the exact same game state from that? It seems overly complicated to me.

Of course, technically, there is no need that save+load is a no-op. You could restore to an equivalent state instead, and continue from there. That however complicates matters since you then have multiple ways to express the same thing, which means all code has to deal with each way that may happen. That usually quickly explodes into toooo many options to deal with.

19 hours ago, Angelic Ice said:

This avoids having empty slots in the file, which is ideal,

Huh? All slots are always filled, except during a move?

 

2 hours ago, Alberth said:

Right now, you have a way to move the player. You may keep the player in one grid, and at the end of a move assign it to the new grid, or you can continuously update its position, and it changes grid when it passes the border, or whatever.

save+load is a no-op, which means you restore to the exact same situation that you had when you pressed "save". If you move the player to the new grid at the end of the move, then the player is logically in the originating grid until the very end of the move. The destination grid is then empty until the moment that the move ends. That is what you want to happen after load too, since "load" acts asif the movement was never interrupted.

 

2 hours ago, Alberth said:

I have no idea what worries you have here. When you press save, the game-state at that moment is saved, such that you can load it back to the exact same situation later. What happens after a save is not part of the saved game-state, and thus won't happen if you load that state again. It's no different then you're standing next to a gem, you press save, you pick up the gem, and you load the save. The gem is back on the floor and not in your inventory.

Maybe this is a part of my problem. I cannot really think of a smart way to serialise actions (player-inputs and resulting game reactions), as my game is mainly done round-based. An input, as a mouse-click to a grid-slot, will run a walk-script, triggering an animation-script and so on, the player has to wait until this has been finished. If the game suddenly needs to be saved, serialising this momentum becomes quite a problem for me.

I barely ever saw a game saving currently processed actions (as jumping, attacking, ..), as to what I experienced, saving is a limited to only certain spots in a level (e.g. save-points) and usually serialises map-data and flags only.  I totally understand the reasoning behind this, players usually opt-in -out of a game's session and probably do not want to save mid-action.

An example for a mid-action: When the player moves from grid-slot 1 to 3, and the user suddenly shuts the phone hence the game tries to save automatically mid-motion/action. How would a move-to-new-slot-task be serialised? One idea would be having a action-task-scheduler (not meant in a multithread-context), that owns currently executing instructions, as

player_moves_to_new_grid_slot, owning values as: running_script and the script's required arguments as goal_slot.

entity_attacks, owning values as: running_script and again the script's required arugments target.

If I would not serialise these task, the transformed player-coordinates would just be loaded in-between grid 1 and 3 but not complete the action.

I mean, I could also have live-coordinates and old-coordinates and simply ignore live-values, therefore dismiss incomplete tasks.

Oh, you have active code that you want/need to save. Hmm, I have seen that in CorsixTH, where almost literally all Lua data is saved by traversing the links. The function code itself doesn't get saved, only an unique name, so it can be re-connected upon load.

It works, but that's about all you can say about in my opinion. Changing anything is a nightmare, as values of variables get attached to their name, so if you rename a variable and load an old save, it crashes. If you add new variables, it crashes.

If you never want to modify your game, and still be able to load old saves, the above is no concern of course.

 

The other option is to attach a "save" and "load" interface to an action. If you save a running action, you query for the data that you must save (and the name of the action of course). When you load, you load the saved data, and recreate the action from it.

 

I don't know how relevant save at any time is for you, but the simpler option is perhaps only to save at a point where there are no such actions that you must rescue.

If the scripts performing movement are purely cosmetic, then their state doesn't need to be saved to disk. If I'm playing a chess game and the rook is moving from A1 to A8 and I quit while it's animating somewhere between A4 and A5, I expect to reload and either see it at A1 where it's still my turn, or A8 where it's the opponent's turn. (I'd prefer the latter.) The underlying game state is one or the other - the animation state, where the piece is moving from the old position to the new, is purely visual and doesn't need to be saved anywhere.

If you do have long-running actions that need to persist - e.g. it's an RTS game and you commanded a unit to walk across the map - then usually the command is attached to the unit that is executing it, and that would be saved out along with all other in-game state (which would reflect the visual position, as that is a valid game state, unlike in the chess example).

The main thing here is to have a clear idea of what is actual game state that absolutely has to persist across sessions, and what is just a visual representation that can be recreated from the game state and which doesn't need saving at all.

Hm, yes. I would totally fine by not saving the actual movement. As you said, either A1 or A8.

There are still some problems for me though. When the movement happens, the coordinates are being transformed already, saving  during an action would resolve in saving those coordinates.

That might lead to errors: Maybe the character moves over field 3 and 4. But field 4 is a hole hence the character falls into the hole.

If the game would be saved during the walking and then opened again, the character might stand on top of field 4, bypassing its fall-script.

In either case, it sounds as if games contain two kinds of values. One set, that can be serialised and another one that is live and can be transformed?

I find it helpful to think of a turn-based game from a functional perspective.

You start with game state X, apply player input A using some game logic, and end up with game state Y. X is always known at any given time, but A is something you have to wait for. In other words:

Y = gameLogic(X, A)

If the player saves before providing any input, then all you have is X. In that case, just save X. If the player saves after providing A, then you have X and A. Save X and A, even if game logic is currently running. Once the game logic is complete and you have Y, then Y for turn N immediately becomes X for turn N+1. Rinse and repeat. Note that this applies generally to any transition between valid game states, not just "turns" (however they may be defined by your game).

If you load a game that only has X, then just restore that game state and continue waiting for player input A. If you load a game with both X and A, then you restore the game state and apply input A as you would normally.

As you can see, it doesn't matter when the player saves, because this process is invariant on how much time it takes to perform the game logic and produce Y. You always save X, and sometimes A if it's available.

Ah, so you suggest to serialise A as well, if X is continuously transformed towards the final state of Y.

So, X could resemble a tuple of coordinates and A the player-input (e.g. a click on the grid), which translates into a move-order.

I should attach this instruction-set to the owner of X and upon saving, serialise both? So coordinates slowly transform...

Start is x = 10 then x = 11, x = 12, ... goal could be 20. And the instruction-set would be continued, being some script. Probably should read into how other games tackle this, as I want to keep high customisability.

Serialising a spin-animation, followed by a teleport/blink to a grid-field during action, might be difficult.

Thanks for all the inputs : )

11 hours ago, Angelic Ice said:

Hm, yes. I would totally fine by not saving the actual movement. As you said, either A1 or A8.

There are still some problems for me though. When the movement happens, the coordinates are being transformed already, saving  during an action would resolve in saving those coordinates.

So, don't do that. The real game state is what you need to save, so don't change that. Change the visual state, which you don't need to save. These should be 2 separate pieces of data. You don't need to have a single "coordinates" variable which you use for everything.

 

11 hours ago, Angelic Ice said:

That might lead to errors: Maybe the character moves over field 3 and 4. But field 4 is a hole hence the character falls into the hole.

If the game would be saved during the walking and then opened again, the character might stand on top of field 4, bypassing its fall-script.

I'd say that this is a problem with the way you are handling the logic. If you know that moving from field 1 to field 8 is a single operation, then you can determine right at the start that you will pass through field 4 and that the character is going to fall in. You don't need to animate them over that position to determine that. Your game rules should know that, at the end of the turn, that character has gone, and that the animation is just for effect.

But, if you truly can't handle this without animating the character and seeing exactly where they appear in pixel-space, then maybe your tiles aren't really the true game space. It would be wise to think deeply on this before you proceed because it will have serious implications for the rest of the game. If you do need this finer level of control then your game state may well be at the visual/pixel/world position instead; in which case, you go down the RTS route, storing the command alongside the unit until the unit finishes moving. But I suspect that is not actually what you need.

 

11 hours ago, Angelic Ice said:

In either case, it sounds as if games contain two kinds of values. One set, that can be serialised and another one that is live and can be transformed?

Exactly, but you're thinking about it the wrong way. The data isn't divided up by "what is serialised" vs "what is not serialised", the data is divided up by "what is an essential part of the game state" vs "what is just changing temporarily to make the game look pretty". You always save the first set of data when you're saving the game state, because it's essential for recreating that state when you resume/load the game. You don't need to save the second set of data because its current state has no effect on play.

 

7 hours ago, Angelic Ice said:

Ah, so you suggest to serialise A as well, if X is continuously transformed towards the final state of Y.

So, X could resemble a tuple of coordinates and A the player-input (e.g. a click on the grid), which translates into a move-order.

I should attach this instruction-set to the owner of X and upon saving, serialise both? So coordinates slowly transform...

Start is x = 10 then x = 11, x = 12, ... goal could be 20. And the instruction-set would be continued, being some script. Probably should read into how other games tackle this, as I want to keep high customisability.

Serialising a spin-animation, followed by a teleport/blink to a grid-field during action, might be difficult.

Thanks for all the inputs : )

In this case, you'd only ever save one of these three for turn N (depending on the precise moment save is pressed during the turn):

  1. Turn = N, X = 10
  2. Turn = N, X = 10, A = click on tile 20
  3. Turn = N+1, X = 20

Note that X is either 10 or 20, and that the change is atomic. The fact that X appears to continually transform between 10 to 20 is just to provide visual feedback to the player, but isn't relevant to the logical game state. If you were making a chess game and a piece was moved from A1 to A3, you might see the piece move over A2 as part of its animation, but you'd never actually save that position. You could just as easily remove all animation, have pieces teleport instantaneously to their final position, and still have a functioning game. But that wouldn't be nearly as interesting or fun :)

This topic is closed to new replies.

Advertisement