-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
184 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |