Plugins

A game based on Fyrox is a plugin to the engine and the editor. Plugin defines global application logic and provides a set of scripts, that can be used to assign custom logic to scene nodes. Every script belongs to only one plugin.

Plugin is an "entry point" of your game, it has a fixed set of methods that can be used for initialization, update, OS event handling, etc. Every plugin is statically linked to the engine (editor), there is no support for hot-reloading due to lack of stable ABI in Rust.

The main purpose of the plugins is to hold and operate on some global application data, that can be used in scripts and provide a set of scripts to the engine.

Structure

Plugin structure is defined by Plugin trait. Typical implementation can be generated by fyrox-template tool, and it looks something like this:

#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
    core::{
        futures::executor::block_on,
        pool::Handle,
        uuid::{uuid, Uuid},
    },
    event::Event,
    event_loop::ControlFlow,
    gui::message::UiMessage,
    plugin::{Plugin, PluginConstructor, PluginContext, PluginRegistrationContext},
    scene::{node::TypeUuidProvider, Scene, SceneLoader},
};

pub struct GameConstructor;

impl TypeUuidProvider for GameConstructor {
    fn type_uuid() -> Uuid {
        uuid!("f615ac42-b259-4a23-bb44-407d753ac178")
    }
}

impl PluginConstructor for GameConstructor {
    fn register(&self, _context: PluginRegistrationContext) {
        // Register your scripts here.
    }

    fn create_instance(
        &self,
        override_scene: Handle<Scene>,
        context: PluginContext,
    ) -> Box<dyn Plugin> {
        Box::new(Game::new(override_scene, context))
    }
}

pub struct Game {
    scene: Handle<Scene>,
}

impl Game {
    pub fn new(override_scene: Handle<Scene>, context: PluginContext) -> Self {
        let scene = if override_scene.is_some() {
            override_scene
        } else {
            // Load a scene from file if there is no override scene specified.
            let scene = block_on(
                block_on(SceneLoader::from_file(
                    "data/scene.rgs",
                    context.serialization_context.clone(),
                ))
                    .unwrap()
                    .finish(context.resource_manager.clone()),
            );

            context.scenes.add(scene)
        };

        Self { scene }
    }
}

impl Plugin for Game {
    fn on_deinit(&mut self, _context: PluginContext) {
        // Do a cleanup here.
    }

    fn update(&mut self, _context: &mut PluginContext, _control_flow: &mut ControlFlow) {
        // Add your global update code here.
    }

    fn id(&self) -> Uuid {
        GameConstructor::type_uuid()
    }

    fn on_os_event(
        &mut self,
        _event: &Event<()>,
        _context: PluginContext,
        _control_flow: &mut ControlFlow,
    ) {
        // Do something on OS event here.
    }

    fn on_ui_message(
        &mut self,
        _context: &mut PluginContext,
        _message: &UiMessage,
        _control_flow: &mut ControlFlow,
    ) {
        // Handle UI events here.
    }
}
}

There are two major parts - GameConstructor and Game itself. GameConstructor implements PluginConstructor and it is responsible for script registration (fn register) and creating the actual game instance (fn create_instance).

  • register - called once on start allowing you to register your scripts. Please note that you must register all your scripts here, otherwise the engine will know nothing about them.
  • create_instance - called once, allowing you to create actual game instance. It is guaranteed to be called once, but where it is called is implementation-defined. For example, the editor will not call this method, it does not create any game instance. The method has override_scene parameter, in short it is a handle to a scene that must be used by your game instead of any other scenes. It is described in Editor and Plugins section down below.

The game instance (struct Game) implements a Plugin trait which can execute actual game logic in one of its methods:

  • on_deinit - it is called when the game is about to shut down. Can be used for any clean up, for example logging that the game has closed.
  • update - it is called each frame at a stable rate (usually 60 Hz) after the plugin is created and fully initialized. It is the main place where you should put object-independent game logic, any other logic should be added via scripts.
  • on_os_event - it is called when the main application window receives an event from operating system, it can be any event such as keyboard, mouse, game pad events or any other events. Please note that as for update method, you should put here only object-independent logic. Scripts can catch window events too.
  • on_ui_message - it is called when there is a message from the user interface, it should be used to react to user actions (like pressed buttons, etc.)
  • id - utility method that should return stable id of the plugin.

Control Flow

Some plugin methods provide access to ControlFlow variable, its main usage in the plugin is to give you ability to stop the game by some conditions. All you need to do is to set it to ControlFlow::Exit and the game will be closed. It also has other variants, but they don't have any particular usage in the plugins.

#![allow(unused)]
fn main() {
fn update(&mut self, _context: &mut PluginContext, control_flow: &mut ControlFlow) {
    if self.some_exit_condition {
        control_flow = ControlFlow::Exit;
    }
}
}

Plugin Context

Vast majority of methods accept PluginContext - it provides almost full access to engine entities, it has access to the renderer, scenes container, resource manager, user interface, main application window. Typical content of the context is something like this:

#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
    engine::{resource_manager::ResourceManager, SerializationContext},
    gui::UserInterface,
    renderer::Renderer,
    scene::SceneContainer,
    window::Window,
};
use std::sync::Arc;
pub struct PluginContext<'a> {
    pub scenes: &'a mut SceneContainer,
    pub resource_manager: &'a ResourceManager,
    pub user_interface: &'a mut UserInterface,
    pub renderer: &'a mut Renderer,
    pub dt: f32,
    pub serialization_context: &'a Arc<SerializationContext>,
    pub window: &'a Window,
}
}
  • scenes - should be used to manage game scenes, an example of scene loading is given in the previous code snipped in Game::new() method.
  • resource_manager - is used to load external resources (scenes, models, textures, animations, source, etc.) from different sources (disk, network storage, etc.)
  • user_interface - use it to create user interface for your game, the interface is scene-independent and will remain the same even if there are multiple scenes created.
  • renderer - can be used to add custom rendering techniques, change quality settings, etc.
  • dt - a time passed since last frame. The actual value is implementation-defined, but on current implementation it is equal to 1/60 of second and does not change event if the frame rate is changing.
  • serialization_context - it can be used to register script and custom scene nodes constructors and at runtime.
  • window - main application window, you can use it to change title, resolution, etc.

Editor and Plugins

When you're running your game from the editor, it starts the game as a separate process and if there's a scene opened in the editor, it tells the game instance to load it on startup. Let's look closely at Game::new method:

#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
    core::{futures::executor::block_on, pool::Handle},
    plugin::PluginContext,
    scene::{Scene, SceneLoader},
};

struct Foo {
    scene: Handle<Scene>,
}

impl Foo {
pub fn new(override_scene: Handle<Scene>, context: PluginContext) -> Self {
    let scene = if override_scene.is_some() {
        override_scene
    } else {
        // Load a scene from file if there is no override scene specified.
        let scene = block_on(
            block_on(SceneLoader::from_file(
                "data/scene.rgs",
                context.serialization_context.clone(),
            ))
                .unwrap()
                .finish(context.resource_manager.clone()),
        );

        context.scenes.add(scene)
    };

    Self { scene }
}
}
}

The override_scene parameter is a handle to another scene instances that is currently opened in the editor, your game plugin must handle this parameter and use provided scene, otherwise the run from the editor will not have the edited scene. If the parameter is undefined (equals to Handle::NONE), then there is no scene loaded in the editor or the game was run outside the editor.