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 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.

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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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.