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.

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 could be linked to the engine (and the editor) in two ways: statically or dynamically using hot reloading. Code hot reloading is usually used for development purposes only.

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. Plugins also have much wider access to engine internals, than scripts. For example, it is possible to change scenes, add render passes, change resolution, etc. which is not possible from scripts.

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() {
#[derive(Visit, Reflect, Debug)]
pub struct Game {
    scene: Handle<Scene>,
}

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

        Self {
            scene: Handle::NONE,
        }
    }
}

impl Plugin for Game {
    fn register(&self, context: PluginRegistrationContext) {
        // Register scripts here.
    }

    fn register_property_editors(&self) -> PropertyEditorDefinitionContainer {
        // Register custom property editors for the editor here.
        PropertyEditorDefinitionContainer::empty()
    }

    fn init(&mut self, scene_path: Option<&str>, context: PluginContext) {
        // Do initialization logic here. Usually it just requests a scene:
        context
            .async_scene_loader
            .request(scene_path.unwrap_or("data/scene.rgs"));
    }

    fn on_loaded(&mut self, context: PluginContext) {
        // For hot reloading only! Only for development.
        // Re-initialize non-serializable data.
    }

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

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

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

    fn on_graphics_context_initialized(&mut self, context: PluginContext) {
        // Executed when graphics context was initialized.
    }

    fn before_rendering(&mut self, context: PluginContext) {
        // Executed before rendering begins.
    }

    fn on_graphics_context_destroyed(&mut self, context: PluginContext) {
        // Executed when graphics context was destroyed.
    }

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

    fn on_scene_begin_loading(&mut self, path: &Path, context: &mut PluginContext) {
        // Handle started scene loading here.
    }

    fn on_scene_loaded(
        &mut self,
        _path: &Path,
        scene: Handle<Scene>,
        data: &[u8],
        context: &mut PluginContext,
    ) {
        if self.scene.is_some() {
            context.scenes.remove(self.scene);
        }

        self.scene = scene;
    }

    fn on_scene_loading_failed(
        &mut self,
        path: &Path,
        error: &VisitError,
        context: &mut PluginContext,
    ) {
        // Handle failed scenes here.
    }
}
}

As you can see, the game structure (struct Game) implements a bunch of traits.

  • Reflect - is needed for static reflection to inspect the content of the plugin.
  • Visit - is mostly needed for hot reloading, to save/load the content of the plugin.
  • Default - provides sensible default state of the game.

Plugin trait is very special - it can execute the actual game logic in one of its methods:

  • register - called once on start allowing you to register your scripts. Important: You must register all your scripts here, otherwise the engine (and the editor) will know nothing about them. Also, you should register loaders for your custom resources here. See Custom Resource chapter more info.
  • init - called once when the plugin registers in the engine. This method allows you to initialize the game into some sensible state. Keep in mind, that the editor will not call this method, it does not create any game instance. The method has scene_path parameter, in short it is a path to a scene that is currently opened in the editor (it will be None if either there's no opened scene or your game was started outside the editor). It is described in Editor and Plugins section down below.
  • 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, but can be configured in the Executor) after the plugin is created and fully initialized. It is the main place where you should put object-independent game logic (such as user interface handling, global application state management, etc.), any other logic should be added via scripts.
  • on_os_event - it is called when the main application window receives an event from the 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 OS 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.)
  • on_graphics_context_initialized - it is called when a graphics context was successfully initialized. This method could be used to access the renderer (to change its quality settings, for instance). You can also access a main window instance and change its properties (such as title, size, resolution, etc.).
  • on_graphics_context_destroyed - it is called when the current graphics context was destroyed. It could happen on a small number of platforms, such as Android. Such platforms usually have some sort of suspension mode, in which you are not allowed to render graphics, to have a "window", etc.
  • before_rendering - it is called when the engine is about to render a new frame. This method is useful to perform offscreen rendering (for example - user interface).
  • on_scene_begin_loading - it is called when the engine starts to load a game scene. This method could be used to show a progress bar or some sort of loading screen, etc.
  • on_scene_loaded - it is called when the engine successfully loaded a game scene. This method could be used to add custom logic to do something with a newly loaded scene.

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() {
pub struct PluginContext<'a, 'b> {
    pub scenes: &'a mut SceneContainer,
    pub resource_manager: &'a ResourceManager,
    pub user_interfaces: &'a mut UiContainer,
    pub graphics_context: &'a mut GraphicsContext,
    pub dt: f32,
    pub lag: &'b mut f32,
    pub serialization_context: &'a Arc<SerializationContext>,
    pub widget_constructors: &'a Arc<WidgetConstructorContainer>,
    pub performance_statistics: &'a PerformanceStatistics,
    pub elapsed_time: f32,
    pub script_processor: &'a ScriptProcessor,
    pub async_scene_loader: &'a mut AsyncSceneLoader,
    pub window_target: Option<&'b EventLoopWindowTarget<()>>,
    pub task_pool: &'a mut TaskPoolHandler,
}
}
  • scenes - a scene container, could be used to manage game scenes - add, remove, borrow. An example of scene loading is given in the previous code snippet in Game::new() method.
  • resource_manager - is used to load external resources (scenes, models, textures, animations, sound buffers, etc.) from different sources (disk, network storage on WebAssembly, etc.)
  • user_interfaces - 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. There's always at least one user interface created, it can be accessed using .first()/first_mut() methods. The engine support unlimited instances of user interfaces.
  • graphics_context - a reference to the graphics_context, it contains a reference to the window and the current renderer. It could be GraphicsContext::Uninitialized if your application is suspended (possible only on Android).
  • dt - a time passed since the last frame. The actual value is implementation-defined, but on current implementation it is equal to 1/60 of a second and does not change event if the frame rate is changing (the engine stabilizes update rate for the logic).
  • lag - a reference to the time accumulator, that holds remaining amount of time that should be used to update a plugin. A caller splits lag into multiple sub-steps using dt and thus stabilizes update rate. The main use of this variable, is to be able to reset lag when you're doing some heavy calculations in a game loop (i.e. loading a new level) so the engine won't try to "catch up" with all the time that was spent in heavy calculation.
  • serialization_context - it can be used to register scripts and custom scene nodes constructors at runtime.
  • widget_constructors - it can be used to register custom widgets.
  • performance_statistics - performance statistics from the last frame. To get a rendering performance statistics, use Renderer::get_statistics method, that could be obtained from the renderer instance in the current graphics context.
  • elapsed_time - amount of time (in seconds) that passed from creation of the engine. Keep in mind, that this value is not guaranteed to match real time. A user can change delta time with which the engine "ticks" and this delta time affects elapsed time.
  • script_processor - a reference to the current script processor instance, which could be used to access a list of scenes that supports scripts.
  • async_scene_loader - a reference to the current asynchronous scene loader instance. It could be used to request a new scene to be loaded.
  • window_target - special field that associates main application event loop (not game loop) with OS-specific windows. It also can be used to alternate control flow of the application.
  • task_pool - task pool for asynchronous task management.

Control Flow

Plugin context provides access to a special variable window_target, which could be used to alternate control flow of the application. The most common use of it is to close the game by calling window_target.unwrap().exit() method. Notice the unwrap() here, window_target could not be available at all times. Ideally you should do checked access here.

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 Plugin::init method:

#![allow(unused)]
fn main() {
    fn init(&mut self, scene_path: Option<&str>, context: PluginContext) {
        // Do initialization logic here. Usually it just requests a scene:
        context
            .async_scene_loader
            .request(scene_path.unwrap_or("data/scene.rgs"));
    }
}

The scene_path parameter is a path to a scene that is currently opened in the editor, your game should use it if you need to load a currently selected scene of the editor in your game. However, it is not strictly necessary - you may desire to start your game from a specific scene all the time, even when the game starts from the editor. If the parameter is None, then there is no scene loaded in the editor or the game was run outside the editor.