Skip to content

Commit

Permalink
finished saved games
Browse files Browse the repository at this point in the history
  • Loading branch information
mrDIMAS committed Oct 24, 2023
1 parent 6a71b36 commit d693116
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
- [Window](./fyrox/ui/window.md)
- [Wrap panel](./fyrox/ui/wrap_panel.md)
- [Serialization](./fyrox/serialization/index.md)
- [Saved Games (WIP)](./fyrox/serialization/save.md)
- [Saved Games](./fyrox/serialization/save.md)
- [Editor](./fyrox/editor/index.md)
- [Property Editors](./fyrox/editor/property_editors.md)
- [Settings](./fyrox/editor/settings.md)
Expand Down
194 changes: 183 additions & 11 deletions src/fyrox/serialization/save.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,193 @@
# Saved Games (WIP)
# Saved Games

Saved game is used to store progress made in a play-through of a game to disk or some other storage. It is very important
for pretty much every game and this chapter will help you to understand basic concepts of saved games in the engine.

## Saved Game Structure

This could sound weird, but saved game in most cases is just a scene with additional data. Let's understand why. At first,
when you're making a save file you need to take a "snapshot" of your game world. Essential way of storing such data is
a scene. Secondly, game plugins is also may store some data that should be saved. By these two facts, it is quite easy
to get a full picture: to make a save all you need to do is to serialize current scene, serialize some other data and
just "dump" it to a file. You might ask: is this efficient to serialize the entire scene? In short: yes. A bit more
detailed answer: when you serialize a scene, it does not store everything, it only stores _changed_ fields and references
to external assets.
when you're making a save file you need to take some sort of "snapshot" of your game world. Essential way of storing
such data is a scene. Secondly, game plugins is also may store some data that should be saved. By these two facts, it
is quite easy to get a full picture: to make a save all you need to do is to serialize current scene, serialize some
other data and just "dump" it to a file. You might ask: is this efficient to serialize the entire scene? In short: yes.
A bit more detailed answer: when you serialize a scene, it does not store everything, it only stores _changed_ fields
and references to external assets.

To load a save file you need to do pretty much the same, but instead of serializing things, you need to deserialize
data from file into a scene and restore data for plugins. So how the engine is able to restore the data on load if it
does not store everything? If you carefully read the book, you might already know the answer -
[property inheritance](/src/fyrox/scene/inheritance.md).
## Usage

Fyrox offers a built-in system for saved games. It does exactly what said in the section above - serializes a "diff" of
your scene which can be loaded later as an ordinary scene and the engine will do all the magic for you. Typical usage of
this system is very simple:

```rust ,no_run
# extern crate fyrox;
# use fyrox::{
# core::{pool::Handle, visitor::Visitor},
# plugin::{Plugin, PluginContext},
# scene::Scene,
# };
# use std::path::Path;
#
struct MyGame {
scene: Handle<Scene>,
}

impl MyGame {
fn new(scene_path: Option<&str>, context: PluginContext) -> Self {
// Load the scene as usual.
context
.async_scene_loader
.request(scene_path.unwrap_or("data/scene.rgs"));

Self {
scene: Handle::NONE,
}
}

fn save_game(&mut self, context: &mut PluginContext) {
let mut visitor = Visitor::new();
// Serialize the current scene.
context.scenes[self.scene]
.save("Scene", &mut visitor)
.unwrap();
// Save it to a file.
visitor.save_binary(Path::new("save.rgs")).unwrap()
}

fn load_game(&mut self, context: &mut PluginContext) {
// Loading of a saved game is very easy - just ask the engine to load your save file.
// Note the difference with `Game::new` - here we use `request_raw` instead of
// `request` method. The main difference is that `request` creates a derived scene
// from a source scene, but `request_raw` loads the scene without any modifications.
context.async_scene_loader.request_raw("save.rgs");
}
}

impl Plugin for MyGame {
fn on_scene_begin_loading(&mut self, _path: &Path, context: &mut PluginContext) {
if self.scene.is_some() {
context.scenes.remove(self.scene);
}
}

fn on_scene_loaded(
&mut self,
_path: &Path,
scene: Handle<Scene>,
_data: &[u8],
_context: &mut PluginContext,
) {
self.scene = scene;
}
}
```

This is a typical structure of a game that supports saving and loading. As you can see, it is pretty much the same as
the standard code, that can be generated by `fyrox-template`. The main difference here is two new methods with
self-describing names: `save_game` and `load_game`. Let's try to understand what each one does.

`save_game` serializes your current game scene into a file. This function is very simple and can be used as-is in
pretty much any game. You can also write additional game data here using the `visitor` instance (see next section).

`load_game` - loads a saved game. It just asks the engine to load your save file as an ordinary scene. Note the difference
with code in `Game::new` - here we use `request_raw` instead of `request` method. The main difference is that `request`
creates a derived scene from a source scene, but `request_raw` loads the scene without any modifications. What is
derived scene anyway? It is a scene, which does not store all the required data inside, instead, it stores links to
places where the data can be obtained from. You can also think of it as a difference between your saved game and an
original scene.

You can bind these two functions to some keys, for example you can use `F5` for save and `F9` for load and call the
respective methods for saving/loading. Also, these methods could be used when a button was pressed, etc.

## Additional Data

As was mentioned in the previous section, it is possible to store additional data in a saved game. It is very simple
to do:


```rust ,no_run
# extern crate fyrox;
# use fyrox::{
# core::{pool::Handle, visitor::prelude::*, visitor::Visitor},
# plugin::{Plugin, PluginContext},
# scene::Scene,
# };
# use std::path::Path;
#
#[derive(Visit, Default)]
struct MyData {
foo: String,
bar: u32,
}

struct MyGame {
scene: Handle<Scene>,
data: MyData,
}

impl MyGame {
fn new(scene_path: Option<&str>, context: PluginContext) -> Self {
// Load the scene as usual.
context
.async_scene_loader
.request(scene_path.unwrap_or("data/scene.rgs"));

Self {
scene: Handle::NONE,
data: Default::default(),
}
}

fn save_game(&mut self, context: &mut PluginContext) {
let mut visitor = Visitor::new();

// Serialize the current scene.
context.scenes[self.scene]
.save("Scene", &mut visitor)
.unwrap();

// Write additional data.
self.data.visit("Data", &mut visitor).unwrap();

// Save it to a file.
visitor.save_binary(Path::new("save.rgs")).unwrap()
}

pub fn load_game(&mut self, context: &mut PluginContext) {
// Loading of a saved game is very easy - just ask the engine to load your scene.
// Note the difference with `Game::new` - here we use `request_raw` instead of
// `request` method. The main difference is that `request` creates a derived scene
// from a source scene, but `request_raw` loads the scene without any modifications.
context.async_scene_loader.request_raw("save.rgs");
}
}

impl Plugin for MyGame {
fn on_scene_begin_loading(&mut self, _path: &Path, context: &mut PluginContext) {
if self.scene.is_some() {
context.scenes.remove(self.scene);
}
}

fn on_scene_loaded(
&mut self,
_path: &Path,
scene: Handle<Scene>,
data: &[u8],
_context: &mut PluginContext,
) {
self.scene = scene;

// Restore the data when the scene was loaded.
if let Ok(mut visitor) = Visitor::load_from_memory(data) {
self.data.visit("Data", &mut visitor).unwrap();
}
}
}
```

The main difference here with the code snippet from the previous section is that now we have `MyData` structure which
we want to save in a save file as well as current scene. We're doing that in `save_game` method by
`self.data.visit("Data", &mut visitor).unwrap();` which serializes our data. To load the data back (deserialize), we
have to wait until the scene is fully loaded and then try to deserialize the data. This is done by the last three lines
of code of `on_scene_loaded` method.

0 comments on commit d693116

Please sign in to comment.