diff --git a/fyrox/tutorials/rpg/intro.html b/fyrox/tutorials/rpg/intro.html index a96d01e2..ba858e7d 100644 --- a/fyrox/tutorials/rpg/intro.html +++ b/fyrox/tutorials/rpg/intro.html @@ -182,7 +182,7 @@
In this series of tutorials we will make a game similar to The Elder Scrolls series (but much, much smaller indeed), -we'll have a main character, a simple world with intractable items and a few kind of enemies. I'll show you how to add an inventory, -a quests journal, and the quests itself. This series should have at least 5 tutorials, but this might change. At the end -of the series we'll have a playable RPG which you will be able to use to continue making your own game. It is very ambitious, -but totally doable with the current state of the engine.
+we'll have a main character, a simple world with intractable items and a few kind of enemies. In this series you'll +understand how to add an inventory, a quests journal, and the quests itself. This series should have at least 5 +tutorials, but this might change. At the end of the series we'll have a playable RPG which you will be able to use to +continue making your own game. It is very ambitious, but totally doable with the current state of the engine.Most of the role-playing games (RPGs for short) using 3rd person camera which allows you to see your character entirely. In this tutorial we'll make something similar. Check the video with final result of the tutorial:
-As you can see, at the end of the tutorial we'll be able to walk and explore a small fantasy world. Let's start by creating -a new cargo project:
-cargo init rpg-tutorial
Add fyrox
as dependency:
[dependencies]
-fyrox = "0.29.0"
+As you can see, at the end of the tutorial we'll be able to walk and explore a small fantasy world. Let's start by
+creating a new game project, by running the following command:
+fyrox-template init --name=rpg --style=3d
+This command will create a new cargo workspace with a few projects inside, we're interested only in game
folder
+in this tutorial.
+rpg
+├───data
+├───editor
+│ └───src
+├───executor
+│ └───src
+├───executor-android
+│ └───src
+├───executor-wasm
+│ └───src
+└───game
+ └───src
-Framework
-Now let's create the window and initialize the engine. We'll skip most engine initialization by using new Framework
helper
-that hides most of the engine initialization and provides unified interface for your games allowing you to focus on
-your game code. Framework
is not mandatory, you may use the previous
-variant with manual engine initialization and "opened" main loop.
-extern crate fyrox;
-use fyrox::{
- core::{color::Color, futures::executor::block_on, pool::Handle},
- engine::executor::Executor,
- event::{Event, WindowEvent},
- event_loop::ControlFlow,
- plugin::{Plugin, PluginConstructor, PluginContext},
- scene::{Scene},
-};
-use fyrox::window::WindowAttributes;
-use fyrox::engine::GraphicsContextParams;
-
-struct Game {
- scene: Handle<Scene>,
-}
-
-struct GameConstructor;
-
-impl PluginConstructor for GameConstructor {
- fn create_instance(&self, _: Handle<Scene>, context: PluginContext) -> Box<dyn Plugin> {
- Box::new(Game::new(context))
- }
-}
-
-impl Game {
- fn new(context: PluginContext) -> Self {
- let mut scene = Scene::new();
-
- scene.rendering_options.ambient_lighting_color = Color::opaque(150, 150, 150);
-
- Self {
- scene: context.scenes.add(scene),
- }
- }
-}
-
-impl Plugin for Game {
- fn update(&mut self, context: &mut PluginContext, _: &mut ControlFlow) {
-
- }
-
- fn on_os_event(
- &mut self,
- event: &Event<()>,
- _context: PluginContext,
- _control_flow: &mut ControlFlow,
- ) {
-
- }
-}
-
-fn main() {
- let mut executor = Executor::from_params(
- Default::default(),
- GraphicsContextParams {
- window_attributes: WindowAttributes {
- title: "RPG".to_string(),
- ..Default::default()
- },
- vsync: true,
- },
- );
- executor.add_plugin_constructor(GameConstructor);
- executor.run();
-}
-It is much easier to initialize the engine now compared to the initialization described in the series of tutorials about
-writing a 3D shooter. If you run it, you'll see a window with black background with an "RPG" title.
+Learn more about fyrox-template
command here. Now we can run
+the game using cargo run --package executor
command, and you should see a white cube floating in blue space.
+
+️⚠️ There are two important commands:
+To run the game use: cargo run --package executor
command
+To run the editor use: cargo run --package editor
command.
+
Assets
For any kind of game you need a lot of various assets, in our case we need a 3D model for our character, a set of
animations, a level, a set of textures for terrain, trees and bushes, barrels, etc. I prepared all assets as a single
ZIP archive which can be downloaded here. Once you've downloaded it, unpack it in ./data
folder.
-Player and camera controller
-Now we can start adding Player to our game. Create a folder player
under your src
directory and add mod.rs
with
-following content:
-#![allow(unused)]
+Player Prefab
+Let's start from assembling our player prefab, that will also have a camera controller in it. At first, let's find out
+what the prefab is - prefab a scene, that contains some scene nodes, which can be instantiated to some other scene
+while preserving "connection" between all properties of the nodes. It means, that if you change something in a prefab,
+the changes will be reflected on every instance of it; on those properties that weren't modified. This is a condensed
+explanation, that may look a bit complicated - read this to learn more about prefabs.
+Now let's open the editor (cargo run --package editor
) and start making our prefab by creating a new scene. Save the
+scene to data/models/paladin/paladin.rgs
by going to File -> Save
. In the opened window, find the path and click
+Save
:
+
+Let's rename the root node of the scene to Paladin
and change its type to RigidBody
:
+
+We need this so out root node of the prefab could move in a scene to which it will be instantiated later. Make sure, that
+the X/Y/Z Rotation Locked
property is set to true
. Also Can Sleep
must be false, otherwise the rigid body will
+be excluded from the physical simulation when it does not move. As you can see, the editor shows a small warning icon
+near the root node - it warns us, that the rigid body does not have a collider and won't be able to participate in
+physical simulation. Let's fix it by adding a capsule collider to it and setting its Begin
, End
, Radius
properties
+accordingly:
+
+The next step is to add an actual character 3D model, this is very easy - find paladin.fbx
in the asset browser using
+its searching functionality and then drag'n'drop (click on the asset, and while holding the button, move the mouse in
+the scene, then release the button) it to the scene:
+
+Now we need to adjust its Local Scale
property, because the model is too big. Set it to 0.01
for all 3 axes, like on
+the screenshot above. Also, adjust position of the capsule collider, so it will fully enclose 3d model. Create a new
+Pivot
node called ModelPivot
and attach the paladin.fbx
node to it by drag'n'dropping the paladin.fbx
node onto
+ModelPivot
. The reason why we need to do this will be explained later in the tutorial.
+
+Camera
+It is the time to add camera controller scene nodes. We need to add three nodes in a chain:
+
+There are three nodes added:
+
+CameraPivot
(Pivot
node type) - it will serve a pivot point around which we will rotate the camera around Y axis.
+(horizontal camera rotation). It should be placed right at the center of the Paladin's head.
+CameraHinge
(Pivot
node type) - it will also be a pivot point, but for X axis (vertical camera rotation)
+Camera
(Camera
node type) - the camera itself, it should be placed on some distance from the Paladin's back.
+
+This nodes configuration will allow us to create some sort of "orbital" (also called arcball) camera as in many 3rd person
+games nowadays.
+Animations
+The next step is to add animations. Create a new Animation Player
node, click Open Animation Editor
near its
+Animations
property in the inspector to open the animation editor:
+
+Dock the animation editor below the scene preview - this way it will be much comfortable to use. Now we need to import
+two animations run.fbx
and idle.fbx
from the data/models/paladin
folder. To do this, click on the button with
+arrow at the tool strip in the animation editor:
+
+The editor asks us for the root node to which import the animation - it our case it is paladin.fbx
. Select it in the
+window and click OK
. Another window opens and asks us about the animation we want to import - find idle.fbx
in the
+tree and click Open
. You should see something like this as a result:
+
+Click on Preview
check box in the tool strip and the animation should play without artifacts. Now repeat the previous
+steps and import running.fbx
. Click Preview
again, and you'll see that the character is running, but not in-place as
+we'd like it to. Let's fix that by applying a Root Motion settings. Click on the RM
button and set it up like so:
+
+Now if you click on Preview
again, you'll see that the character is now moving in-place. But what we did by applying
+the root motion? We forced the engine to extract movement vector from the hips of the character that could be later used
+to move the capsule rigid body we've made early. This way the animation itself will drive the character and the actual
+movement will perfectly match the physical movement.
+At this point we have two separate animations that work independently. But what if we want to add a smooth transition
+between the two (or more)? This is where animation blending state machines comes into play. Create a new state machine
+and assign an animation player to it:
+
+The animation player will be used as a source of animations for our state machine. Now open the ABSM Editor
by clicking
+the Open ABSM Editor...
button in the inspector (right above the animation player property). Dock the editor and select
+a Base Layer
in the dropdown list in the toolbar. Next, we need to add two states - Idle
and Running
. This can be
+done by right-clicking on in the State Graph
and selecting Create State
:
+
+A state requires animation source to be usable, we can specify it by double-clicking on it (or right-click -> Enter State
)
+and creating a Play Animation
pose node in the State Viewer
(right-click -> Play Animation
):
+
+Select the Play Animation
node and in the Inspector
select the Idle
animation from the dropdown list near the
+Animation
property. Repeat the same steps for the Running
state, but in this case set Running
animation.
+Now when we two states ready, we need to create transitions between the two. Transition is a "rule", that defines whether
+a current active state can be switched to another one. While doing so, the engine will blend an animation coming from
+two states. To create a transition, right-click on a state and click Create Transition
. Do the same in the opposite
+direction. As a result, you should have something like this:
+
+A transition requires a boolean value to "understand" whether an actual transition is possible or not. Let's add one
+in the Parameters
section of the editor. Click on the small +
button and change the name to Running
and the type
+to the Rule
:
+
+Let's assign the rule to our transitions, select the Idle -> Running
transition and in the Inspector set its condition
+to the following:
+
+Running -> Idle
requires a reverse condition, the engine has a computational graph for this purpose (to compute
+boolean expressions). Set the condition of it to the following:
+
+As you can see we negate (using the Not
boolean operator) the value of the Running
parameter and use it compute the
+final value for the transition. At this point we can check how our animation blending works. Click on Preview
check box,
+and you should see that the character is currently being in the Idle
state, now click at the checkbox in the Running
+parameter, and you'll see that the Idle -> Running
transition started and ended shortly after. If you uncheck the
+parameter, the character will switch back to idle.
+This was the last step in this long procedure or making the prefab. As you can see, we haven't written a single line of
+code and saw the results immediately, without a need to compile anything.
+Player Script
+Finally, we can start writing some code. There won't be much of it, but it is still required. Fyrox allows you to add
+custom game logic to scene nodes using scripts. Scripts "skeleton" contains quite a lot of boilerplate code and to
+prevent this tedious work, fyrox-template
offers a sub-command called script
, which allows you to generate a script
+skeleton in a single command. Go to root folder of your project and execute the following command there:
+fyrox-template script --name=player
+
+The CLI tool will create the new module in game/src
folder called player.rs
and all you need to do is to register
+the module in two places. The first place is to add mod player;
line somewhere at the beginning of the game/src/lib.rs
.
+The second place is PluginConstructor::register
method - every script must be registered before use. Let's do so by adding
+the following code to the method:
+#![allow(unused)]
fn main() {
extern crate fyrox;
-
-#[cfg(test)]
-use crate::player::camera::CameraController;
-
-// Import everything we need for the tutorial.
-use fyrox::{
- animation::{
- machine::{Machine, MachineLayer, Parameter, PoseNode, State, Transition},
- Animation,
- },
- core::{
- algebra::{UnitQuaternion, Vector3},
- pool::Handle,
- },
- asset::manager::ResourceManager,
- event::{DeviceEvent, ElementState, KeyboardInput, VirtualKeyCode},
- resource::model::{Model, ModelResourceExtension},
- scene::{
- animation::AnimationPlayer,
- base::BaseBuilder,
- collider::{ColliderBuilder, ColliderShape},
- graph::{Graph, physics::CoefficientCombineRule},
- node::Node,
- rigidbody::RigidBodyBuilder,
- transform::TransformBuilder,
- Scene,
- },
-};
-
-#[cfg(test)]
-mod camera;
-
-struct CameraController;
-impl CameraController {
- async fn new(_: &mut Graph, _: ResourceManager) -> Self { Self }
+use fyrox::{
+ core::{reflect::prelude::*, uuid::Uuid, visitor::prelude::*, TypeUuidProvider},
+ impl_component_provider,
+ plugin::{Plugin, PluginConstructor, PluginContext, PluginRegistrationContext},
+ script::ScriptTrait,
+};
+
+#[derive(Clone, Visit, Reflect, Debug, Default)]
+struct Player;
+
+impl_component_provider!(Player);
+
+impl TypeUuidProvider for Player {
+ fn type_uuid() -> Uuid {
+ unimplemented!()
+ }
}
-
-pub struct Player {
- model: Handle<Node>,
- camera_controller: CameraController,
-}
-
-impl Player {
- pub async fn new(resource_manager: ResourceManager, scene: &mut Scene) -> Self {
- // Load paladin 3D model and create its instance in the scene.
- let model = resource_manager
- .request::<Model, _>("data/models/paladin/paladin.fbx")
- .await
- .unwrap()
- .instantiate(scene);
-
- scene.graph[model]
- .local_transform_mut()
- // Move the model a bit down because its center is at model's feet
- // and we'd get floating model without this offset.
- .set_position(Vector3::new(0.0, -0.75, 0.0))
- // Scale down paladin's model because it is too big.
- .set_scale(Vector3::new(0.02, 0.02, 0.02));
-
- Self {
- model,
-
- // As a final stage create camera controller.
- camera_controller: CameraController::new(&mut scene.graph, resource_manager).await,
- }
- }
-}
-}
-Let's disassemble this heap of code line by line. At first, we're creating pivot for our character, we'll use it as a
-"mounting point" for character's 3D model, also it will have a physical body, but that will be added later in this
-tutorial. Next, we're loading paladin 3D model and creating its instance in the scene, we need only geometry without
-animations, so we use instantiate_geometry
here, animations will be added later in this tutorial. Next we scale the
-model a bit, because it is too big. Also, we're moving the model a bit down because its center is at paladin's feet so
-when we're attaching the model to the pivot, it will "stay" on the pivot. We want it to stay on ground, so we're moving
-it down by height of the model. Finally, we're attaching the model to the pivot, forcing the engine to move
-the model together with pivot. In the end we're creating camera controller, it needs its own module, so add camera.rs
-module under src/player
with following content:
-#![allow(unused)]
-fn main() {
-extern crate fyrox;
-// Import everything we need for the tutorial.
-use fyrox::{
- core::{
- algebra::{UnitQuaternion, Vector3},
- pool::Handle,
- },
- asset::manager::ResourceManager,
- event::DeviceEvent,
- resource::texture::{Texture, TextureWrapMode},
- scene::{
- base::BaseBuilder,
- camera::{CameraBuilder, SkyBox, SkyBoxBuilder},
- graph::Graph,
- node::Node,
- transform::TransformBuilder,
- pivot::PivotBuilder
- },
-};
-
-async fn create_skybox(resource_manager: ResourceManager) -> SkyBox {
- // Load skybox textures in parallel.
- let (front, back, left, right, top, bottom) = fyrox::core::futures::join!(
- resource_manager.request::<Texture, _>("data/textures/skybox/front.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/back.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/left.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/right.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/up.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/down.jpg")
- );
-
- // Unwrap everything.
- let skybox = SkyBoxBuilder {
- front: Some(front.unwrap()),
- back: Some(back.unwrap()),
- left: Some(left.unwrap()),
- right: Some(right.unwrap()),
- top: Some(top.unwrap()),
- bottom: Some(bottom.unwrap()),
- }
- .build()
- .unwrap();
-
- // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges
- // of the skybox.
- let cubemap = skybox.cubemap();
- let mut data = cubemap.as_ref().unwrap().data_ref();
- data.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
- data.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
-
- skybox
-}
-
-pub struct CameraController {
- pivot: Handle<Node>,
- hinge: Handle<Node>,
- camera: Handle<Node>,
-}
-
-impl CameraController {
- pub async fn new(graph: &mut Graph, resource_manager: ResourceManager) -> Self {
- let camera;
- let hinge;
- let pivot = PivotBuilder::new(BaseBuilder::new()
- .with_children(&[{
- hinge = PivotBuilder::new(BaseBuilder::new()
- .with_local_transform(
- TransformBuilder::new()
- .with_local_position(Vector3::new(0.0, 0.55, 0.0))
- .build(),
- )
- .with_children(&[{
- camera = CameraBuilder::new(
- BaseBuilder::new().with_local_transform(
- TransformBuilder::new()
- .with_local_position(Vector3::new(0.0, 0.0, -2.0))
- .build(),
- ),
- )
- .with_z_far(48.0)
- .with_skybox(create_skybox(resource_manager).await)
- .build(graph);
- camera
- }]))
- .build(graph);
- hinge
- }]))
- .build(graph);
-
- Self {
- pivot,
- hinge,
- camera,
- }
- }
-}
+
+impl ScriptTrait for Player {
+ fn id(&self) -> Uuid {
+ unimplemented!()
+ }
+}
+
+pub struct GameConstructor;
+
+impl PluginConstructor for GameConstructor {
+ fn register(&self, context: PluginRegistrationContext) {
+ context
+ .serialization_context
+ .script_constructors
+ .add::<Player>("Player");
+ }
+
+ fn create_instance(
+ &self,
+ scene_path: Option<&str>,
+ context: PluginContext,
+ ) -> Box<dyn Plugin> {
+ unimplemented!()
+ }
+}
}
-To understand what this code does let's look closely at this picture:
-
-The pivot is marked yellow here, the hinge - green, and finally the camera is just a trapeze. Lines with arrows shows
-how the nodes linked together. As you can see we're attaching the hinge to the pivot and move it up slightly (usually to the
-height of the character). Next we're attaching the camera to the hinge and move it back so in default position it will
-be behind the character. To understand why we need such layout, let's find out how we need to move and rotate the
-camera. We need to rotate the camera around imaginary axis that goes through hinge ("in" the screen on the picture) -
-in this layout the camera will always look at character's head and rotate around local hinge's X axis. So to do that
-we need to rotate the hinge around X axis, not the camera. Here's the picture to help your understanding this better.
-
-That was just one of the axes, now we need to understand how to rotate the camera around Y axis, but preserving the
-rotation around X axis. This is very simple, we have the pivot for that. Remember that each of the nodes (pivot, hinge, camera)
-are linked together, so if we'll rotate the pivot around Y axis the hinge will rotate too as well as the camera. Fow
-now our camera controller does not have an ability to rotate, we'll add this later in the tutorial.
-Now let's load a level where our character will "live", add level.rs
with following content:
-#![allow(unused)]
+Preparation steps are now finished, and we can start filling the script with some useful code. Navigate to the player.rs
+and you'll see quite a lot of code. Most of the methods, however, can be removed, and we're only interested in on_update
+and on_os_event
. But for now, let's add the following fields in the Player
struct:
+#![allow(unused)]
fn main() {
extern crate fyrox;
-use fyrox::{
- core::pool::Handle,
- asset::manager::{ResourceManager}, resource::model::{Model, ModelResourceExtension},
- scene::{node::Node, Scene},
-};
-
-pub struct Level {
- root: Handle<Node>,
-}
-
-impl Level {
- pub async fn new(resource_manager: ResourceManager, scene: &mut Scene) -> Self {
- let root = resource_manager
- .request::<Model, _>("data/levels/level.rgs")
- .await
- .unwrap()
- .instantiate(scene);
-
- Self { root }
- }
-}
-}
-This small piece of code just loads the scene I made for this tutorial. It has a terrain and some decorations, including
-houses, trees, bushes, barrels, etc. The scene was made in the Fyroxed and can be freely edited without any
-problems. Just open the scene and modify it as you need.
-Now we need to "glue" all the pieces (the player, and the level) together, let's go back to main.rs
and change it to
-the following code:
-extern crate fyrox;
-#[cfg(test)]
-use crate::{level::Level, player::Player};
-use fyrox::{
- core::{color::Color, futures::executor::block_on, pool::Handle},
- engine::{executor::Executor},
- asset::manager::ResourceManager,
- event::{Event, WindowEvent},
- event_loop::ControlFlow,
- plugin::{Plugin, PluginConstructor, PluginContext},
- scene::{Scene},
-};
-use fyrox::window::WindowAttributes;
-use fyrox::engine::GraphicsContextParams;
-
-#[cfg(test)]
-mod level;
-#[cfg(test)]
-mod player;
-
-struct Player;
-impl Player {
- async fn new(_: ResourceManager, _: &mut Scene) -> Self { Self }
-}
-
-struct Level;
-impl Level {
- async fn new(_: ResourceManager, _: &mut Scene) -> Self { Self }
-}
-
-struct Game {
- scene: Handle<Scene>,
- level: Level,
- player: Player,
-}
-
-struct GameConstructor;
-
-impl PluginConstructor for GameConstructor {
- fn create_instance(&self, _: Handle<Scene>, context: PluginContext) -> Box<dyn Plugin> {
- Box::new(Game::new(context))
- }
-}
-
-impl Game {
- fn new(context: PluginContext) -> Self {
- let mut scene = Scene::new();
-
- scene.ambient_lighting_color = Color::opaque(150, 150, 150);
-
- let player = block_on(Player::new(context.resource_manager.clone(), &mut scene));
-
- Self {
- player,
- level: block_on(Level::new(context.resource_manager.clone(), &mut scene)),
- scene: context.scenes.add(scene),
- }
- }
-}
-
-impl Plugin for Game {
- fn update(&mut self, context: &mut PluginContext, _: &mut ControlFlow) {
-
- }
-
- fn on_os_event(
- &mut self,
- event: &Event<()>,
- _context: PluginContext,
- _control_flow: &mut ControlFlow,
- ) {
-
- }
-}
+
use fyrox::{
+ core::{
+ math::SmoothAngle, pool::Handle, reflect::prelude::*, variable::InheritableVariable,
+ visitor::prelude::*,
+ },
+ scene::node::Node,
+};
+
+#[derive(Visit, Reflect, Default, Debug, Clone)]
+pub struct Player {
+ #[visit(optional)]
+ camera_pivot: InheritableVariable<Handle<Node>>,
-fn main() {
- let mut executor = Executor::from_params(
- Default::default(),
- GraphicsContextParams {
- window_attributes: WindowAttributes {
- title: "RPG".to_string(),
- ..Default::default()
- },
- vsync: true,
- },
- );
- executor.add_plugin_constructor(GameConstructor);
- executor.run();
-}
-
-As you can see, everything is pretty straightforward: at first we're creating a new scene, set its ambient lighting to
-"daylight", next we're creating the player and the level. Finally, we're adding the scene to the engine and now if you
-run the game you should see something like this:
-
-For now everything is static, let's fix that by adding the ability to move the character and rotate the camera.
-Camera movement
-Let's start from the camera movement and rotation. We need two new fields in the CameraController
:
-#![allow(unused)]
-fn main() {
-struct Stub {
-// An angle around local Y axis of the pivot.
-yaw: f32,
-// An angle around local X axis of the hinge.
-pitch: f32,
-}
-}
-Do not forget to initialize them with zeros:
-#![allow(unused)]
-fn main() {
-Self {
- ...,
- yaw: 0.0,
- pitch: 0.0,
-}
-}
-Now we need to handle device events coming from the OS to rotate the camera. Add following method to the impl CameraController
:
-#![allow(unused)]
-fn main() {
-pub fn handle_device_event(&mut self, device_event: &DeviceEvent) {
- if let DeviceEvent::MouseMotion { delta } = device_event {
- const MOUSE_SENSITIVITY: f32 = 0.015;
+ #[visit(optional)]
+ camera_hinge: InheritableVariable<Handle<Node>>,
- self.yaw -= (delta.0 as f32) * MOUSE_SENSITIVITY;
- self.pitch = (self.pitch + (delta.1 as f32) * MOUSE_SENSITIVITY)
- // Limit vertical angle to [-90; 90] degrees range
- .max(-90.0f32.to_radians())
- .min(90.0f32.to_radians());
- }
-}
-}
-In this method we use only MouseMotion
events, because CameraController does not move - it can only rotate. The method
-is pretty straightforward. We're changing yaw and pitch using mouse offsets in two axes. X axis changes yaw, Y axis changes
-pitch. Pitch should be limited in specific range to prevent camera to rotate 360 degrees around object, we need angle
-to be in [-90; 90]
range.
-Once we've changed yaw and pitch, we need to apply rotations to the hinge and the camera. To do that, we need to add
-a new method to the impl CameraController
:
-#![allow(unused)]
-fn main() {
-pub fn update(&mut self, graph: &mut Graph) {
- // Apply rotation to the pivot.
- graph[self.pivot]
- .local_transform_mut()
- .set_rotation(UnitQuaternion::from_axis_angle(
- &Vector3::y_axis(),
- self.yaw,
- ));
+ #[visit(optional)]
+ state_machine: InheritableVariable<Handle<Node>>,
- // Apply rotation to the hinge.
- graph[self.hinge]
- .local_transform_mut()
- .set_rotation(UnitQuaternion::from_axis_angle(
- &Vector3::x_axis(),
- self.pitch,
- ));
-}
-}
-It is a very simple method, it borrows nodes, and applies rotations around specific axes. Now we need to call those two
-methods from somewhere. The most suitable place is impl Player
, because Player
owns an instance of CameraController
:
-#![allow(unused)]
-fn main() {
-pub fn handle_device_event(&mut self, device_event: &DeviceEvent) {
- self.camera_controller.handle_device_event(device_event)
-}
+ #[visit(optional)]
+ model_pivot: InheritableVariable<Handle<Node>>,
-pub fn update(&mut self, scene: &mut Scene) {
- self.camera_controller.update(&mut scene.graph);
-}
-}
-For now both methods are just proxies, but it will be changed pretty soon. Now we need to call the proxies, but from where?
-The most suitable place is on_tick
and on_device_event
of the GameState
trait implementation for our Game
structure:
-#![allow(unused)]
-fn main() {
-fn on_tick(&mut self, engine: &mut Engine, dt: f32, _control_flow: &mut ControlFlow) {
- let scene = &mut engine.scenes[self.scene];
+ #[visit(optional)]
+ model: InheritableVariable<Handle<Node>>,
- self.player.update(scene);
-}
+ #[visit(optional)]
+ model_yaw: InheritableVariable<SmoothAngle>,
-fn on_device_event(
- &mut self,
- _engine: &mut Engine,
- _device_id: DeviceId,
- event: DeviceEvent,
-) {
- self.player.handle_device_event(&event);
-}
-}
-Now you can run the game, and the camera should rotate when you're moving your mouse. Now it's the time to add an ability
-to walk for our character.
-Player locomotion
-Our player still can't move, in this section we'll fix it. Player's movement for third person camera differs from the
-movement of first person. For the third person camera we must move the player either where the camera looks or according
-to pressed keys on the keyboard. Let's start by adding input controller, it will hold info about needed movement:
-#![allow(unused)]
-fn main() {
-#[derive(Default)]
-struct InputController {
+ #[reflect(hidden)]
+ #[visit(skip)]
walk_forward: bool,
+
+ #[reflect(hidden)]
+ #[visit(skip)]
walk_backward: bool,
+
+ #[reflect(hidden)]
+ #[visit(skip)]
walk_left: bool,
+
+ #[reflect(hidden)]
+ #[visit(skip)]
walk_right: bool,
+
+ #[reflect(hidden)]
+ #[visit(skip)]
+ yaw: f32,
+
+ #[reflect(hidden)]
+ #[visit(skip)]
+ pitch: f32,
}
}
-Add new field to the Player
:
-#![allow(unused)]
-fn main() {
-input_controller: InputController,
-}
-And initialize it with Default::default
in the Player::new
:
-#![allow(unused)]
-fn main() {
-Self {
- ...,
- input_controller: Default::default(),
-}
-}
-Now we need to change the state of the input controller, to do that we'll use keyboard events. Add following method to
-the impl Player
:
-#![allow(unused)]
-fn main() {
-pub fn handle_key_event(&mut self, key: &KeyboardInput) {
- if let Some(key_code) = key.virtual_keycode {
- match key_code {
- VirtualKeyCode::W => {
- self.input_controller.walk_forward = key.state == ElementState::Pressed
- }
- VirtualKeyCode::S => {
- self.input_controller.walk_backward = key.state == ElementState::Pressed
- }
- VirtualKeyCode::A => {
- self.input_controller.walk_left = key.state == ElementState::Pressed
- }
- VirtualKeyCode::D => {
- self.input_controller.walk_right = key.state == ElementState::Pressed
- }
- _ => (),
- }
- }
-}
-}
-Now we need to call this method, we'll do it from on_window_event
in the GameState
trait implementation for our
-Game
:
-#![allow(unused)]
-fn main() {
-fn on_window_event(&mut self, _engine: &mut Engine, event: WindowEvent) {
- match event {
- WindowEvent::KeyboardInput { input, .. } => {
- self.player.handle_key_event(&input);
- }
- _ => (),
- }
-}
-}
-Ok, now we have input controller functioning. Now we can start adding movement logic to the player. Let's start by adding
-a physical body to the player. We'll use a capsule rigid body with locked rotations for that. Add these lines somewhere
-in Player::new
:
-#![allow(unused)]
+There are quite a lot of them, but all of them will be in use. The first four fields will contain handles to scene nodes
+we've made earlier, the model_yaw
field contains a SmoothAngle
which is used for smooth angle interpolation we'll
+use later in tutorial. Please note that these fields marked with #[visit(optional)]
attribute, which tells the engine
+that these fields can be missing and should be replaced with default values in this case. This is very useful attribute
+if you're adding new fields to some existing script, it will prevent serialization error. The rest of the fields contains
+runtime information about movement state (move_forward
, move_backward
, walk_left
, walk_right
) and the
+camera orientation (yaw
and pitch
fields).
+A few notes why the first five fields are wrapped in the InheritableVariable
- it is to support property inheritance
+mechanism for these fields. The engine will save the values for these variables only if they're manually modified, on
+loading, however, it will replace non-modified values with the ones from parent prefab. If it sounds too complicated for
+you, then you should probably read this chapter.
+Let's start writing player controller's logic.
+Event Handling
+We'll start from keyboard and mouse event handling, add the following code to the impl ScriptTrait for Player
:
+#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
- core::algebra::Vector3,
- core::pool::Handle,
- scene::{
- Scene,
- base::BaseBuilder,
- collider::{ColliderBuilder, ColliderShape},
- graph::physics::CoefficientCombineRule,
- rigidbody::RigidBodyBuilder,
- transform::TransformBuilder,
- },
+ event::{DeviceEvent, ElementState, Event, WindowEvent},
+ keyboard::KeyCode,
+ script::ScriptContext,
};
-fn f(scene: &mut Scene) {
-let model = Handle::NONE;
-let collider;
-let body = RigidBodyBuilder::new(
- BaseBuilder::new()
- .with_local_transform(
- TransformBuilder::new()
- .with_local_position(Vector3::new(0.0, 2.0, 0.0))
- .build(),
- )
- .with_children(&[
- {
- // Attach the model to the pivot. This will force model to move together with the pivot.
- model
- },
- {
- // Create capsule collider with friction disabled. We need to disable friction because linear
- // velocity will be set manually, but the physics engine will reduce it using friction so it
- // won't let us to set linear velocity precisely.
- collider = ColliderBuilder::new(BaseBuilder::new())
- .with_shape(ColliderShape::capsule_y(0.55, 0.15))
- .with_friction_combine_rule(CoefficientCombineRule::Min)
- .with_friction(0.0)
- .build(&mut scene.graph);
- collider
- },
- ]),
-)
-.with_locked_rotations(true)
-.with_can_sleep(false)
-.build(&mut scene.graph);
+
+struct Player {
+ walk_forward: bool,
+ walk_backward: bool,
+ walk_left: bool,
+ walk_right: bool,
+ yaw: f32,
+ pitch: f32,
+}
+
+impl Player {
+fn on_os_event(&mut self, event: &Event<()>, ctx: &mut ScriptContext) {
+ match event {
+ Event::WindowEvent { event, .. } => {
+ if let WindowEvent::KeyboardInput { event, .. } = event {
+ let pressed = event.state == ElementState::Pressed;
+ match event.physical_key {
+ KeyCode::KeyW => self.walk_forward = pressed,
+ KeyCode::KeyS => self.walk_backward = pressed,
+ KeyCode::KeyA => self.walk_left = pressed,
+ KeyCode::KeyD => self.walk_right = pressed,
+ _ => (),
+ }
+ }
+ }
+ Event::DeviceEvent { event, .. } => {
+ if let DeviceEvent::MouseMotion { delta } = event {
+ let mouse_sens = 0.2 * ctx.dt;
+ self.yaw -= (delta.0 as f32) * mouse_sens;
+ self.pitch = (self.pitch + (delta.1 as f32) * mouse_sens)
+ .clamp(-90.0f32.to_radians(), 90.0f32.to_radians());
+ }
+ }
+ _ => (),
+ }
+}
}
}
-Now, once our character has physical body, we can move it. Add these lines to the end of Player::update
:
-#![allow(unused)]
-fn main() {
-let body = scene.graph[self.body].as_rigid_body_mut();
-
-let look_vector = body
- .look_vector()
- .try_normalize(f32::EPSILON)
- .unwrap_or(Vector3::z());
-
-let side_vector = body
- .side_vector()
- .try_normalize(f32::EPSILON)
- .unwrap_or(Vector3::x());
-
-let position = **body.local_transform().position();
-
-let mut velocity = Vector3::default();
-
-if self.input_controller.walk_right {
- velocity -= side_vector;
-}
-if self.input_controller.walk_left {
- velocity += side_vector;
-}
-if self.input_controller.walk_forward {
- velocity += look_vector;
-}
-if self.input_controller.walk_backward {
- velocity -= look_vector;
-}
-
-let speed = 1.35 * dt;
-let velocity = velocity
- .try_normalize(f32::EPSILON)
- .and_then(|v| Some(v.scale(speed)))
- .unwrap_or(Vector3::default());
-
-// Apply linear velocity.
-body.set_lin_vel(Vector3::new(
- velocity.x / dt,
- body.lin_vel().y,
- velocity.z / dt,
-));
-
-let is_moving = velocity.norm_squared() > 0.0;
-if is_moving {
- // Since we have free camera while not moving, we have to sync rotation of pivot
- // with rotation of camera so character will start moving in look direction.
- body.local_transform_mut()
- .set_rotation(UnitQuaternion::from_axis_angle(
- &Vector3::y_axis(),
- self.camera_controller.yaw,
- ));
-
- // Apply additional rotation to model - it will turn in front of walking direction.
- let angle: f32 = if self.input_controller.walk_left {
- if self.input_controller.walk_forward {
- 45.0
- } else if self.input_controller.walk_backward {
- 135.0
- } else {
- 90.0
- }
- } else if self.input_controller.walk_right {
- if self.input_controller.walk_forward {
- -45.0
- } else if self.input_controller.walk_backward {
- -135.0
- } else {
- -90.0
- }
- } else if self.input_controller.walk_backward {
- 180.0
- } else {
- 0.0
- };
-
- scene.graph[self.model].local_transform_mut().set_rotation(
- UnitQuaternion::from_axis_angle(&Vector3::y_axis(), angle.to_radians()),
- );
-}
-
-// Sync camera controller position with player's position.
-scene.graph[self.camera_controller.pivot]
- .local_transform_mut()
- .set_position(position + velocity);
-}
-There is lots of code, let's thoroughly go through. At first, we're getting two vectors from the pivot: X and Z axes of
-the global transform of the pivot. We'll use them to move the character. Next we're using the state of the input
-controller to form a new velocity vector. Then we're normalizing velocity vector and multiply it with desired speed of
-movement. Normalization is needed to make the vector unit length to prevent speed variations in various directions. Next
-we're applying the velocity to the rigid body, also we're locking any angular movement to prevent player's capsule
-from tilting.
-If the player is not moving, we're not syncing its rotation with camera's rotation - this allows us to look at the
-character from any side while not moving. However, if the player is moving, we must sync its rotation with the rotation
-of the camera controller. If we'd do this straightforward (by just syncing rotations) it would look very unnatural,
-especially in case of side movements. To fix this we have this large chain of if..else
that selects appropriate
-additional rotation for the player's model. This rotation allows us, for example, look forward and move the character
-backwards.
-As the final step we're syncing position of the camera controller with the position of the pivot. Now if you run the game
-you'll be able to walk around using [W][S][A][D]
keys. However, it looks very ugly - the character's model is in T-pose,
-let's fix this.
-Animations
-At this point our character can move, and we can rotate the camera around it, but the character is still in T-pose and
-does not have any animation. In this section we'll animate it. To keep this tutorial at reasonable length, we'll
-add just an idle and walk animations and smooth transitions between them. Add following code at the end of player.rs
:
-#![allow(unused)]
+This code consists of two major sections: KeyboardInput
event handling and MouseMotion
event handling. Let's start
+from KeyboardInput
event. At the beginning of it we're checking if a key was pressed or not and saving it to the
+pressed
flag, then we check for W
, S
, A
, D
keys and set each movement flag accordingly.
+The MouseMotion
event handling is different: we're using mouse movement delta to calculate new yaw and pitch values
+for our camera. Pitch calculation also includes angle clamping in -90.0..90.0
degree range.
+Logic
+The next important step is to apply all the data we have to a bunch of scene nodes the player consists of. Let's fill
+the on_update
method with the following code:
+#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
- animation::{
- machine::{Machine, MachineLayer, Parameter, PoseNode, State, Transition},
- Animation,
+ animation::machine::Parameter,
+ core::{
+ algebra::{UnitQuaternion, Vector3},
+ math::SmoothAngle,
+ pool::Handle,
+ reflect::prelude::*,
+ uuid::{uuid, Uuid},
+ variable::InheritableVariable,
+ visitor::prelude::*,
+ TypeUuidProvider,
},
- core::pool::Handle,
- asset::manager::ResourceManager,
- resource::model::{Model, ModelResource, ModelResourceExtension},
- scene::{node::Node, animation::AnimationPlayer, Scene},
+ event::{DeviceEvent, ElementState, Event, WindowEvent},
+ impl_component_provider,
+ keyboard::KeyCode,
+ scene::{animation::absm::AnimationBlendingStateMachine, node::Node, rigidbody::RigidBody},
+ script::{ScriptContext, ScriptTrait},
};
-
-// Simple helper method to create a state supplied with PlayAnimation node.
-fn create_play_animation_state(
- animation_resource: ModelResource,
- name: &str,
- layer: &mut MachineLayer,
- scene: &mut Scene,
- model: Handle<Node>,
-) -> (Handle<Animation>, Handle<State>) {
- // Animations retargetting just makes an instance of animation and binds it to
- // given model using names of bones.
- let animation = *animation_resource
- .retarget_animations(model, &mut scene.graph)
- .get(0)
- .unwrap();
- // Create new PlayAnimation node and add it to machine.
- let node = layer.add_node(PoseNode::make_play_animation(animation));
- // Make a state using the node we've made.
- let state = layer.add_state(State::new(name, node));
- (animation, state)
-}
-
-pub struct AnimationMachineInput {
- // Whether a bot is walking or not.
- pub walk: bool,
-}
-
-pub struct AnimationMachine {
- machine: Machine,
- animation_player: Handle<Node>,
-}
-
-impl AnimationMachine {
- // Names of parameters that will be used for transition rules in machine.
- const IDLE_TO_WALK: &'static str = "IdleToWalk";
- const WALK_TO_IDLE: &'static str = "WalkToIdle";
-
- pub async fn new(
- scene: &mut Scene,
- model: Handle<Node>,
- resource_manager: ResourceManager,
- ) -> Self {
- let animation_player = scene.graph.find(model, &mut |n| {
- n.query_component_ref::<AnimationPlayer>().is_some()
- }).unwrap().0;
-
- let mut machine = Machine::new();
-
- let root = machine.layers_mut().first_mut().unwrap();
+
+#[derive(Visit, Reflect, Default, Debug, Clone)]
+pub struct Player {
+ camera_pivot: InheritableVariable<Handle<Node>>,
+ camera_hinge: InheritableVariable<Handle<Node>>,
+ state_machine: InheritableVariable<Handle<Node>>,
+ model_pivot: InheritableVariable<Handle<Node>>,
+ model: InheritableVariable<Handle<Node>>,
+ model_yaw: InheritableVariable<SmoothAngle>,
+ walk_forward: bool,
+ walk_backward: bool,
+ walk_left: bool,
+ walk_right: bool,
+ yaw: f32,
+ pitch: f32,
+}
+
+impl_component_provider!(Player);
+
+impl TypeUuidProvider for Player {
+ fn type_uuid() -> Uuid {
+ unimplemented!();
+ }
+}
+
+impl ScriptTrait for Player {
+ fn on_update(&mut self, ctx: &mut ScriptContext) {
+ // Step 1. Fetch the velocity vector from the animation blending state machine.
+ let transform = ctx.scene.graph[*self.model].global_transform();
+ let mut velocity = Vector3::default();
+ if let Some(state_machine) = ctx
+ .scene
+ .graph
+ .try_get(*self.state_machine)
+ .and_then(|node| node.query_component_ref::<AnimationBlendingStateMachine>())
+ {
+ if let Some(root_motion) = state_machine.machine().pose().root_motion() {
+ velocity = transform
+ .transform_vector(&root_motion.delta_position)
+ .scale(1.0 / ctx.dt);
+ }
+ }
+
+ // Step 2. Apply the velocity to the rigid body and lock rotations.
+ if let Some(body) = ctx.scene.graph.try_get_mut_of_type::<RigidBody>(ctx.handle) {
+ body.set_ang_vel(Default::default());
+ body.set_lin_vel(Vector3::new(velocity.x, body.lin_vel().y, velocity.z));
+ }
- // Load animations in parallel.
- let (walk_animation_resource, idle_animation_resource) = fyrox::core::futures::join!(
- resource_manager.request::<Model, _>("data/models/paladin/walk.fbx"),
- resource_manager.request::<Model, _>("data/models/paladin/idle.fbx"),
- );
+ // Step 3. Rotate the model pivot according to the movement direction.
+ let quat_yaw = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), self.yaw);
- // Now create two states with different animations.
- let (_, idle_state) = create_play_animation_state(
- idle_animation_resource.unwrap(),
- "Idle",
- root,
- scene,
- model,
- );
+ if velocity.norm_squared() > 0.0 {
+ // Since we have free camera while not moving, we have to sync rotation of pivot
+ // with rotation of camera so character will start moving in look direction.
+ if let Some(model_pivot) = ctx.scene.graph.try_get_mut(*self.model_pivot) {
+ model_pivot.local_transform_mut().set_rotation(quat_yaw);
+ }
- let (walk_animation, walk_state) = create_play_animation_state(
- walk_animation_resource.unwrap(),
- "Walk",
- root,
- scene,
- model,
- );
+ // Apply additional rotation to model - it will turn in front of walking direction.
+ let angle: f32 = if self.walk_left {
+ if self.walk_forward {
+ 45.0
+ } else if self.walk_backward {
+ 135.0
+ } else {
+ 90.0
+ }
+ } else if self.walk_right {
+ if self.walk_forward {
+ -45.0
+ } else if self.walk_backward {
+ -135.0
+ } else {
+ -90.0
+ }
+ } else if self.walk_backward {
+ 180.0
+ } else {
+ 0.0
+ };
- // Next, define transitions between states.
- root.add_transition(Transition::new(
- // A name for debugging.
- "Idle->Walk",
- // Source state.
- idle_state,
- // Target state.
- walk_state,
- // Transition time in seconds.
- 0.4,
- // A name of transition rule parameter.
- Self::IDLE_TO_WALK,
- ));
- root.add_transition(Transition::new(
- "Walk->Idle",
- walk_state,
- idle_state,
- 0.4,
- Self::WALK_TO_IDLE,
- ));
+ self.model_yaw.set_target(angle.to_radians()).update(ctx.dt);
- // Define entry state.
- root.set_entry_state(idle_state);
+ if let Some(model) = ctx.scene.graph.try_get_mut(*self.model) {
+ model
+ .local_transform_mut()
+ .set_rotation(UnitQuaternion::from_axis_angle(
+ &Vector3::y_axis(),
+ self.model_yaw.angle,
+ ));
+ }
+ }
- Self {
- machine,
- animation_player,
+ if let Some(camera_pivot) = ctx.scene.graph.try_get_mut(*self.camera_pivot) {
+ camera_pivot.local_transform_mut().set_rotation(quat_yaw);
}
- }
- pub fn update(&mut self, scene: &mut Scene, dt: f32, input: AnimationMachineInput) {
- let animation_player = scene.graph[self.animation_player]
- .query_component_mut::<AnimationPlayer>()
- .unwrap();
+ // Rotate camera hinge - this will make camera move up and down while look at character
+ // (well not exactly on character - on characters head)
+ if let Some(camera_hinge) = ctx.scene.graph.try_get_mut(*self.camera_hinge) {
+ camera_hinge
+ .local_transform_mut()
+ .set_rotation(UnitQuaternion::from_axis_angle(
+ &Vector3::x_axis(),
+ self.pitch,
+ ));
+ }
- self.machine
- // Set transition parameters.
- .set_parameter(Self::WALK_TO_IDLE, Parameter::Rule(!input.walk))
- .set_parameter(Self::IDLE_TO_WALK, Parameter::Rule(input.walk))
- // Update machine and evaluate final pose.
- .evaluate_pose(animation_player.animations_mut().get_value_mut_silent(), dt)
- // Apply the pose to the graph.
- .apply(&mut scene.graph);
+ // Step 4. Feed the animation blending state machine with the current state of the player.
+ if let Some(state_machine) = ctx
+ .scene
+ .graph
+ .try_get_mut(*self.state_machine)
+ .and_then(|node| node.query_component_mut::<AnimationBlendingStateMachine>())
+ {
+ let moving =
+ self.walk_left || self.walk_right || self.walk_forward || self.walk_backward;
+
+ state_machine
+ .machine_mut()
+ .get_value_mut_silent()
+ .set_parameter("Running", Parameter::Rule(moving));
+ }
}
-}
-}
-This is a simple animation blending machine, for more info check
-"Animations" section of "Writing a 3D shooter using Fyrox #3"
-tutorial, it has detailed explanation how animation blending machines work. In short, here we're loading two animations,
-and create two transitions between them and then applying final pose to the character.
-Now we need to create an instance of the AnimationMachine
, add a field to the Player
:
-#![allow(unused)]
-fn main() {
-...,
-animation_machine: AnimationMachine,
-}
-And initialize it in the Player::new
, before camera_controller
:
-#![allow(unused)]
-fn main() {
-...,
-animation_machine: AnimationMachine::new(scene, model, resource_manager.clone()).await,
-...
-}
-The last thing we need to do is to update animation machine each frame, we'll do this in Player::update
, at the end
-of the method:
-#![allow(unused)]
-fn main() {
-self.animation_machine
- .update(scene, dt, AnimationMachineInput { walk: is_moving });
+
+ fn id(&self) -> Uuid {
+ Self::type_uuid()
+ }
+}
}
-Now if you run the game, you should see the character idling if not moving, and it should play "walking" animation if
-moving. That's it for this tutorial, in the next tutorial we'll "teach" the character to use swords.
+That's a big chunk of code, but it mostly consists of a set of separate steps. Let's try to understand what each step does.
+Step 1 extracts the root motion vector from the animation blending state machine: at first, we're getting the current
+transformation matrix of the Paladin's model. Then we're trying to borrow the ABSM node from the scene. If it is
+successful, then we're trying to extract the root motion vector from the final pose of the ABSM. If we have one, then
+we need to transform it from the local space to the world space - we're doing this using matrix-vector multiplication.
+And as the last step, we're scaling the vector by delta time to get the final velocity in world coordinates that can
+be used to move the rigid body.
+Step 2 uses the root motion vector to move the rigid body. The body is the node to which the script is assigned to,
+so we're using ctx.handle
to borrow a "self" reference and setting the new linear and angular velocities.
+Step 3 is the largest (code-wise) step, yet very simple. All we do here is rotating the camera and the model pivot in
+according to pressed keys. The code should be self-explanatory.
+Step 4 feeds the animation blending state machine with the variables it needs to perform state transitions. Currently,
+we have only one variable - Running
and to set it, we're trying to borrow the ABSM using its handle, then we're
+using the state of four of our movement variable to combine them into one and use this flag to set the value in the
+ABSM.
+Binding
+Now, when we have finished coding part, we can open paladin.rgs
in the editor again and assign the script to it:
+
+Make sure to correctly set the script fields (as on the screenshot above), otherwise it won't work correctly.
+Level
+Use your imagination to create a game level (or just use the one from the assets pack for this tutorial). Level design
+is not covered by this tutorial. You can create a simple level using a Terrain, a few 3D models from the assets pack:
+
+The most important part, however, is to add a player instance to the level:
+
+Now all you need to do is to click on the green >
button and run the game.
Conclusion
-In this tutorial we've learned how create a walking character. Created simple character controller and walked on
-the scene. I hope you liked this tutorial, and if so, please consider supporting the project on
-Patreon or do a one-time donation via BuyMeACoffee.
-The source code for this tutorial is available on GitHub.
-
+In this tutorial we've learned how to set up physics for humanoid characters, how to create simple 3rd person camera
+controllers, how to import and blend multiple animation into one, how to use root motion to extract motion vector from
+animations. We also learned how to create prefabs and use them correctly. Finally, we have created a simple level and
+instantiated the character prefab on it.
diff --git a/print.html b/print.html
index dd6d0732..09f6c0b3 100644
--- a/print.html
+++ b/print.html
@@ -10426,7 +10426,7 @@ GitHub
RPG Tutorial Part 1 - Character Controller
Source code: GitHub
@@ -10443,899 +10443,505 @@ Table
Introduction
In this series of tutorials we will make a game similar to The Elder Scrolls series (but much, much smaller indeed),
-we'll have a main character, a simple world with intractable items and a few kind of enemies. I'll show you how to add an inventory,
-a quests journal, and the quests itself. This series should have at least 5 tutorials, but this might change. At the end
-of the series we'll have a playable RPG which you will be able to use to continue making your own game. It is very ambitious,
-but totally doable with the current state of the engine.
+we'll have a main character, a simple world with intractable items and a few kind of enemies. In this series you'll
+understand how to add an inventory, a quests journal, and the quests itself. This series should have at least 5
+tutorials, but this might change. At the end of the series we'll have a playable RPG which you will be able to use to
+continue making your own game. It is very ambitious, but totally doable with the current state of the engine.
Most of the role-playing games (RPGs for short) using 3rd person camera which allows you to see your character entirely.
In this tutorial we'll make something similar. Check the video with final result of the tutorial:
-As you can see, at the end of the tutorial we'll be able to walk and explore a small fantasy world. Let's start by creating
-a new cargo project:
-cargo init rpg-tutorial
-Add fyrox
as dependency:
-[dependencies]
-fyrox = "0.29.0"
-
-Framework
-Now let's create the window and initialize the engine. We'll skip most engine initialization by using new Framework
helper
-that hides most of the engine initialization and provides unified interface for your games allowing you to focus on
-your game code. Framework
is not mandatory, you may use the previous
-variant with manual engine initialization and "opened" main loop.
-extern crate fyrox;
-use fyrox::{
- core::{color::Color, futures::executor::block_on, pool::Handle},
- engine::executor::Executor,
- event::{Event, WindowEvent},
- event_loop::ControlFlow,
- plugin::{Plugin, PluginConstructor, PluginContext},
- scene::{Scene},
-};
-use fyrox::window::WindowAttributes;
-use fyrox::engine::GraphicsContextParams;
-
-struct Game {
- scene: Handle<Scene>,
-}
-
-struct GameConstructor;
-
-impl PluginConstructor for GameConstructor {
- fn create_instance(&self, _: Handle<Scene>, context: PluginContext) -> Box<dyn Plugin> {
- Box::new(Game::new(context))
- }
-}
-
-impl Game {
- fn new(context: PluginContext) -> Self {
- let mut scene = Scene::new();
-
- scene.rendering_options.ambient_lighting_color = Color::opaque(150, 150, 150);
-
- Self {
- scene: context.scenes.add(scene),
- }
- }
-}
-
-impl Plugin for Game {
- fn update(&mut self, context: &mut PluginContext, _: &mut ControlFlow) {
-
- }
-
- fn on_os_event(
- &mut self,
- event: &Event<()>,
- _context: PluginContext,
- _control_flow: &mut ControlFlow,
- ) {
-
- }
-}
-
-fn main() {
- let mut executor = Executor::from_params(
- Default::default(),
- GraphicsContextParams {
- window_attributes: WindowAttributes {
- title: "RPG".to_string(),
- ..Default::default()
- },
- vsync: true,
- },
- );
- executor.add_plugin_constructor(GameConstructor);
- executor.run();
-}
-It is much easier to initialize the engine now compared to the initialization described in the series of tutorials about
-writing a 3D shooter. If you run it, you'll see a window with black background with an "RPG" title.
-Assets
-For any kind of game you need a lot of various assets, in our case we need a 3D model for our character, a set of
-animations, a level, a set of textures for terrain, trees and bushes, barrels, etc. I prepared all assets as a single
-ZIP archive which can be downloaded here. Once you've downloaded it, unpack it in ./data
folder.
-Player and camera controller
-Now we can start adding Player to our game. Create a folder player
under your src
directory and add mod.rs
with
-following content:
-#![allow(unused)]
-fn main() {
-extern crate fyrox;
-
-#[cfg(test)]
-use crate::player::camera::CameraController;
-
-// Import everything we need for the tutorial.
-use fyrox::{
- animation::{
- machine::{Machine, MachineLayer, Parameter, PoseNode, State, Transition},
- Animation,
- },
- core::{
- algebra::{UnitQuaternion, Vector3},
- pool::Handle,
- },
- asset::manager::ResourceManager,
- event::{DeviceEvent, ElementState, KeyboardInput, VirtualKeyCode},
- resource::model::{Model, ModelResourceExtension},
- scene::{
- animation::AnimationPlayer,
- base::BaseBuilder,
- collider::{ColliderBuilder, ColliderShape},
- graph::{Graph, physics::CoefficientCombineRule},
- node::Node,
- rigidbody::RigidBodyBuilder,
- transform::TransformBuilder,
- Scene,
- },
-};
-
-#[cfg(test)]
-mod camera;
-
-struct CameraController;
-impl CameraController {
- async fn new(_: &mut Graph, _: ResourceManager) -> Self { Self }
-}
-
-pub struct Player {
- model: Handle<Node>,
- camera_controller: CameraController,
-}
-
-impl Player {
- pub async fn new(resource_manager: ResourceManager, scene: &mut Scene) -> Self {
- // Load paladin 3D model and create its instance in the scene.
- let model = resource_manager
- .request::<Model, _>("data/models/paladin/paladin.fbx")
- .await
- .unwrap()
- .instantiate(scene);
-
- scene.graph[model]
- .local_transform_mut()
- // Move the model a bit down because its center is at model's feet
- // and we'd get floating model without this offset.
- .set_position(Vector3::new(0.0, -0.75, 0.0))
- // Scale down paladin's model because it is too big.
- .set_scale(Vector3::new(0.02, 0.02, 0.02));
-
- Self {
- model,
-
- // As a final stage create camera controller.
- camera_controller: CameraController::new(&mut scene.graph, resource_manager).await,
- }
- }
-}
-}
-Let's disassemble this heap of code line by line. At first, we're creating pivot for our character, we'll use it as a
-"mounting point" for character's 3D model, also it will have a physical body, but that will be added later in this
-tutorial. Next, we're loading paladin 3D model and creating its instance in the scene, we need only geometry without
-animations, so we use instantiate_geometry
here, animations will be added later in this tutorial. Next we scale the
-model a bit, because it is too big. Also, we're moving the model a bit down because its center is at paladin's feet so
-when we're attaching the model to the pivot, it will "stay" on the pivot. We want it to stay on ground, so we're moving
-it down by height of the model. Finally, we're attaching the model to the pivot, forcing the engine to move
-the model together with pivot. In the end we're creating camera controller, it needs its own module, so add camera.rs
-module under src/player
with following content:
-#![allow(unused)]
-fn main() {
-extern crate fyrox;
-// Import everything we need for the tutorial.
-use fyrox::{
- core::{
- algebra::{UnitQuaternion, Vector3},
- pool::Handle,
- },
- asset::manager::ResourceManager,
- event::DeviceEvent,
- resource::texture::{Texture, TextureWrapMode},
- scene::{
- base::BaseBuilder,
- camera::{CameraBuilder, SkyBox, SkyBoxBuilder},
- graph::Graph,
- node::Node,
- transform::TransformBuilder,
- pivot::PivotBuilder
- },
-};
-
-async fn create_skybox(resource_manager: ResourceManager) -> SkyBox {
- // Load skybox textures in parallel.
- let (front, back, left, right, top, bottom) = fyrox::core::futures::join!(
- resource_manager.request::<Texture, _>("data/textures/skybox/front.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/back.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/left.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/right.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/up.jpg"),
- resource_manager.request::<Texture, _>("data/textures/skybox/down.jpg")
- );
-
- // Unwrap everything.
- let skybox = SkyBoxBuilder {
- front: Some(front.unwrap()),
- back: Some(back.unwrap()),
- left: Some(left.unwrap()),
- right: Some(right.unwrap()),
- top: Some(top.unwrap()),
- bottom: Some(bottom.unwrap()),
- }
- .build()
- .unwrap();
-
- // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges
- // of the skybox.
- let cubemap = skybox.cubemap();
- let mut data = cubemap.as_ref().unwrap().data_ref();
- data.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
- data.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
-
- skybox
-}
-
-pub struct CameraController {
- pivot: Handle<Node>,
- hinge: Handle<Node>,
- camera: Handle<Node>,
-}
-
-impl CameraController {
- pub async fn new(graph: &mut Graph, resource_manager: ResourceManager) -> Self {
- let camera;
- let hinge;
- let pivot = PivotBuilder::new(BaseBuilder::new()
- .with_children(&[{
- hinge = PivotBuilder::new(BaseBuilder::new()
- .with_local_transform(
- TransformBuilder::new()
- .with_local_position(Vector3::new(0.0, 0.55, 0.0))
- .build(),
- )
- .with_children(&[{
- camera = CameraBuilder::new(
- BaseBuilder::new().with_local_transform(
- TransformBuilder::new()
- .with_local_position(Vector3::new(0.0, 0.0, -2.0))
- .build(),
- ),
- )
- .with_z_far(48.0)
- .with_skybox(create_skybox(resource_manager).await)
- .build(graph);
- camera
- }]))
- .build(graph);
- hinge
- }]))
- .build(graph);
-
- Self {
- pivot,
- hinge,
- camera,
- }
- }
-}
-}
-To understand what this code does let's look closely at this picture:
-
-The pivot is marked yellow here, the hinge - green, and finally the camera is just a trapeze. Lines with arrows shows
-how the nodes linked together. As you can see we're attaching the hinge to the pivot and move it up slightly (usually to the
-height of the character). Next we're attaching the camera to the hinge and move it back so in default position it will
-be behind the character. To understand why we need such layout, let's find out how we need to move and rotate the
-camera. We need to rotate the camera around imaginary axis that goes through hinge ("in" the screen on the picture) -
-in this layout the camera will always look at character's head and rotate around local hinge's X axis. So to do that
-we need to rotate the hinge around X axis, not the camera. Here's the picture to help your understanding this better.
-
-That was just one of the axes, now we need to understand how to rotate the camera around Y axis, but preserving the
-rotation around X axis. This is very simple, we have the pivot for that. Remember that each of the nodes (pivot, hinge, camera)
-are linked together, so if we'll rotate the pivot around Y axis the hinge will rotate too as well as the camera. Fow
-now our camera controller does not have an ability to rotate, we'll add this later in the tutorial.
-Now let's load a level where our character will "live", add level.rs
with following content:
-#![allow(unused)]
-fn main() {
-extern crate fyrox;
-use fyrox::{
- core::pool::Handle,
- asset::manager::{ResourceManager}, resource::model::{Model, ModelResourceExtension},
- scene::{node::Node, Scene},
-};
-
-pub struct Level {
- root: Handle<Node>,
-}
-
-impl Level {
- pub async fn new(resource_manager: ResourceManager, scene: &mut Scene) -> Self {
- let root = resource_manager
- .request::<Model, _>("data/levels/level.rgs")
- .await
- .unwrap()
- .instantiate(scene);
-
- Self { root }
- }
-}
-}
-This small piece of code just loads the scene I made for this tutorial. It has a terrain and some decorations, including
-houses, trees, bushes, barrels, etc. The scene was made in the Fyroxed and can be freely edited without any
-problems. Just open the scene and modify it as you need.
-Now we need to "glue" all the pieces (the player, and the level) together, let's go back to main.rs
and change it to
-the following code:
-extern crate fyrox;
-#[cfg(test)]
-use crate::{level::Level, player::Player};
-use fyrox::{
- core::{color::Color, futures::executor::block_on, pool::Handle},
- engine::{executor::Executor},
- asset::manager::ResourceManager,
- event::{Event, WindowEvent},
- event_loop::ControlFlow,
- plugin::{Plugin, PluginConstructor, PluginContext},
- scene::{Scene},
-};
-use fyrox::window::WindowAttributes;
-use fyrox::engine::GraphicsContextParams;
-
-#[cfg(test)]
-mod level;
-#[cfg(test)]
-mod player;
-
-struct Player;
-impl Player {
- async fn new(_: ResourceManager, _: &mut Scene) -> Self { Self }
-}
-
-struct Level;
-impl Level {
- async fn new(_: ResourceManager, _: &mut Scene) -> Self { Self }
-}
-
-struct Game {
- scene: Handle<Scene>,
- level: Level,
- player: Player,
-}
-
-struct GameConstructor;
-
-impl PluginConstructor for GameConstructor {
- fn create_instance(&self, _: Handle<Scene>, context: PluginContext) -> Box<dyn Plugin> {
- Box::new(Game::new(context))
- }
-}
-
-impl Game {
- fn new(context: PluginContext) -> Self {
- let mut scene = Scene::new();
-
- scene.ambient_lighting_color = Color::opaque(150, 150, 150);
-
- let player = block_on(Player::new(context.resource_manager.clone(), &mut scene));
-
- Self {
- player,
- level: block_on(Level::new(context.resource_manager.clone(), &mut scene)),
- scene: context.scenes.add(scene),
- }
- }
-}
-
-impl Plugin for Game {
- fn update(&mut self, context: &mut PluginContext, _: &mut ControlFlow) {
-
- }
-
- fn on_os_event(
- &mut self,
- event: &Event<()>,
- _context: PluginContext,
- _control_flow: &mut ControlFlow,
- ) {
-
- }
-}
-
-fn main() {
- let mut executor = Executor::from_params(
- Default::default(),
- GraphicsContextParams {
- window_attributes: WindowAttributes {
- title: "RPG".to_string(),
- ..Default::default()
- },
- vsync: true,
- },
- );
- executor.add_plugin_constructor(GameConstructor);
- executor.run();
-}
-
-As you can see, everything is pretty straightforward: at first we're creating a new scene, set its ambient lighting to
-"daylight", next we're creating the player and the level. Finally, we're adding the scene to the engine and now if you
-run the game you should see something like this:
-
-For now everything is static, let's fix that by adding the ability to move the character and rotate the camera.
-Camera movement
-Let's start from the camera movement and rotation. We need two new fields in the CameraController
:
-#![allow(unused)]
-fn main() {
-struct Stub {
-// An angle around local Y axis of the pivot.
-yaw: f32,
-// An angle around local X axis of the hinge.
-pitch: f32,
-}
-}
-Do not forget to initialize them with zeros:
-#![allow(unused)]
-fn main() {
-Self {
- ...,
- yaw: 0.0,
- pitch: 0.0,
-}
-}
-Now we need to handle device events coming from the OS to rotate the camera. Add following method to the impl CameraController
:
-#![allow(unused)]
-fn main() {
-pub fn handle_device_event(&mut self, device_event: &DeviceEvent) {
- if let DeviceEvent::MouseMotion { delta } = device_event {
- const MOUSE_SENSITIVITY: f32 = 0.015;
-
- self.yaw -= (delta.0 as f32) * MOUSE_SENSITIVITY;
- self.pitch = (self.pitch + (delta.1 as f32) * MOUSE_SENSITIVITY)
- // Limit vertical angle to [-90; 90] degrees range
- .max(-90.0f32.to_radians())
- .min(90.0f32.to_radians());
- }
-}
-}
-In this method we use only MouseMotion
events, because CameraController does not move - it can only rotate. The method
-is pretty straightforward. We're changing yaw and pitch using mouse offsets in two axes. X axis changes yaw, Y axis changes
-pitch. Pitch should be limited in specific range to prevent camera to rotate 360 degrees around object, we need angle
-to be in [-90; 90]
range.
-Once we've changed yaw and pitch, we need to apply rotations to the hinge and the camera. To do that, we need to add
-a new method to the impl CameraController
:
-#![allow(unused)]
-fn main() {
-pub fn update(&mut self, graph: &mut Graph) {
- // Apply rotation to the pivot.
- graph[self.pivot]
- .local_transform_mut()
- .set_rotation(UnitQuaternion::from_axis_angle(
- &Vector3::y_axis(),
- self.yaw,
- ));
-
- // Apply rotation to the hinge.
- graph[self.hinge]
- .local_transform_mut()
- .set_rotation(UnitQuaternion::from_axis_angle(
- &Vector3::x_axis(),
- self.pitch,
- ));
-}
-}
-It is a very simple method, it borrows nodes, and applies rotations around specific axes. Now we need to call those two
-methods from somewhere. The most suitable place is impl Player
, because Player
owns an instance of CameraController
:
-#![allow(unused)]
-fn main() {
-pub fn handle_device_event(&mut self, device_event: &DeviceEvent) {
- self.camera_controller.handle_device_event(device_event)
-}
-
-pub fn update(&mut self, scene: &mut Scene) {
- self.camera_controller.update(&mut scene.graph);
-}
-}
-For now both methods are just proxies, but it will be changed pretty soon. Now we need to call the proxies, but from where?
-The most suitable place is on_tick
and on_device_event
of the GameState
trait implementation for our Game
structure:
-#![allow(unused)]
-fn main() {
-fn on_tick(&mut self, engine: &mut Engine, dt: f32, _control_flow: &mut ControlFlow) {
- let scene = &mut engine.scenes[self.scene];
-
- self.player.update(scene);
-}
-
-fn on_device_event(
- &mut self,
- _engine: &mut Engine,
- _device_id: DeviceId,
- event: DeviceEvent,
-) {
- self.player.handle_device_event(&event);
-}
-}
-Now you can run the game, and the camera should rotate when you're moving your mouse. Now it's the time to add an ability
-to walk for our character.
-Player locomotion
-Our player still can't move, in this section we'll fix it. Player's movement for third person camera differs from the
-movement of first person. For the third person camera we must move the player either where the camera looks or according
-to pressed keys on the keyboard. Let's start by adding input controller, it will hold info about needed movement:
-#![allow(unused)]
-fn main() {
-#[derive(Default)]
-struct InputController {
- walk_forward: bool,
- walk_backward: bool,
- walk_left: bool,
- walk_right: bool,
-}
-}
-Add new field to the Player
:
-#![allow(unused)]
-fn main() {
-input_controller: InputController,
-}
-And initialize it with Default::default
in the Player::new
:
-#![allow(unused)]
-fn main() {
-Self {
- ...,
- input_controller: Default::default(),
-}
-}
-Now we need to change the state of the input controller, to do that we'll use keyboard events. Add following method to
-the impl Player
:
-#![allow(unused)]
-fn main() {
-pub fn handle_key_event(&mut self, key: &KeyboardInput) {
- if let Some(key_code) = key.virtual_keycode {
- match key_code {
- VirtualKeyCode::W => {
- self.input_controller.walk_forward = key.state == ElementState::Pressed
- }
- VirtualKeyCode::S => {
- self.input_controller.walk_backward = key.state == ElementState::Pressed
- }
- VirtualKeyCode::A => {
- self.input_controller.walk_left = key.state == ElementState::Pressed
- }
- VirtualKeyCode::D => {
- self.input_controller.walk_right = key.state == ElementState::Pressed
- }
- _ => (),
- }
- }
-}
-}
-Now we need to call this method, we'll do it from on_window_event
in the GameState
trait implementation for our
-Game
:
-#![allow(unused)]
+As you can see, at the end of the tutorial we'll be able to walk and explore a small fantasy world. Let's start by
+creating a new game project, by running the following command:
+fyrox-template init --name=rpg --style=3d
+This command will create a new cargo workspace with a few projects inside, we're interested only in game
folder
+in this tutorial.
+rpg
+├───data
+├───editor
+│ └───src
+├───executor
+│ └───src
+├───executor-android
+│ └───src
+├───executor-wasm
+│ └───src
+└───game
+ └───src
+
+Learn more about fyrox-template
command here. Now we can run
+the game using cargo run --package executor
command, and you should see a white cube floating in blue space.
+
+️⚠️ There are two important commands:
+To run the game use: cargo run --package executor
command
+To run the editor use: cargo run --package editor
command.
+
+Assets
+For any kind of game you need a lot of various assets, in our case we need a 3D model for our character, a set of
+animations, a level, a set of textures for terrain, trees and bushes, barrels, etc. I prepared all assets as a single
+ZIP archive which can be downloaded here. Once you've downloaded it, unpack it in ./data
folder.
+Player Prefab
+Let's start from assembling our player prefab, that will also have a camera controller in it. At first, let's find out
+what the prefab is - prefab a scene, that contains some scene nodes, which can be instantiated to some other scene
+while preserving "connection" between all properties of the nodes. It means, that if you change something in a prefab,
+the changes will be reflected on every instance of it; on those properties that weren't modified. This is a condensed
+explanation, that may look a bit complicated - read this to learn more about prefabs.
+Now let's open the editor (cargo run --package editor
) and start making our prefab by creating a new scene. Save the
+scene to data/models/paladin/paladin.rgs
by going to File -> Save
. In the opened window, find the path and click
+Save
:
+
+Let's rename the root node of the scene to Paladin
and change its type to RigidBody
:
+
+We need this so out root node of the prefab could move in a scene to which it will be instantiated later. Make sure, that
+the X/Y/Z Rotation Locked
property is set to true
. Also Can Sleep
must be false, otherwise the rigid body will
+be excluded from the physical simulation when it does not move. As you can see, the editor shows a small warning icon
+near the root node - it warns us, that the rigid body does not have a collider and won't be able to participate in
+physical simulation. Let's fix it by adding a capsule collider to it and setting its Begin
, End
, Radius
properties
+accordingly:
+
+The next step is to add an actual character 3D model, this is very easy - find paladin.fbx
in the asset browser using
+its searching functionality and then drag'n'drop (click on the asset, and while holding the button, move the mouse in
+the scene, then release the button) it to the scene:
+
+Now we need to adjust its Local Scale
property, because the model is too big. Set it to 0.01
for all 3 axes, like on
+the screenshot above. Also, adjust position of the capsule collider, so it will fully enclose 3d model. Create a new
+Pivot
node called ModelPivot
and attach the paladin.fbx
node to it by drag'n'dropping the paladin.fbx
node onto
+ModelPivot
. The reason why we need to do this will be explained later in the tutorial.
+
+Camera
+It is the time to add camera controller scene nodes. We need to add three nodes in a chain:
+
+There are three nodes added:
+
+CameraPivot
(Pivot
node type) - it will serve a pivot point around which we will rotate the camera around Y axis.
+(horizontal camera rotation). It should be placed right at the center of the Paladin's head.
+CameraHinge
(Pivot
node type) - it will also be a pivot point, but for X axis (vertical camera rotation)
+Camera
(Camera
node type) - the camera itself, it should be placed on some distance from the Paladin's back.
+
+This nodes configuration will allow us to create some sort of "orbital" (also called arcball) camera as in many 3rd person
+games nowadays.
+Animations
+The next step is to add animations. Create a new Animation Player
node, click Open Animation Editor
near its
+Animations
property in the inspector to open the animation editor:
+
+Dock the animation editor below the scene preview - this way it will be much comfortable to use. Now we need to import
+two animations run.fbx
and idle.fbx
from the data/models/paladin
folder. To do this, click on the button with
+arrow at the tool strip in the animation editor:
+
+The editor asks us for the root node to which import the animation - it our case it is paladin.fbx
. Select it in the
+window and click OK
. Another window opens and asks us about the animation we want to import - find idle.fbx
in the
+tree and click Open
. You should see something like this as a result:
+
+Click on Preview
check box in the tool strip and the animation should play without artifacts. Now repeat the previous
+steps and import running.fbx
. Click Preview
again, and you'll see that the character is running, but not in-place as
+we'd like it to. Let's fix that by applying a Root Motion settings. Click on the RM
button and set it up like so:
+
+Now if you click on Preview
again, you'll see that the character is now moving in-place. But what we did by applying
+the root motion? We forced the engine to extract movement vector from the hips of the character that could be later used
+to move the capsule rigid body we've made early. This way the animation itself will drive the character and the actual
+movement will perfectly match the physical movement.
+At this point we have two separate animations that work independently. But what if we want to add a smooth transition
+between the two (or more)? This is where animation blending state machines comes into play. Create a new state machine
+and assign an animation player to it:
+
+The animation player will be used as a source of animations for our state machine. Now open the ABSM Editor
by clicking
+the Open ABSM Editor...
button in the inspector (right above the animation player property). Dock the editor and select
+a Base Layer
in the dropdown list in the toolbar. Next, we need to add two states - Idle
and Running
. This can be
+done by right-clicking on in the State Graph
and selecting Create State
:
+
+A state requires animation source to be usable, we can specify it by double-clicking on it (or right-click -> Enter State
)
+and creating a Play Animation
pose node in the State Viewer
(right-click -> Play Animation
):
+
+Select the Play Animation
node and in the Inspector
select the Idle
animation from the dropdown list near the
+Animation
property. Repeat the same steps for the Running
state, but in this case set Running
animation.
+Now when we two states ready, we need to create transitions between the two. Transition is a "rule", that defines whether
+a current active state can be switched to another one. While doing so, the engine will blend an animation coming from
+two states. To create a transition, right-click on a state and click Create Transition
. Do the same in the opposite
+direction. As a result, you should have something like this:
+
+A transition requires a boolean value to "understand" whether an actual transition is possible or not. Let's add one
+in the Parameters
section of the editor. Click on the small +
button and change the name to Running
and the type
+to the Rule
:
+
+Let's assign the rule to our transitions, select the Idle -> Running
transition and in the Inspector set its condition
+to the following:
+
+Running -> Idle
requires a reverse condition, the engine has a computational graph for this purpose (to compute
+boolean expressions). Set the condition of it to the following:
+
+As you can see we negate (using the Not
boolean operator) the value of the Running
parameter and use it compute the
+final value for the transition. At this point we can check how our animation blending works. Click on Preview
check box,
+and you should see that the character is currently being in the Idle
state, now click at the checkbox in the Running
+parameter, and you'll see that the Idle -> Running
transition started and ended shortly after. If you uncheck the
+parameter, the character will switch back to idle.
+This was the last step in this long procedure or making the prefab. As you can see, we haven't written a single line of
+code and saw the results immediately, without a need to compile anything.
+Player Script
+Finally, we can start writing some code. There won't be much of it, but it is still required. Fyrox allows you to add
+custom game logic to scene nodes using scripts. Scripts "skeleton" contains quite a lot of boilerplate code and to
+prevent this tedious work, fyrox-template
offers a sub-command called script
, which allows you to generate a script
+skeleton in a single command. Go to root folder of your project and execute the following command there:
+fyrox-template script --name=player
+
+The CLI tool will create the new module in game/src
folder called player.rs
and all you need to do is to register
+the module in two places. The first place is to add mod player;
line somewhere at the beginning of the game/src/lib.rs
.
+The second place is PluginConstructor::register
method - every script must be registered before use. Let's do so by adding
+the following code to the method:
+#![allow(unused)]
fn main() {
-fn on_window_event(&mut self, _engine: &mut Engine, event: WindowEvent) {
- match event {
- WindowEvent::KeyboardInput { input, .. } => {
- self.player.handle_key_event(&input);
- }
- _ => (),
- }
-}
+
extern crate fyrox;
+use fyrox::{
+ core::{reflect::prelude::*, uuid::Uuid, visitor::prelude::*, TypeUuidProvider},
+ impl_component_provider,
+ plugin::{Plugin, PluginConstructor, PluginContext, PluginRegistrationContext},
+ script::ScriptTrait,
+};
+
+#[derive(Clone, Visit, Reflect, Debug, Default)]
+struct Player;
+
+impl_component_provider!(Player);
+
+impl TypeUuidProvider for Player {
+ fn type_uuid() -> Uuid {
+ unimplemented!()
+ }
+}
+
+impl ScriptTrait for Player {
+ fn id(&self) -> Uuid {
+ unimplemented!()
+ }
+}
+
+pub struct GameConstructor;
+
+impl PluginConstructor for GameConstructor {
+ fn register(&self, context: PluginRegistrationContext) {
+ context
+ .serialization_context
+ .script_constructors
+ .add::<Player>("Player");
+ }
+
+ fn create_instance(
+ &self,
+ scene_path: Option<&str>,
+ context: PluginContext,
+ ) -> Box<dyn Plugin> {
+ unimplemented!()
+ }
+}
}
-Ok, now we have input controller functioning. Now we can start adding movement logic to the player. Let's start by adding
-a physical body to the player. We'll use a capsule rigid body with locked rotations for that. Add these lines somewhere
-in Player::new
:
-#![allow(unused)]
+Preparation steps are now finished, and we can start filling the script with some useful code. Navigate to the player.rs
+and you'll see quite a lot of code. Most of the methods, however, can be removed, and we're only interested in on_update
+and on_os_event
. But for now, let's add the following fields in the Player
struct:
+#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
- core::algebra::Vector3,
- core::pool::Handle,
- scene::{
- Scene,
- base::BaseBuilder,
- collider::{ColliderBuilder, ColliderShape},
- graph::physics::CoefficientCombineRule,
- rigidbody::RigidBodyBuilder,
- transform::TransformBuilder,
+ core::{
+ math::SmoothAngle, pool::Handle, reflect::prelude::*, variable::InheritableVariable,
+ visitor::prelude::*,
},
+ scene::node::Node,
};
-fn f(scene: &mut Scene) {
-let model = Handle::NONE;
-let collider;
-let body = RigidBodyBuilder::new(
- BaseBuilder::new()
- .with_local_transform(
- TransformBuilder::new()
- .with_local_position(Vector3::new(0.0, 2.0, 0.0))
- .build(),
- )
- .with_children(&[
- {
- // Attach the model to the pivot. This will force model to move together with the pivot.
- model
- },
- {
- // Create capsule collider with friction disabled. We need to disable friction because linear
- // velocity will be set manually, but the physics engine will reduce it using friction so it
- // won't let us to set linear velocity precisely.
- collider = ColliderBuilder::new(BaseBuilder::new())
- .with_shape(ColliderShape::capsule_y(0.55, 0.15))
- .with_friction_combine_rule(CoefficientCombineRule::Min)
- .with_friction(0.0)
- .build(&mut scene.graph);
- collider
- },
- ]),
-)
-.with_locked_rotations(true)
-.with_can_sleep(false)
-.build(&mut scene.graph);
-}
-}
-Now, once our character has physical body, we can move it. Add these lines to the end of Player::update
:
-#![allow(unused)]
-fn main() {
-let body = scene.graph[self.body].as_rigid_body_mut();
+
+#[derive(Visit, Reflect, Default, Debug, Clone)]
+pub struct Player {
+ #[visit(optional)]
+ camera_pivot: InheritableVariable<Handle<Node>>,
-let look_vector = body
- .look_vector()
- .try_normalize(f32::EPSILON)
- .unwrap_or(Vector3::z());
+ #[visit(optional)]
+ camera_hinge: InheritableVariable<Handle<Node>>,
-let side_vector = body
- .side_vector()
- .try_normalize(f32::EPSILON)
- .unwrap_or(Vector3::x());
+ #[visit(optional)]
+ state_machine: InheritableVariable<Handle<Node>>,
-let position = **body.local_transform().position();
+ #[visit(optional)]
+ model_pivot: InheritableVariable<Handle<Node>>,
-let mut velocity = Vector3::default();
+ #[visit(optional)]
+ model: InheritableVariable<Handle<Node>>,
-if self.input_controller.walk_right {
- velocity -= side_vector;
-}
-if self.input_controller.walk_left {
- velocity += side_vector;
-}
-if self.input_controller.walk_forward {
- velocity += look_vector;
-}
-if self.input_controller.walk_backward {
- velocity -= look_vector;
-}
+ #[visit(optional)]
+ model_yaw: InheritableVariable<SmoothAngle>,
-let speed = 1.35 * dt;
-let velocity = velocity
- .try_normalize(f32::EPSILON)
- .and_then(|v| Some(v.scale(speed)))
- .unwrap_or(Vector3::default());
+ #[reflect(hidden)]
+ #[visit(skip)]
+ walk_forward: bool,
-// Apply linear velocity.
-body.set_lin_vel(Vector3::new(
- velocity.x / dt,
- body.lin_vel().y,
- velocity.z / dt,
-));
+ #[reflect(hidden)]
+ #[visit(skip)]
+ walk_backward: bool,
-let is_moving = velocity.norm_squared() > 0.0;
-if is_moving {
- // Since we have free camera while not moving, we have to sync rotation of pivot
- // with rotation of camera so character will start moving in look direction.
- body.local_transform_mut()
- .set_rotation(UnitQuaternion::from_axis_angle(
- &Vector3::y_axis(),
- self.camera_controller.yaw,
- ));
+ #[reflect(hidden)]
+ #[visit(skip)]
+ walk_left: bool,
- // Apply additional rotation to model - it will turn in front of walking direction.
- let angle: f32 = if self.input_controller.walk_left {
- if self.input_controller.walk_forward {
- 45.0
- } else if self.input_controller.walk_backward {
- 135.0
- } else {
- 90.0
- }
- } else if self.input_controller.walk_right {
- if self.input_controller.walk_forward {
- -45.0
- } else if self.input_controller.walk_backward {
- -135.0
- } else {
- -90.0
- }
- } else if self.input_controller.walk_backward {
- 180.0
- } else {
- 0.0
- };
+ #[reflect(hidden)]
+ #[visit(skip)]
+ walk_right: bool,
+
+ #[reflect(hidden)]
+ #[visit(skip)]
+ yaw: f32,
- scene.graph[self.model].local_transform_mut().set_rotation(
- UnitQuaternion::from_axis_angle(&Vector3::y_axis(), angle.to_radians()),
- );
+ #[reflect(hidden)]
+ #[visit(skip)]
+ pitch: f32,
}
-
-// Sync camera controller position with player's position.
-scene.graph[self.camera_controller.pivot]
- .local_transform_mut()
- .set_position(position + velocity);
-}
-There is lots of code, let's thoroughly go through. At first, we're getting two vectors from the pivot: X and Z axes of
-the global transform of the pivot. We'll use them to move the character. Next we're using the state of the input
-controller to form a new velocity vector. Then we're normalizing velocity vector and multiply it with desired speed of
-movement. Normalization is needed to make the vector unit length to prevent speed variations in various directions. Next
-we're applying the velocity to the rigid body, also we're locking any angular movement to prevent player's capsule
-from tilting.
-If the player is not moving, we're not syncing its rotation with camera's rotation - this allows us to look at the
-character from any side while not moving. However, if the player is moving, we must sync its rotation with the rotation
-of the camera controller. If we'd do this straightforward (by just syncing rotations) it would look very unnatural,
-especially in case of side movements. To fix this we have this large chain of if..else
that selects appropriate
-additional rotation for the player's model. This rotation allows us, for example, look forward and move the character
-backwards.
-As the final step we're syncing position of the camera controller with the position of the pivot. Now if you run the game
-you'll be able to walk around using [W][S][A][D]
keys. However, it looks very ugly - the character's model is in T-pose,
-let's fix this.
-Animations
-At this point our character can move, and we can rotate the camera around it, but the character is still in T-pose and
-does not have any animation. In this section we'll animate it. To keep this tutorial at reasonable length, we'll
-add just an idle and walk animations and smooth transitions between them. Add following code at the end of player.rs
:
-#![allow(unused)]
+}
+There are quite a lot of them, but all of them will be in use. The first four fields will contain handles to scene nodes
+we've made earlier, the model_yaw
field contains a SmoothAngle
which is used for smooth angle interpolation we'll
+use later in tutorial. Please note that these fields marked with #[visit(optional)]
attribute, which tells the engine
+that these fields can be missing and should be replaced with default values in this case. This is very useful attribute
+if you're adding new fields to some existing script, it will prevent serialization error. The rest of the fields contains
+runtime information about movement state (move_forward
, move_backward
, walk_left
, walk_right
) and the
+camera orientation (yaw
and pitch
fields).
+A few notes why the first five fields are wrapped in the InheritableVariable
- it is to support property inheritance
+mechanism for these fields. The engine will save the values for these variables only if they're manually modified, on
+loading, however, it will replace non-modified values with the ones from parent prefab. If it sounds too complicated for
+you, then you should probably read this chapter.
+Let's start writing player controller's logic.
+Event Handling
+We'll start from keyboard and mouse event handling, add the following code to the impl ScriptTrait for Player
:
+#![allow(unused)]
fn main() {
extern crate fyrox;
use fyrox::{
- animation::{
- machine::{Machine, MachineLayer, Parameter, PoseNode, State, Transition},
- Animation,
- },
- core::pool::Handle,
- asset::manager::ResourceManager,
- resource::model::{Model, ModelResource, ModelResourceExtension},
- scene::{node::Node, animation::AnimationPlayer, Scene},
+ event::{DeviceEvent, ElementState, Event, WindowEvent},
+ keyboard::KeyCode,
+ script::ScriptContext,
};
-
-// Simple helper method to create a state supplied with PlayAnimation node.
-fn create_play_animation_state(
- animation_resource: ModelResource,
- name: &str,
- layer: &mut MachineLayer,
- scene: &mut Scene,
- model: Handle<Node>,
-) -> (Handle<Animation>, Handle<State>) {
- // Animations retargetting just makes an instance of animation and binds it to
- // given model using names of bones.
- let animation = *animation_resource
- .retarget_animations(model, &mut scene.graph)
- .get(0)
- .unwrap();
- // Create new PlayAnimation node and add it to machine.
- let node = layer.add_node(PoseNode::make_play_animation(animation));
- // Make a state using the node we've made.
- let state = layer.add_state(State::new(name, node));
- (animation, state)
-}
-
-pub struct AnimationMachineInput {
- // Whether a bot is walking or not.
- pub walk: bool,
-}
-
-pub struct AnimationMachine {
- machine: Machine,
- animation_player: Handle<Node>,
+
+struct Player {
+ walk_forward: bool,
+ walk_backward: bool,
+ walk_left: bool,
+ walk_right: bool,
+ yaw: f32,
+ pitch: f32,
+}
+
+impl Player {
+fn on_os_event(&mut self, event: &Event<()>, ctx: &mut ScriptContext) {
+ match event {
+ Event::WindowEvent { event, .. } => {
+ if let WindowEvent::KeyboardInput { event, .. } = event {
+ let pressed = event.state == ElementState::Pressed;
+ match event.physical_key {
+ KeyCode::KeyW => self.walk_forward = pressed,
+ KeyCode::KeyS => self.walk_backward = pressed,
+ KeyCode::KeyA => self.walk_left = pressed,
+ KeyCode::KeyD => self.walk_right = pressed,
+ _ => (),
+ }
+ }
+ }
+ Event::DeviceEvent { event, .. } => {
+ if let DeviceEvent::MouseMotion { delta } = event {
+ let mouse_sens = 0.2 * ctx.dt;
+ self.yaw -= (delta.0 as f32) * mouse_sens;
+ self.pitch = (self.pitch + (delta.1 as f32) * mouse_sens)
+ .clamp(-90.0f32.to_radians(), 90.0f32.to_radians());
+ }
+ }
+ _ => (),
+ }
}
+}
+}
+This code consists of two major sections: KeyboardInput
event handling and MouseMotion
event handling. Let's start
+from KeyboardInput
event. At the beginning of it we're checking if a key was pressed or not and saving it to the
+pressed
flag, then we check for W
, S
, A
, D
keys and set each movement flag accordingly.
+The MouseMotion
event handling is different: we're using mouse movement delta to calculate new yaw and pitch values
+for our camera. Pitch calculation also includes angle clamping in -90.0..90.0
degree range.
+Logic
+The next important step is to apply all the data we have to a bunch of scene nodes the player consists of. Let's fill
+the on_update
method with the following code:
+#![allow(unused)]
+fn main() {
+extern crate fyrox;
+use fyrox::{
+ animation::machine::Parameter,
+ core::{
+ algebra::{UnitQuaternion, Vector3},
+ math::SmoothAngle,
+ pool::Handle,
+ reflect::prelude::*,
+ uuid::{uuid, Uuid},
+ variable::InheritableVariable,
+ visitor::prelude::*,
+ TypeUuidProvider,
+ },
+ event::{DeviceEvent, ElementState, Event, WindowEvent},
+ impl_component_provider,
+ keyboard::KeyCode,
+ scene::{animation::absm::AnimationBlendingStateMachine, node::Node, rigidbody::RigidBody},
+ script::{ScriptContext, ScriptTrait},
+};
+
+#[derive(Visit, Reflect, Default, Debug, Clone)]
+pub struct Player {
+ camera_pivot: InheritableVariable<Handle<Node>>,
+ camera_hinge: InheritableVariable<Handle<Node>>,
+ state_machine: InheritableVariable<Handle<Node>>,
+ model_pivot: InheritableVariable<Handle<Node>>,
+ model: InheritableVariable<Handle<Node>>,
+ model_yaw: InheritableVariable<SmoothAngle>,
+ walk_forward: bool,
+ walk_backward: bool,
+ walk_left: bool,
+ walk_right: bool,
+ yaw: f32,
+ pitch: f32,
+}
+
+impl_component_provider!(Player);
+
+impl TypeUuidProvider for Player {
+ fn type_uuid() -> Uuid {
+ unimplemented!();
+ }
+}
+
+impl ScriptTrait for Player {
+ fn on_update(&mut self, ctx: &mut ScriptContext) {
+ // Step 1. Fetch the velocity vector from the animation blending state machine.
+ let transform = ctx.scene.graph[*self.model].global_transform();
+ let mut velocity = Vector3::default();
+ if let Some(state_machine) = ctx
+ .scene
+ .graph
+ .try_get(*self.state_machine)
+ .and_then(|node| node.query_component_ref::<AnimationBlendingStateMachine>())
+ {
+ if let Some(root_motion) = state_machine.machine().pose().root_motion() {
+ velocity = transform
+ .transform_vector(&root_motion.delta_position)
+ .scale(1.0 / ctx.dt);
+ }
+ }
+
+ // Step 2. Apply the velocity to the rigid body and lock rotations.
+ if let Some(body) = ctx.scene.graph.try_get_mut_of_type::<RigidBody>(ctx.handle) {
+ body.set_ang_vel(Default::default());
+ body.set_lin_vel(Vector3::new(velocity.x, body.lin_vel().y, velocity.z));
+ }
-impl AnimationMachine {
- // Names of parameters that will be used for transition rules in machine.
- const IDLE_TO_WALK: &'static str = "IdleToWalk";
- const WALK_TO_IDLE: &'static str = "WalkToIdle";
-
- pub async fn new(
- scene: &mut Scene,
- model: Handle<Node>,
- resource_manager: ResourceManager,
- ) -> Self {
- let animation_player = scene.graph.find(model, &mut |n| {
- n.query_component_ref::<AnimationPlayer>().is_some()
- }).unwrap().0;
-
- let mut machine = Machine::new();
-
- let root = machine.layers_mut().first_mut().unwrap();
+ // Step 3. Rotate the model pivot according to the movement direction.
+ let quat_yaw = UnitQuaternion::from_axis_angle(&Vector3::y_axis(), self.yaw);
- // Load animations in parallel.
- let (walk_animation_resource, idle_animation_resource) = fyrox::core::futures::join!(
- resource_manager.request::<Model, _>("data/models/paladin/walk.fbx"),
- resource_manager.request::<Model, _>("data/models/paladin/idle.fbx"),
- );
+ if velocity.norm_squared() > 0.0 {
+ // Since we have free camera while not moving, we have to sync rotation of pivot
+ // with rotation of camera so character will start moving in look direction.
+ if let Some(model_pivot) = ctx.scene.graph.try_get_mut(*self.model_pivot) {
+ model_pivot.local_transform_mut().set_rotation(quat_yaw);
+ }
- // Now create two states with different animations.
- let (_, idle_state) = create_play_animation_state(
- idle_animation_resource.unwrap(),
- "Idle",
- root,
- scene,
- model,
- );
+ // Apply additional rotation to model - it will turn in front of walking direction.
+ let angle: f32 = if self.walk_left {
+ if self.walk_forward {
+ 45.0
+ } else if self.walk_backward {
+ 135.0
+ } else {
+ 90.0
+ }
+ } else if self.walk_right {
+ if self.walk_forward {
+ -45.0
+ } else if self.walk_backward {
+ -135.0
+ } else {
+ -90.0
+ }
+ } else if self.walk_backward {
+ 180.0
+ } else {
+ 0.0
+ };
- let (walk_animation, walk_state) = create_play_animation_state(
- walk_animation_resource.unwrap(),
- "Walk",
- root,
- scene,
- model,
- );
+ self.model_yaw.set_target(angle.to_radians()).update(ctx.dt);
- // Next, define transitions between states.
- root.add_transition(Transition::new(
- // A name for debugging.
- "Idle->Walk",
- // Source state.
- idle_state,
- // Target state.
- walk_state,
- // Transition time in seconds.
- 0.4,
- // A name of transition rule parameter.
- Self::IDLE_TO_WALK,
- ));
- root.add_transition(Transition::new(
- "Walk->Idle",
- walk_state,
- idle_state,
- 0.4,
- Self::WALK_TO_IDLE,
- ));
+ if let Some(model) = ctx.scene.graph.try_get_mut(*self.model) {
+ model
+ .local_transform_mut()
+ .set_rotation(UnitQuaternion::from_axis_angle(
+ &Vector3::y_axis(),
+ self.model_yaw.angle,
+ ));
+ }
+ }
- // Define entry state.
- root.set_entry_state(idle_state);
+ if let Some(camera_pivot) = ctx.scene.graph.try_get_mut(*self.camera_pivot) {
+ camera_pivot.local_transform_mut().set_rotation(quat_yaw);
+ }
- Self {
- machine,
- animation_player,
+ // Rotate camera hinge - this will make camera move up and down while look at character
+ // (well not exactly on character - on characters head)
+ if let Some(camera_hinge) = ctx.scene.graph.try_get_mut(*self.camera_hinge) {
+ camera_hinge
+ .local_transform_mut()
+ .set_rotation(UnitQuaternion::from_axis_angle(
+ &Vector3::x_axis(),
+ self.pitch,
+ ));
}
- }
- pub fn update(&mut self, scene: &mut Scene, dt: f32, input: AnimationMachineInput) {
- let animation_player = scene.graph[self.animation_player]
- .query_component_mut::<AnimationPlayer>()
- .unwrap();
+ // Step 4. Feed the animation blending state machine with the current state of the player.
+ if let Some(state_machine) = ctx
+ .scene
+ .graph
+ .try_get_mut(*self.state_machine)
+ .and_then(|node| node.query_component_mut::<AnimationBlendingStateMachine>())
+ {
+ let moving =
+ self.walk_left || self.walk_right || self.walk_forward || self.walk_backward;
- self.machine
- // Set transition parameters.
- .set_parameter(Self::WALK_TO_IDLE, Parameter::Rule(!input.walk))
- .set_parameter(Self::IDLE_TO_WALK, Parameter::Rule(input.walk))
- // Update machine and evaluate final pose.
- .evaluate_pose(animation_player.animations_mut().get_value_mut_silent(), dt)
- // Apply the pose to the graph.
- .apply(&mut scene.graph);
+ state_machine
+ .machine_mut()
+ .get_value_mut_silent()
+ .set_parameter("Running", Parameter::Rule(moving));
+ }
}
-}
-}
-This is a simple animation blending machine, for more info check
-"Animations" section of "Writing a 3D shooter using Fyrox #3"
-tutorial, it has detailed explanation how animation blending machines work. In short, here we're loading two animations,
-and create two transitions between them and then applying final pose to the character.
-Now we need to create an instance of the AnimationMachine
, add a field to the Player
:
-#![allow(unused)]
-fn main() {
-...,
-animation_machine: AnimationMachine,
-}
-And initialize it in the Player::new
, before camera_controller
:
-#![allow(unused)]
-fn main() {
-...,
-animation_machine: AnimationMachine::new(scene, model, resource_manager.clone()).await,
-...
-}
-The last thing we need to do is to update animation machine each frame, we'll do this in Player::update
, at the end
-of the method:
-#![allow(unused)]
-fn main() {
-self.animation_machine
- .update(scene, dt, AnimationMachineInput { walk: is_moving });
+
+ fn id(&self) -> Uuid {
+ Self::type_uuid()
+ }
+}
}
-Now if you run the game, you should see the character idling if not moving, and it should play "walking" animation if
-moving. That's it for this tutorial, in the next tutorial we'll "teach" the character to use swords.
+That's a big chunk of code, but it mostly consists of a set of separate steps. Let's try to understand what each step does.
+Step 1 extracts the root motion vector from the animation blending state machine: at first, we're getting the current
+transformation matrix of the Paladin's model. Then we're trying to borrow the ABSM node from the scene. If it is
+successful, then we're trying to extract the root motion vector from the final pose of the ABSM. If we have one, then
+we need to transform it from the local space to the world space - we're doing this using matrix-vector multiplication.
+And as the last step, we're scaling the vector by delta time to get the final velocity in world coordinates that can
+be used to move the rigid body.
+Step 2 uses the root motion vector to move the rigid body. The body is the node to which the script is assigned to,
+so we're using ctx.handle
to borrow a "self" reference and setting the new linear and angular velocities.
+Step 3 is the largest (code-wise) step, yet very simple. All we do here is rotating the camera and the model pivot in
+according to pressed keys. The code should be self-explanatory.
+Step 4 feeds the animation blending state machine with the variables it needs to perform state transitions. Currently,
+we have only one variable - Running
and to set it, we're trying to borrow the ABSM using its handle, then we're
+using the state of four of our movement variable to combine them into one and use this flag to set the value in the
+ABSM.
+Binding
+Now, when we have finished coding part, we can open paladin.rgs
in the editor again and assign the script to it:
+
+Make sure to correctly set the script fields (as on the screenshot above), otherwise it won't work correctly.
+Level
+Use your imagination to create a game level (or just use the one from the assets pack for this tutorial). Level design
+is not covered by this tutorial. You can create a simple level using a Terrain, a few 3D models from the assets pack:
+
+The most important part, however, is to add a player instance to the level:
+
+Now all you need to do is to click on the green >
button and run the game.
Conclusion
-In this tutorial we've learned how create a walking character. Created simple character controller and walked on
-the scene. I hope you liked this tutorial, and if so, please consider supporting the project on
-Patreon or do a one-time donation via BuyMeACoffee.
-The source code for this tutorial is available on GitHub.
-
+In this tutorial we've learned how to set up physics for humanoid characters, how to create simple 3rd person camera
+controllers, how to import and blend multiple animation into one, how to use root motion to extract motion vector from
+animations. We also learned how to create prefabs and use them correctly. Finally, we have created a simple level and
+instantiated the character prefab on it.
2D Platformer Tutorial
Table of Contents
diff --git a/searchindex.js b/searchindex.js
index 30258045..0d2ebfb5 100644
--- a/searchindex.js
+++ b/searchindex.js
@@ -1 +1 @@
-Object.assign(window.search, {"doc_urls":["introduction.html#fyrox-game-engine-book","introduction.html#engine-version","introduction.html#how-to-read-the-book","introduction.html#api-documentation","introduction.html#required-knowledge","introduction.html#support-the-development","fyrox/introduction/index.html#introduction","fyrox/introduction/introduction.html#introduction-to-fyrox","fyrox/introduction/introduction.html#what-can-the-engine-do","fyrox/introduction/introduction.html#how-the-engine-work","fyrox/introduction/introduction.html#programming-languages","fyrox/introduction/introduction.html#engine-features","fyrox/introduction/introduction.html#general","fyrox/introduction/introduction.html#rendering","fyrox/introduction/introduction.html#scene","fyrox/introduction/introduction.html#sound","fyrox/introduction/introduction.html#serialization","fyrox/introduction/introduction.html#animation","fyrox/introduction/introduction.html#asset-management","fyrox/introduction/introduction.html#artificial-intelligence-ai","fyrox/introduction/introduction.html#user-interface-ui","fyrox/introduction/introduction.html#physics","fyrox/introduction/requirements.html#system-requirements","fyrox/introduction/requirements.html#supported-platforms","fyrox/introduction/basic_concepts.html#basic-concepts","fyrox/introduction/basic_concepts.html#classic-oop","fyrox/introduction/basic_concepts.html#scenes","fyrox/introduction/basic_concepts.html#nodes-and-scene-graph","fyrox/introduction/basic_concepts.html#plugins","fyrox/introduction/basic_concepts.html#scripts","fyrox/introduction/philosophy_and_goals.html#design-philosophy-and-goals","fyrox/introduction/philosophy_and_goals.html#safety","fyrox/introduction/philosophy_and_goals.html#performance","fyrox/introduction/philosophy_and_goals.html#ease-of-use","fyrox/introduction/philosophy_and_goals.html#battle-tested","fyrox/introduction/faq.html#frequently-asked-questions","fyrox/introduction/faq.html#which-graphics-api-does-the-engine-use","fyrox/introduction/faq.html#why-not-use-alternatives-now","fyrox/introduction/faq.html#is-the-engine-based-on-ecs","fyrox/introduction/faq.html#what-kinds-of-games-can-i-make-using-fyrox","fyrox/beginning/getting_started.html#getting-started","fyrox/beginning/scripting.html#editor-plugins-and-scripts","fyrox/beginning/scripting.html#quick-start","fyrox/beginning/scripting.html#platform-specific-dependencies","fyrox/beginning/scripting.html#linux","fyrox/beginning/scripting.html#project-generator","fyrox/beginning/scripting.html#using-the-latest-engine-version","fyrox/beginning/scripting.html#automatic","fyrox/beginning/scripting.html#manual","fyrox/beginning/scripting.html#adding-game-logic","fyrox/beginning/editor_overview.html#fyroxed-overview","fyrox/beginning/editor_overview.html#windows","fyrox/beginning/editor_overview.html#creating-or-loading-a-scene","fyrox/beginning/editor_overview.html#populating-a-scene","fyrox/beginning/editor_overview.html#saving-a-scene","fyrox/beginning/editor_overview.html#undoing-and-redoing","fyrox/beginning/editor_overview.html#controls","fyrox/beginning/editor_overview.html#editor-camera-movement","fyrox/beginning/editor_overview.html#others","fyrox/beginning/editor_overview.html#play-mode","fyrox/beginning/editor_overview.html#additional-utilities","fyrox/beginning/scene_and_scene_graph.html#scene-and-scene-graph","fyrox/beginning/scene_and_scene_graph.html#building-blocks-or-scene-nodes","fyrox/beginning/scene_and_scene_graph.html#local-and-global-coordinates","fyrox/beginning/assets.html#assets","fyrox/beginning/assets.html#asset-types","fyrox/beginning/assets.html#asset-management","fyrox/beginning/assets.html#asset-instantiation","fyrox/beginning/assets.html#loading-assets","fyrox/beginning/data_management.html#data-management","fyrox/beginning/data_management.html#motivation","fyrox/beginning/data_management.html#technical-details","fyrox/beginning/data_management.html#advantages","fyrox/beginning/data_management.html#disadvantages","fyrox/beginning/data_management.html#usage","fyrox/beginning/data_management.html#borrowing","fyrox/beginning/data_management.html#freeing","fyrox/beginning/data_management.html#take-and-reserve","fyrox/beginning/data_management.html#iterators","fyrox/beginning/data_management.html#direct-access","fyrox/beginning/data_management.html#validation","fyrox/beginning/data_management.html#type-erased-handles","fyrox/beginning/data_management.html#getting-a-handle-to-an-object-by-its-reference","fyrox/beginning/data_management.html#iterate-over-and-filter-out-objects","fyrox/scripting/scripting.html#scripting","fyrox/scripting/plugin.html#plugins","fyrox/scripting/plugin.html#structure","fyrox/scripting/plugin.html#control-flow","fyrox/scripting/plugin.html#plugin-context","fyrox/scripting/plugin.html#editor-and-plugins","fyrox/scripting/executor.html#executor","fyrox/scripting/executor.html#usage","fyrox/scripting/executor.html#typical-use-cases","fyrox/scripting/executor.html#setting-window-title","fyrox/scripting/script.html#scripts","fyrox/scripting/script.html#when-to-use-scripts-and-when-not","fyrox/scripting/script.html#script-structure","fyrox/scripting/script.html#script-template-generator","fyrox/scripting/script.html#script-registration","fyrox/scripting/script.html#script-attachment","fyrox/scripting/script.html#script-context","fyrox/scripting/script.html#execution-order","fyrox/scripting/script.html#message-passing","fyrox/scene/scene.html#scene","fyrox/scene/scene.html#how-to-create","fyrox/scene/scene.html#using-fyroxed","fyrox/scene/scene.html#create-scene-manually","fyrox/scene/scene.html#where-all-my-scenes-located","fyrox/scene/scene.html#building-scene-asynchronously","fyrox/scene/scene.html#managing-multiple-scenes","fyrox/scene/scene.html#ambient-lighting","fyrox/scene/graph.html#graph","fyrox/scene/graph.html#how-to-create","fyrox/scene/graph.html#adding-nodes","fyrox/scene/graph.html#using-node-builders","fyrox/scene/graph.html#adding-a-node-manually","fyrox/scene/graph.html#how-to-modify-the-hierarchy","fyrox/scene/graph.html#how-to-remove-nodes","fyrox/scene/transform.html#transformation","fyrox/scene/prefab.html#prefabs","fyrox/scene/prefab.html#how-to-create-and-use-a-prefab","fyrox/scene/prefab.html#property-inheritance","fyrox/scene/prefab.html#hierarchical-prefabs","fyrox/scene/inheritance.html#property-inheritance","fyrox/scene/inheritance.html#how-to-create-inheritable-properties","fyrox/scene/inheritance.html#which-fields-should-be-inheritable","fyrox/scene/inheritance.html#editor","fyrox/scene/base_node.html#base-node","fyrox/scene/base_node.html#how-to-create","fyrox/scene/base_node.html#building-a-complex-hierarchy","fyrox/scene/base_node.html#transform","fyrox/scene/base_node.html#visibility","fyrox/scene/base_node.html#enablingdisabling-scene-nodes","fyrox/scene/mesh_node.html#mesh-node","fyrox/scene/mesh_node.html#surfaces","fyrox/scene/mesh_node.html#how-to-create","fyrox/scene/mesh_node.html#using-a-3d-modelling-software","fyrox/scene/mesh_node.html#creating-a-procedural-mesh","fyrox/scene/mesh_node.html#animation","fyrox/scene/light_node.html#light-node","fyrox/scene/light_node.html#light-types","fyrox/scene/light_node.html#directional-light","fyrox/scene/light_node.html#point-light","fyrox/scene/light_node.html#spotlight","fyrox/scene/light_node.html#light-scattering","fyrox/scene/light_node.html#shadows","fyrox/scene/light_node.html#performance","fyrox/scene/sprite_node.html#sprite","fyrox/scene/sprite_node.html#how-to-create","fyrox/scene/sprite_node.html#general-rules","fyrox/scene/sprite_node.html#limitations","fyrox/scene/particle_system_node.html#particle-system","fyrox/scene/particle_system_node.html#basic-concepts","fyrox/scene/particle_system_node.html#particle","fyrox/scene/particle_system_node.html#emitters","fyrox/scene/particle_system_node.html#how-to-create","fyrox/scene/particle_system_node.html#using-the-editor","fyrox/scene/particle_system_node.html#using-the-code","fyrox/scene/particle_system_node.html#using-prefabs","fyrox/scene/particle_system_node.html#soft-particles","fyrox/scene/particle_system_node.html#restarting-emission","fyrox/scene/particle_system_node.html#enabling-or-disabling-particle-systems","fyrox/scene/particle_system_node.html#performance","fyrox/scene/particle_system_node.html#limitations","fyrox/scene/terrain_node.html#terrain","fyrox/scene/terrain_node.html#basic-concepts","fyrox/scene/terrain_node.html#heightmap","fyrox/scene/terrain_node.html#layers","fyrox/scene/terrain_node.html#creating-terrain-in-the-editor","fyrox/scene/terrain_node.html#creating-terrain-from-code","fyrox/scene/terrain_node.html#physics","fyrox/scene/terrain_node.html#performance","fyrox/scene/terrain_node.html#chunking","fyrox/scene/terrain_node.html#level-of-detail","fyrox/scene/terrain_node.html#limitations-and-known-issues","fyrox/scene/camera_node.html#camera-node","fyrox/scene/camera_node.html#how-to-create","fyrox/scene/camera_node.html#projection-modes","fyrox/scene/camera_node.html#perspective","fyrox/scene/camera_node.html#orthographic","fyrox/scene/camera_node.html#performance","fyrox/scene/camera_node.html#skybox","fyrox/scene/camera_node.html#color-grading-look-up-tables","fyrox/scene/camera_node.html#picking","fyrox/scene/camera_node.html#advanced-picking","fyrox/scene/camera_node.html#exposure-and-hdr","fyrox/scene/decal_node.html#decal-node","fyrox/scene/decal_node.html#how-to-create","fyrox/scene/decal_node.html#textures","fyrox/scene/decal_node.html#rendering","fyrox/scene/decal_node.html#bounds","fyrox/scene/decal_node.html#layers","fyrox/scene/decal_node.html#performance","fyrox/scene/rectangle.html#rectangle-node","fyrox/scene/rectangle.html#how-to-create","fyrox/scene/rectangle.html#specifying-image-portion-for-rendering","fyrox/scene/rectangle.html#performance","fyrox/scene/rectangle.html#limitations","fyrox/scene/custom_node.html#custom-scene-node","fyrox/scene/custom_node.html#limitations","fyrox/scene/custom_node.html#editor-support","fyrox/physics/physics.html#physics","fyrox/physics/physics.html#differences-between-3d-and-2d","fyrox/physics/rigid_body.html#rigid-body-node","fyrox/physics/rigid_body.html#how-to-create","fyrox/physics/rigid_body.html#colliders","fyrox/physics/rigid_body.html#force-and-torque","fyrox/physics/rigid_body.html#kinematic-rigid-bodies","fyrox/physics/rigid_body.html#continuous-collision-detection","fyrox/physics/rigid_body.html#dominance","fyrox/physics/rigid_body.html#2d-rigid-bodies","fyrox/physics/collider.html#collider-node","fyrox/physics/collider.html#shapes","fyrox/physics/collider.html#how-to-create","fyrox/physics/collider.html#collision-filtering","fyrox/physics/collider.html#using-colliders-for-hit-boxes","fyrox/physics/joint.html#joint","fyrox/physics/joint.html#bodies-binding","fyrox/physics/joint.html#how-to-create","fyrox/physics/joint.html#limits","fyrox/physics/joint.html#usage","fyrox/physics/ray.html#ray-casting","fyrox/physics/ray.html#avoiding-unnecessary-allocations","fyrox/physics/ragdoll.html#ragdoll","fyrox/physics/ragdoll.html#how-to-create","fyrox/physics/ragdoll.html#video-tutorials","fyrox/sound/index.html#sound-system","fyrox/sound/bus.html#audio-bus","fyrox/sound/bus.html#graph","fyrox/sound/bus.html#effects","fyrox/sound/bus.html#editor","fyrox/sound/sound.html#sound","fyrox/sound/sound.html#how-to-create","fyrox/sound/sound.html#from-editor","fyrox/sound/sound.html#from-code","fyrox/sound/sound.html#2d-and-3d","fyrox/sound/sound.html#audio-bus","fyrox/sound/hrtf.html#head-related-transfer-function","fyrox/sound/hrtf.html#hrtf-on-practice","fyrox/sound/hrtf.html#performance","fyrox/animation/animation.html#animation","fyrox/animation/animation.html#web-demo","fyrox/animation/animation.html#track-binding","fyrox/animation/animation.html#time-slice-and-looping","fyrox/animation/animation.html#speed","fyrox/animation/animation.html#enabling-or-disabling-animations","fyrox/animation/animation.html#signals","fyrox/animation/animation.html#creating-from-code","fyrox/animation/anim_editor.html#animation-editor","fyrox/animation/anim_editor.html#typical-workflow","fyrox/animation/anim_editor.html#toolbar","fyrox/animation/anim_editor.html#track-list","fyrox/animation/anim_editor.html#track-context-menu","fyrox/animation/anim_editor.html#curve-editor","fyrox/animation/anim_editor.html#time-ruler-context-menu","fyrox/animation/anim_editor.html#key-frame-context-menu","fyrox/animation/anim_editor.html#property-binding","fyrox/animation/anim_editor.html#animation-importing","fyrox/animation/anim_editor.html#preview-mode","fyrox/animation/anim_editor.html#root-motion","fyrox/animation/anim_editor.html#limitations","fyrox/animation/blending.html#animation-blending","fyrox/animation/blending.html#multiple-absm-per-model","fyrox/animation/absm_editor.html#animation-blending-state-machine-absm-editor","fyrox/animation/absm_editor.html#toolbar","fyrox/animation/absm_editor.html#parameters","fyrox/animation/absm_editor.html#state-graph","fyrox/animation/absm_editor.html#state-context-menu","fyrox/animation/absm_editor.html#transition-context-menu","fyrox/animation/absm_editor.html#state-properties","fyrox/animation/absm_editor.html#transition-properties","fyrox/animation/absm_editor.html#state-viewer","fyrox/animation/absm_editor.html#play-animation-properties","fyrox/animation/absm_editor.html#blend-animations-properties","fyrox/animation/absm_editor.html#blend-animations-by-index-properties","fyrox/animation/absm_editor.html#connection-context-menu","fyrox/animation/absm_editor.html#node-context-menu","fyrox/animation/absm_editor.html#layer-mask","fyrox/animation/absm_editor.html#preview-mode","fyrox/ai/ai.html#artificial-intelligence-wip","fyrox/ai/beh_tree.html#behaviour-trees-wip","fyrox/ai/pathfinding.html#path-finding","fyrox/ai/pathfinding.html#examples","fyrox/ai/pathfinding.html#what-to-use","fyrox/ai/pathfinding.html#performance","fyrox/ai/navmesh.html#navigational-meshes","fyrox/ai/navmesh.html#editor","fyrox/ai/navmesh.html#automatic-generation","fyrox/ai/navmesh.html#agents","fyrox/rendering/rendering.html#rendering-wip","fyrox/rendering/shaders.html#shaders","fyrox/rendering/shaders.html#shaders-language","fyrox/rendering/shaders.html#structure","fyrox/rendering/shaders.html#properties","fyrox/rendering/shaders.html#built-in-properties","fyrox/rendering/shaders.html#predefined-render-passes","fyrox/rendering/shaders.html#drawing-parameters","fyrox/rendering/shaders.html#vertex-shader","fyrox/rendering/shaders.html#pixel-shader","fyrox/rendering/materials.html#materials","fyrox/rendering/materials.html#performance","fyrox/rendering/materials.html#standard-material","fyrox/rendering/materials.html#transparency","fyrox/rendering/materials.html#material-import","fyrox/rendering/materials.html#blender","fyrox/rendering/materials.html#3ds-max","fyrox/rendering/lightmaps.html#light-maps-wip","fyrox/rendering/settings.html#settings","fyrox/rendering/settings.html#presets","fyrox/rendering/settings.html#how-to-apply","fyrox/rendering/render_pass.html#render-pass","fyrox/rendering/render_pass.html#creating-a-render-pass","fyrox/rendering/render_pass.html#registering-a-render-pass","fyrox/rendering/normal_maps.html#normal-maps","fyrox/rendering/normal_maps.html#format","fyrox/rendering/normal_maps.html#solving-issues","fyrox/resources/resources.html#asset-management","fyrox/resources/resources.html#general-info","fyrox/resources/resources.html#best-practices","fyrox/resources/resources.html#asset-browser","fyrox/resources/resources.html#api-docs","fyrox/resources/model.html#model-resources","fyrox/resources/model.html#supported-formats","fyrox/resources/model.html#instantiation","fyrox/resources/model.html#material-import","fyrox/resources/model.html#tips-for-blender","fyrox/resources/model.html#tips-for-3ds-max","fyrox/resources/texture.html#textures","fyrox/resources/texture.html#supported-formats","fyrox/resources/texture.html#compressed-textures","fyrox/resources/texture.html#import-options","fyrox/resources/texture.html#render-target","fyrox/resources/sound.html#sound-wip","fyrox/resources/curve.html#curve-wip","fyrox/resources/custom.html#custom-resources","fyrox/resources/custom.html#example","fyrox/resources/custom.html#editor-support","fyrox/ui/ui.html#user-interface","fyrox/ui/ui.html#web-demo","fyrox/ui/basic_concepts/basic_concepts.html#basic-concepts","fyrox/ui/basic_concepts/basic_concepts.html#stateful","fyrox/ui/basic_concepts/basic_concepts.html#node-based-architecture","fyrox/ui/basic_concepts/basic_concepts.html#composition","fyrox/ui/basic_concepts/basic_concepts.html#component-querying","fyrox/ui/basic_concepts/basic_concepts.html#message-passing","fyrox/ui/basic_concepts/basic_concepts.html#message-routing-strategies","fyrox/ui/basic_concepts/basic_concepts.html#layout","fyrox/ui/basic_concepts/basic_concepts.html#limitations","fyrox/ui/general_rules.html#general-rules","fyrox/ui/general_rules.html#fluent-syntax","fyrox/ui/general_rules.html#should-i-create-a-custom-widget-or-use-composition-of-other-widgets","fyrox/ui/rendering.html#rendering","fyrox/ui/rendering.html#offscreen-rendering","fyrox/ui/rendering.html#embedding-scene-into-ui","fyrox/ui/font.html#font","fyrox/ui/font.html#create-new-font","fyrox/ui/font.html#loading-font-from-file","fyrox/ui/font.html#creating-font-from-memory","fyrox/ui/font.html#default-font","fyrox/ui/font.html#how-to-change-font-size","fyrox/ui/font.html#character-set","fyrox/ui/style.html#styles","fyrox/ui/widgets.html#widgets","fyrox/ui/widgets.html#containers","fyrox/ui/widgets.html#visual","fyrox/ui/widgets.html#controls","fyrox/ui/button.html#button","fyrox/ui/button.html#simple-button-with-text","fyrox/ui/button.html#a-button-with-image","fyrox/ui/button.html#message-handling","fyrox/ui/button.html#using-a-button-to-exit-the-game","fyrox/ui/border.html#border","fyrox/ui/canvas.html#canvas","fyrox/ui/canvas.html#how-to-create","fyrox/ui/canvas.html#how-to-position-children-nodes","fyrox/ui/canvas.html#tips","fyrox/ui/checkbox/check_box.html#check-box","fyrox/ui/checkbox/check_box.html#how-it-looks","fyrox/ui/checkbox/check_box.html#how-to-create","fyrox/ui/checkbox/check_box.html#message-handling","fyrox/ui/checkbox/check_box.html#theme","fyrox/ui/curve_editor.html#curve-editor-wip","fyrox/ui/decorator.html#decorator","fyrox/ui/dock.html#docking-manager-wip","fyrox/ui/dropdown_list.html#dropdown-list-wip","fyrox/ui/expander.html#expander-wip","fyrox/ui/file_browser.html#file-browser-wip","fyrox/ui/grid.html#grid","fyrox/ui/image.html#image","fyrox/ui/image.html#usage","fyrox/ui/image.html#equal-size-to-source","fyrox/ui/image.html#vertical-flip","fyrox/ui/inspector.html#inspector-wip","fyrox/ui/list_view.html#list-view-wip","fyrox/ui/menu.html#menu-wip","fyrox/ui/message_box.html#message-box-wip","fyrox/ui/numeric.html#numericupdown-widget","fyrox/ui/numeric.html#how-to-create","fyrox/ui/numeric.html#limits","fyrox/ui/numeric.html#step","fyrox/ui/numeric.html#precision","fyrox/ui/popup.html#popup-wip","fyrox/ui/progress_bar.html#progress-bar-wip","fyrox/ui/range.html#range-wip","fyrox/ui/rect.html#rect-editor-wip","fyrox/ui/scroll_bar.html#scroll-bar","fyrox/ui/scroll_bar.html#example","fyrox/ui/scroll_bar.html#orientation","fyrox/ui/scroll_bar.html#show-values","fyrox/ui/scroll_bar.html#step","fyrox/ui/scroll_panel.html#scroll-panel-wip","fyrox/ui/scroll_viewer.html#scroll-viewer-wip","fyrox/ui/stack_panel.html#stack-panel","fyrox/ui/stack_panel.html#stack-panel-orientation","fyrox/ui/tab_control.html#tab-control","fyrox/ui/tab_control.html#tab-header-styling","fyrox/ui/text.html#text","fyrox/ui/text.html#how-to-create","fyrox/ui/text.html#text-alignment-and-word-wrapping","fyrox/ui/text.html#background","fyrox/ui/text.html#fonts-and-colors","fyrox/ui/text.html#font-size","fyrox/ui/text.html#shadows","fyrox/ui/text.html#messages","fyrox/ui/text_box.html#text-box","fyrox/ui/text_box.html#how-to-create","fyrox/ui/text_box.html#text-alignment-and-word-wrapping","fyrox/ui/text_box.html#fonts-and-colors","fyrox/ui/text_box.html#font-size","fyrox/ui/text_box.html#messages","fyrox/ui/text_box.html#shortcuts","fyrox/ui/text_box.html#multiline-text-box","fyrox/ui/text_box.html#read-only-mode","fyrox/ui/text_box.html#mask-character","fyrox/ui/text_box.html#text-commit-mode","fyrox/ui/text_box.html#filtering","fyrox/ui/text_box.html#style","fyrox/ui/tree.html#tree-wip","fyrox/ui/vector_image.html#vector-image-wip","fyrox/ui/window.html#window","fyrox/ui/window.html#initial-open-state","fyrox/ui/window.html#styling-the-buttons","fyrox/ui/window.html#modal-aka-forced-focus","fyrox/ui/wrap_panel.html#wrap-panel","fyrox/ui/wrap_panel.html#how-to-create","fyrox/ui/wrap_panel.html#orientation","fyrox/ui/wrap_panel.html#use-cases","fyrox/serialization/index.html#serialization","fyrox/serialization/index.html#usage","fyrox/serialization/index.html#proc-macro-derivevisit","fyrox/serialization/index.html#manual-implementation","fyrox/serialization/index.html#serialization-and-deserialization","fyrox/serialization/index.html#environment","fyrox/serialization/index.html#limitations","fyrox/serialization/save.html#saved-games-wip","fyrox/serialization/save.html#saved-game-structure","fyrox/editor/index.html#editor","fyrox/editor/property_editors.html#property-editors","fyrox/editor/property_editors.html#adding-property-editors","fyrox/editor/property_editors.html#structures","fyrox/editor/property_editors.html#enumerations","fyrox/editor/property_editors.html#inheritable-properties","fyrox/editor/property_editors.html#collections","fyrox/editor/property_editors.html#custom-property-editors","fyrox/editor/settings.html#settings","fyrox/editor/settings.html#selection","fyrox/editor/settings.html#graphics","fyrox/editor/settings.html#debugging","fyrox/editor/settings.html#move-mode-settings","fyrox/editor/settings.html#rotate-mode-settings","fyrox/editor/settings.html#model","fyrox/editor/settings.html#camera","fyrox/misc/misc.html#miscellaneous","fyrox/misc/log.html#logging","fyrox/misc/log.html#writing-to-the-log","fyrox/tutorials/tutorials.html#tutorials","fyrox/tutorials/fps/intro.html#first-person-shooter-tutorial","fyrox/tutorials/fps/intro.html#fyrox-and-fyroxed-version","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#fps-tutorial-part-1---character-controller","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#table-of-contents","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#introduction","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#creating-a-window","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#creating-your-first-scene","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#using-the-scene","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#character-controller","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#finishing-touch","fyrox/tutorials/fps/tutorial-1/tutorial-part-1.html#conclusion","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#fps-tutorial-part-2---weapons","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#table-of-contents","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#introduction","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#adding-weapons","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#game-architecture","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#recoil","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#impact-effects","fyrox/tutorials/fps/tutorial-2/tutorial-part-2.html#conclusion","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#fps-tutorial-part-1---bots-and-ai","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#table-of-contents","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#introduction","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#bots","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#animations","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#simple-ai","fyrox/tutorials/fps/tutorial-3/tutorial-part-3.html#conclusion","fyrox/tutorials/rpg/intro.html#role-playing-game-tutorial","fyrox/tutorials/rpg/intro.html#fyrox-and-fyroxed-version","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#rpg-tutorial-part-1---character-controller","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#table-of-contents","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#introduction","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#framework","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#assets","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#player-and-camera-controller","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#camera-movement","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#player-locomotion","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#animations","fyrox/tutorials/rpg/tutorial-1/tutorial-part-1.html#conclusion","fyrox/tutorials/platformer/part1.html#2d-platformer-tutorial","fyrox/tutorials/platformer/part1.html#table-of-contents","fyrox/tutorials/platformer/part1.html#introduction","fyrox/tutorials/platformer/part1.html#project","fyrox/tutorials/platformer/part1.html#using-the-editor","fyrox/tutorials/platformer/part1.html#scripts---player","fyrox/tutorials/platformer/part1.html#animation","fyrox/tutorials/platformer/part1.html#conclusion","fyrox/performance/index.html#performance","fyrox/performance/index.html#ecs","fyrox/performance/index.html#architecture","fyrox/obsolete/obsolete.html#obsolete-features","fyrox/obsolete/installation.html#installation-obsolete","fyrox/obsolete/installation.html#using-stable-version","fyrox/obsolete/installation.html#using-latest-unstable-version","fyrox/obsolete/installation.html#editor-installation","fyrox/obsolete/custom_game_loop.html#custom-game-loop"],"index":{"documentStore":{"docInfo":{"0":{"body":32,"breadcrumbs":5,"title":4},"1":{"body":22,"breadcrumbs":3,"title":2},"10":{"body":21,"breadcrumbs":5,"title":2},"100":{"body":186,"breadcrumbs":4,"title":2},"101":{"body":94,"breadcrumbs":4,"title":2},"102":{"body":285,"breadcrumbs":4,"title":2},"103":{"body":33,"breadcrumbs":2,"title":1},"104":{"body":23,"breadcrumbs":2,"title":1},"105":{"body":89,"breadcrumbs":3,"title":2},"106":{"body":37,"breadcrumbs":4,"title":3},"107":{"body":60,"breadcrumbs":3,"title":2},"108":{"body":59,"breadcrumbs":4,"title":3},"109":{"body":48,"breadcrumbs":4,"title":3},"11":{"body":7,"breadcrumbs":5,"title":2},"110":{"body":58,"breadcrumbs":3,"title":2},"111":{"body":19,"breadcrumbs":3,"title":1},"112":{"body":12,"breadcrumbs":3,"title":1},"113":{"body":11,"breadcrumbs":4,"title":2},"114":{"body":348,"breadcrumbs":5,"title":3},"115":{"body":41,"breadcrumbs":5,"title":3},"116":{"body":72,"breadcrumbs":4,"title":2},"117":{"body":65,"breadcrumbs":4,"title":2},"118":{"body":79,"breadcrumbs":3,"title":1},"119":{"body":178,"breadcrumbs":3,"title":1},"12":{"body":33,"breadcrumbs":4,"title":1},"120":{"body":24,"breadcrumbs":5,"title":3},"121":{"body":35,"breadcrumbs":4,"title":2},"122":{"body":29,"breadcrumbs":4,"title":2},"123":{"body":66,"breadcrumbs":5,"title":2},"124":{"body":93,"breadcrumbs":6,"title":3},"125":{"body":120,"breadcrumbs":5,"title":2},"126":{"body":45,"breadcrumbs":4,"title":1},"127":{"body":39,"breadcrumbs":5,"title":2},"128":{"body":28,"breadcrumbs":4,"title":1},"129":{"body":101,"breadcrumbs":6,"title":3},"13":{"body":83,"breadcrumbs":4,"title":1},"130":{"body":69,"breadcrumbs":4,"title":1},"131":{"body":82,"breadcrumbs":4,"title":1},"132":{"body":55,"breadcrumbs":6,"title":3},"133":{"body":22,"breadcrumbs":5,"title":2},"134":{"body":29,"breadcrumbs":4,"title":1},"135":{"body":29,"breadcrumbs":4,"title":1},"136":{"body":85,"breadcrumbs":7,"title":4},"137":{"body":116,"breadcrumbs":6,"title":3},"138":{"body":12,"breadcrumbs":4,"title":1},"139":{"body":9,"breadcrumbs":5,"title":2},"14":{"body":38,"breadcrumbs":4,"title":1},"140":{"body":9,"breadcrumbs":5,"title":2},"141":{"body":106,"breadcrumbs":5,"title":2},"142":{"body":36,"breadcrumbs":5,"title":2},"143":{"body":36,"breadcrumbs":4,"title":1},"144":{"body":98,"breadcrumbs":5,"title":2},"145":{"body":86,"breadcrumbs":4,"title":1},"146":{"body":39,"breadcrumbs":4,"title":1},"147":{"body":45,"breadcrumbs":4,"title":1},"148":{"body":59,"breadcrumbs":4,"title":1},"149":{"body":51,"breadcrumbs":5,"title":2},"15":{"body":29,"breadcrumbs":4,"title":1},"150":{"body":22,"breadcrumbs":4,"title":1},"151":{"body":37,"breadcrumbs":6,"title":2},"152":{"body":30,"breadcrumbs":6,"title":2},"153":{"body":87,"breadcrumbs":5,"title":1},"154":{"body":206,"breadcrumbs":5,"title":1},"155":{"body":11,"breadcrumbs":5,"title":1},"156":{"body":72,"breadcrumbs":6,"title":2},"157":{"body":117,"breadcrumbs":6,"title":2},"158":{"body":16,"breadcrumbs":6,"title":2},"159":{"body":46,"breadcrumbs":6,"title":2},"16":{"body":9,"breadcrumbs":4,"title":1},"160":{"body":14,"breadcrumbs":6,"title":2},"161":{"body":32,"breadcrumbs":8,"title":4},"162":{"body":26,"breadcrumbs":5,"title":1},"163":{"body":74,"breadcrumbs":5,"title":1},"164":{"body":31,"breadcrumbs":4,"title":1},"165":{"body":15,"breadcrumbs":5,"title":2},"166":{"body":38,"breadcrumbs":4,"title":1},"167":{"body":71,"breadcrumbs":4,"title":1},"168":{"body":95,"breadcrumbs":6,"title":3},"169":{"body":191,"breadcrumbs":6,"title":3},"17":{"body":16,"breadcrumbs":4,"title":1},"170":{"body":57,"breadcrumbs":4,"title":1},"171":{"body":46,"breadcrumbs":4,"title":1},"172":{"body":82,"breadcrumbs":4,"title":1},"173":{"body":92,"breadcrumbs":5,"title":2},"174":{"body":20,"breadcrumbs":6,"title":3},"175":{"body":22,"breadcrumbs":5,"title":2},"176":{"body":33,"breadcrumbs":4,"title":1},"177":{"body":10,"breadcrumbs":5,"title":2},"178":{"body":79,"breadcrumbs":4,"title":1},"179":{"body":85,"breadcrumbs":4,"title":1},"18":{"body":26,"breadcrumbs":5,"title":2},"180":{"body":67,"breadcrumbs":4,"title":1},"181":{"body":127,"breadcrumbs":4,"title":1},"182":{"body":99,"breadcrumbs":8,"title":5},"183":{"body":52,"breadcrumbs":4,"title":1},"184":{"body":166,"breadcrumbs":5,"title":2},"185":{"body":1,"breadcrumbs":5,"title":2},"186":{"body":35,"breadcrumbs":5,"title":2},"187":{"body":29,"breadcrumbs":4,"title":1},"188":{"body":9,"breadcrumbs":4,"title":1},"189":{"body":28,"breadcrumbs":4,"title":1},"19":{"body":4,"breadcrumbs":6,"title":3},"190":{"body":80,"breadcrumbs":4,"title":1},"191":{"body":30,"breadcrumbs":4,"title":1},"192":{"body":15,"breadcrumbs":4,"title":1},"193":{"body":32,"breadcrumbs":5,"title":2},"194":{"body":43,"breadcrumbs":4,"title":1},"195":{"body":91,"breadcrumbs":7,"title":4},"196":{"body":15,"breadcrumbs":4,"title":1},"197":{"body":157,"breadcrumbs":4,"title":1},"198":{"body":116,"breadcrumbs":6,"title":3},"199":{"body":42,"breadcrumbs":4,"title":1},"2":{"body":36,"breadcrumbs":3,"title":2},"20":{"body":113,"breadcrumbs":6,"title":3},"200":{"body":12,"breadcrumbs":5,"title":2},"201":{"body":89,"breadcrumbs":3,"title":1},"202":{"body":50,"breadcrumbs":6,"title":4},"203":{"body":27,"breadcrumbs":7,"title":3},"204":{"body":40,"breadcrumbs":5,"title":1},"205":{"body":111,"breadcrumbs":5,"title":1},"206":{"body":59,"breadcrumbs":6,"title":2},"207":{"body":50,"breadcrumbs":7,"title":3},"208":{"body":28,"breadcrumbs":7,"title":3},"209":{"body":48,"breadcrumbs":5,"title":1},"21":{"body":20,"breadcrumbs":4,"title":1},"210":{"body":13,"breadcrumbs":7,"title":3},"211":{"body":40,"breadcrumbs":5,"title":2},"212":{"body":127,"breadcrumbs":4,"title":1},"213":{"body":53,"breadcrumbs":4,"title":1},"214":{"body":62,"breadcrumbs":5,"title":2},"215":{"body":77,"breadcrumbs":7,"title":4},"216":{"body":106,"breadcrumbs":4,"title":1},"217":{"body":30,"breadcrumbs":5,"title":2},"218":{"body":87,"breadcrumbs":4,"title":1},"219":{"body":58,"breadcrumbs":4,"title":1},"22":{"body":62,"breadcrumbs":7,"title":2},"220":{"body":43,"breadcrumbs":4,"title":1},"221":{"body":185,"breadcrumbs":6,"title":2},"222":{"body":94,"breadcrumbs":7,"title":3},"223":{"body":35,"breadcrumbs":4,"title":1},"224":{"body":208,"breadcrumbs":4,"title":1},"225":{"body":10,"breadcrumbs":5,"title":2},"226":{"body":28,"breadcrumbs":4,"title":2},"227":{"body":43,"breadcrumbs":6,"title":2},"228":{"body":86,"breadcrumbs":5,"title":1},"229":{"body":98,"breadcrumbs":5,"title":1},"23":{"body":33,"breadcrumbs":7,"title":2},"230":{"body":58,"breadcrumbs":5,"title":1},"231":{"body":8,"breadcrumbs":5,"title":1},"232":{"body":8,"breadcrumbs":5,"title":1},"233":{"body":245,"breadcrumbs":5,"title":1},"234":{"body":178,"breadcrumbs":5,"title":1},"235":{"body":61,"breadcrumbs":6,"title":2},"236":{"body":30,"breadcrumbs":6,"title":2},"237":{"body":137,"breadcrumbs":7,"title":4},"238":{"body":54,"breadcrumbs":5,"title":2},"239":{"body":30,"breadcrumbs":4,"title":1},"24":{"body":14,"breadcrumbs":5,"title":2},"240":{"body":200,"breadcrumbs":3,"title":1},"241":{"body":15,"breadcrumbs":4,"title":2},"242":{"body":33,"breadcrumbs":4,"title":2},"243":{"body":28,"breadcrumbs":5,"title":3},"244":{"body":27,"breadcrumbs":3,"title":1},"245":{"body":13,"breadcrumbs":5,"title":3},"246":{"body":67,"breadcrumbs":3,"title":1},"247":{"body":164,"breadcrumbs":4,"title":2},"248":{"body":78,"breadcrumbs":6,"title":2},"249":{"body":41,"breadcrumbs":6,"title":2},"25":{"body":15,"breadcrumbs":5,"title":2},"250":{"body":176,"breadcrumbs":5,"title":1},"251":{"body":83,"breadcrumbs":6,"title":2},"252":{"body":15,"breadcrumbs":7,"title":3},"253":{"body":80,"breadcrumbs":6,"title":2},"254":{"body":21,"breadcrumbs":8,"title":4},"255":{"body":76,"breadcrumbs":8,"title":4},"256":{"body":38,"breadcrumbs":6,"title":2},"257":{"body":83,"breadcrumbs":6,"title":2},"258":{"body":29,"breadcrumbs":6,"title":2},"259":{"body":59,"breadcrumbs":6,"title":2},"26":{"body":38,"breadcrumbs":4,"title":1},"260":{"body":25,"breadcrumbs":5,"title":1},"261":{"body":269,"breadcrumbs":6,"title":2},"262":{"body":25,"breadcrumbs":8,"title":4},"263":{"body":161,"breadcrumbs":10,"title":6},"264":{"body":94,"breadcrumbs":5,"title":1},"265":{"body":79,"breadcrumbs":5,"title":1},"266":{"body":40,"breadcrumbs":6,"title":2},"267":{"body":23,"breadcrumbs":7,"title":3},"268":{"body":8,"breadcrumbs":7,"title":3},"269":{"body":22,"breadcrumbs":6,"title":2},"27":{"body":138,"breadcrumbs":6,"title":3},"270":{"body":59,"breadcrumbs":6,"title":2},"271":{"body":100,"breadcrumbs":6,"title":2},"272":{"body":18,"breadcrumbs":7,"title":3},"273":{"body":38,"breadcrumbs":7,"title":3},"274":{"body":48,"breadcrumbs":8,"title":4},"275":{"body":17,"breadcrumbs":7,"title":3},"276":{"body":23,"breadcrumbs":7,"title":3},"277":{"body":41,"breadcrumbs":6,"title":2},"278":{"body":51,"breadcrumbs":6,"title":2},"279":{"body":0,"breadcrumbs":6,"title":3},"28":{"body":15,"breadcrumbs":4,"title":1},"280":{"body":0,"breadcrumbs":9,"title":3},"281":{"body":21,"breadcrumbs":7,"title":2},"282":{"body":105,"breadcrumbs":6,"title":1},"283":{"body":37,"breadcrumbs":6,"title":1},"284":{"body":17,"breadcrumbs":6,"title":1},"285":{"body":48,"breadcrumbs":7,"title":2},"286":{"body":54,"breadcrumbs":6,"title":1},"287":{"body":10,"breadcrumbs":7,"title":2},"288":{"body":154,"breadcrumbs":6,"title":1},"289":{"body":0,"breadcrumbs":3,"title":2},"29":{"body":15,"breadcrumbs":4,"title":1},"290":{"body":50,"breadcrumbs":3,"title":1},"291":{"body":63,"breadcrumbs":4,"title":2},"292":{"body":184,"breadcrumbs":3,"title":1},"293":{"body":37,"breadcrumbs":3,"title":1},"294":{"body":7,"breadcrumbs":4,"title":2},"295":{"body":83,"breadcrumbs":5,"title":3},"296":{"body":44,"breadcrumbs":4,"title":2},"297":{"body":38,"breadcrumbs":4,"title":2},"298":{"body":40,"breadcrumbs":4,"title":2},"299":{"body":102,"breadcrumbs":3,"title":1},"3":{"body":11,"breadcrumbs":3,"title":2},"30":{"body":97,"breadcrumbs":7,"title":3},"300":{"body":37,"breadcrumbs":3,"title":1},"301":{"body":263,"breadcrumbs":4,"title":2},"302":{"body":61,"breadcrumbs":3,"title":1},"303":{"body":50,"breadcrumbs":4,"title":2},"304":{"body":16,"breadcrumbs":3,"title":1},"305":{"body":10,"breadcrumbs":4,"title":2},"306":{"body":0,"breadcrumbs":7,"title":3},"307":{"body":354,"breadcrumbs":3,"title":1},"308":{"body":18,"breadcrumbs":3,"title":1},"309":{"body":85,"breadcrumbs":3,"title":1},"31":{"body":62,"breadcrumbs":5,"title":1},"310":{"body":33,"breadcrumbs":5,"title":2},"311":{"body":170,"breadcrumbs":6,"title":3},"312":{"body":57,"breadcrumbs":6,"title":3},"313":{"body":13,"breadcrumbs":5,"title":2},"314":{"body":92,"breadcrumbs":4,"title":1},"315":{"body":91,"breadcrumbs":5,"title":2},"316":{"body":13,"breadcrumbs":4,"title":2},"317":{"body":13,"breadcrumbs":4,"title":2},"318":{"body":141,"breadcrumbs":4,"title":2},"319":{"body":92,"breadcrumbs":4,"title":2},"32":{"body":31,"breadcrumbs":5,"title":1},"320":{"body":5,"breadcrumbs":4,"title":2},"321":{"body":0,"breadcrumbs":5,"title":2},"322":{"body":25,"breadcrumbs":5,"title":2},"323":{"body":52,"breadcrumbs":4,"title":1},"324":{"body":126,"breadcrumbs":5,"title":2},"325":{"body":25,"breadcrumbs":5,"title":2},"326":{"body":24,"breadcrumbs":6,"title":3},"327":{"body":25,"breadcrumbs":4,"title":1},"328":{"body":20,"breadcrumbs":5,"title":2},"329":{"body":114,"breadcrumbs":5,"title":2},"33":{"body":34,"breadcrumbs":6,"title":2},"330":{"body":77,"breadcrumbs":5,"title":2},"331":{"body":27,"breadcrumbs":5,"title":2},"332":{"body":0,"breadcrumbs":7,"title":2},"333":{"body":0,"breadcrumbs":6,"title":2},"334":{"body":114,"breadcrumbs":6,"title":2},"335":{"body":290,"breadcrumbs":5,"title":1},"336":{"body":65,"breadcrumbs":6,"title":2},"337":{"body":32,"breadcrumbs":4,"title":2},"338":{"body":15,"breadcrumbs":4,"title":2},"339":{"body":9,"breadcrumbs":6,"title":2},"34":{"body":18,"breadcrumbs":6,"title":2},"340":{"body":75,"breadcrumbs":5,"title":1},"341":{"body":80,"breadcrumbs":7,"title":3},"342":{"body":34,"breadcrumbs":5,"title":1},"343":{"body":89,"breadcrumbs":6,"title":2},"344":{"body":137,"breadcrumbs":6,"title":2},"345":{"body":63,"breadcrumbs":7,"title":3},"346":{"body":54,"breadcrumbs":5,"title":1},"347":{"body":58,"breadcrumbs":5,"title":1},"348":{"body":9,"breadcrumbs":6,"title":2},"349":{"body":214,"breadcrumbs":6,"title":2},"35":{"body":6,"breadcrumbs":7,"title":3},"350":{"body":50,"breadcrumbs":10,"title":6},"351":{"body":37,"breadcrumbs":4,"title":1},"352":{"body":333,"breadcrumbs":5,"title":2},"353":{"body":138,"breadcrumbs":6,"title":3},"354":{"body":17,"breadcrumbs":4,"title":1},"355":{"body":11,"breadcrumbs":6,"title":3},"356":{"body":50,"breadcrumbs":6,"title":3},"357":{"body":75,"breadcrumbs":6,"title":3},"358":{"body":54,"breadcrumbs":5,"title":2},"359":{"body":12,"breadcrumbs":6,"title":3},"36":{"body":42,"breadcrumbs":8,"title":4},"360":{"body":52,"breadcrumbs":5,"title":2},"361":{"body":113,"breadcrumbs":4,"title":1},"362":{"body":22,"breadcrumbs":4,"title":1},"363":{"body":210,"breadcrumbs":4,"title":1},"364":{"body":94,"breadcrumbs":4,"title":1},"365":{"body":176,"breadcrumbs":4,"title":1},"366":{"body":1,"breadcrumbs":5,"title":1},"367":{"body":69,"breadcrumbs":7,"title":3},"368":{"body":44,"breadcrumbs":6,"title":2},"369":{"body":50,"breadcrumbs":6,"title":2},"37":{"body":24,"breadcrumbs":7,"title":3},"370":{"body":66,"breadcrumbs":8,"title":4},"371":{"body":159,"breadcrumbs":5,"title":1},"372":{"body":38,"breadcrumbs":5,"title":1},"373":{"body":28,"breadcrumbs":5,"title":1},"374":{"body":65,"breadcrumbs":7,"title":3},"375":{"body":24,"breadcrumbs":5,"title":1},"376":{"body":24,"breadcrumbs":7,"title":2},"377":{"body":8,"breadcrumbs":6,"title":1},"378":{"body":103,"breadcrumbs":6,"title":1},"379":{"body":63,"breadcrumbs":7,"title":2},"38":{"body":29,"breadcrumbs":7,"title":3},"380":{"body":33,"breadcrumbs":6,"title":1},"381":{"body":2,"breadcrumbs":9,"title":3},"382":{"body":55,"breadcrumbs":6,"title":1},"383":{"body":18,"breadcrumbs":9,"title":3},"384":{"body":23,"breadcrumbs":9,"title":3},"385":{"body":9,"breadcrumbs":7,"title":2},"386":{"body":13,"breadcrumbs":9,"title":3},"387":{"body":212,"breadcrumbs":5,"title":1},"388":{"body":22,"breadcrumbs":5,"title":1},"389":{"body":75,"breadcrumbs":5,"title":1},"39":{"body":19,"breadcrumbs":9,"title":5},"390":{"body":139,"breadcrumbs":7,"title":3},"391":{"body":73,"breadcrumbs":6,"title":2},"392":{"body":11,"breadcrumbs":7,"title":2},"393":{"body":2,"breadcrumbs":9,"title":3},"394":{"body":0,"breadcrumbs":7,"title":2},"395":{"body":2,"breadcrumbs":9,"title":3},"396":{"body":16,"breadcrumbs":7,"title":2},"397":{"body":50,"breadcrumbs":6,"title":1},"398":{"body":42,"breadcrumbs":6,"title":1},"399":{"body":44,"breadcrumbs":6,"title":1},"4":{"body":26,"breadcrumbs":3,"title":2},"40":{"body":58,"breadcrumbs":4,"title":2},"400":{"body":49,"breadcrumbs":6,"title":1},"401":{"body":0,"breadcrumbs":7,"title":2},"402":{"body":2,"breadcrumbs":9,"title":3},"403":{"body":2,"breadcrumbs":7,"title":2},"404":{"body":1,"breadcrumbs":8,"title":3},"405":{"body":30,"breadcrumbs":7,"title":2},"406":{"body":81,"breadcrumbs":6,"title":1},"407":{"body":18,"breadcrumbs":6,"title":1},"408":{"body":23,"breadcrumbs":7,"title":2},"409":{"body":15,"breadcrumbs":6,"title":1},"41":{"body":41,"breadcrumbs":8,"title":3},"410":{"body":0,"breadcrumbs":9,"title":3},"411":{"body":2,"breadcrumbs":9,"title":3},"412":{"body":104,"breadcrumbs":7,"title":2},"413":{"body":43,"breadcrumbs":8,"title":3},"414":{"body":156,"breadcrumbs":7,"title":2},"415":{"body":77,"breadcrumbs":8,"title":3},"416":{"body":13,"breadcrumbs":5,"title":1},"417":{"body":25,"breadcrumbs":5,"title":1},"418":{"body":96,"breadcrumbs":8,"title":4},"419":{"body":66,"breadcrumbs":5,"title":1},"42":{"body":33,"breadcrumbs":7,"title":2},"420":{"body":106,"breadcrumbs":6,"title":2},"421":{"body":20,"breadcrumbs":6,"title":2},"422":{"body":72,"breadcrumbs":5,"title":1},"423":{"body":117,"breadcrumbs":5,"title":1},"424":{"body":16,"breadcrumbs":7,"title":2},"425":{"body":25,"breadcrumbs":6,"title":1},"426":{"body":96,"breadcrumbs":9,"title":4},"427":{"body":105,"breadcrumbs":7,"title":2},"428":{"body":20,"breadcrumbs":7,"title":2},"429":{"body":99,"breadcrumbs":6,"title":1},"43":{"body":31,"breadcrumbs":8,"title":3},"430":{"body":68,"breadcrumbs":6,"title":1},"431":{"body":15,"breadcrumbs":8,"title":3},"432":{"body":11,"breadcrumbs":7,"title":2},"433":{"body":19,"breadcrumbs":7,"title":2},"434":{"body":69,"breadcrumbs":8,"title":3},"435":{"body":46,"breadcrumbs":6,"title":1},"436":{"body":13,"breadcrumbs":6,"title":1},"437":{"body":1,"breadcrumbs":7,"title":2},"438":{"body":0,"breadcrumbs":9,"title":3},"439":{"body":201,"breadcrumbs":5,"title":1},"44":{"body":35,"breadcrumbs":6,"title":1},"440":{"body":16,"breadcrumbs":7,"title":3},"441":{"body":14,"breadcrumbs":6,"title":2},"442":{"body":22,"breadcrumbs":8,"title":4},"443":{"body":21,"breadcrumbs":7,"title":2},"444":{"body":27,"breadcrumbs":6,"title":1},"445":{"body":14,"breadcrumbs":6,"title":1},"446":{"body":12,"breadcrumbs":7,"title":2},"447":{"body":62,"breadcrumbs":2,"title":1},"448":{"body":15,"breadcrumbs":2,"title":1},"449":{"body":131,"breadcrumbs":4,"title":3},"45":{"body":191,"breadcrumbs":7,"title":2},"450":{"body":106,"breadcrumbs":3,"title":2},"451":{"body":111,"breadcrumbs":3,"title":2},"452":{"body":62,"breadcrumbs":2,"title":1},"453":{"body":12,"breadcrumbs":2,"title":1},"454":{"body":24,"breadcrumbs":7,"title":3},"455":{"body":101,"breadcrumbs":7,"title":3},"456":{"body":56,"breadcrumbs":2,"title":1},"457":{"body":53,"breadcrumbs":5,"title":2},"458":{"body":22,"breadcrumbs":6,"title":3},"459":{"body":53,"breadcrumbs":4,"title":1},"46":{"body":25,"breadcrumbs":9,"title":4},"460":{"body":99,"breadcrumbs":4,"title":1},"461":{"body":91,"breadcrumbs":5,"title":2},"462":{"body":86,"breadcrumbs":4,"title":1},"463":{"body":9,"breadcrumbs":6,"title":3},"464":{"body":9,"breadcrumbs":3,"title":1},"465":{"body":38,"breadcrumbs":3,"title":1},"466":{"body":48,"breadcrumbs":3,"title":1},"467":{"body":57,"breadcrumbs":3,"title":1},"468":{"body":37,"breadcrumbs":5,"title":3},"469":{"body":37,"breadcrumbs":5,"title":3},"47":{"body":60,"breadcrumbs":6,"title":1},"470":{"body":29,"breadcrumbs":3,"title":1},"471":{"body":37,"breadcrumbs":3,"title":1},"472":{"body":8,"breadcrumbs":2,"title":1},"473":{"body":53,"breadcrumbs":3,"title":1},"474":{"body":38,"breadcrumbs":4,"title":2},"475":{"body":25,"breadcrumbs":2,"title":1},"476":{"body":27,"breadcrumbs":7,"title":4},"477":{"body":30,"breadcrumbs":6,"title":3},"478":{"body":13,"breadcrumbs":11,"title":6},"479":{"body":13,"breadcrumbs":7,"title":2},"48":{"body":154,"breadcrumbs":6,"title":1},"480":{"body":44,"breadcrumbs":6,"title":1},"481":{"body":787,"breadcrumbs":7,"title":2},"482":{"body":359,"breadcrumbs":8,"title":3},"483":{"body":260,"breadcrumbs":7,"title":2},"484":{"body":794,"breadcrumbs":7,"title":2},"485":{"body":171,"breadcrumbs":7,"title":2},"486":{"body":37,"breadcrumbs":6,"title":1},"487":{"body":13,"breadcrumbs":9,"title":5},"488":{"body":9,"breadcrumbs":6,"title":2},"489":{"body":30,"breadcrumbs":5,"title":1},"49":{"body":22,"breadcrumbs":8,"title":3},"490":{"body":386,"breadcrumbs":6,"title":2},"491":{"body":1137,"breadcrumbs":6,"title":2},"492":{"body":225,"breadcrumbs":5,"title":1},"493":{"body":311,"breadcrumbs":6,"title":2},"494":{"body":38,"breadcrumbs":5,"title":1},"495":{"body":13,"breadcrumbs":11,"title":6},"496":{"body":6,"breadcrumbs":7,"title":2},"497":{"body":27,"breadcrumbs":6,"title":1},"498":{"body":384,"breadcrumbs":6,"title":1},"499":{"body":1130,"breadcrumbs":6,"title":1},"5":{"body":10,"breadcrumbs":3,"title":2},"50":{"body":38,"breadcrumbs":6,"title":2},"500":{"body":370,"breadcrumbs":7,"title":2},"501":{"body":30,"breadcrumbs":6,"title":1},"502":{"body":27,"breadcrumbs":7,"title":4},"503":{"body":30,"breadcrumbs":6,"title":3},"504":{"body":3,"breadcrumbs":11,"title":6},"505":{"body":12,"breadcrumbs":7,"title":2},"506":{"body":98,"breadcrumbs":6,"title":1},"507":{"body":150,"breadcrumbs":6,"title":1},"508":{"body":34,"breadcrumbs":6,"title":1},"509":{"body":789,"breadcrumbs":8,"title":3},"51":{"body":143,"breadcrumbs":5,"title":1},"510":{"body":267,"breadcrumbs":7,"title":2},"511":{"body":485,"breadcrumbs":7,"title":2},"512":{"body":368,"breadcrumbs":6,"title":1},"513":{"body":33,"breadcrumbs":6,"title":1},"514":{"body":0,"breadcrumbs":7,"title":3},"515":{"body":13,"breadcrumbs":6,"title":2},"516":{"body":37,"breadcrumbs":5,"title":1},"517":{"body":130,"breadcrumbs":5,"title":1},"518":{"body":494,"breadcrumbs":6,"title":2},"519":{"body":755,"breadcrumbs":6,"title":2},"52":{"body":48,"breadcrumbs":7,"title":3},"520":{"body":183,"breadcrumbs":5,"title":1},"521":{"body":22,"breadcrumbs":5,"title":1},"522":{"body":19,"breadcrumbs":2,"title":1},"523":{"body":146,"breadcrumbs":2,"title":1},"524":{"body":277,"breadcrumbs":2,"title":1},"525":{"body":7,"breadcrumbs":3,"title":2},"526":{"body":40,"breadcrumbs":5,"title":2},"527":{"body":17,"breadcrumbs":6,"title":3},"528":{"body":47,"breadcrumbs":7,"title":4},"529":{"body":32,"breadcrumbs":5,"title":2},"53":{"body":91,"breadcrumbs":6,"title":2},"530":{"body":301,"breadcrumbs":8,"title":3},"54":{"body":24,"breadcrumbs":6,"title":2},"55":{"body":21,"breadcrumbs":6,"title":2},"56":{"body":12,"breadcrumbs":5,"title":1},"57":{"body":24,"breadcrumbs":7,"title":3},"58":{"body":45,"breadcrumbs":5,"title":1},"59":{"body":68,"breadcrumbs":6,"title":2},"6":{"body":27,"breadcrumbs":2,"title":1},"60":{"body":44,"breadcrumbs":6,"title":2},"61":{"body":145,"breadcrumbs":8,"title":3},"62":{"body":357,"breadcrumbs":9,"title":4},"63":{"body":95,"breadcrumbs":8,"title":3},"64":{"body":19,"breadcrumbs":4,"title":1},"65":{"body":101,"breadcrumbs":5,"title":2},"66":{"body":50,"breadcrumbs":5,"title":2},"67":{"body":94,"breadcrumbs":5,"title":2},"68":{"body":29,"breadcrumbs":5,"title":2},"69":{"body":23,"breadcrumbs":6,"title":2},"7":{"body":39,"breadcrumbs":5,"title":2},"70":{"body":183,"breadcrumbs":5,"title":1},"71":{"body":80,"breadcrumbs":6,"title":2},"72":{"body":65,"breadcrumbs":5,"title":1},"73":{"body":54,"breadcrumbs":5,"title":1},"74":{"body":83,"breadcrumbs":5,"title":1},"75":{"body":47,"breadcrumbs":5,"title":1},"76":{"body":29,"breadcrumbs":5,"title":1},"77":{"body":158,"breadcrumbs":6,"title":2},"78":{"body":54,"breadcrumbs":5,"title":1},"79":{"body":7,"breadcrumbs":6,"title":2},"8":{"body":16,"breadcrumbs":4,"title":1},"80":{"body":6,"breadcrumbs":5,"title":1},"81":{"body":39,"breadcrumbs":7,"title":3},"82":{"body":8,"breadcrumbs":8,"title":4},"83":{"body":9,"breadcrumbs":9,"title":5},"84":{"body":51,"breadcrumbs":2,"title":1},"85":{"body":108,"breadcrumbs":3,"title":1},"86":{"body":432,"breadcrumbs":3,"title":1},"87":{"body":36,"breadcrumbs":4,"title":2},"88":{"body":320,"breadcrumbs":4,"title":2},"89":{"body":90,"breadcrumbs":4,"title":2},"9":{"body":35,"breadcrumbs":5,"title":2},"90":{"body":40,"breadcrumbs":3,"title":1},"91":{"body":80,"breadcrumbs":3,"title":1},"92":{"body":6,"breadcrumbs":5,"title":3},"93":{"body":28,"breadcrumbs":5,"title":3},"94":{"body":16,"breadcrumbs":3,"title":1},"95":{"body":55,"breadcrumbs":4,"title":2},"96":{"body":205,"breadcrumbs":4,"title":2},"97":{"body":81,"breadcrumbs":5,"title":3},"98":{"body":119,"breadcrumbs":4,"title":2},"99":{"body":91,"breadcrumbs":4,"title":2}},"docs":{"0":{"body":"Practical reference and user guides for Fyrox Game Engine and its editor FyroxEd . ⚠️ Tip: If you want to start using the engine as fast as possible - read this chapter . Warning: The book is in early development stage, you can help to improve it by making a contribution in its repository . Don't be shy, every tip is helpful!","breadcrumbs":"About the Book » Fyrox Game Engine Book","id":"0","title":"Fyrox Game Engine Book"},"1":{"body":"Fyrox Team is trying to keep the book up-to-date with the latest version from master branch. If something does not compile with the latest release from crates.io, then you need to use the latest engine from GitHub repo .","breadcrumbs":"About the Book » Engine Version","id":"1","title":"Engine Version"},"10":{"body":"Everything of your game can be written entirely in Rust, utilizing its safety guarantees as well as speed. However, it is possible to use any scripting language you want, but that's have no built-in support and you need to implement this manually.","breadcrumbs":"Introduction » Introduction to Fyrox » Programming languages","id":"10","title":"Programming languages"},"100":{"body":"Script context provides access to the environment that can be used to modify engine and game state from scripts. Typical content of the context is something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# engine::{ScriptMessageDispatcher},\n# plugin::Plugin, asset::manager::ResourceManager,\n# scene::{node::Node, Scene},\n# script::ScriptMessageSender\n# };\npub struct ScriptContext<'a, 'b, 'c> { pub dt: f32, pub elapsed_time: f32, pub plugins: &'a mut [Box], pub handle: Handle, pub scene: &'b mut Scene, pub resource_manager: &'a ResourceManager, pub message_sender: &'c ScriptMessageSender, pub message_dispatcher: &'c mut ScriptMessageDispatcher,\n} dt - amount of time passed since last frame. The value of the variable is implementation-defined, usually it is something like 1/60 (0.016) of a second. elapsed_time - amount of time that passed since start of your game (in seconds). plugins - a mutable reference to all registered plugins, it allows you to access some \"global\" game data that does not belong to any object. For example, a plugin could store key mapping used for player controls, you can access it using plugins field and find desired plugin. In case of a single plugin, you just need to cast the reference to a particular type using context.plugins[0].cast::().unwrap() call. handle - a handle of the node to which the script is assigned to (parent node). You can borrow the node using context.scene.graph[handle] call. Typecasting can be used to obtain a reference to a particular node type. scene - a reference to parent scene of the script, it provides you full access to scene content, allowing you to add/modify/remove scene nodes. resource_manager - a reference to resource manager, you can use it to load and instantiate assets. message_sender - a message sender. Every message sent via this sender will be then passed to every ScriptTrait::on_message method of every script. message_dispatcher - a message dispatcher. If you need to receive messages of a particular type, you must subscribe to a type explicitly.","breadcrumbs":"Scripting » Scripts » Script Context","id":"100","title":"Script Context"},"101":{"body":"Scripts have strictly defined execution order for their methods (the order if execution is linear and do not depend on actual tree structure of the graph where the script is located): on_init - called first for every script instance on_start - called after every on_init is called on_update - called zero or more times per one render frame. The engine stabilizes update rate of the logic, so if your game runs at 15 FPS, the logic will still run at 60 FPS thus the on_update will be called 4 times per frame. The method can also be not called at all, if the FPS is very high. For example, if your game runs at 240 FPS, then on_update will be called once per 4 frames. on_message - called once per incoming message. on_os_event - called once per incoming OS event. on_deinit - called at the end of the update cycle once when the script (or parent node) is about to be deleted.","breadcrumbs":"Scripting » Scripts » Execution order","id":"101","title":"Execution order"},"102":{"body":"Script system of Fyrox supports message passing for scripts. Message passing is a mechanism that allows you to send some data (message) to a node, hierarchy of nodes or the entire graph. Each script can subscribe for a specific message type. It is an efficient way for decoupling scripts from each other. For instance, you may want to detect and respond to some event in your game. In this case when the event has happened, you send a message of a type and every \"subscriber\" will react to it. This way subscribers will not know anything about sender(s); they'll only use message data to do some actions. A simple example where the message passing can be useful is when you need to react to some event in your game. Imagine, that you have weapons in your game, and they can have a laser sight that flashes with a different color when some target was hit. In very naive approach you can handle all laser sights where you handle all intersection for projectiles, but this adds a very tight coupling between laser sight and projectiles. This is totally unnecessary coupling can be made loose by using message passing. Instead of handling laser sights directly, all you need to do is to broadcast an ActorDamaged { actor: Handle, attacker: Handle } message. Laser sight in its turn can subscribe for such message and handle all incoming messages and compare attacker with owner of the laser sight and if the hit was made by attacker flash with some different color. In code this would like so: # extern crate fyrox;\n# use fyrox::{\n# core::{pool::Handle, reflect::prelude::*, uuid::Uuid, visitor::prelude::*},\n# impl_component_provider,\n# scene::node::Node,\n# script::{ScriptContext, ScriptMessageContext, ScriptMessagePayload, ScriptTrait},\n# core::log::Log,\n# };\n# enum Message { Damage { actor: Handle, attacker: Handle, },\n} #[derive(Default, Clone, Reflect, Visit, Debug)]\nstruct Projectile;\n# # impl_component_provider!(Projectile); impl ScriptTrait for Projectile { fn on_update(&mut self, ctx: &mut ScriptContext) { // Broadcast the message globally. ctx.message_sender.send_global(Message::Damage { actor: Default::default(), attacker: ctx.handle, }); }\n# # fn id(&self) -> Uuid {\n# todo!()\n# }\n} #[derive(Default, Clone, Reflect, Visit, Debug)]\nstruct LaserSight;\n# # impl_component_provider!(LaserSight); impl ScriptTrait for LaserSight { fn on_start(&mut self, ctx: &mut ScriptContext) { // Subscript to messages. ctx.message_dispatcher.subscribe_to::(ctx.handle); } fn on_message( &mut self, message: &mut dyn ScriptMessagePayload, _ctx: &mut ScriptMessageContext, ) { // React to message. if let Some(Message::Damage { actor, attacker }) = message.downcast_ref::() { Log::info(format!(\"{actor} damaged {attacker}\",)) } }\n# # fn id(&self) -> Uuid {\n# todo!()\n# }\n} There are few key parts: You should explicitly subscribe script instance to a message type, otherwise messages of the type won't be delivered to your script. This is done using the message dispatcher: ctx.message_dispatcher.subscribe_to::(ctx.handle);. This should be done in on_start method, however it is possible to subscribe/unsubscribe at runime. You can react to messages only in special method on_message - here you just need to check for message type using pattern matching and do something useful. Try to use message passing in all cases, loose coupling significantly improves code quality and readability, however in simple projects it can be ignored completely.","breadcrumbs":"Scripting » Scripts » Message passing","id":"102","title":"Message passing"},"103":{"body":"Scene is a container for game entities. Currently, scenes in the engine manage following entities: Graph Animations Physics (rigid bodies, colliders, joints) Sound Scene allows you to create isolated \"world\" which won't interact with other scenes, it is very useful for many more or less complex games.","breadcrumbs":"Scene » Scene","id":"103","title":"Scene"},"104":{"body":"A scene could be created either in FyroxEd or programmatically. You can also combine both approaches, where you build all \"static\" content in the editor and adding rest of the entities (bots, interactive objects, etc.) manually by instantiating respective prefabs at runtime.","breadcrumbs":"Scene » How to create","id":"104","title":"How to create"},"105":{"body":"There is a separate chapter in the book that should help you to create a scene. After a scene is created, you can load it as any other 3D model (or prefab) using the resource manager: # extern crate fyrox;\n# use fyrox::{\n# core::{futures::executor::block_on, pool::Handle},\n# asset::manager::{ResourceManager}, resource::model::{Model, ModelResourceExtension},\n# scene::{node::Node, Scene},\n# };\n# use std::path::Path; fn load_scene(resource_manager: ResourceManager) -> Scene { // Create parent scene. let mut scene = Scene::new(); // Request child scene and block until it loading. let scene_resource = block_on( resource_manager .request::(\"path/to/your/scene.rgs\"), ) .unwrap(); // Create an instance of the scene in the parent scene. let child_scene = scene_resource.instantiate(&mut scene); scene\n} Please note that here we're creating an empty scene and only then instantiating another scene into it. Why is this needed? Child scene is considered as prefab , and it is \"instantiated\" in the parent scene. Considering it as prefab allows you modifying your scene separately and serialization/deserialization will be able to correctly apply any changes in the scene.","breadcrumbs":"Scene » Using FyroxEd","id":"105","title":"Using FyroxEd"},"106":{"body":"A scene could also be created manually: # extern crate fyrox;\n# use fyrox::{core::pool::Handle, engine::Engine, scene::Scene}; fn create_scene(engine: &mut Engine) -> Handle { let mut scene = Scene::new(); // Use node builders, create sounds, add physics, etc. here to fill the scene. engine.scenes.add(scene)\n} See respective node builders docs to populate the scene.","breadcrumbs":"Scene » Create scene manually","id":"106","title":"Create scene manually"},"107":{"body":"All scenes \"lives\" in the engine, the engine has ownership over your scene after you've added it in the engine. You can borrow a scene at any time using its handle and do some changes: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# event_loop::ControlFlow,\n# plugin::{Plugin, PluginContext},\n# scene::Scene,\n# };\n# struct Game { scene: Handle,\n} impl Plugin for Game { fn update(&mut self, context: &mut PluginContext, control_flow: &mut ControlFlow) { // Borrow a scene using its handle. `try_get` performs immutable borrow, to mutably borrow the scene // use `try_get_mut`. if let Some(scene) = context.scenes.try_get(self.scene) { // Do something. println!(\"{:?}\", scene.graph.performance_statistics); } }\n}","breadcrumbs":"Scene » Where all my scenes located?","id":"107","title":"Where all my scenes located?"},"108":{"body":"You can create your scene in separate thread and then pass it to main thread to insert it in the engine. Why this is needed? Remember the last time you've played a relatively large game, you've probably noticed that it have loading screens and loading screen has some fancy interactive stuff with progress bar. Loading screen is fully responsive while the game doing hard job loading the world for you. Got it already? Asynchronous scene loading is needed to create/load large scenes with tons of resources without blocking main thread, thus leaving the game fully responsive.","breadcrumbs":"Scene » Building scene asynchronously","id":"108","title":"Building scene asynchronously"},"109":{"body":"Usually you should have only one scene active (unless you're making something very special), you should use .enabled flag of a scene to turn it off or on. Deactivated scenes won't be rendered, the physics won't be updated, the sound will stop, and so on. In other words the scene will be frozen. This is useful for situations when you often need to switch between scenes, leaving other scene in frozen state. One of the examples where this can be useful is menus. In most games when you're entering the menu, game world is paused.","breadcrumbs":"Scene » Managing multiple scenes","id":"109","title":"Managing multiple scenes"},"11":{"body":"This is a more or less complete (yet, it can be outdated) list of engine features:","breadcrumbs":"Introduction » Introduction to Fyrox » Engine Features","id":"11","title":"Engine Features"},"110":{"body":"Every scene has default ambient lighting, it is defined by a single RGB color. By default, every scene has some pre-defined ambient lighting, it is bright enough, so you can see your objects. In some cases you may need to adjust it or even make it black (for horror games for instance), this can be achieved by a single line of code: # extern crate fyrox;\n# use fyrox::scene::Scene;\n# use fyrox::core::color::Color;\n# let mut scene = Scene::default();\n# scene.rendering_options.ambient_lighting_color = Color::opaque(30, 30, 30); Please keep in mind that ambient lighting does not mean global illumination, it is a different lighting technique which is not available in the engine yet.","breadcrumbs":"Scene » Ambient lighting","id":"110","title":"Ambient lighting"},"111":{"body":"Graph is a set of objects with hierarchical relationships between each object. It is one of the most important entities in the engine. Graph takes care of your scene objects and does all the hard work for you.","breadcrumbs":"Scene » Graph » Graph","id":"111","title":"Graph"},"112":{"body":"You don't need to create a graph manually, every scene has its own instance of the graph. It can be accessed pretty easily: scene_ref.graph","breadcrumbs":"Scene » Graph » How to create","id":"112","title":"How to create"},"113":{"body":"There are two ways of adding nodes to the graph, either using node builders or manually by calling graph.add_node.","breadcrumbs":"Scene » Graph » Adding nodes","id":"113","title":"Adding nodes"},"114":{"body":"Every node in the engine has its respective builder which can be used to create an instance of the node. Using builders is a preferable way to create scene nodes. There are following node builders: BaseBuilder - creates an instance of base node. See Base node for more info. PivotBuilder - creates an instance of pivot node. See Base node for more info. CameraBuilder - creates an instance of camera node. See Camera node for more info. MeshBuilder - creates an instance of mesh node. See Mesh node for more info. LightBuilder - creates an instance of light node. See Light node for more info. SpriteBuilder - creates an instance of sprite node. See Sprite node for more info. ParticleSystemBuilder - creates an instance of particle system node. See Particle system node for more info. TerrainBuilder - creates an instance of terrain node. See Terrain node for more info. DecalBuilder - creates an instance of decal node. See Decal node for more info. RigidBody - creates an instance of rigid body node. See Rigid body for more info. Collider - creates an instance of collider node. See Collider for more info. Joint - creates an instance of joint node. See Joint for more info. Rectangle - creates an instance of 2D rectangle node. See Rectangle for more info. Every builder, other than BaseBuilder, accepts BaseBuilder as a parameter in .new(..) method. Why so? Because every node (other than Base) is \"derived\" from Base via composition and the derived builder must know how to build Base node. While it may sound confusing, it is actually very useful and clear. Consider this example: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{\n# base::BaseBuilder, camera::CameraBuilder, node::Node, transform::TransformBuilder,\n# Scene,\n# },\n# }; fn create_camera(scene: &mut Scene) -> Handle { CameraBuilder::new( // Here we passing a base builder. Note that, since we can build Base node separately // we can pass any custom values to it while building. BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(2.0, 0.0, 3.0)) .build(), ), ) // Here we just setting desired Camera properties. .with_fov(60.0f32.to_radians()) .build(&mut scene.graph)\n} As you can see, we're creating an instance of BaseBuilder and fill it with desired properties as well as filling the CameraBuilder's instance properties. This is a very flexible mechanism, allowing you to build complex hierarchies in a declarative manner: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{\n# base::BaseBuilder, camera::CameraBuilder, mesh::MeshBuilder, node::Node,\n# sprite::SpriteBuilder, transform::TransformBuilder, Scene,\n# },\n# }; fn create_node(scene: &mut Scene) -> Handle { CameraBuilder::new( BaseBuilder::new() // Add some children nodes. .with_children(&[ // A staff... MeshBuilder::new( BaseBuilder::new() .with_name(\"MyFancyStaff\") .with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(0.5, 0.5, 1.0)) .build(), ), ) .build(&mut scene.graph), // and a spell. SpriteBuilder::new( BaseBuilder::new() .with_name(\"MyFancyFireball\") .with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(-0.5, 0.5, 1.0)) .build(), ), ) .build(&mut scene.graph), ]) .with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(2.0, 0.0, 3.0)) .build(), ), ) .with_fov(60.0f32.to_radians()) .build(&mut scene.graph)\n} This code snippet creates a camera for first-person role-playing game's player, it will have a staff in \"right-hand\" and a spell in the left hand. Of course all of this is very simplified, but should give you the main idea. Note that staff and fireball will be children nodes of camera, and when setting their transform, we're actually setting local transform which means that the transform will be relative to camera's. The staff and the spell will move together with the camera.","breadcrumbs":"Scene » Graph » Using node builders","id":"114","title":"Using node builders"},"115":{"body":"For some rare cases you may also want to delay adding a node to the graph, specifically for that purpose, every node builder has .build_node method which creates an instance of Node but does not add it to the graph. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{base::BaseBuilder, camera::CameraBuilder, node::Node, Scene},\n# }; fn create_node(scene: &mut Scene) -> Handle { let node: Node = CameraBuilder::new(BaseBuilder::new()).build_node(); // We must explicitly add the node to the graph. scene.graph.add_node(node)\n}","breadcrumbs":"Scene » Graph » Adding a node manually","id":"115","title":"Adding a node manually"},"116":{"body":"For many cases you can't use builders to create complex hierarchy, the simplest example of such situation when you're creating an instance of some 3D model. If you want the instance to be a child object of some other object, you should attach it explicitly by using graph.link_nodes(..): # extern crate fyrox;\n# use fyrox::{\n# core::{futures::executor::block_on, pool::Handle},\n# asset::manager::ResourceManager, resource::model::{Model, ModelResourceExtension},\n# scene::{base::BaseBuilder, camera::CameraBuilder, node::Node, Scene},\n# }; fn link_weapon_to_camera( scene: &mut Scene, camera: Handle, resource_manager: ResourceManager,\n) { let weapon = block_on( resource_manager .request::(\"path/to/weapon.fbx\"), ) .unwrap() .instantiate(scene); // Link weapon to the camera. scene.graph.link_nodes(weapon, camera);\n} Here we've loaded a weapon 3D model, instantiated it on scene and attached to existing camera.","breadcrumbs":"Scene » Graph » How to modify the hierarchy","id":"116","title":"How to modify the hierarchy"},"117":{"body":"A node could be removed by simply calling graph.remove_node(handle), this method removes the node from the graph with all of its children nodes . Sometimes this is unwanted behaviour, and you want to preserve children nodes while deleting parent node. To do that, you need to explicitly detach children nodes of the node you're about to delete: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{node::Node, Scene},\n# }; fn remove_preserve_children(scene: &mut Scene, node_to_remove: Handle) { for child in scene.graph[node_to_remove].children().to_vec() { scene.graph.unlink_node(child); } scene.graph.remove_node(node_to_remove);\n} After calling this function, every child node of node_to_remove will be detached from it and the node_to_remove will be deleted. remove_node has some limitations: it cannot be used to extract \"sub-graph\" from the graph, it just drops nodes immediately.","breadcrumbs":"Scene » Graph » How to remove nodes","id":"117","title":"How to remove nodes"},"118":{"body":"Transformation (transform for short) - is a special entity that changes coordinate system from one to another. It is used primarily in scene nodes to store their position/rotation/scale/pivots/etc. Fyrox has quite complex transformations, that supports: Position (T) Rotation (R) Scale (S) Pre-rotation (Rpre) Post-rotation (Rpost) Rotation Pivot (Rp) Rotation Offset (Roff) Scaling Offset (Soff) Scaling Pivot (Sp) Final transformation matrix will be Transform = T * Roff * Rp * Rpre * R * Rpost * Rp⁻¹ * Soff * Sp * S * Sp⁻¹. In 99.9% cases first three are enough for pretty much every task. Other six components used for specific stuff (mainly for nodes that imported from FBX file format).","breadcrumbs":"Scene » Transformation » Transformation","id":"118","title":"Transformation"},"119":{"body":"A prefab is a separate scene that can be instantiated in some other scene, while preserving links between properties of its instances and of its parent prefab. Prefabs allow you to create a part of a scene and have multiple instances of it in other scenes. Let's quickly check what that means on practice. The engine has a prefab system which allows you to build hierarchical scenes which can include any number of other scenes as child scenes. Child scenes can have their own child scenes and so on. This is very efficient decoupling mechanism that allows you to put pieces of the scene in separate scenes (prefabs) and modify them independently. The changes in child scenes will be automatically reflected to all parent scenes. Here is the very simple example of why this is important: imagine you need to populate a town with 3D models of cars. Each kind of car has its own 3D model and for example, a collision body that won't allow the player to walk through cars. How would you do this? The simplest (and dumbest) solution is to copy dozens of car models in the scene, and you're done. Imagine that now you need to change something in your car, for example, add a trunk that can be opened. What will you do? Of course, you should \"iterate\" over each car model and do the required changes, you simply don't have any other option. This will eat huge amount of time and in general it is very non-productive. This is where prefabs will save you hours of work. All you need to do is to create a car prefab and instantiate it multiple times in your scene. When you'll need to change something in the car, you simply go to the prefab and change it. After that every prefab instance will have your changes! Prefabs can be used to create self-contained entities in your game, examples of this includes: visual effects, any scripted game entities (bots, turrets, player, doors, etc.). Such prefabs can be either directly instantiated in a scene in the editor, or instantiated at runtime when needed.","breadcrumbs":"Scene » Prefabs » Prefabs","id":"119","title":"Prefabs"},"12":{"body":"Exceptional safety, reliability, and speed. PC (Windows, Linux, macOS), Android, Web (WebAssembly) support . Modern, PBR rendering pipeline. Comprehensive documentation . Guide book 2D support. Integrated editor. Fast iterative compilation. Classic object-oriented design. Lots of examples.","breadcrumbs":"Introduction » Introduction to Fyrox » General","id":"12","title":"General"},"120":{"body":"All you need to do is to make a scene in the editor with all required objects and save it! After that, you can use the scene in other scenes and just do its instantiation, as in usual 3D models. You can either instantiate it from the editor by drag'n'drop a prefab to scene previewer, or do standard model resource instantiation","breadcrumbs":"Scene » Prefabs » How to create and use a prefab","id":"120","title":"How to create and use a prefab"},"121":{"body":"As already mentioned in the intro section, instances inherit properties from their parent prefabs. For example, you can change position of an object in prefab and every instance will reflect that change - the object's instances will also move. This works until there's no manual change to a property in instance, if you do so, your change is considered with higher priority. See this chapter for more info.","breadcrumbs":"Scene » Prefabs » Property inheritance","id":"121","title":"Property inheritance"},"122":{"body":"Prefabs can have other prefab instances inside it. This means that you can, for example, create a room populated with instances of other prefabs (bookshelves, chairs, tables, etc.) and then use the room prefab to build a bigger scene. The changes in the base prefabs will be reflected in their instances, regardless of how deep the hierarchy is.","breadcrumbs":"Scene » Prefabs » Hierarchical Prefabs","id":"122","title":"Hierarchical Prefabs"},"123":{"body":"Property inheritance is used to propagate changes of unmodified properties from a prefab to its instances. For example, you can change scale of a node in a prefab and its instances will have the same scale too, unless the scale is set explicitly in an instance. Such feature allows you to tweak instances, add some unique details to them, but take general properties from parent prefabs. Property inheritance works for prefab hierarchies of any depth, this means that you can create something like this: a room prefab can have multiple instances of various furniture prefabs in it, while the furniture prefabs can also be constructed from other prefabs and so on. In this case if you modify a property in one of the prefabs in the chain, all instance will immediately sync their unmodified properties.","breadcrumbs":"Scene » Property Inheritance » Property Inheritance","id":"123","title":"Property Inheritance"},"124":{"body":"It is possible to use property inheritance for script variables. To make a property of your script inheritable, all you need is to wrap its value using InheritableVariable wrapper. # extern crate fyrox;\n# use fyrox::core::variable::InheritableVariable;\n# use fyrox::core::visitor::prelude::*;\n# use fyrox::core::reflect::prelude::*;\n#[derive(Reflect, Visit, Default, Clone, Debug)]\nstruct MyScript { foo: InheritableVariable\n} The engine will automatically resolve the correct value for the property when a scene with the script is loaded. If your property was modified, then its value will remain the same, it won't be overwritten by parent's value. Keep in mind, that the type of the inheritable variable must be cloneable and support reflection. InheritableVariable implements the Deref + DerefMut traits, this means that any access via the DerefMut trait will mark the property as modified. This could be undesired in some cases so InheritableVariable supports special xxx_silent methods that don't touch the internal modifiers and allows you to substitute the value with some other \"silently\" - without marking the variable as modified.","breadcrumbs":"Scene » Property Inheritance » How To Create Inheritable Properties","id":"124","title":"How To Create Inheritable Properties"},"125":{"body":"Inheritable variables intended to be \"atomic\" - it means that the variable stores some simple variable (f32, String, Handle, etc.). While it is possible to store \"compound\" variables (InheritableVariable), it is not advised because of inheritance mechanism. When the engine sees inheritable variable, it searches the same variable in a parent entity and copies its value to the child, thus completely replacing its content. In this case, even if you have inheritable variables inside compound field, they won't be inherited correctly. Let's demonstrate this in the following code snippet: # extern crate fyrox;\n# use fyrox::core::reflect::prelude::*;\n# use fyrox::core::variable::InheritableVariable;\n# #[derive(Reflect, Clone, PartialEq, Eq, Debug)]\nstruct SomeComplexData { foo: InheritableVariable, bar: InheritableVariable,\n} #[derive(Reflect, Debug)]\nstruct MyEntity { some_field: InheritableVariable, // This field won't be inherited correctly - at first it will take parent's value and then // will try to inherit inner fields, but its is useless step, because inner data is already // a full copy of parent's field value. incorrectly_inheritable_data: InheritableVariable, // Subfields of this field will be correctly inherited, because the field itself is not inheritable. inheritable_data: SomeComplexData,\n} This code snippet should clarify, that inheritable fields should contain some \"simple\" data, and almost never - complex structs.","breadcrumbs":"Scene » Property Inheritance » Which Fields Should Be Inheritable?","id":"125","title":"Which Fields Should Be Inheritable?"},"126":{"body":"The editor wraps all inheritable properties in a special widget that supports property reversion. Reversion allows you to drop current changes and take the parent's property value. This is useful if you want a property to inherit its parent's value. In the Inspector it looks like this: revert Clicking on the < button will take the value from the parent prefab and the property won't be marked as modified anymore. In case there is no parent prefab, the button will just drop modified flag.","breadcrumbs":"Scene » Property Inheritance » Editor","id":"126","title":"Editor"},"127":{"body":"Base node is a scene node that stores hierarchical information (a handle to the parent node and a set of handles to children nodes), local and global transform, name, tag, lifetime, etc. It has self-describing name - it is used as a base node for every other scene node (via composition). It has no graphical information, so it is invisible all the time, but it is useful as a \"container\" for children nodes.","breadcrumbs":"Scene » Base Node » Base node","id":"127","title":"Base node"},"128":{"body":"Use the PivotBuilder to create an instance of the Pivot node (remember Base node itself is used only to build other node types): # extern crate fyrox;\n# use fyrox::scene::{base::BaseBuilder, pivot::PivotBuilder, Scene};\n# fn build_node(scene: &mut Scene) {\nlet handle = PivotBuilder::new(BaseBuilder::new()).build(&mut scene.graph);\n# }","breadcrumbs":"Scene » Base Node » How to create","id":"128","title":"How to create"},"129":{"body":"To build a complex hierarchy of some nodes, use .with_children() method of the BaseBuilder, it allows you to build a hierarchy of any complexity: # extern crate fyrox;\n# use fyrox::scene::{base::BaseBuilder, pivot::PivotBuilder, camera::CameraBuilder, Scene};\n#\n# fn build_node(scene: &mut Scene) {\nlet handle = PivotBuilder::new(BaseBuilder::new() .with_children(&[ CameraBuilder::new(BaseBuilder::new()).build(&mut scene.graph), PivotBuilder::new(BaseBuilder::new() .with_children(&[PivotBuilder::new(BaseBuilder::new()).build(&mut scene.graph)])) .build(&mut scene.graph), ])) .build(&mut scene.graph);\n# } Note that when we're building a Camera instance, we're passing a new instance of BaseBuilder to it, this instance can also be used to set some properties and a set of children nodes. The \"fluent syntax\" is not mandatory to use, the above code snipped could be rewritten like this: # extern crate fyrox;\n# use fyrox::scene::{base::BaseBuilder, pivot::PivotBuilder, camera::CameraBuilder, Scene};\n# # fn build_node(scene: &mut Scene) {\nlet camera = CameraBuilder::new(BaseBuilder::new()).build(&mut scene.graph); let child_base = PivotBuilder::new(BaseBuilder::new()).build(&mut scene.graph); let base = PivotBuilder::new(BaseBuilder::new() .with_children(&[child_base])) .build(&mut scene.graph); let handle = PivotBuilder::new(BaseBuilder::new() .with_children(&[camera, base])) .build(&mut scene.graph);\n# } However, it looks less informative, because it loses the hierarchical view and it is harder to tell the relations between objects.","breadcrumbs":"Scene » Base Node » Building a complex hierarchy","id":"129","title":"Building a complex hierarchy"},"13":{"body":"Custom shaders, materials, and rendering techniques. Physically-based rendering. Metallic workflow. High dynamic range (HDR) rendering. Tone mapping. Color grading. Auto-exposure. Gamma correction. Deferred shading. Directional light. Point lights + shadows. Spotlights + shadows. Screen-Space Ambient Occlusion (SSAO). Soft shadows. Volumetric light (spot, point). Batching. Instancing. Fast Approximate Anti-Aliasing (FXAA). Normal mapping. Parallax mapping. Render in texture. Forward rendering for transparent objects. Sky box. Deferred decals. Multi-camera rendering. Lightmapping. Soft particles. Fully customizable vertex format. Compressed textures support. High-quality mip-map on-demand generation.","breadcrumbs":"Introduction » Introduction to Fyrox » Rendering","id":"13","title":"Rendering"},"130":{"body":"Base node has a local transform that allows you to translate/scale/rotate/etc. your node as you want to. For example, to move a node at specific location you could use this: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{node::Node, Scene},\n# };\n#\n# fn translate_node(scene: &mut Scene, node_handle: Handle) {\nscene.graph[node_handle] .local_transform_mut() .set_position(Vector3::new(1.0, 0.0, 2.0));\n# } You could also chain multiple set_x calls, like so: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{node::Node, Scene},\n# };\n#\n# fn transform_node(scene: &mut Scene, node_handle: Handle) {\nscene.graph[node_handle] .local_transform_mut() .set_position(Vector3::new(1.0, 0.0, 2.0)) .set_scale(Vector3::new(2.0, 2.0, 2.0)) .set_rotation_offset(Vector3::new(1.0, 1.0, 0.0));\n# } See more info about transformations here .","breadcrumbs":"Scene » Base Node » Transform","id":"130","title":"Transform"},"131":{"body":"Base node stores all info about local visibility and global visibility (with parent's chain visibility included). Changing node's visibility could be useful if you want to improve performance by hiding distant objects (however it strongly advised to use level-of-detail for this) or to hide some objects in your scene. There are three main methods to set or fetch visibility: set_visibility - sets local visibility for a node. visibility - returns current local visibility of a node. global_visibility - returns combined visibility of a node. It includes visibility of every parent node in the hierarchy, so if you have a parent node with some children nodes and set parent's visibility to false, global visibility of children nodes will be false too, even if local visibility is true. This is useful technique for hiding complex objects with lots of children nodes.","breadcrumbs":"Scene » Base Node » Visibility","id":"131","title":"Visibility"},"132":{"body":"A scene node could be enabled or disabled. Disabled nodes are excluded from a game loop and has almost zero CPU consumption (their global transform/visibility/enabled state is still updated due to limitations of the engine). Disabling a node could be useful if you need to completely freeze some hierarchy and do keep it in this state until it is enabled again. It could be useful to disable parts of a scene with which a player cannot interact to improve performance. Keep in mind, that enabled state is hierarchical like visibility. When you're disabling a parent node with some children nodes, the children nodes will be disabled too.","breadcrumbs":"Scene » Base Node » Enabling/disabling scene nodes","id":"132","title":"Enabling/disabling scene nodes"},"133":{"body":"Mesh is a scene node that represents a 3D model. This one of the most commonly used nodes in almost every game. Meshes could be easily created either programmatically or be made in some 3D modelling software (like Blender) and loaded in your scene.","breadcrumbs":"Scene » Mesh Node » Mesh node","id":"133","title":"Mesh node"},"134":{"body":"Surface is a set of triangles that uses the same material . Mesh node could contain zero of more surfaces; each surface contains a set of vertices and indices that binds vertices with triangles. Mesh nodes split into surfaces to be rendered effectively by modern GPUs.","breadcrumbs":"Scene » Mesh Node » Surfaces","id":"134","title":"Surfaces"},"135":{"body":"There are basically two ways, how to pick one depends on your needs. In general, using a 3D modelling software is the way to go, especially with tons and tons of free 3D models available online. ⚠️ The engine supports only FBX file format for 3D models!","breadcrumbs":"Scene » Mesh Node » How to create","id":"135","title":"How to create"},"136":{"body":"To create a 3D model, you could use Blender and then export it to FBX file format. To load your 3D model in the game, you should do few simple steps (loading a 3D model does not differ from a prefab instantiation): # extern crate fyrox; use fyrox::{ core::{futures::executor::block_on, pool::Handle}, asset::manager::{ResourceManager}, resource::model::{Model, ModelResourceExtension}, scene::{node::Node, Scene},\n};\nuse std::path::Path; fn load_model_to_scene( scene: &mut Scene, path: &Path, resource_manager: ResourceManager,\n) -> Handle { // Request model resource and block until it loading. let model_resource = block_on(resource_manager.request::(path)) .unwrap(); // Create an instance of the resource in the scene. model_resource.instantiate(scene)\n} This code snippet intentionally omits proper async/await usage (instead it just blocks current thread until model is loading) and error handling. In the real game you should carefully handle all errors and use async/await properly.","breadcrumbs":"Scene » Mesh Node » Using a 3D modelling software","id":"136","title":"Using a 3D modelling software"},"137":{"body":"A mesh instance could be created from code, such meshes are called \"procedural\". They're suitable for cases when you cannot create a mesh in 3D modelling software. # extern crate fyrox; use fyrox::{ core::{ algebra::{Matrix4, Vector3}, parking_lot::Mutex, pool::Handle, sstorage::ImmutableString, }, asset::manager::ResourceManager, resource::model::{Model, ModelResourceExtension}, resource::texture::Texture, material::{shader::SamplerFallback, Material, PropertyValue, SharedMaterial}, scene::{ base::BaseBuilder, mesh::{ surface::{SurfaceBuilder, SurfaceData, SurfaceSharedData}, MeshBuilder, }, node::Node, transform::TransformBuilder, Scene, },\n};\nuse std::sync::Arc; fn create_procedural_mesh( scene: &mut Scene, resource_manager: ResourceManager,\n) -> Handle { let mut material = Material::standard(); // Material is completely optional, but here we'll demonstrate that it is possible to // create procedural meshes with any material you want. material .set_property( &ImmutableString::new(\"diffuseTexture\"), PropertyValue::Sampler { value: Some(resource_manager.request::(\"some_texture.jpg\")), fallback: SamplerFallback::White, }, ) .unwrap(); // Notice the MeshBuilder. MeshBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(0.0, -0.25, 0.0)) .build(), ), ) .with_surfaces(vec![SurfaceBuilder::new(SurfaceSharedData::new( // Our procedural mesh will have a form of squashed cube. // A mesh can have unlimited amount of surfaces. SurfaceData::make_cube(Matrix4::new_nonuniform_scaling(&Vector3::new( 25.0, 0.25, 25.0, ))), )) .with_material(SharedMaterial::new(material)) .build()]) .build(&mut scene.graph)\n} As you can see, creating a mesh procedurally requires lots of manual work and not so easy.","breadcrumbs":"Scene » Mesh Node » Creating a procedural mesh","id":"137","title":"Creating a procedural mesh"},"138":{"body":"Mesh node supports bone-based animation (skinning). See Animation chapter for more info.","breadcrumbs":"Scene » Mesh Node » Animation","id":"138","title":"Animation"},"139":{"body":"The engine offers complex lighting system with various types of light sources.","breadcrumbs":"Scene » Light Node » Light node","id":"139","title":"Light node"},"14":{"body":"Multiple scenes. Full-featured scene graph. Level-of-detail (LOD) support. GPU Skinning. Various scene nodes: Pivot. Camera. Decal. Mesh. Particle system. Sprite. Multilayer terrain. Rectangle (2D Sprites) Rigid body + Rigid Body 2D Collider + Collider 2D Joint + Joint 2D","breadcrumbs":"Introduction » Introduction to Fyrox » Scene","id":"14","title":"Scene"},"140":{"body":"There are three main types of light sources: directional, point, and spot lights.","breadcrumbs":"Scene » Light Node » Light types","id":"140","title":"Light types"},"141":{"body":"Directional light does not have a position, its rays are always parallel, and it has a particular direction in space. An example of directional light in real-life could be our Sun. Even if it is a point light, it is so far away from the Earth, so we can assume that its rays are always parallel. Directional light sources are suitable for outdoor scenes. A directional light source could be created like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# light::{directional::DirectionalLightBuilder, BaseLightBuilder},\n# node::Node,\n# Scene,\n# },\n# }; fn create_directional_light(scene: &mut Scene) -> Handle { DirectionalLightBuilder::new(BaseLightBuilder::new(BaseBuilder::new())) .build(&mut scene.graph)\n} By default, the light source will be oriented to lit \"the ground\". In other words its direction will be faced towards (0.0, -1.0, 0.0) vector. You can rotate it as you want by setting local transform of it while building. Something like this: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{UnitQuaternion, Vector3},\n# pool::Handle,\n# },\n# scene::{\n# base::BaseBuilder,\n# light::{directional::DirectionalLightBuilder, BaseLightBuilder},\n# node::Node,\n# transform::TransformBuilder,\n# Scene,\n# },\n# }; fn create_directional_light(scene: &mut Scene) -> Handle { DirectionalLightBuilder::new(BaseLightBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_rotation(UnitQuaternion::from_axis_angle( &Vector3::x_axis(), -45.0f32.to_radians(), )) .build(), ), )) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Light Node » Directional light","id":"141","title":"Directional light"},"142":{"body":"Point light is a light source that emits lights in all directions, it has a position, but does not have an orientation. An example of a point light source: light bulb. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# light::{point::PointLightBuilder, BaseLightBuilder},\n# node::Node,\n# Scene,\n# },\n# }; fn create_point_light(scene: &mut Scene) -> Handle { PointLightBuilder::new(BaseLightBuilder::new(BaseBuilder::new())) .with_radius(5.0) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Light Node » Point light","id":"142","title":"Point light"},"143":{"body":"Spotlight is a light source that emits lights in cone shape, it has a position and orientation. An example of a spotlight source: flashlight. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# light::{spot::SpotLightBuilder, BaseLightBuilder},\n# node::Node,\n# Scene,\n# },\n# }; fn create_spot_light(scene: &mut Scene) -> Handle { SpotLightBuilder::new(BaseLightBuilder::new(BaseBuilder::new())) .with_distance(5.0) .with_hotspot_cone_angle(50.0f32.to_radians()) .with_falloff_angle_delta(10.0f32.to_radians()) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Light Node » Spotlight","id":"143","title":"Spotlight"},"144":{"body":"scattering Spot and point lights support light scattering effect. Imagine you're walking with a flashlight in a foggy weather, the fog will scatter the light from your flashlight making it, so you'll see the \"light volume\". Light scattering is enabled by default , so you don't have to do anything to enable it. However, in some cases you might want to disable it, you can do this either while building a light source or change light scattering options on existing light source. Here is the small example of how to do that. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{node::Node, light::BaseLight, Scene},\n# }; fn disable_light_scatter(scene: &mut Scene, light_handle: Handle) { scene.graph[light_handle] .query_component_mut::() .unwrap() .enable_scatter(false);\n} You could also change the amount of scattering per each color channel, using this you could imitate the Rayleigh scattering : # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{node::Node, light::BaseLight, Scene},\n# }; fn use_rayleigh_scattering(scene: &mut Scene, light_handle: Handle) { scene.graph[light_handle] .query_component_mut::() .unwrap() .set_scatter(Vector3::new(0.03, 0.035, 0.055));\n}","breadcrumbs":"Scene » Light Node » Light scattering","id":"144","title":"Light scattering"},"145":{"body":"By default, light sources cast shadows. You can change this by using set_cast_shadows method of a light source. You should carefully manage shadows: shadows giving the most significant performance impact, you should keep the amount of light sources that can cast shadows at lowest possible amount to keep performance at good levels. You can also turn on/off shadows when you need: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{node::Node, light::BaseLight, Scene},\n# }; fn switch_shadows(scene: &mut Scene, light_handle: Handle, cast_shadows: bool) { scene.graph[light_handle] .query_component_mut::() .unwrap() .set_cast_shadows(cast_shadows);\n} Not every light should cast shadows, for example a small light that a player can see only in a distance can have shadows disabled. You should set the appropriate values depending on your scene, just remember: the fewer the shadows the better the performance. The most expensive shadows are from point lights, the less, from spotlights and directional lights.","breadcrumbs":"Scene » Light Node » Shadows","id":"145","title":"Shadows"},"146":{"body":"Lights are not cheap, every light source has some performance impact. As a general rule, try to keep the amount of light sources at reasonable levels and especially try to avoid creating tons of light sources in a small area. Keep in mind that the less area the light needs to \"cover\", the higher the performance. This means that you can have tons of small light sources for free.","breadcrumbs":"Scene » Light Node » Performance","id":"146","title":"Performance"},"147":{"body":"Sprite is just a quad mesh that is always facing camera. It has size, color, rotation around \"look\" axis and a texture. Sprites are useful mostly for projectiles, like glowing plasma, and for things that should always face a camera. ⚠️ It should be noted that sprites are not meant to be used for 2D games , they're only for 3D. Use Rectangle node if you need 2D sprites, they have optimized renderer which can handle tons of sprites at once (sprite batching).","breadcrumbs":"Scene » Sprite Node » Sprite","id":"147","title":"Sprite"},"148":{"body":"A sprite instance could be created using SpriteBuilder: # extern crate fyrox;\n# use fyrox::{\n# core::{color::Color, pool::Handle}, resource::texture::Texture,\n# scene::{base::BaseBuilder, node::Node, sprite::SpriteBuilder, Scene},\n# }; fn create_sprite(scene: &mut Scene) -> Handle { SpriteBuilder::new(BaseBuilder::new()) .with_size(2.0) .with_rotation(45.0f32.to_radians()) .with_color(Color::RED) .build(&mut scene.graph)\n} A sprite with a texture could be created by using .with_texture method of the builder: # extern crate fyrox;\nuse fyrox::{ core::pool::Handle, asset::manager::ResourceManager, resource::texture::Texture, scene::{base::BaseBuilder, node::Node, sprite::SpriteBuilder, Scene},\n}; fn create_sprite(scene: &mut Scene, resource_manager: ResourceManager) -> Handle { SpriteBuilder::new(BaseBuilder::new()) .with_texture(resource_manager.request::(\"path/to/your/texture.png\")) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Sprite Node » How to create","id":"148","title":"How to create"},"149":{"body":"Sprites must not be used to create any visual effects that involve many particles. You should use particle systems for that. Why so? Particles systems are very well optimized for managing huge amounts of particles at the same time, but sprites are not. Each sprite is quite heavy to be used as a particle in particle systems, it has a lot of \"useless\" info that will eat a lot of memory. ⚠️ Currently, the renderer will render each sprite in a separate draw call, which is very inefficient. So you should avoid creating lots of sprites.","breadcrumbs":"Scene » Sprite Node » General rules","id":"149","title":"General rules"},"15":{"body":"High quality binaural sound with HRTF support . Generic and spatial sound sources. Built-in streaming for large sounds. Raw samples playback support. WAV/OGG format support. HRTF support for excellent positioning and binaural effects. Reverb effect.","breadcrumbs":"Introduction » Introduction to Fyrox » Sound","id":"15","title":"Sound"},"150":{"body":"Sprites are not supporting any sort of lighting, if you need lighted sprites, you need to create your own render pass and use Mesh node with custom shader that will orient all faces towards camera and will do lighting calculations.","breadcrumbs":"Scene » Sprite Node » Limitations","id":"150","title":"Limitations"},"151":{"body":"Particle system is a scene node that is used to create complex visual effects (VFX). It operates on huge amount of particles at once allowing you to do complex simulation that involves large amount of particles. Typically, particle systems are used to create following visual effects: smoke, sparks, blood splatters, steam, etc. smoke","breadcrumbs":"Scene » Particle System Node » Particle system","id":"151","title":"Particle system"},"152":{"body":"Particle system uses single texture for every particle in the system, only Red channel is used. Red channel interpreted as an alpha for all particles. Every particle is affected by Acceleration parameters of the particle system. It defines acceleration (in m/s2) that will affect velocities of every particle. It is used to simulate gravity.","breadcrumbs":"Scene » Particle System Node » Basic Concepts","id":"152","title":"Basic Concepts"},"153":{"body":"Particle is a square (not quadrilateral, this is important) with a texture which is always facing towards camera. It has the following properties: Position - defines a position in local coordinates of particle system (this means that if you rotate a particle system, all particles will be rotated too). Velocity - defines a speed vector (in local coordinates) that will be used to modify local position of the particle each frame. Size - size (in meters) of the square shape of the particle. Size Modifier - a numeric value (in meters per second), that will be added to the Size at each frame, it is used to modify size of the particles. Lifetime - amount of time (in seconds) that the particle can be active for. Rotation - angle (in radians) that defines rotation around particle-to-camera axis (clockwise). Rotation Speed - speed (in radians per second, rad/s) of rotation of the particle. Color - RGBA color of the particle.","breadcrumbs":"Scene » Particle System Node » Particle","id":"153","title":"Particle"},"154":{"body":"Particle system uses emitters to define a set of zones where particles will be spawned, it also defines initial ranges of parameters of particles. Particle system must have at least one emitter to generate particles. Emitter can be one of the following types: Cuboid - emits particles uniformly in a cuboid shape, the shape cannot be rotated, only translated. Sphere - emits particles uniformly in a sphere shape. Cylinder - emits particle uniformly in a cylinder shape, the shape cannot be rotated, only translated. Each emitter have fixed set of parameters that affects initial values for every spawned particle: Position - emitter have its own local position (position relative to parent particle system node), this helps you to create complex particle systems that may spawn particles from multiple zones in space at once. Max Particles - maximum amount of particles available for spawn. By default, it is None, which says that there is no limit. Spawn Rate - rate (in units per second) defines how fast the emitter will spawn particles. Lifetime Range - numeric range (in seconds) for particle lifetime values. The lower the beginning of the range the less spawned particles will live, and vice versa. Size Range - numeric range (in meters) for particle size. Size Modifier Range - numeric range (in meters per second, m/s) for particle size modifier parameter. X/Y/Z Velocity Range - a numeric range (in meters per second, m/s) for a respective velocity axis (X, Y, Z) that defines initial speed along the axis. Rotation Range - a numeric range (in radians) for initial rotation of a new particle. Rotation Speed Range - a numeric range (in radians per second, rad/s) for rotation speed of a new particle. Important: Every range (like Lifetime Range, Size Range, etc.) parameter generates random value for respective parameter of a particle. You can tweak the seed of current random number generator (fyrox::core::thread_rng()) to ensure that generated values will be different each time.","breadcrumbs":"Scene » Particle System Node » Emitters","id":"154","title":"Emitters"},"155":{"body":"There are multiple ways of creating a particle system, pick one that best suits your current needs.","breadcrumbs":"Scene » Particle System Node » How to create","id":"155","title":"How to create"},"156":{"body":"The best way to create a particle system is to configure it in the editor, creating from code is possible too (see below), but way harder and may be not intuitive, because of the large amount of parameters. The editor allows you see the result and tweak it very fast. Create a particle system by Create -> Particle System and then you can start editing its properties. By default, new particle system has one Sphere particle emitter, you can add new emitters by clicking + button at the right of Emitters property in the Inspector (or remove by clicking -). Here's a simple example: particle system Now start tweaking desired parameters, it is hard to give any recommendations of how to achieve a particular effect, only practice matters here.","breadcrumbs":"Scene » Particle System Node » Using the editor","id":"156","title":"Using the editor"},"157":{"body":"You can also create particle systems from code (in case if you need some procedurally-generated effects): # extern crate fyrox;\n# use fyrox::scene::particle_system::{\n# emitter::sphere::SphereEmitter, ParticleSystemBuilder, emitter::Emitter,\n# emitter::base::BaseEmitterBuilder, emitter::sphere::SphereEmitterBuilder\n# };\n# use fyrox::asset::manager::ResourceManager;\n# use fyrox::core::algebra::Vector3;\n# use fyrox::scene::graph::Graph;\n# use fyrox::scene::node::Node;\n# use fyrox::scene::transform::TransformBuilder;\n# use fyrox::core::color_gradient::{GradientPoint, ColorGradient};\n# use fyrox::scene::base::BaseBuilder;\n# use fyrox::core::color::Color;\n# use fyrox::resource::texture::Texture;\n# use std::path::Path;\n# use fyrox::resource::texture::TexturePixelKind;\nfn create_smoke(graph: &mut Graph, resource_manager: &mut ResourceManager, pos: Vector3) { ParticleSystemBuilder::new(BaseBuilder::new() .with_lifetime(5.0) .with_local_transform(TransformBuilder::new() .with_local_position(pos) .build())) .with_acceleration(Vector3::new(0.0, 0.0, 0.0)) .with_color_over_lifetime_gradient({ let mut gradient = ColorGradient::new(); gradient.add_point(GradientPoint::new(0.00, Color::from_rgba(150, 150, 150, 0))); gradient.add_point(GradientPoint::new(0.05, Color::from_rgba(150, 150, 150, 220))); gradient.add_point(GradientPoint::new(0.85, Color::from_rgba(255, 255, 255, 180))); gradient.add_point(GradientPoint::new(1.00, Color::from_rgba(255, 255, 255, 0))); gradient }) .with_emitters(vec![ SphereEmitterBuilder::new(BaseEmitterBuilder::new() .with_max_particles(100) .with_spawn_rate(50) .with_x_velocity_range(-0.01..0.01) .with_y_velocity_range(0.02..0.03) .with_z_velocity_range(-0.01..0.01)) .with_radius(0.01) .build() ]) .with_texture(resource_manager.request::(Path::new(\"data/particles/smoke_04.tga\"))) .build(graph);\n} This code creates smoke effect with smooth dissolving (by using color-over-lifetime gradient). Please refer to API docs for particle system for more information.","breadcrumbs":"Scene » Particle System Node » Using the code","id":"157","title":"Using the code"},"158":{"body":"If you need to create particle systems made in the editor, you can always use prefabs. Create a scene with desired particle system and then instantiate it to your scene.","breadcrumbs":"Scene » Particle System Node » Using prefabs","id":"158","title":"Using prefabs"},"159":{"body":"Fyrox used special technique, called soft particles, that smooths sharp transitions between particles and scene geometry: soft particles This technique especially useful for effects such as smoke, fog, etc. where you don't want to see the \"edge\" between particles and scene geometry. You can tweak this effect using Soft Boundary Sharpness Factor, the larger the value the more \"sharp\" the edge will be and vice versa.","breadcrumbs":"Scene » Particle System Node » Soft particles","id":"159","title":"Soft particles"},"16":{"body":"Powerful serialization system Almost every entity of the engine can be serialized No need to write your own serialization.","breadcrumbs":"Introduction » Introduction to Fyrox » Serialization","id":"16","title":"Serialization"},"160":{"body":"You can \"rewind\" particle systems in the \"initial\" state by calling particle_system.clear_particles() method, it will remove all generated particles and emission will start over.","breadcrumbs":"Scene » Particle System Node » Restarting emission","id":"160","title":"Restarting emission"},"161":{"body":"By default, every particle system is enabled. Sometimes there is a need to create a particle system, but not enable it (for example for some delayed effect). You can achieve this by calling particle_system.set_enabled(true/false) method. Disabled particle systems will still be drawn, but emission and animation will be stopped. To hide particle system completely, use particle_system.set_visibility(false) method.","breadcrumbs":"Scene » Particle System Node » Enabling or disabling particle systems","id":"161","title":"Enabling or disabling particle systems"},"162":{"body":"Particle systems using special renderer that optimized to draw millions of particles with very low overhead, however particles simulated on CPU side and may significantly impact overall performance when there are many particle systems with lots of particles in each.","breadcrumbs":"Scene » Particle System Node » Performance","id":"162","title":"Performance"},"163":{"body":"Current particle system implementation is not deterministic , this means that the state of the particles will be different at each run of your game. Also you cannot rewind the particle system, nor set a particular position in time. This fact limits potential usages of the particle system, however it is still useful for any effects that does not have to be deterministic, like sparks, smoke, steam, etc. This is a known issue, and it will eventually be fixed by adding a new kind of particle systems. Tracking issue could be found here . Particle systems does not interact with lighting, this means that particles will not be lit by light sources in the scene. The editor still (in 0.27) does not have an ability to edit color-over-lifetime curve, you should set it manually from code after particle system instantiation using respective method .","breadcrumbs":"Scene » Particle System Node » Limitations","id":"163","title":"Limitations"},"164":{"body":"Terrain is a scene node that represents uniform grid of cells where each cell can have different height. Other, commonly known name for terrain is heightmap. Terrains used to create maps for open-world games, it be used to create hills, mountains, plateau, roads, etc. terrain","breadcrumbs":"Scene » Terrain Node » Terrain","id":"164","title":"Terrain"},"165":{"body":"There are few basic concepts that you should understand before trying to use terrains. This will help you to understand design decisions and potential use cases.","breadcrumbs":"Scene » Terrain Node » Basic concepts","id":"165","title":"Basic concepts"},"166":{"body":"As it was already mentioned, terrain is a uniform grid where X and Z coordinates of cells have fixed values, while Y can change. In this case we can store only width, height and resolution numerical parameters to calculate X and Z coordinates, while Y is stored in a separate array which is then used to modify heights of cells. Such array is called heightmap . terrain mesh","breadcrumbs":"Scene » Terrain Node » Heightmap","id":"166","title":"Heightmap"},"167":{"body":"Layer is a material + mask applied to terrain's mesh. Mask is a separate, greyscale texture that defines in which parts of the terrain the material should be visible or not. White pixels in the mask makes the material to be visible, black - completely transparent, everything between helps you to create smooth transitions between layers. Here's a simple example of multiple layers: terrain layers layout There are 3 layers: 1 - dirt, 2 - grass, 3 - rocks and grass. As you can see, there are smooth transitions between each layer, it is achieved by layer's mask. Each layer uses separate material, which can be edited from respective property editor in the Inspector: terrain layer material","breadcrumbs":"Scene » Terrain Node » Layers","id":"167","title":"Layers"},"168":{"body":"You can create a terrain node by clicking Create -> Terrain. It will create a terrain with fixed width, height, and resolution (see limitations ). Once the terrain is created, select it in the World Viewer and click on Hill icon on the toolbar. This will enable terrain editing, brush options panel should also appear. See the picture below with all the steps: terrain editing The green rectangle on the terrain under the cursor represents current brush. You can edit brush options in the Brush Options window: brush options You can select a shape (either circle or rectangle with configurable size) and a mode (either modify the height map, or draw on mask of specific layer). When editing terrain's height, left mouse button raises height map, but if Shift key is pressed it lowers it instead. Something similar is applied to the mask editing - left mouse button draws, but if hold Shift - it will erase mask content.","breadcrumbs":"Scene » Terrain Node » Creating terrain in the editor","id":"168","title":"Creating terrain in the editor"},"169":{"body":"Terrain can always be created from code, here's comprehensive example of how to create and modify terrain from code: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::Vector2, algebra::Vector3, parking_lot::Mutex, pool::Handle,\n# sstorage::ImmutableString,\n# },\n# asset::manager::ResourceManager, resource::texture::Texture,\n# material::{shader::SamplerFallback, Material, PropertyValue, SharedMaterial},\n# rand::{thread_rng, Rng},\n# scene::{\n# base::BaseBuilder,\n# graph::Graph,\n# node::Node,\n# terrain::{Brush, BrushMode, BrushShape, Layer, TerrainBuilder},\n# },\n# };\n# use std::sync::Arc;\n# fn setup_layer_material( material: &mut Material, resource_manager: ResourceManager, diffuse_texture: &str, normal_texture: &str,\n) { material .set_property( &ImmutableString::new(\"diffuseTexture\"), PropertyValue::Sampler { value: Some(resource_manager.request::(diffuse_texture)), fallback: SamplerFallback::White, }, ) .unwrap(); material .set_property( &ImmutableString::new(\"normalTexture\"), PropertyValue::Sampler { value: Some(resource_manager.request::(normal_texture)), fallback: SamplerFallback::Normal, }, ) .unwrap(); material .set_property( &ImmutableString::new(\"texCoordScale\"), PropertyValue::Vector2(Vector2::new(10.0, 10.0)), ) .unwrap();\n} fn create_random_two_layer_terrain(graph: &mut Graph, resource_manager: &ResourceManager) -> Handle { let terrain = TerrainBuilder::new(BaseBuilder::new()) .with_layers(vec![ Layer { material: { let mut material = Material::standard_terrain(); setup_layer_material( &mut material, resource_manager.clone(), \"examples/data/Grass_DiffuseColor.jpg\", \"examples/data/Grass_NormalColor.jpg\", ); SharedMaterial::new(material) }, .. Default::default() }, Layer { material: { let mut material = Material::standard_terrain(); setup_layer_material( &mut material, resource_manager.clone(), \"examples/data/Rock_DiffuseColor.jpg\", \"examples/data/Rock_Normal.jpg\", ); SharedMaterial::new(material) }, .. Default::default() }, ]) .build(graph); let terrain_ref = graph[terrain].as_terrain_mut(); // Draw something on the terrain. for _ in 0..60 { let x = thread_rng().gen_range(4.0..60.00); let z = thread_rng().gen_range(4.0..60.00); let radius = thread_rng().gen_range(2.0..4.0); let height = thread_rng().gen_range(1.0..3.0); // Pull terrain. terrain_ref.draw(&Brush { center: Vector3::new(x, 0.0, z), shape: BrushShape::Circle { radius }, mode: BrushMode::ModifyHeightMap { amount: height }, }); // Draw rock texture on top. terrain_ref.draw(&Brush { center: Vector3::new(x, 0.0, z), shape: BrushShape::Circle { radius }, mode: BrushMode::DrawOnMask { layer: 1, alpha: 1.0, }, }); } terrain\n} As you can see there is quite a lot of code, ideally you should use editor all the times, because handling everything from code could be very tedious. The result of its execution (if all textures are set correctly) could be something like this (keep in mind that terrain will be random everytime you run the code): terrain from code","breadcrumbs":"Scene » Terrain Node » Creating terrain from code","id":"169","title":"Creating terrain from code"},"17":{"body":"Animation blending state machine - similar to Mecanim in Unity Engine. Animation retargetting - allows you to remap animation from one model to another.","breadcrumbs":"Introduction » Introduction to Fyrox » Animation","id":"17","title":"Animation"},"170":{"body":"By default, terrains does not have respective physical body and shape, it should be added manually. Create a static rigid body node with a collider with Heightmap shape ( learn more about colliders ). Then attach the terrain to the rigid body. Keep in mind that terrain's origin differs from Heightmap rigid body, so you need to offset the terrain to match its physical representation. Enable physics visualization in editor settings to see physical shapes and move terrain. Now to move the terrain you should move the body, instead of the terrain (because of parent-child relations ).","breadcrumbs":"Scene » Terrain Node » Physics","id":"170","title":"Physics"},"171":{"body":"Terrain rendering complexity have linear dependency with the amount of layers terrain have. Each layer forces the engine to re-render terrain's geometry with different textures and mask. Typical amount of layers is from 4 to 8. For example, a terrain could have the following layers: dirt, grass, rock, snow. This is a relatively lightweight scheme. In any case, you should measure frame time to understand how each new layer affects performance in your case.","breadcrumbs":"Scene » Terrain Node » Performance","id":"171","title":"Performance"},"172":{"body":"Terrain itself does not define any geometry or rendering data, instead it uses one or more chunks for that purpose. Each chunk could be considered as a \"sub-terrain\". You can \"stack\" any amount of chunks from any side of the terrain. To do that, you define a range of chunks along each axes. This is very useful if you need to extend your terrain in a particular direction. Imagine that you've created a terrain with just one chunk (0..1 range on both axes), but suddenly you found that you need to extend the terrain to add some new game locations. In this case you can change the range of chunks at the desired axis. For instance, if you want to add a new location to the right from your single chunk, then you should change width_chunks range to 0..2 and leave length_chunks as is (0..1). This way terrain will be extended, and you can start shaping the new location.","breadcrumbs":"Scene » Terrain Node » Chunking","id":"172","title":"Chunking"},"173":{"body":"Terrain has automatic LOD system, which means that the closest portions of it will be rendered with the highest possible quality (defined by the resolution of height map and masks), while the furthest portions will be rendered with the lowest quality. This effectively balances GPU load and allows you to render huge terrains with low overhead. The main parameter that affects LOD system is block_size (Terrain::set_block_size), which defines size of the patch that will be used for rendering. It is used to divide the size of the height map into a fixed set of blocks using quad-tree algorithm. Current implementation uses modified version of CDLOD algorithm without patch morphing. Apparently it is not needed, since bilinear filtration in vertex shader prevents seams to occur. Current implementation makes it possible to render huge terrains (64x64 km) with 4096x4096 heightmap resolution in about a millisecond on average low-to-middle-end GPU.","breadcrumbs":"Scene » Terrain Node » Level-of-detail","id":"173","title":"Level-of-detail"},"174":{"body":"There is no way to cut holes in the terrain yet, it makes impossible to create caves. There is also no way to create ledges, use separate meshes to imitate this. See tracking issue for more info.","breadcrumbs":"Scene » Terrain Node » Limitations and known issues","id":"174","title":"Limitations and known issues"},"175":{"body":"Camera is a special scene node that allows you to \"look\" at your scene from any point and with any orientation. Currently, the engine supports only perspective cameras, which could be represented as a frustum volume. Everything that \"intersects\" with the frustum will be rendered. Frustum","breadcrumbs":"Scene » Camera Node » Camera node","id":"175","title":"Camera node"},"176":{"body":"An instance of camera node could be created using CameraBuilder: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{base::BaseBuilder, camera::CameraBuilder, node::Node, Scene},\n# }; fn create_camera(scene: &mut Scene) -> Handle { CameraBuilder::new(BaseBuilder::new()) // Set some properties. .with_fov(80.0f32.to_radians()) .with_z_far(256.0) .build(&mut scene.graph)\n} Orientation and position should be set in BaseBuilder as usual.","breadcrumbs":"Scene » Camera Node » How to create","id":"176","title":"How to create"},"177":{"body":"Projection mode defines how your scene will look like after rendering, there are two projection modes available.","breadcrumbs":"Scene » Camera Node » Projection modes","id":"177","title":"Projection modes"},"178":{"body":"Perspective projection makes distant objects smaller and parallel lines converging when using it, it is the most common projection type for 3D games. By default, each camera uses perspective projection. It's defined by three parameters that describes frustum volume: Field of view angle Near clipping plane location Far clipping plane location Here is a simple example of how to create a camera with perspective projection: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# camera::{CameraBuilder, PerspectiveProjection, Projection},\n# graph::Graph,\n# node::Node,\n# },\n# };\nfn create_perspective_camera(graph: &mut Graph) -> Handle { CameraBuilder::new(BaseBuilder::new()) .with_projection(Projection::Perspective(PerspectiveProjection { // Keep in mind that field of view expressed in radians! fov: 60.0f32.to_radians(), z_near: 0.025, z_far: 1024.0, })) .build(graph)\n}","breadcrumbs":"Scene » Camera Node » Perspective","id":"178","title":"Perspective"},"179":{"body":"Orthographic projection prevents parallel lines from converging, it does not affect object size with distance. If you're making 2D games or isometric 3D games, this is the projection mode you're looking for. Orthographic projection defined by three parameters: Vertical Size Near Clipping Plane Far Clipping Plane Vertical size defines how large the \"box\" will be in vertical axis, horizontal size is derived from vertical size by multiplying vertical size with aspect ratio. Here is a simple example of how to create a camera with orthographic projection: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# camera::{CameraBuilder, OrthographicProjection, Projection},\n# graph::Graph,\n# node::Node,\n# },\n# };\nfn create_perspective_camera(graph: &mut Graph) -> Handle { CameraBuilder::new(BaseBuilder::new()) .with_projection(Projection::Orthographic(OrthographicProjection { vertical_size: 5.0, z_near: 0.025, z_far: 1024.0, })) .build(graph)\n}","breadcrumbs":"Scene » Camera Node » Orthographic","id":"179","title":"Orthographic"},"18":{"body":"Advanced asset manager. Fully asynchronous asset loading. PNG, JPG, TGA, DDS, etc. textures. FBX models loader. WAV, OGG sound formats. Compressed textures support (DXT1, DXT3, DTX5).","breadcrumbs":"Introduction » Introduction to Fyrox » Asset management","id":"18","title":"Asset management"},"180":{"body":"Each camera forces engine to re-render scene one more time, which can be very resource-intensive (both CPU and GPU) operation. To reduce GPU load, try to keep the Far Clipping Plane at lowest possible values. For example, if you're making a game with closed environment (lots of corridors, small rooms, etc.) set the Far clipping Plane to max possible distance that can be \"seen\" in your game - if the largest thing is a corridor, then set the Far clipping Plane to slightly exceed the length. This will force the engine to clip everything that is out of bounds and do not draw such objects.","breadcrumbs":"Scene » Camera Node » Performance","id":"180","title":"Performance"},"181":{"body":"Outdoor scenes usually have distant objects that can't be reached, these can be mountains, sky, distant forest, etc. such objects can be pre-rendered and then applied to a huge cube around camera, it always will be rendered first and will be the background of your scene. To create a Skybox and set it to a camera, you can use the following code: # extern crate fyrox;\n# use fyrox::{\n# core::{futures::executor::block_on, pool::Handle},\n# asset::manager::ResourceManager,\n# resource::texture::{Texture, TextureWrapMode},\n# scene::{\n# base::BaseBuilder,\n# camera::{CameraBuilder, SkyBox, SkyBoxBuilder},\n# node::Node,\n# Scene,\n# },\n# }; async fn create_skybox(resource_manager: ResourceManager) -> SkyBox { // Load skybox textures in parallel. let (front, back, left, right, top, bottom) = fyrox::core::futures::join!( resource_manager.request::(\"path/to/front.jpg\"), resource_manager.request::(\"path/to/back.jpg\"), resource_manager.request::(\"path/to/left.jpg\"), resource_manager.request::(\"path/to/right.jpg\"), resource_manager.request::(\"path/to/up.jpg\"), resource_manager.request::(\"path/to/down.jpg\") ); // Unwrap everything. let skybox = SkyBoxBuilder { front: Some(front.unwrap()), back: Some(back.unwrap()), left: Some(left.unwrap()), right: Some(right.unwrap()), top: Some(top.unwrap()), bottom: Some(bottom.unwrap()), } .build() .unwrap(); // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges // of the skybox. let skybox_texture = skybox.cubemap().unwrap(); let mut data = skybox_texture.data_ref(); data.set_s_wrap_mode(TextureWrapMode::ClampToEdge); data.set_t_wrap_mode(TextureWrapMode::ClampToEdge); skybox\n} fn create_camera(scene: &mut Scene, resource_manager: ResourceManager) -> Handle { CameraBuilder::new(BaseBuilder::new()) .with_skybox(block_on(create_skybox(resource_manager))) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Camera Node » Skybox","id":"181","title":"Skybox"},"182":{"body":"Color grading Look-Up Tables (LUT) allows you to transform color space of your frame. Probably everyone saw the famous \"mexican\" movie effect when everything becomes yellow-ish when action takes place in Mexico, this is done via color grading LUT effect. When used wisely, it can significantly improve perception of your scene. Here is the same scene having no color correction along with another case that has \"mexico\" color correction: Scene Look-up-table No Color Correction Neutral LUT With Color Correction Neutral LUT To use color grading LUT you could do something like this: # extern crate fyrox;\n# use fyrox::{\n# core::{futures::executor::block_on, pool::Handle},\n# asset::manager::ResourceManager, resource::texture::Texture,\n# scene::{\n# base::BaseBuilder,\n# camera::{CameraBuilder, ColorGradingLut},\n# node::Node,\n# Scene,\n# },\n# }; fn create_camera_with_lut( scene: &mut Scene, resource_manager: ResourceManager,\n) -> Handle { CameraBuilder::new(BaseBuilder::new()) .with_color_grading_enabled(true) .with_color_grading_lut( block_on(ColorGradingLut::new( resource_manager.request::(\"path/to/lut.jpg\"), )) .unwrap(), ) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Camera Node » Color grading look-up tables","id":"182","title":"Color grading look-up tables"},"183":{"body":"In some games you may need to do mouse picking of objects in your scene. To do that, at first you need to somehow convert a point on the screen to ray in the world. Camera has make_ray method exactly for that purpose: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector2, math::ray::Ray},\n# renderer::Renderer,\n# scene::camera::Camera,\n# };\n# fn make_picking_ray(camera: &Camera, point: Vector2, renderer: &Renderer) -> Ray { camera.make_ray(point, renderer.get_frame_bounds())\n} The ray then can be used to perform a ray cast over physics entities . This is the simplest way of camera picking, and you should prefer it most of the time.","breadcrumbs":"Scene » Camera Node » Picking","id":"183","title":"Picking"},"184":{"body":"Important : The following picking method is for advanced engine users only, if you don't know the math you should not use it. If you know the math and don't want to create physical entities, you can use this ray to perform manual ray intersection check: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::Vector3,\n# algebra::{Matrix4, Point3},\n# math::TriangleDefinition,\n# math::{ray::Ray, Vector3Ext},\n# },\n# scene::node::Node,\n# scene::mesh::{\n# buffer::{VertexAttributeUsage, VertexReadTrait},\n# surface::SurfaceData,\n# Mesh,\n# },\n# };\n# fn read_vertex_position(data: &SurfaceData, i: u32) -> Option> { data.vertex_buffer .get(i as usize) .and_then(|v| v.read_3_f32(VertexAttributeUsage::Position).ok())\n} fn transform_vertex(vertex: Vector3, transform: &Matrix4) -> Vector3 { transform.transform_point(&Point3::from(vertex)).coords\n} fn read_triangle( data: &SurfaceData, triangle: &TriangleDefinition, transform: &Matrix4,\n) -> Option<[Vector3; 3]> { let a = transform_vertex(read_vertex_position(data, triangle[0])?, transform); let b = transform_vertex(read_vertex_position(data, triangle[1])?, transform); let c = transform_vertex(read_vertex_position(data, triangle[2])?, transform); Some([a, b, c])\n} pub fn precise_ray_test( node: &Node, ray: &Ray, ignore_back_faces: bool,\n) -> Option<(f32, Vector3)> { let mut closest_distance = f32::MAX; let mut closest_point = None; if let Some(mesh) = node.query_component_ref::() { let transform = mesh.global_transform(); for surface in mesh.surfaces().iter() { let data = surface.data(); let data = data.lock(); for triangle in data .geometry_buffer .iter() .filter_map(|t| read_triangle(&data, t, &transform)) { if ignore_back_faces { // If normal of the triangle is facing in the same direction as ray's direction, // then we skip such triangle. let normal = (triangle[1] - triangle[0]).cross(&(triangle[2] - triangle[0])); if normal.dot(&ray.dir) >= 0.0 { continue; } } if let Some(pt) = ray.triangle_intersection_point(&triangle) { let distance = ray.origin.sqr_distance(&pt); if distance < closest_distance { closest_distance = distance; closest_point = Some(pt); } } } } } closest_point.map(|pt| (closest_distance, pt))\n} precise_ray_test is what you need, it performs precise intersection check with geometry of a mesh node. It returns a tuple of the closest distance and the closest intersection point.","breadcrumbs":"Scene » Camera Node » Advanced picking","id":"184","title":"Advanced picking"},"185":{"body":"(WIP)","breadcrumbs":"Scene » Camera Node » Exposure and HDR","id":"185","title":"Exposure and HDR"},"186":{"body":"Decal nodes allow you to \"project\" a texture onto your scene within some specific bounds. It is widely used for bullet holes, blood splatter, dirt, cracks and so on. Here is the example of the decal applied to the scene: Decal The rust marks are applied on existing geometry of the scene by projecting a rust texture in specific direction.","breadcrumbs":"Scene » Decal Node » Decal node","id":"186","title":"Decal node"},"187":{"body":"A decal instance can be created using DecalBuilder: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::texture::Texture,\n# scene::{base::BaseBuilder, decal::DecalBuilder, node::Node, Scene},\n# }; fn create_decal(scene: &mut Scene, resource_manager: ResourceManager) -> Handle { DecalBuilder::new(BaseBuilder::new()) .with_diffuse_texture(resource_manager.request::(\"path/to/your/decal.png\")) .build(&mut scene.graph)\n}","breadcrumbs":"Scene » Decal Node » How to create","id":"187","title":"How to create"},"188":{"body":"You can specify which textures the decal will be projecting, currently there is only diffuse and normal maps supported.","breadcrumbs":"Scene » Decal Node » Textures","id":"188","title":"Textures"},"189":{"body":"Currently, the engine supports only deferred decals , which means that decals modify the information stored in G-Buffer. This fact means that decals will be lit correctly with other geometry in the scene. However, if you have some objects in your scene that uses forward rendering path, your decals won't be applied to them.","breadcrumbs":"Scene » Decal Node » Rendering","id":"189","title":"Rendering"},"19":{"body":"A* pathfinder. Navmesh. Behavior trees.","breadcrumbs":"Introduction » Introduction to Fyrox » Artificial Intelligence (AI)","id":"19","title":"Artificial Intelligence (AI)"},"190":{"body":"Decal uses Object-Oriented Bounding Box (OOB) to determine pixels on which decal's textures will be projected, everything that got into OOB will be covered. Exact bounds can be set by tweaking local transform of a decal. If you want your decal to be larger, set its scale to some large value. To position a decal - use local position, to rotate - local rotation. A decal defines a cube that projects a texture on every pixel of a scene that got into the cube. Exact cube size is defined by decal's local scale. For example, if you have a decal with scale of (1.0, 2.0, 0.1) then the size of the cube (in local coordinates) will be width = 1.0, height = 2.0 and depth = 0.1. The decal can be rotated as any other scene node. Its final size and orientation are defined by the chain of transformations of parent nodes.","breadcrumbs":"Scene » Decal Node » Bounds","id":"190","title":"Bounds"},"191":{"body":"There are situations when you want to prevent some geometry from being covered with a decal, to do that the engine offers a concept of layers. A decal will be applied to a geometry if and only if they have matching layer index. This allows you to create environment damage decals and they won't affect dynamic objects since they're located on different layers.","breadcrumbs":"Scene » Decal Node » Layers","id":"191","title":"Layers"},"192":{"body":"Current implementation of decals is relatively cheap, this allows you to create many decals on scene. However, you should keep the amount of decals at a reasonable level.","breadcrumbs":"Scene » Decal Node » Performance","id":"192","title":"Performance"},"193":{"body":"Rectangle is the simplest \"2D\" node, it can be used to create \"2D\" graphics. 2D is in quotes here because the node is actually a 3D node, like everything else in the engine. Here is an example scene made with the rectangle nodes and an orthographic camera: 2d scene As you can see it is a good basis for 2D games.","breadcrumbs":"Scene » Rectangle Node » Rectangle node","id":"193","title":"Rectangle node"},"194":{"body":"Use the RectangleBuilder to create Rectangle nodes: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, color::Color, pool::Handle},\n# asset::manager::ResourceManager, resource::texture::Texture,\n# scene::{\n# base::BaseBuilder, dim2::rectangle::RectangleBuilder, graph::Graph, node::Node,\n# transform::TransformBuilder,\n# },\n# };\nfn create_rect(graph: &mut Graph, resource_manager: ResourceManager) -> Handle { RectangleBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() // Size of the rectangle is defined only by scale. .with_local_scale(Vector3::new(0.4, 0.2, 1.0)) .build(), ), ) .with_color(Color::RED) .with_texture(resource_manager.request::(\"path/to/your_texture.jpg\")) .build(graph)\n}","breadcrumbs":"Scene » Rectangle Node » How to create","id":"194","title":"How to create"},"195":{"body":"By default, Rectangle node uses entire image for rendering, but for some applications it is not enough. For example, you may want to use sprite sheets to animate your 2D entities. In this case you need to be able to use only portion of an image. It is possible to do by using set_uv_rect method of the Rectangle node. Here's an example of setting right-top quarter of an image to be used by a Rectangle node: # extern crate fyrox;\n# use fyrox::{core::math::Rect, scene::dim2::rectangle::Rectangle};\n# fn set_2nd_quarter_image_portion(rectangle: &mut Rectangle) { rectangle.set_uv_rect(Rect::new( 0.5, // Offset by 50% to the right 0.0, // No need to offset to bottom. 0.5, // Use half (50%) of width and height 0.5, ));\n} Keep in mind that every part of uv rectangle is proportional. For example 0.5 means 50%, 1.5 = 150% and so on. If width or height is exceeding 1.0 and the texture being used is set to Wrapping mode at respective axis, the image will tile across axes.","breadcrumbs":"Scene » Rectangle Node » Specifying image portion for rendering","id":"195","title":"Specifying image portion for rendering"},"196":{"body":"Rectangles use specialized renderer that is heavily optimized to render tons of rectangles at once, so you can use rectangles almost for everything in 2D games.","breadcrumbs":"Scene » Rectangle Node » Performance","id":"196","title":"Performance"},"197":{"body":"Rectangle nodes does not support custom materials - it is a simplified version of a Mesh node that allows you draw a rectangle with a texture and a color. Its main purpose is to be able to start making games as quick as possible without diving too deep into details (shaders, render passes, etc.). You can still create a \"rectangle\" with custom material, use Mesh node with single rectangle surface: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{Matrix4, Vector3},\n# parking_lot::Mutex,\n# pool::Handle,\n# },\n# material::{Material, SharedMaterial},\n# scene::{\n# base::BaseBuilder,\n# graph::Graph,\n# mesh::{\n# surface::{SurfaceBuilder, SurfaceData, SurfaceSharedData},\n# MeshBuilder, RenderPath,\n# },\n# node::Node,\n# transform::TransformBuilder,\n# },\n# };\n# use std::sync::Arc; fn create_rect_with_custom_material( graph: &mut Graph, material: SharedMaterial,\n) -> Handle { MeshBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_scale(Vector3::new(0.4, 0.2, 1.0)) .build(), ), ) .with_surfaces(vec![SurfaceBuilder::new(SurfaceSharedData::new( SurfaceData::make_quad(&Matrix4::identity()), )) .with_material(material) .build()]) .with_render_path(RenderPath::Forward) .build(graph)\n} This will effectively \"mimic\" the Rectangle node, but will allow you to use the full power of custom shaders. Keep in mind that Mesh nodes will be rendered via Deferred Renderer, while Rectangle nodes rendered with specialized renderer, that might result in some graphical artifacts. Rectangle nodes has limited lighting support, it means that they still will be lit by standard scene lights, but it will be a very simple diffuse lighting without any \"physically correct\" lighting. This is perfectly ok for 95% of 2D games, if you want to add custom lighting then you should use custom shader. Rectangle nodes works well with 2D physics nodes, check 2D physics section of the book for more info.","breadcrumbs":"Scene » Rectangle Node » Limitations","id":"197","title":"Limitations"},"198":{"body":"Sometimes there is a need to have custom scene nodes, it is possible to do, but it requires quite a lot of boilerplate code. # extern crate fyrox;\nuse fyrox::{ core::{ reflect::prelude::*, math::aabb::AxisAlignedBoundingBox, pool::Handle, uuid::{uuid, Uuid}, variable::InheritError, visitor::prelude::*, }, asset::manager::ResourceManager, scene::{ base::Base, node::{Node, NodeTrait}, },\n};\nuse std::ops::{Deref, DerefMut}; #[derive(Clone, Reflect, Visit, Debug)]\npub struct CustomNode { base: Base,\n} impl Deref for CustomNode { type Target = Base; fn deref(&self) -> &Self::Target { &self.base }\n} impl DerefMut for CustomNode { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.base }\n} impl NodeTrait for CustomNode { fyrox::impl_query_component!(); fn local_bounding_box(&self) -> AxisAlignedBoundingBox { self.base.local_bounding_box() } fn world_bounding_box(&self) -> AxisAlignedBoundingBox { self.base.world_bounding_box() } fn id(&self) -> Uuid { // Provide unique id for serialization needs. It must be unique, use https://www.uuidgenerator.net/ // to generate one. uuid!(\"f592e7f7-5e34-4043-9226-407c7457bb48\") }\n} Once the node is defined, you can create is as usual and put in the graph: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{camera::Camera, graph::Graph, node::Node},\n# };\n# type CustomNode = Camera;\n# fn add_custom_node(graph: &mut Graph) -> Handle { graph.add_node(Node::new(CustomNode::default()))\n}","breadcrumbs":"Scene » Custom Node » Custom Scene Node","id":"198","title":"Custom Scene Node"},"199":{"body":"Scene nodes have no access to outer context, this means that you cannot reference any data that is located outside graph easily. You still can define a global variable that will be accessible, but it is considered as a hack and should be avoided. If you want to add custom logic to scene nodes, then you should use scripts instead. Custom nodes are intended for very specific use cases, such as adding \"data sources\" for renderer, etc.","breadcrumbs":"Scene » Custom Node » Limitations","id":"199","title":"Limitations"},"2":{"body":"Almost every chapter in this book can be read in any order, but we recommend reading Chapters 1, 2, 3 (they're quite small) and then going through Platformer Tutorial (2D) while learning more about specific areas that interest you from the other chapters. There is also a First-Person Shooter Tutorial (3D) , but it is based on framework which considered obsolete, yet it is still very helpful.","breadcrumbs":"About the Book » How to read the book","id":"2","title":"How to read the book"},"20":{"body":"Advanced node-based UI with lots of widgets. More than 32 widgets Powerful layout system. Full TTF/OTF fonts support. Based on message passing. Fully customizable. GAPI-agnostic. OS-agnostic. Button widget. Border widget. Canvas widget. Color picker widget. Color field widget. Check box widget. Decorator widget. Drop-down list widget. Grid widget. Image widget. List view widget. Popup widget. Progress bar widget. Scroll bar widget. Scroll panel widget. Scroll viewer widget. Stack panel widget. Tab control widget. Text widget. Text box widget. Tree widget. Window widget. File browser widget. File selector widget. Docking manager widget. NumericUpDown widget. Vector3 editor widget. Menu widget. Menu item widget. Message box widget. Wrap panel widget. Curve editor widget. User defined widget.","breadcrumbs":"Introduction » Introduction to Fyrox » User Interface (UI)","id":"20","title":"User Interface (UI)"},"200":{"body":"For now, you cannot create custom nodes from the editor. This will be available in future versions of the engine; when editor plugins will be supported.","breadcrumbs":"Scene » Custom Node » Editor support","id":"200","title":"Editor support"},"201":{"body":"The engine have full-featured physics engine under the hood (Rapier), it helps you to simulate physics in your games. There is first-class support for both 2D and 3D physics. There are three main physics entities in the engine: Rigid Body - responsible for rigid body dynamics simulation, must have at least one collider to be able to interact with other rigid bodies in the world. Collider - responsible for collision detection. Joint - responsible for motion restriction between two rigid bodies. All these entities are ordinary scene nodes, so they can be arranged into any hierarchy in the scene. However there some rules that have to be followed to make physics simulation work as intended: Rigid body node must have at least one direct child Collider node, otherwise rigid body won't interact with other rigid bodies in the world. Joint node must have two direct child rigid bodies, otherwise joint will have no effect.","breadcrumbs":"Scene » Physics » Physics","id":"201","title":"Physics"},"202":{"body":"There is a very few differences between 3D and 2D physics, the most obvious is that 2D physics does simulation only in oXY plane (the plane of the screen). 2D physics has less collider shapes available since some 3D shapes degenerate in 2D, for example cylinder 3D shape in 2D is just a rectangle. There is also lesser amount of joints available in 2D, there is no revolute joint for example. Unlike 3D physics entities, 2D physics entities exist in the separate scene::dim2 module.","breadcrumbs":"Scene » Physics » Differences between 3D and 2D","id":"202","title":"Differences between 3D and 2D"},"203":{"body":"Rigid body node is the one of main physical entities in the engine. Rigid body nodes can be affected by gravity, external forces and other rigid bodies. Use rigid body node everywhere you need natural physical behaviour for your objects.","breadcrumbs":"Scene » Physics » Rigid Body » Rigid body node","id":"203","title":"Rigid body node"},"204":{"body":"Use RigidBodyBuilder to create a rigid body instance: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{\n# base::BaseBuilder,\n# collider::{ColliderBuilder, ColliderShape},\n# graph::Graph,\n# node::Node,\n# rigidbody::RigidBodyBuilder,\n# },\n# };\nfn create_cube_rigid_body(graph: &mut Graph) -> Handle { RigidBodyBuilder::new(BaseBuilder::new().with_children(&[ // Rigid body must have at least one collider ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::cuboid(0.5, 0.5, 0.5)) .build(graph), ])) .with_mass(2.0) .with_lin_vel(Vector3::new(0.0, 3.0, 1.0)) .build(graph)\n}","breadcrumbs":"Scene » Physics » Rigid Body » How to create","id":"204","title":"How to create"},"205":{"body":"Rigid body must have at least one collider to participate in simulation properly, multiple colliders can be used to create complex shapes from simple shapes, you can create concave objects this way. Every collider must be a direct child node of a rigid body. In the editor it could look like this: colliders Note that, Box node here is an instance of Rigid Body 2D, and it has Collider 2D as a child and some sprite. This structure (when a rigid body has a collider as a child) is mandatory for physics engine to work correctly! Collider won't work (participate in physical simulation) without a rigid body and a rigid body won't work without a collider. This applied to both 2D and 3D. Keep in mind, that your graphical representation of an object (some node like Mesh, Sprite, etc.) must be attached to a rigid body. Otherwise, the rigid body will move, but the graphical representation won't. You can also arrange it other way around: a graphical node can have rigid body with a collider, but that requires the rigid body to be kinematic. This is used to create hit boxes , or any other things that should have physical representation, but move together with graphical node.","breadcrumbs":"Scene » Physics » Rigid Body » Colliders","id":"205","title":"Colliders"},"206":{"body":"You can apply forces and torque to any rigid body, but only dynamic bodies will be affected. There is two ways of applying force to a rigid body: at center of mass or at particular point at the body: # extern crate fyrox;\n# use fyrox::{core::algebra::Vector3, scene::rigidbody::RigidBody};\nfn apply_force_and_torque(rigid_body: &mut RigidBody) { // Push rigid body forward at the center of mass. rigid_body.apply_force(Vector3::new(0.0, 0.0, 1.0)); // Kick rigid body at the side (this will also make it rotate) rigid_body.apply_force_at_point(Vector3::new(0.0, 0.0, 1.0), Vector3::new(1.0, 0.0, 0.0)); // Turn rigid body around center of mass. rigid_body.apply_torque(Vector3::new(0.0, 3.0, 0.0));\n}","breadcrumbs":"Scene » Physics » Rigid Body » Force and torque","id":"206","title":"Force and torque"},"207":{"body":"Sometimes you may want to have direct control over position/rotation of a rigid body and tell the physics engine to not do simulation for the body. This can be achieved by making the rigid body kinematic : # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# scene::{\n# base::BaseBuilder,\n# collider::{ColliderBuilder, ColliderShape},\n# graph::Graph,\n# node::Node,\n# rigidbody::{RigidBodyBuilder, RigidBodyType},\n# },\n# }; fn create_kinematic_rigid_body(graph: &mut Graph) -> Handle { RigidBodyBuilder::new(BaseBuilder::new().with_children(&[ // Rigid body must have at least one collider ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::cuboid(0.5, 0.5, 0.5)) .build(graph), ])) .with_body_type(RigidBodyType::KinematicPositionBased) .build(graph)\n}","breadcrumbs":"Scene » Physics » Rigid Body » Kinematic rigid bodies","id":"207","title":"Kinematic rigid bodies"},"208":{"body":"Fast-moving rigid bodies can \"fly through\" other objects (for example a bullet can completely ignore walls if it is moving too fast), this happens because of discrete calculation. This can be fixed by using continuous collision detection, to enable it use either .with_ccd_enabled(state) of RigidBodyBuilder or .set_ccd_enabled(state) of RigidBody.","breadcrumbs":"Scene » Physics » Rigid Body » Continuous collision detection","id":"208","title":"Continuous collision detection"},"209":{"body":"Dominance allows you to set a priority of forces applied to rigid bodies. It defines which rigid body can affect what rigid body, for example you can set the highest dominance for actors and leave dominance of everything else at zero, this way actors will be able to push any other dynamic bodies, but dynamic bodies won't affect actors. This is useful when you don't want your actors be pushed by surrounding objects (like if someone throws a box at an actor, it will stay still if it has higher dominance)","breadcrumbs":"Scene » Physics » Rigid Body » Dominance","id":"209","title":"Dominance"},"21":{"body":"Advanced physics (thanks to the rapier physics engine) Rigid bodies. Rich set of various colliders. Joints. Ray cast. Many other useful features. 2D support.","breadcrumbs":"Introduction » Introduction to Fyrox » Physics","id":"21","title":"Physics"},"210":{"body":"2D rigid bodies have no difference with 3D, except the simulation happens in oXY plane and Z coordinate is ignored.","breadcrumbs":"Scene » Physics » Rigid Body » 2D rigid bodies","id":"210","title":"2D rigid bodies"},"211":{"body":"Collider is a geometrical shape that is used for collision detection, contact manifold generation, etc. Colliders are used in pair with rigid bodies, they make rigid body participate in collisions. Important: Colliders only works in pair with rigid bodies! Colliders won't be used by the engine, unless they're direct children of a rigid body. Read this chapter for more info.","breadcrumbs":"Scene » Physics » Collider » Collider node","id":"211","title":"Collider node"},"212":{"body":"Collider can have almost any shape, the engine offers the following shapes for 3D: Ball - dynamic sphere shape. Cylinder - dynamic cylinder shape. Cone - dynamic cone shape. Cuboid - dynamic box shape. Capsule - dynamic capsule shape. Segment - dynamic segment (\"line\") shape Triangle - simple dynamic triangle shape Triangle mesh - static concave shape, can be used together with any static level geometry (wall, floors, ceilings, anything else) Height field - static height field shape, can be used together with terrains. Polyhedron - dynamic concave shape. Also, there is a similar, but smaller set for 2D (because some shapes degenerate in 2D): Ball - dynamic circle shape. Cuboid - dynamic rectangle shape. Capsule - dynamic capsule shape. Segment - dynamic segment (\"line\") shape. Triangle - dynamic triangle shape. Trimesh - static triangle mesh shape. Heightfield - static height field shape. Dynamic in both lists means that such shapes can be used together with dynamic rigid bodies, they'll correctly handle all collisions and simulation will look as it should. Static means that such shape should be used only with static rigid bodies.","breadcrumbs":"Scene » Physics » Collider » Shapes","id":"212","title":"Shapes"},"213":{"body":"Use ColliderBuilder to create an instance of collider from code with any shape you want. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# collider::{ColliderBuilder, ColliderShape},\n# graph::Graph,\n# node::Node,\n# },\n# };\nfn create_capsule_collider(graph: &mut Graph) -> Handle { ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::capsule_y(0.5, 0.2)) .with_friction(1.0) .build(graph)\n} In the editor you can use MainMenu -> Create -> Physics -> Collider, or right-click on a node in World Viewer and select Add Child -> Physics -> Collider. Collider must be direct child of a rigid body, colliders do nothing on their own!","breadcrumbs":"Scene » Physics » Collider » How to create","id":"213","title":"How to create"},"214":{"body":"Sometimes there's a need to prevent collision between various groups of colliders. Fyrox supports bit-wise collision filtering exactly for this purpose. For instance, you may have two groups of colliders: actors and powerups, and you want the actors to completely ignore collisions with powerups (and vice versa). In this case you can set collision groups for actors like so: actors collision groups And set the collision groups for powerups like so: powerups collision groups As you can see, actors and powerups now have separate memberships (read - groups) and filters. This way, the actors will collide with everything, but powerups and vice versa.","breadcrumbs":"Scene » Physics » Collider » Collision filtering","id":"214","title":"Collision filtering"},"215":{"body":"You can use colliders to simulate hit boxes for your game characters. It can be done by creating a rigid body with KinematicPositionBased type and an appropriate collider as a child node. As the last step you need to attach the body to a bone in your character's model. Here's a quick example from the editor: hitbox As you can see, the rigid body has a capsule collider as a child and the body is attached to the neck bone. The body has KinematicPositionBased type, which will ensure that the body won't be simulated, instead its position will be synchronized with the position of the parent bone. To actually use the hit boxes in your game, you can either use a ray-casting to perform a hit scan or you can use contacts information to fetch the stuff with which a hit box was contacted. See Ray casting chapter of the section.","breadcrumbs":"Scene » Physics » Collider » Using colliders for hit boxes","id":"215","title":"Using colliders for hit boxes"},"216":{"body":"Joint is a configurable link between two rigid bodies, it restricts relative motion of two bodies. Fyrox provides a fixed set of joints that are suitable for various applications. Fixed Joint - hard link between two bodies, it is the same is if two rigid bodies were \"welded\" to each other with a metal rod. Revolute Joint - restricts all translational movement and any rotations around Y and Z axes, but leaves rotation around local X axis free. An example of the joint from real world is a door hinge, it allows the door to rotate around single axis, but not move. Prismatic Joint - restricts all rotations, movement is allowed along single axis (local X of the joint). An example of the joint from real world could be a slider that supports drawers on a table. Ball Joint - restricts all movement, but leaves rotations unrestricted. An example of a ball joint from real world could be human shoulder. 2D joints does not have revolute joints, because it degenerates into ball joint.","breadcrumbs":"Scene » Physics » Joint » Joint","id":"216","title":"Joint"},"217":{"body":"When the joint is created and all bodies are set to it, it uses self global transform and bodies global transforms to calculate local frames for bodies. This process is called binding , it happens once when the joint is created, but can be initiated by moving the joint to some other position by changing local transform of the joint.","breadcrumbs":"Scene » Physics » Joint » Bodies Binding","id":"217","title":"Bodies Binding"},"218":{"body":"To create a joint from code use JointBuilder: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{\n# base::BaseBuilder,\n# graph::Graph,\n# joint::{BallJoint, JointBuilder, JointParams},\n# node::Node,\n# },\n# };\nfn create_joint(graph: &mut Graph, body1: Handle, body2: Handle) -> Handle { JointBuilder::new(BaseBuilder::new()) .with_body1(body1) .with_body2(body2) .with_params(JointParams::BallJoint(BallJoint { x_limits_enabled: false, x_limits_angles: Default::default(), y_limits_enabled: false, y_limits_angles: Default::default(), z_limits_enabled: false, z_limits_angles: Default::default(), })) .build(graph)\n} Once the joint is created, it will bind given bodies, using the process describe in the above section. To create a joint from editor, use MainMenu -> Create -> Physics -> Joint, select the new joint and find Body1 and Body2 properties. Assign the fields by holding Alt key and drag'n'drop a rigid body to a field. Move the joint to correct position to ensure the binding will happen as intended.","breadcrumbs":"Scene » Physics » Joint » How to create","id":"218","title":"How to create"},"219":{"body":"You can restrict motion on primary joint axis (rotational and translational) by setting a limit to desired axis. Ball Joint have three angular limits, one per rotation around an axis. The angle range is given in radians. Prismatic Joint have only one limit it is maximum linear distance between two bodies along primary joint axis. Revolute Joint have a single angular limit around primary axis. The angle range is given in radians. Fixed Joint does not have any limit setting, because it locks all degrees of freedom.","breadcrumbs":"Scene » Physics » Joint » Limits","id":"219","title":"Limits"},"22":{"body":"As any other software, Fyrox has its own system requirements that will provide the best user experience. CPU - at least 2 core CPU with 1.5 GHz per each core. The more is better. GPU - any relatively modern GPU with OpenGL 3.3+ support. If the editor fails to start, then it is most likely your video card does not support OpenGL 3.3+. Do not try to run the editor on virtual machines, pretty much all of them have rudimentary support for graphics APIs which won't let you run the editor. RAM - at least 1 Gb of RAM. The more is better. VRAM - at least 256 Mb of video memory. It highly depends on your game.","breadcrumbs":"Introduction » System Requirements and Supported Platforms » System Requirements","id":"22","title":"System Requirements"},"220":{"body":"Joints can be used to create many game entities, such as doors, chains and rag dolls. The most interesting here is rag doll. It is used to create realistic behaviour for humans and creatures in games. In general, it is a set of rigid bodies, colliders and joints. Where each joint configured to match joints of a creature, for example ball joint could be used for shoulders, revolute joints for knees and elbows.","breadcrumbs":"Scene » Physics » Joint » Usage","id":"220","title":"Usage"},"221":{"body":"Ray casting allows you to query intersections of a ray with rigid bodies in a scene. Typical usage for ray casting is hit-scan weapons (weapons that shoots high-speed projectiles), AI collision avoidance, etc. To query intersections, use physics world instance of a scene graph: # extern crate fyrox;\n# use fyrox::{\n# core::algebra::{Point3, Vector3},\n# scene::graph::{\n# physics::{Intersection, RayCastOptions},\n# Graph,\n# },\n# };\n# fn do_ray_cast(graph: &mut Graph, begin: Vector3, end: Vector3) -> Vec { let mut buffer = Vec::new(); let ray_direction = end - begin; graph.physics.cast_ray( RayCastOptions { ray_origin: Point3::from(begin), ray_direction, max_len: ray_direction.norm(), groups: Default::default(), sort_results: true, }, &mut buffer, ); buffer\n} The function above will return a collection of intersections that are sorted by intersection distance (a distance from beginning of the ray to an intersection point). Each intersection is represented by the following structure: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{Point3, Vector3},\n# pool::Handle,\n# },\n# scene::{graph::physics::FeatureId, node::Node},\n# };\npub struct Intersection { pub collider: Handle, pub normal: Vector3, pub position: Point3, pub feature: FeatureId, pub toi: f32,\n} collider - a handle of the collider with which intersection was detected. To obtain a handle to rigid body, borrow the collider and fetch its parent field: graph[collider].parent(). normal - a normal at the intersection position in world coordinates. position - a position of the intersection in world coordinates. feature - additional data that contains a kind of the feature with which intersection was detected as well as its index. FeatureId::Face might have index that is greater than amount of triangles in a triangle mesh, this means that intersection was detected from \"back\" side of a face. To \"fix\" that index, simply subtract amount of triangles of a triangle mesh from the value. toi - (time of impact) a distance from ray's origin to position.","breadcrumbs":"Scene » Physics » Ray Casting » Ray Casting","id":"221","title":"Ray Casting"},"222":{"body":"As you might've noticed, the function above return Vec which allocates intersections on heap. This is relatively slow and could be sped up a lot by using static array on stack: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{Point3, Vector3},\n# arrayvec::ArrayVec,\n# },\n# scene::graph::{\n# physics::{Intersection, RayCastOptions},\n# Graph,\n# },\n# };\n# fn do_static_ray_cast( graph: &mut Graph, begin: Vector3, end: Vector3,\n) -> ArrayVec { let mut buffer = ArrayVec::::new(); let ray_direction = end - begin; graph.physics.cast_ray( RayCastOptions { ray_origin: Point3::from(begin), ray_direction, max_len: ray_direction.norm(), groups: Default::default(), sort_results: true, }, &mut buffer, ); buffer\n} fn usage_example(graph: &mut Graph, begin: Vector3, end: Vector3) { // Fetch first 32 intersections. dbg!(do_static_ray_cast::<32>(graph, begin, end));\n} usage_example shows how to use the do_static_ray_cast function - all you need to do is to specify maximum amount of intersections you're interested in as a generic parameter.","breadcrumbs":"Scene » Physics » Ray Casting » Avoiding unnecessary allocations","id":"222","title":"Avoiding unnecessary allocations"},"223":{"body":"Ragdoll physics is a sort of procedural animation, that allows you to create naturally looking death animations and body physics in general. Ragdoll is just an arbitrary combination of rigid bodies, colliders, joints. Rigid bodies and colliders define physical \"boundaries\" for limbs of your character, while joints restrict relative motion (linear and rotational).","breadcrumbs":"Scene » Physics » Ragdoll » Ragdoll","id":"223","title":"Ragdoll"},"224":{"body":"Creating a ragdoll manually is a very tedious procedure, you need to create rigid bodies and colliders for every body part of your character, place them correctly, adjust their size, etc. Then you need to create a set of joints, that connects body parts, and then setup linear and angular limits. To save time, Fyrox has a special tool called Ragdoll Wizard: ragdoll wizard It can be opened from Utils menu and contains quite a lot of node handle fields that needs to be filled. Thankfully, there's an Autofill button, by pressing which, the wizard will try to find respective bones of the skeleton and put their handles in the respective fields in the wizard. For now, it is configured to work with mixamo skeletons. Other parameters are listed below: Total Mass - total mass of the ragdoll, it will be used to configure masses of rigid bodies of body parts. Use CCD - a flag, that defines whether the continuous collision detection (CCD) for body parts should be used or not. It is advised to keep this flag on, otherwise body parts might get stuck or fall through the floor, leading to \"explosive\" ragdoll behaviour. Can Sleep - a flag, that defines whether the body parts can \"sleep\" or not. Sleep in this case means, that a body part can be excluded from physical simulation if it is not moving for some time. Collision Groups and Solver Groups could be used to configure collision filtering . It is very important in case if your character has a physical capsule, that is used to \"standard\" character physics. In this case body parts must ignore physical capsule (and vice versa), otherwise your ragdoll will \"explode\". After everything is filled in, you can click OK button and if everything is correct, you should see a bunch of new scene nodes in the world viewer, located under a Ragdoll scene node: ragdoll result As you can see, the amount of entities you'd have to create and configure manually is quite high. Keep in mind, that ragdoll wizard can't generate perfect ragdoll, because of lack of information. The generated ragdoll will most likely require some minor tweaks (mostly joint angular limits).","breadcrumbs":"Scene » Physics » Ragdoll » How To Create","id":"224","title":"How To Create"},"225":{"body":"There's one video tutorial about ragdoll wizard, it also shows the final results in game:","breadcrumbs":"Scene » Physics » Ragdoll » Video Tutorials","id":"225","title":"Video Tutorials"},"226":{"body":"Fyrox has quite powerful and flexible audio system which will be covered in this chapter. Basic \"building blocks\" are sound sources, sound buffers, audio processing buses with various sound effects, sound context. Read the next chapters to learn more.","breadcrumbs":"Scene » Sound » Sound System","id":"226","title":"Sound System"},"227":{"body":"Audio bus is an audio processing unit that takes audio samples from any number of sound sources and passes them through a chain of effects (zero or more). Processed samples then can be either sent to an audio playback device (speakers, headphones, etc.) or to some other audio bus. There's always one audio bus (primary) that sends its data to an audio playback device, every other audio buses are considered secondary.","breadcrumbs":"Scene » Sound » Audio Bus » Audio Bus","id":"227","title":"Audio Bus"},"228":{"body":"As stated above, any audio bus (except primary), can output its audio samples to some other audio bus (primary or secondary). Such relationship forms an audio bus graph: data flow diagram As you can see, there can be any number of sound sources which attached to the respective audio buses. Each audio bus can have any number of effects (such as lowpass, highpass, etc. filtering; reverb effect and more). Finally, each audio bus is connected to some other audio bus. Such complex audio processing structure allows you to create pretty much any sound environment. For example, you can create an audio bus with a reverb effect, that will represent a huge hangar with lots of echoes. Then you attach all sound sources located in this \"hangar\" to the audio bus and your sound sources will sound more naturally, according to environment.","breadcrumbs":"Scene » Sound » Audio Bus » Graph","id":"228","title":"Graph"},"229":{"body":"Audio bus can have zero or more audio processing effects. The effects applied one after another (see the arrows on the picture above). You can set any of the following effects: Attenuation - changes \"volume\" of input sound samples. Reverb - adds echoes, early and late reflections. Could be used to simulate environment with high reflectivity (hangars, parking lots, etc.) Low Pass Filter - passes all frequencies below the specified cut-off frequency. High Pass Filter - passes all frequencies above the specified cut-off frequency. Band Pass Filter - passes all frequencies in a given range around the specified cut-off frequency. All Pass Filter - shifts phase of the signal by 90 degrees at the specified cut-off frequency. Low Shelf Filter - reduces amplitude of frequencies in a shape like this ̅ _ at the cutoff frequency. High Shelf Filter - reduces amplitude of frequencies in a shape like this _/̅ at the cutoff frequency.","breadcrumbs":"Scene » Sound » Audio Bus » Effects","id":"229","title":"Effects"},"23":{"body":"Platform Engine Editor Windows ✅ ✅ Linux ✅ ✅ macOS ✅¹ ✅ WebAssembly ✅ ❌² Android ✅ ❌² ✅ - first-class support ❌ - not supported ¹ - macOS suffers from bad GPU performance on Intel chipsets, M1+ works well. ² - the editor works only on PC, it requires rich filesystem functionality as well as decent threading support.","breadcrumbs":"Introduction » System Requirements and Supported Platforms » Supported Platforms","id":"23","title":"Supported Platforms"},"230":{"body":"In the editor, audio bus graph is located in the Audio Context panel: audio context Primary audio bus is located at the left of the panel, every other audio bus is located to the right. Each audio bus (except primary) has a dropdown list (at the bottom), that specifies output audio bus. The list of effect is located in the center; it can be edited in the Inspector (right side of the image). To attach a sound source to an audio bus, select in the scene and find Audio Bus property in the Inspector and set it to the name of desired audio bus.","breadcrumbs":"Scene » Sound » Audio Bus » Editor","id":"230","title":"Editor"},"231":{"body":"In Fyrox, sounds are nodes of type Sound, with all the consequent properties and workflows.","breadcrumbs":"Scene » Sound » Sound Node » Sound","id":"231","title":"Sound"},"232":{"body":"There are two major ways to create sound sources: from the editor and from code.","breadcrumbs":"Scene » Sound » Sound Node » How to create","id":"232","title":"How to create"},"233":{"body":"A sound source could be created from Create menu (or from the same menu by right-clicking on a node in the world viewer): create After the source is created, you can select it and start editing its properties: sound Buffer - a sound buffer resource, that will be used as a source of samples. If it is empty, then no sound will be played. Drag'n'drop a sound resource from the Asset Browser here to assign it to the source. Play Once - a flag, that defines whether the engine should automatically delete the sound source node from the scene when it is finished playing. Could be useful for one-shot sounds. Gain - a numeric value in [0..1] range, that defines total volume of the sound source. Keep in mind, that this value sets the volume in linear scale, while physically-correct approach would be to use logarithmic scale. This will be fixed in future versions. Panning - a numeric value in [-1..1] range, that defines how loud audio channels will be. -1 - all the sound will be routed to the left channel, 1 - to the right channel. This option works only with 2D sounds (whose spatial blend factor is 0.0) Status - a switch with three possible states: Stopped, Playing, Paused. By default, every sound source is in stopped state, do not forget to switch it to the Playing state, otherwise you won't hear anything. Looping - a flag, that defines whether the sound source should be playing infinitely, or not. Looping sound source will never switch their status to Stopped. Pitch - playback speed multiplier. By default, it is 1.0 which means default speed. Max Distance - maximum distance, at which the sound source is affected by distance attenuation (for 3D sounds). By default, it set to max possible value. Lower values could be used to prevent sound source from be silent at certain distance. Rolloff Factor - a numeric value, that defines how fast the volume of the sound source will decay with increasing distance to a listener. Playback Time - desired time from which the playback should start (in seconds). Spatial Blend - a numeric value, that defines blending factor between 2D and 3D sound, where 0.0 - the sound is fully 2D, 1.0 - the sound is fully 3D. By default, the value is 1.0. Audio Bus - a name of an audio bus, that will be used to process the samples from the sound source. By default, it is set to Primary. It should match the name of some audio bus, that will be used in your scene. More info about audio processing could found here .","breadcrumbs":"Scene » Sound » Sound Node » From Editor","id":"233","title":"From Editor"},"234":{"body":"Audio files are loaded using the resource manager: # extern crate fyrox;\n# use fyrox::{engine::Engine, scene::Scene, scene::sound::SoundBuffer};\n# fn build_node(engine: Engine, scene: &mut Scene) {\nlet sound = engine .resource_manager .request::(\"/path/to/resource.ogg\");\n# } Then, the node is built using the standard builder pattern: # extern crate fyrox;\n# use fyrox::{\n# engine::Engine,\n# scene::{\n# base::BaseBuilder,\n# sound::{SoundBuilder, Status, SoundBuffer},\n# Scene,\n# },\n# };\n# fn build_node(engine: Engine, scene: &mut Scene) {\n# let sound = engine\n# .resource_manager\n# .request::(\"/path/to/resource.ogg\");\n#\nlet sound_handle = SoundBuilder::new(BaseBuilder::new()) .with_buffer(Some(sound)) .with_status(Status::Playing) .with_play_once(true) .build(&mut scene.graph);\n# } There are a few notable things in the example above. The first is that sounds don't play automatically; in order to do so, we need to invoke .with_status(Status::Playing). The second is that sound nodes are not dropped automatically after playback; dropping it can be performed in two ways. One way is to use the convenient builder API .with_play_once(true); another is to use the graph APIs: # extern crate fyrox;\n# use fyrox::{\n# engine::Engine,\n# scene::{\n# base::BaseBuilder,\n# sound::{SoundBuilder, Status},\n# Scene,\n# },\n# };\n# fn build_node(engine: Engine, scene: &mut Scene) {\nlet sound_handle = SoundBuilder::new(BaseBuilder::new()).build(&mut scene.graph); let sound = scene.graph[sound_handle].as_sound(); if sound.status() == Status::Stopped { scene.graph.remove_node(sound_handle);\n}\n# } If we want to play background music (or anyway a repeated sound), we just set the looping property when building the node: # extern crate fyrox;\n# use fyrox::{\n# engine::Engine,\n# scene::{base::BaseBuilder, sound::SoundBuilder, Scene},\n# };\n# fn build_node(engine: Engine, scene: &mut Scene) {\nSoundBuilder::new(BaseBuilder::new()) .with_looping(true) // etc. .build(&mut scene.graph);\n# } In order to stream large audio files, instead of loading them entirely in memory, the simplest strategy is to create a corresponding .options file, with the following content: ( stream: true\n) If the audio file is called, for example, /path/to/background.ogg, call this /path/to/background.ogg.options.","breadcrumbs":"Scene » Sound » Sound Node » From Code","id":"234","title":"From Code"},"235":{"body":"There's no strict separation between 2D and 3D sound sources. The same source could be switched from 2D to 3D (and vice versa) at runtime, by just adjusting Spatial Blend property. Spatial blend factor is a numeric value, that defines blending factor between 2D and 3D sound, where 0.0 - the sound is fully 2D, 1.0 - the sound is fully 3D. By default, the value is 1.0 which makes it 3D. Intermediate values could be used to create \"ambisonic\" sound sources - when the source sounds like it is placed at some position in the world, but some part of it is just 2D and does not depend on positioning.","breadcrumbs":"Scene » Sound » Sound Node » 2D and 3D","id":"235","title":"2D and 3D"},"236":{"body":"It is possible to specify target audio bus to which the sound will output its audio samples. Audio bus is responsible for various audio processing, such as filtering, reverb, etc. To specify output audio bus, just use the set_audio_bus method and set the name of an audio bus.","breadcrumbs":"Scene » Sound » Sound Node » Audio bus","id":"236","title":"Audio bus"},"237":{"body":"Head Related Transfer Function (HRTF for short) is special audio processing technique that improves audio spatialization. By default, sound spatialization is very simple - volume of each audio channel (left and right) changes accordingly to orientation of the listener. While this simple and fast, it does not provide good audio spatialization - sometimes it is hard to tell from which direction the actual sound is coming from. To solve this issue, we can use head-related transfer function. Despite its scary, mathematical name, it is easy to understand what it's doing. Instead of uniformly changing volume of all frequencies of the signal (as the naive spatialization does), it changes them separately for each channel. The exact \"gains\" of each frequency of each channel is depends on the contents of head-related transfer function. This is done for each azimuth and elevation angles, which gives full picture of how audio signal from each direction travels to each ear. HRTF is usually recorded using a head model with ears with a microphone inside each ear. To capture head-related impulse response (time domain) at a fixed distance and angle pair (azimuth and elevation), a very short impulse of sound is produced. Microphones inside each ear records the signal, and then HRIR (time domain) can be converted in HRTF (frequency domain).","breadcrumbs":"Scene » Sound » HRTF » Head Related Transfer Function","id":"237","title":"Head Related Transfer Function"},"238":{"body":"The theory above could be boring, however it is very simple to use HRTF on practice. Pick a HRIR sphere from the database (any of *.bin files) and load it in the Audio Context panel: hrtf Once it is loaded, all sounds in the scene will use the HRTF for rendering. The same can be achieved by code: # extern crate fyrox;\n# use fyrox::scene::{\n# graph::Graph,\n# sound::{self, HrirSphere, HrirSphereResource, HrirSphereResourceExt, HrtfRenderer, Renderer},\n# };\n# fn use_hrtf(graph: &mut Graph) { let hrir_sphere = HrirSphereResource::from_hrir_sphere( HrirSphere::from_file(\"path/to/hrir.bin\", sound::SAMPLE_RATE).unwrap(), \"path/to/hrir.bin\".into()); graph .sound_context .state() .set_renderer(Renderer::HrtfRenderer(HrtfRenderer::new(hrir_sphere)));\n}","breadcrumbs":"Scene » Sound » HRTF » HRTF on practice","id":"238","title":"HRTF on practice"},"239":{"body":"HRTF is heavy. It is 5-6 times slower than the simple spatialization, so use it only on middle-end or high-end hardware. HRTF performance is linearly dependent on the amount of sound sources: the more sound sources use HRTF, the worse performance will be and vice versa.","breadcrumbs":"Scene » Sound » HRTF » Performance","id":"239","title":"Performance"},"24":{"body":"Let's briefly get over some basic concepts of the engine, there's not much, but all of them are crucial to understand design decisions made in the engine.","breadcrumbs":"Introduction » Basic concepts » Basic concepts","id":"24","title":"Basic concepts"},"240":{"body":"Animation allows you to change properties of scene nodes at runtime using a set of key frames. Animation consists of multiple tracks, where each track is bound to a property of a scene node. A track can animate any numeric properties, starting from numbers (including bool) end ending by 2/3/4 dimensional vectors. Each component (number, x/y/z/w vector components) is stored in a parametric curve (see [crate::core::curve::Curve] docs for more info). Every parametric curve contains zero or more key frames . Graphically this could be represented like so: Timeline v Time > |---------------|------------------------------------> | | Track1 > | node.position | | X curve |..1..........5...........10.......... | Y curve |..2.........-2..................1.... < Curve key frames | Z curve |..1..........9......................4 |_______________| Track2 | node.property | | ............ |..................................... | ............ |..................................... | ............ |..................................... Each key frame is just a real number with interpolation mode. Interpolation mode tells the engine how to calculate intermediate values between key frames. There are three kinds of interpolation used in animations (you can skip \"boring math\" if you want): Constant - intermediate value will be calculated using leftmost value of two. Constant \"interpolation\" is usually used to create step-like behaviour, the most common case is to \"interpolate\" two boolean values. Linear - intermediate value will be calculated using linear interpolation i = left + (right - left) / t, where t = (time_position - left) / (right - left). t is always in 0..1 range. Linear interpolation is usually used to create \"straight\" transitions between two values. Cubic - intermediate value will be calculated using Hermite cubic spline: i = (2t^3 - 3t^2 + 1) * left + (t^3 - 2t^2 + t) * left_tangent + (-2t^3 + 3t^2) * right + (t^3 - t^2) * right_tangent, where t = (time_position - left) / (right - left) (t is always in 0..1 range), left_tangent and right_tangent is usually a tan(angle). Cubic interpolation is usually used to create \"smooth\" transitions between two values.","breadcrumbs":"Scene » Animation » Animation","id":"240","title":"Animation"},"241":{"body":"You can explore animation system capabilities in this web demo . Keep in mind, that it was designed to run on PC and wasn't tested on mobile devices.","breadcrumbs":"Scene » Animation » Web Demo","id":"241","title":"Web Demo"},"242":{"body":"Each track is always bound to a property in a node, either by its name or by a special binding. The name is used to fetch the property using reflection, the special binding is a faster way of fetching built-in properties. It is usually used to animate position, scale and rotation (these are the most common properties available in every scene node).","breadcrumbs":"Scene » Animation » Track binding","id":"242","title":"Track binding"},"243":{"body":"While key frames on the curves can be located at arbitrary position in time, animations usually plays a specific time slice. By default, each animation will play on a given time slice infinitely - it is called animation looping , it works in both playback directions.","breadcrumbs":"Scene » Animation » Time slice and looping","id":"243","title":"Time slice and looping"},"244":{"body":"You can vary playback speed in wide range, by default every animation has playback speed multiplier set to 1.0. The multiplier tells how faster (>1) or slower (<1) the animation needs to be played. Negative speed multiplier values will reverse playback.","breadcrumbs":"Scene » Animation » Speed","id":"244","title":"Speed"},"245":{"body":"Sometimes there's a need to disable/enable an animation or check if it is enabled or not, you can do this by using the pair of respective methods - [Animation::set_enabled] and [Animation::is_enabled].","breadcrumbs":"Scene » Animation » Enabling or disabling animations","id":"245","title":"Enabling or disabling animations"},"246":{"body":"Signal is a named marker on specific time position on the animation timeline. Signal will emit an event if the animation playback time passes signal's position from left-to-right (or vice versa depending on playback direction). Signals are usually used to attach some specific actions to a position in time. For example, you can have a walking animation and you want to emit sounds when character's feet touch ground. In this case you need to add a few signals at times when each foot touches the ground. After that all you need to do is to fetch animation events one-by-one and emit respective sounds. See [AnimationSignal] docs for more info and examples.","breadcrumbs":"Scene » Animation » Signals","id":"246","title":"Signals"},"247":{"body":"Usually, animations are created from the editor or some external tool and then imported in the engine. Before trying the example below, please read the docs for [crate::scene::animation::AnimationPlayer] node, it is much more convenient way of animating other nodes. The node can be created from the editor and you don't even need to write any code. Use the following example code as a guide only if you need to create procedural animations: # extern crate fyrox;\n# use fyrox::{\n# animation::{\n# container::{TrackDataContainer, TrackValueKind},\n# track::Track,\n# value::ValueBinding,\n# Animation,\n# },\n# core::{\n# curve::{Curve, CurveKey, CurveKeyKind},\n# pool::Handle,\n# },\n# scene::{\n# node::Node,\n# base::BaseBuilder,\n# graph::Graph,\n# pivot::PivotBuilder\n# }\n# };\nfn create_animation(node: Handle) -> Animation { let mut frames_container = TrackDataContainer::new(TrackValueKind::Vector3); // We'll animate only X coordinate (at index 0). frames_container.curves_mut()[0] = Curve::from(vec![ CurveKey::new(0.5, 2.0, CurveKeyKind::Linear), CurveKey::new(0.75, 1.0, CurveKeyKind::Linear), CurveKey::new(1.0, 3.0, CurveKeyKind::Linear), ]); // Create a track that will animated the node using the curve above. let mut track = Track::new(frames_container, ValueBinding::Position); track.set_target(node); // Finally create an animation and set its time slice and turn it on. let mut animation = Animation::default(); animation.add_track(track); animation.set_time_slice(0.0..1.0); animation.set_enabled(true); animation\n}\n// Create a graph with a node.\nlet mut graph = Graph::new();\nlet some_node = PivotBuilder::new(BaseBuilder::new()).build(&mut graph);\n// Create the animation.\nlet mut animation = create_animation(some_node);\n// Emulate some ticks (like it was updated from the main loop of your game).\nfor _ in 0..10 { animation.tick(1.0 / 60.0); animation.pose().apply(&mut graph);\n} The code above creates a simple animation that moves a node along X axis in various ways. The usage of the animation is only for the sake of completeness of the example. In the real games you need to add the animation to an animation player scene node and it will do the job for you.","breadcrumbs":"Scene » Animation » Creating From Code","id":"247","title":"Creating From Code"},"248":{"body":"anim editor Animation Editor is a tool that helps you to create and preview animations. This is a powerful tool that can be used to animate pretty much any numeric property. It has three main parts: Toolbar - contains a set of tools that changes a particular part of an animation (name, length, speed, etc.) Track List - contains a list of tracks of nodes that will be animated. Curve Editor - curve editor allows you to edit behaviour of a numeric parameter over the time. The editor can be opened in two ways - using Utils -> Animation Editor or by selecting an animation player node and clicking Open Animation Editor button in the inspector. open1 open2 In both ways you still need to select an animation player for editing.","breadcrumbs":"Scene » Animation » Animation Editor » Animation Editor","id":"248","title":"Animation Editor"},"249":{"body":"At first, you need to create or import an animation, then you need to set its time slice to desired range (see Time Slice in the section below), then you need to add a few tracks for desired properties and finally add some keys. You can preview the results at any time, keep in mind that any attempt to change an animation while it is the preview mode, will revert every change from the preview mode and only then apply your change.","breadcrumbs":"Scene » Animation » Animation Editor » Typical Workflow","id":"249","title":"Typical Workflow"},"25":{"body":"The engine uses somewhat classic OOP with composition over inheritance - complex objects in the engine can be constructed using simpler objects.","breadcrumbs":"Introduction » Basic concepts » Classic OOP","id":"25","title":"Classic OOP"},"250":{"body":"The toolbar contains a set of tools that changes a particular part of an animation (name, length, speed, etc.). It looks like this: toolbar Animation Name - name of a currently selected animation. Add Animation - adds a new empty animation with the name from the text box at the left to the animation player. Import Animation - starts animation importing process. See Animation Importing section for more info. Reimport Animation - re-imports the animation from an external file, it is useful if you need to change animation's content, while keep references to it valid. Rename Animation - renames a currently selected animation using the name from the text box at the left. Animation Selector - allows you to switch currently edited animation. Delete Animation - deletes a currently selected animation, tries to select last animation from the list if possible. Duplicate Animation - clones a currently selected animation. Loop Animation - enables or disables looping of a currently selected animation. Enable Animation - enables or disables a currently selected animation. Animation Speed - sets a new playback speed of a currently selected animation. Time Slice - a time range (in seconds) which defines start and end time of a currently selected animation. The range is highlighted in the curve editor. Root Motion - open root motion settings. See Root Motion section for more info. Preview Switch - enables or disables animation preview. See Preview Mode section for more info. Play/Pause - plays or pauses a currently selected animation (allowed only in the preview mode). Stop - stops a currently selected animation (allowed only in the preview mode).","breadcrumbs":"Scene » Animation » Animation Editor » Toolbar","id":"250","title":"Toolbar"},"251":{"body":"The track list contains a list of tracks of nodes that will be animated. It looks like this: track list Filter Bar - filters the track list by finding tracks whose names matching the filter. You can use this to find tracks that belong to a particular scene node. Clear Filter - clears the filter, the track list will show all the tracks after this. Collapse All - collapses all the tracks in the list. Expand All - expands all the tracks in the list. Track - a track with some number of children parametric curves. Track Component Curve - parametric curve that serves a data source for the animation for a particular track. Track Switch - enables or disables a track; disabled tracks won't \"touch\" their properties. Add Track - starts property binding process, see Property Binding section for more info.","breadcrumbs":"Scene » Animation » Animation Editor » Track List","id":"251","title":"Track List"},"252":{"body":"context menu Remove Selected Tracks - removes selected tracks; you can remove multiple tracks at a time by selecting them while holding Ctrl.","breadcrumbs":"Scene » Animation » Animation Editor » Track Context Menu","id":"252","title":"Track Context Menu"},"253":{"body":"Curve editor allows you to edit parametric curves (one at a time). A curve consists of zero or more key frames with various transition rules between current and the next. The editor looks like this: curve editor Time Ruler - shows time values and every signal of a currently selected animation. A click on the time ruler will move the playback cursor at the click position. You can move it by clicking at the cursor and moving the mouse while holding the left mouse button. Animation signals can be moved in the same fashion. Parametric Curve - a curve that defines how a value changes over time. Time Thumb - animation playback cursor, useful only for preview. Animation Signal - some animation signal that will produce animation events when the playback cursor passes it.","breadcrumbs":"Scene » Animation » Animation Editor » Curve Editor","id":"253","title":"Curve Editor"},"254":{"body":"time ruler context menu Remove Signal - removes an animation signal under the mouse cursor. Add Signal - adds a new animation signal at the mouse cursor position.","breadcrumbs":"Scene » Animation » Animation Editor » Time Ruler Context Menu","id":"254","title":"Time Ruler Context Menu"},"255":{"body":"key frame context menu Location - shows a key location and allows you to change it. Useful for setting precise values. Value - shows a key value and allows you to change it. Useful for setting precise values. Add Key - adds a new key to the curve. Remove - removes all selected keys. You can select multiple keys either by box selection (click and drag the mouse to active box selection) or by clicking on separate keys while holding Ctrl. Key... - allows you to change the interpolation type of key. It could be one of the following values: Constant, Linear, Cubic. Zoom To Fit - tries to find zooming values (for both axes) and the view position with which the entire curve fits in the viewport.","breadcrumbs":"Scene » Animation » Animation Editor » Key Frame Context Menu","id":"255","title":"Key Frame Context Menu"},"256":{"body":"To animate a property all you need to do is to click on Add Track... button at the bottom of the track list, select a node to animate and then select a property that will be animated. There are two windows that will be shown one after another: step1 step2 You can cancel property binding at any time by clicking Cancel in any of the windows. Keep in mind that you can animate only numeric properties, so not every property is shown in the window.","breadcrumbs":"Scene » Animation » Animation Editor » Property Binding","id":"256","title":"Property Binding"},"257":{"body":"Animations can be stored in separate files, but the engine requires all of them to be in a single Animation Player. To put an animation from an external resource (an FBX, for instance) in the animation player you can use animation importing. To do that, click on animation import icon and then select a root node of the hierarchy that is animated in the external animation file, then select the animation file and click Ok. The engine will try to import the animation and map it to the given hierarchy, mapping is done using node names, so animated node names must match in both your scene and your external animation file. step1 step2 Content of existing animations can be replaced by reimporting. Click on a button with two circular arrows to reimport your animation. It could be useful if you changed your animation in some external editor (Blender for example) and want to apply changes in your game.","breadcrumbs":"Scene » Animation » Animation Editor » Animation Importing","id":"257","title":"Animation Importing"},"258":{"body":"Preview mode helps you to see and debug your animation. After activating the mode, you need to play the animation by clicking the Play/Pause button: anim editor Any significant change made in the scene will automatically deactivate the preview mode reverting all the changes made by playing animation.","breadcrumbs":"Scene » Animation » Animation Editor » Preview Mode","id":"258","title":"Preview Mode"},"259":{"body":"Root motion is a special technique that transfers motion from some node in a hierarchy to a physical capsule, which is then used to perform the actual motion. You can enable/disable/setup it in the drop-down menu that opens by clicking RM button: root motion The most important part here is the Root handle, it should be set to a root node that moves by your animation, usually it is called like \"hips\" or similar: root node After that, you need to apply filters for axes - most of the locomotion animations \"works\" in oXZ plane, so Y axis should be ignored. Also, if you don't have any turns in your animation, you can also filter out the rotation part.","breadcrumbs":"Scene » Animation » Animation Editor » Root Motion","id":"259","title":"Root Motion"},"26":{"body":"In Fyrox, you break down your game in a set of reusable scenes. Pretty much anything can be a scene: a player, a weapon, a bot, level parts, etc. Scenes can be nested one into another, this helps you to break down complex scenes into reusable parts. Scene in Fyrox is also plays a role of prefab, there's pretty much no difference between them.","breadcrumbs":"Introduction » Basic concepts » Scenes","id":"26","title":"Scenes"},"260":{"body":"For now there's no dopesheet mode in the editor, you can edit only one numeric parameter at a time. Also, there's no capture mode - this is a special mode in which the editor automatically adds your changes in the scene to the animation. These limitations will be removed in the future versions.","breadcrumbs":"Scene » Animation » Animation Editor » Limitations","id":"260","title":"Limitations"},"261":{"body":"Animation blending is a powerful feature that allows you to mix multiple animations into one. Each animation is mixed with a various weights which in sum gives 1.0 (100%). By having opposite coefficients (k1 = 0 -> 1, k2 = 1 -> 0) changing in time it is possible to create transition effect. Handling transitions with all the coefficients is a routine job, the engine can handle it for you giving you some nice features: Multiple states with smooth transitions between them Ability to blend multiple animations in one and use it as pose source for blending Ability to specify a set of variables that will be used as blending coefficients and transition rules. All these features consolidated in so-called animation blending state machine (ABSM). Machine is used to blend multiple animation as well as perform automatic \"smooth\" transition between states. Let's have a quick look at a simple machine graph: +-------------+ | Idle Anim | +------+------+ | Walk Weight |\n+-----------+ +-------+ Walk->Idle Rule |\n| Walk Anim +------+ | |\n+-----------+ | | +-------+ +---+---+ | Blend | | +-------->+ | | +------+ Walk | | Idle |\n+-----------+ | | | +<--------+ |\n| Aim Anim +------+ | +--+----+ +---+---+\n+-----------+ +-------+ | ^ Aim Weight | Idle->Walk Rule | | | Walk->Run Rule | +---------+ | Run->Idle Rule | | | | +--->+ Run +---+ | | +----+----+ | | +------+------+ | Run Anim | +-------------+ Here we have Walk, Idle and Run states which use different sources of poses: Walk - is the most complicated here - it uses result of blending between Aim and Walk animations with different weights. This is useful if your character can only walk or can walk and aim at the same time. Desired pose determined by Walk Weight and Aim Weight parameters combination. Run and idle both directly use animation as pose source. There are four transitions between three states each with its own rule. Rule is just a boolean parameter that indicates that transition should be activated. Let's look at the code example of the above state graph: # extern crate fyrox;\nuse fyrox::{ animation::machine::{ Machine, State, Transition, PoseNode, node::blend::BlendPose, Parameter, PlayAnimation, PoseWeight, node::blend::BlendAnimations }, core::pool::Handle\n}; // Assume that these are correct handles.\nlet idle_animation = Handle::default();\nlet walk_animation = Handle::default();\nlet aim_animation = Handle::default(); let mut machine = Machine::new(); let root_layer = machine.layers_mut().first_mut().unwrap(); let aim = root_layer.add_node(PoseNode::PlayAnimation(PlayAnimation::new(aim_animation)));\nlet walk = root_layer.add_node(PoseNode::PlayAnimation(PlayAnimation::new(walk_animation))); // Blend two animations together\nlet blend_aim_walk = root_layer.add_node(PoseNode::BlendAnimations( BlendAnimations::new(vec![ BlendPose::new(PoseWeight::Constant(0.75), aim), BlendPose::new(PoseWeight::Constant(0.25), walk) ])\n)); let walk_state = root_layer.add_state(State::new(\"Walk\", blend_aim_walk)); let idle = root_layer.add_node(PoseNode::PlayAnimation(PlayAnimation::new(idle_animation)));\nlet idle_state = root_layer.add_state(State::new(\"Idle\", idle)); root_layer.add_transition(Transition::new(\"Walk->Idle\", walk_state, idle_state, 1.0, \"WalkToIdle\"));\nroot_layer.add_transition(Transition::new(\"Idle->Walk\", idle_state, walk_state, 1.0, \"IdleToWalk\")); As you can see, everything is quite straightforward. Even such simple state machine requires quite a lot of code, which can be removed by using ABSM editor. Read the next chapter to learn about it.","breadcrumbs":"Scene » Animation » Animation Blending » Animation Blending","id":"261","title":"Animation Blending"},"262":{"body":"You can use multiple machines to animate single model - for example one machine can be used for locomotion and other for combat. This means that locomotion machine will take control over lower body and combat machine will control upper body.","breadcrumbs":"Scene » Animation » Animation Blending » Multiple ABSM per model","id":"262","title":"Multiple ABSM per model"},"263":{"body":"While it is possible to create and manage animation blending and state manually from code, it quickly becomes too annoying and hardly manageable. To help you create and manage blending machines in easy way, the engine offers an ABSM Editor tool. This chapter is an overview of the editor, it is quite complex, but the guide should help you to figure out which part is made for what. Next chapter will help you to create your first animation blending state machine. absm editor The editor has four main parts (panels): Toolbar - contains a set of tools to edit animation layers and enable/disable preview mode. See Toolbar section for more info. Parameters - allows you to edit various variables that are responsible for transitions, weight parameters for blending, etc. See Parameters section for more info. State Graph - allows you to create, delete, edit states and transition between them. See State Graph section for more info. State Viewer - allows you to edit pose source for a state. Pose source can be represented either by a single node that plays an animation, or a series of play animation nodes connected to blending nodes (which can be connected to other blending nodes, etc.). See State Viewer section for more info. The editor can be opened in two ways - using Utils -> ABSM Editor or by selecting an animation blending state machine node and clicking Open ABSM Editor... button: open1 open1 In both ways you still need to select an an animation blending state machine node for editing.","breadcrumbs":"Scene » Animation » ABSM Editor » Animation Blending State Machine (ABSM) Editor","id":"263","title":"Animation Blending State Machine (ABSM) Editor"},"264":{"body":"toolbar Preview Switch - enables or disables preview mode for the ABSM. See Preview Mode section for more info. Layer Name - name of the selected layer. Type a new name here to rename currently selected layer (hit enter or just click elsewhere to rename). Add Layer - adds a new layer with the name in the Layer Name text box to the ABSM. ABSM can have multiple layers with the same name, but it strongly advised to set unique names here. Remove Current Layer - removes currently selected layer. You can delete all layers, but in this case your ABSM won't have any effect. Layer Selector - allows you to select a layer for editing, default selection is none. Layer Mask - opens a Layer Mask Editor and helps you to edit the layer mask of the current layer. See Layer Mask section for more info.","breadcrumbs":"Scene » Animation » ABSM Editor » Toolbar","id":"264","title":"Toolbar"},"265":{"body":"Parameter is a named and typed variable that provides the animation system with some data required for it to work. There are only three type of parameters: Rule - boolean value that used as a trigger for transitions. When transition is using some rule, it checks the value of the parameter and if it is true transition starts. Weight - real number (f32) that is used a weight when you're blending multiple animations into one. Index - natural number (i32) that is used as an animation selector. parameters Add Parameters - adds a new parameter to the parameters' container. Remove a Parameter - removes selected parameter from the parameters' container. Parameter Name - allows you to set a parameter name. Parameter Type - allows you to select the type of the parameter. Parameter Value - allows you to set parameter value.","breadcrumbs":"Scene » Animation » ABSM Editor » Parameters","id":"265","title":"Parameters"},"266":{"body":"State Graph allows you to create states and transitions between them. state graph State - state is final animation for a set of scene nodes, only one state can be active at a time. Transition - is an ordered connection between two states, it defines how much time it needed to perform blending of two states. Root State - is an entry state of the current layer.","breadcrumbs":"Scene » Animation » ABSM Editor » State Graph","id":"266","title":"State Graph"},"267":{"body":"state context menu Create Transition - starts transition creation from the current state to some other. Remove - removes the state. Set As Entry State - marks the state as an entry state (this state will be active at beginning).","breadcrumbs":"Scene » Animation » ABSM Editor » State Context Menu","id":"267","title":"State Context Menu"},"268":{"body":"transition context menu Remove Transition - removes selected transition.","breadcrumbs":"Scene » Animation » ABSM Editor » Transition Context Menu","id":"268","title":"Transition Context Menu"},"269":{"body":"Select a State node to edit the following properties: state properties Position - is a location of the state on the canvas. Name - name of the state. Root - handle of the backing animation node inside the state.","breadcrumbs":"Scene » Animation » ABSM Editor » State Properties","id":"269","title":"State Properties"},"27":{"body":"A scene is made of one or more nodes (every scene must have at least one root node, to which everything else is attached). Scene node contains specific set of properties as well as one optional script instance which is responsible for custom game logic. Typical structure of a scene node could be represented by the following example. The base object for every scene node is a Base node, it contains a transform, a list of children, etc. A more complex node, that extends functionality of the Base node stores an instance of Base inside of them. For example, a Mesh node is a Base node plus some specific info (a list of surfaces, material, etc.). The \"hierarchy\" depth is unlimited - a Light node in the engine is an enumeration of three possible types of light source. Directional, Point, and Spot light sources both use BaseLight node, which in its turn contains Base node inside. Graphically it can be represented like so: `Point`\n|__ Point Light Properties (radius, etc.)\n|__`BaseLight` |__ Base Light Properties (color, etc.) |__`Base` |__ Base Node Properties (transform, children nodes, etc.) As you can see, this forms the nice tree (graph) that shows what the object contains. This is very natural way of describing scene nodes, it gives you the full power of building an object of any complexity.","breadcrumbs":"Introduction » Basic concepts » Nodes and Scene Graph","id":"27","title":"Nodes and Scene Graph"},"270":{"body":"Select a Transition node to edit the following properties: transition properties Name - name of the state. Transition Time - amount of time for blending between two states (in seconds). Elapsed Time - starting amount of blending time. Source - handle of a source state. Desc - handle of a destination state. Rule - a name of Rule type parameter that defines whether the transition can be activated or not. Invert Rule - defines whether to invert the value of Rule or not. Blend Factor - defines a percentage (in 0..1 range) of how much transition was active.","breadcrumbs":"Scene » Animation » ABSM Editor » Transition Properties","id":"270","title":"Transition Properties"},"271":{"body":"State Viewer allows you to edit contents of states. You can create animation blending chains of any complexity, the simplest content of a state is just a single Play Animation node. Currently, the engine supports just three animation blending nodes: Play Animation - takes animation pose directly from specified animation, does nothing to it. Blend Animations - takes multiple animation poses from respective animations and blends them together with respective blend weights. Blend Animations By Index - takes multiple animation poses from respective animations and switches between them with \"smooth\" transition using an index parameter. state viewer Node - is a source of animation for blending. Connection - defines how nodes are connected to each other. To create a new connection, click on a small dot on a node, hold the button and start dragging to a dot on some other node. Root Node - root node is marked green; root node is a final source of animation for the parent state.","breadcrumbs":"Scene » Animation » ABSM Editor » State Viewer","id":"271","title":"State Viewer"},"272":{"body":"Select a Play Animation node to edit the following properties: play animation properties Position - is a location of the node on the canvas. Animation - an animation to fetch the pose from.","breadcrumbs":"Scene » Animation » ABSM Editor » Play Animation Properties","id":"272","title":"Play Animation Properties"},"273":{"body":"Select a Blend Animations node to edit the following properties: blend animations properties Position - is a location of the node on the canvas. Pose Sources - a set of input poses. To add a pose either click on + or +Input on the node itself. Don't forget to connect some nodes to the new input poses. Weight - a weight of the pose; could be either a constant value or some parameter.","breadcrumbs":"Scene » Animation » ABSM Editor » Blend Animations Properties","id":"273","title":"Blend Animations Properties"},"274":{"body":"Select a Blend Animations By Index node to edit the following properties: blend animations by index properties Position - is a location of the node on the canvas. Index Parameter - a name of an indexing parameter (must be Index type). Inputs - a set of input poses. To add a pose either click on + or +Input on the node itself. Don't forget to connect some nodes to the new input poses. Blend Time - defines how much time is needed to transition to the pose.","breadcrumbs":"Scene » Animation » ABSM Editor » Blend Animations By Index Properties","id":"274","title":"Blend Animations By Index Properties"},"275":{"body":"Every connection has a context menu that can be shown by a right-click on a connection. connection context menu Remove Connection - removes the connection between parent nodes.","breadcrumbs":"Scene » Animation » ABSM Editor » Connection Context Menu","id":"275","title":"Connection Context Menu"},"276":{"body":"Every node has a context menu that can be shown by a right-click on a connection. node context menu Set As Root - sets the node as the final pose source of the parent state. Remove - removes the node from the state.","breadcrumbs":"Scene » Animation » ABSM Editor » Node Context Menu","id":"276","title":"Node Context Menu"},"277":{"body":"layer mask Layer mask editor allows you to select which nodes won't be animated by the current animation layer. Selected nodes are marked with dark color. To select multiple nodes at once, hold Ctrl and click on items. The text box at the top of the window allows you to search for a particular scene node. To save edited layer mask click OK.","breadcrumbs":"Scene » Animation » ABSM Editor » Layer Mask","id":"277","title":"Layer Mask"},"278":{"body":"Preview mode turns on the animation blending state machine and its animation player and allows you to see the result of the work of the machine. Any significant changes in the scene automatically disables the preview mode and any changes done by the machine is discarded. While the preview mode is active, you can freely change the values of the parameters to see how the machine will react to this. This helps you to debug your state machine, it is especially useful for complex state machines with lots of layers. Here's how the preview mode works: absm","breadcrumbs":"Scene » Animation » ABSM Editor » Preview Mode","id":"278","title":"Preview Mode"},"279":{"body":"","breadcrumbs":"Artificial Intelligence (WIP) » Artificial Intelligence (WIP)","id":"279","title":"Artificial Intelligence (WIP)"},"28":{"body":"Plugin is a container for \"global\" game data and logic, its main usage is to provide scripts with some data and to manage global game state.","breadcrumbs":"Introduction » Basic concepts » Plugins","id":"28","title":"Plugins"},"280":{"body":"","breadcrumbs":"Artificial Intelligence (WIP) » Behaviour Trees (WIP) » Behaviour Trees (WIP)","id":"280","title":"Behaviour Trees (WIP)"},"281":{"body":"Fyrox has built-in A* (A-star) algorithm for pathfinding. It can be used to find a path on arbitrary graph without cycles. It could be a simple grid where each point knows about its \"neighbours\", navigational mesh , or some other graph.","breadcrumbs":"Artificial Intelligence (WIP) » Path Finding » Path Finding","id":"281","title":"Path Finding"},"282":{"body":"The simplest examples could be a search of path on uniform grid. This could be useful for games with open worlds, strategies, and any other types of games that uses uniform grid for pathfinding. # extern crate fyrox;\nuse fyrox::{ core::algebra::Vector3, utils::astar::{PathFinder, PathVertex},\n}; fn astar_on_uniform_grid() { // Create vertices. let size = 40; let mut vertices = Vec::new(); for y in 0..size { for x in 0..size { vertices.push(PathVertex::new(Vector3::new(x as f32, y as f32, 0.0))); } } let mut pathfinder = PathFinder::new(); pathfinder.set_vertices(vertices); // Link vertices to form a uniform grid. for y in 0..(size - 1) { for x in 0..(size - 1) { pathfinder.link_bidirect(y * size + x, y * size + x + 1); pathfinder.link_bidirect(y * size + x, (y + 1) * size + x); } } // Build a path from vertex 0 to vertex 100. let mut path = Vec::new(); assert!(pathfinder.build(0, 100, &mut path).is_ok());\n} Keep in mind, that the returned path is always reversed (its first point corresponds to an end point). You need either to reverse the path, or (which is much faster) just iterate in reverse over its points.","breadcrumbs":"Artificial Intelligence (WIP) » Path Finding » Examples","id":"282","title":"Examples"},"283":{"body":"A* is very simple, yet powerful algorithm. However, it is not always suitable, because it searches only on graph vertices and cannot build paths that are lying on a surface of arbitrary meshes. Simple path finding on a uniform grid is ok for some games (strategies for instance), but in FPS games it will look awful. In this case you should use navigational meshes which can build path on a surface of arbitrary meshes.","breadcrumbs":"Artificial Intelligence (WIP) » Path Finding » What to use","id":"283","title":"What to use"},"284":{"body":"Current A* implementation is not ideal and may hurt performance if you need to calculate a lot of paths on large graphs. It will be optimized in the future (see tracking issue for info).","breadcrumbs":"Artificial Intelligence (WIP) » Path Finding » Performance","id":"284","title":"Performance"},"285":{"body":"Navigational mesh (navmesh for short) is a surface which can be used for path finding. Unlike A* Pathfinder , it can build arbitrary paths on a surface of large polygons, making a path from point A to point B linear (standard pathfinder builds path only from vertex to vertex). Navmeshes should be used when you have an arbitrary \"walkable\" surface, for example, a game level with rooms, hallways, multiple floors and so on. A* pathfinder should be used for strategies or any other types of games with uniform pathfinding grid.","breadcrumbs":"Artificial Intelligence (WIP) » Navigational Meshes » Navigational Meshes","id":"285","title":"Navigational Meshes"},"286":{"body":"Navigational meshes can be created and edited in the FyroxEd. At first, create a \"Navigational Mesh\" node, select it and switch to \"navmesh\" interaction mode: navmesh Now you can edit the navmesh. For now, editing capabilities are quite limited and the only way to edit the navmesh is to Shift+Drag one if its edges: navmesh edit You can also delete edges and vertices: select a vertex or an edge and press Delete key. If you need to create closed loops, use \"Connect Edges\" button in the \"Navmesh\" floating panel: navmesh connect","breadcrumbs":"Artificial Intelligence (WIP) » Navigational Meshes » Editor","id":"286","title":"Editor"},"287":{"body":"Fyrox does not support automatic navigational mesh generation yet. You can help by adding such feature.","breadcrumbs":"Artificial Intelligence (WIP) » Navigational Meshes » Automatic generation","id":"287","title":"Automatic generation"},"288":{"body":"Navigational mesh agent helps you to build paths along the surface of a navigational mesh and follow it. Agents can be used to drive the motion of your game characters. Every agent knows about its target and automatically rebuilds the path if the target has moved. Navmesh agents are able to move along the path, providing you with their current position, so you can use it to perform an actual motion of your game characters. Agents work together with navigational meshes, you need to update their state every frame, so they can recalculate path if needed. A simple example could something like this: # extern crate fyrox;\n# use fyrox::utils::navmesh::NavmeshAgent;\n# struct Foo { // Add this to your script\nagent: NavmeshAgent\n# } After that, you need to update the agent every frame to make sure it will follow the target: # extern crate fyrox;\n# use fyrox::{\n# core::algebra::Vector3, scene::navmesh::NavigationalMesh, utils::navmesh::NavmeshAgent,\n# };\nfn update_agent( agent: &mut NavmeshAgent, target: Vector3, dt: f32, navmesh: &mut NavigationalMesh,\n) { // Set the target to follow and the speed. agent.set_target(target); agent.set_speed(1.0); // Update the agent. agent.update(dt, navmesh.navmesh_mut()).unwrap(); // Print its position - you can use this position as target point of your game character. println!(\"{}\", agent.position());\n} This method should be called in on_update of your script. It accepts four parameters: a reference to the agent, a target which it will follow, a time step (context.dt), and a reference to navigational mesh node. You can fetch navigational mesh from the scene graph by its name: # extern crate fyrox;\n# use fyrox::scene::{navmesh::NavigationalMesh, Scene};\nfn find_navmesh<'a>(scene: &'a mut Scene, name: &str) -> &'a mut NavigationalMesh { let handle = scene.graph.find_by_name_from_root(name).unwrap().0; scene.graph[handle].as_navigational_mesh_mut()\n}","breadcrumbs":"Artificial Intelligence (WIP) » Navigational Meshes » Agents","id":"288","title":"Agents"},"289":{"body":"","breadcrumbs":"Rendering » Rendering (WIP)","id":"289","title":"Rendering (WIP)"},"29":{"body":"Script - is a separate piece of data and logic, that can be attached to scene nodes. This is primary (but not single) way of adding custom game logic.","breadcrumbs":"Introduction » Basic concepts » Scripts","id":"29","title":"Scripts"},"290":{"body":"Shader is a set of programs that run directly on graphics adapter. Each program from the set is called sub-shader . Sub-shaders linked with render pass, each render pass defines \"where\" to draw an object. \"where\" means that you can set up your own render pass and the renderer will use the sub-shader with your render pass. For the ease of use there are a number of predefined render passes . Shaders have properties of various types that can be used together with materials to draw an object.","breadcrumbs":"Rendering » Shaders » Shaders","id":"290","title":"Shaders"},"291":{"body":"The engine uses GLSL shading language for every sub-shader. There are numerous GLSL guides over the internet, so there is no need to \"re-post\" the well documented info again. There are very few differences: No need to define a version of the shader. Every shader source will be pre-processed, and it will get correct version automatically. Preprocessing is needed because the same shader could run on OpenGL and WebGL (OpenGL ES) which have some differences. There is a \"standard\" library of useful methods which is automatically included in every shader source at preprocessing stage. The library source could be found here . It is well documented, and you may find some functions useful for you job.","breadcrumbs":"Rendering » Shaders » Shaders language","id":"291","title":"Shaders language"},"292":{"body":"Shader has rigid structure that could be described in this code snippet: ( // A set of properties, there could be any amount of properties. properties: [ ( // Each property must have a name. This name must match with respective // uniforms! That's is the whole point of having properties. name: \"diffuseTexture\", // Value has limited set of possible variants. value: Sampler(default: None, fallback: White) ) ], // A set of render passes (see next section for more info) passes: [ ( // Name must match with the name of either standard render pass (see below) or // one of your passes. name: \"Forward\", // A set of parameters that regulate renderer pipeline state. // This is mandatory field of each render pass. draw_parameters: DrawParameters( // A face to cull. Either Front or Back. cull_face: Some(Back), // Color mask. Defines which colors should be written to render target. color_write: ColorMask( red: true, green: true, blue: true, alpha: true, ), // Whether to modify depth buffer or not. depth_write: true, // Whether to use stencil test or not. stencil_test: None, // Whether to perform depth test when drawing. depth_test: true, // Blending options. blend: Some(BlendFunc( sfactor: SrcAlpha, dfactor: OneMinusSrcAlpha, )), // Stencil options. stencil_op: StencilOp( fail: Keep, zfail: Keep, zpass: Keep, write_mask: 0xFFFF_FFFF, ), ), // Vertex shader code. vertex_shader: r#\" layout(location = 0) in vec3 vertexPosition; layout(location = 1) in vec2 vertexTexCoord; uniform mat4 rg3d_worldViewProjection; out vec2 texCoord; void main() { texCoord = vertexTexCoord; gl_Position = rg3d_worldViewProjection * vertexPosition; } \"#; // Pixel shader code. pixel_shader: r#\" // Note that the name of this uniform match the name of the property up above. uniform sampler2D diffuseTexture; out vec4 FragColor; in vec2 texCoord; void main() { FragColor = diffuseColor * texture(diffuseTexture, texCoord); } \"#; ) ],\n)","breadcrumbs":"Rendering » Shaders » Structure","id":"292","title":"Structure"},"293":{"body":"Property is a named variable of some type. Properties are directly tied with the uniforms in the sub-shaders, for each you can have a property called time, and then you can define uniform float time; in your sub-shader and the engine will pass a property value to that uniform for you before drawing an object. Properties placed in a \"global namespace\", which means that every sub-shader has \"access\" to the properties.","breadcrumbs":"Rendering » Shaders » Properties","id":"293","title":"Properties"},"294":{"body":"There are a number of built-in properties, the full list is available here","breadcrumbs":"Rendering » Shaders » Built-in properties","id":"294","title":"Built-in properties"},"295":{"body":"Predefined render passes helps you to create your own shader without a need to create your own render pass and to quickly start writing your shaders. GBuffer - A pass that fills a set with render target sized textures with various data about each rendered object. These textures then are used for physically-based lighting. Use this pass when you want the standard lighting to work with your objects. Forward - A pass that draws an object directly in render target. This pass is very limiting, it does not support lighting, shadows, etc. It should be only used to render translucent objects. SpotShadow - A pass that emits depth values for an object, later this depth map will be used to render shadows. PointShadow - A pass that emits distance from a fragment to a point light, later this depth map will be used to render shadows.","breadcrumbs":"Rendering » Shaders » Predefined render passes","id":"295","title":"Predefined render passes"},"296":{"body":"Drawing parameters defines which GPU functions to use and at which state. For example, to render transparent objects you need to enable blending with specific blending rules. Or you need to disable culling to draw objects from both sides. This is when draw parameters come in handy. There is a relatively large list of drawing parameters, and it could confuse a person who isn't used to work with graphics. Thankfully there is a good documentation about this available here","breadcrumbs":"Rendering » Shaders » Drawing parameters","id":"296","title":"Drawing parameters"},"297":{"body":"Vertex shader operates on single vertices, it must provide at least the position of the vertex in clipping space. In other words it has to do at least this: layout(location = 0) in vec3 vertexPosition; uniform mat4 rg3d_worldViewProjection; // Note the built-in variable. void main()\n{ gl_Position = rg3d_worldViewProjection * vertexPosition;\n} This is the simplest vertex shader, using vertex shaders you can create various graphical effects that affects vertices.","breadcrumbs":"Rendering » Shaders » Vertex shader","id":"297","title":"Vertex shader"},"298":{"body":"Pixel shader (or more precisely - fragment shader), operates on a small fragment of your render target. In general pixels shaders just writes some color to a render target (or multiple targets) using some program. out vec4 FragColor; void main()\n{ FragColor = vec4(1, 0, 0, 1);\n} This is the simplest pixel shader, it just fills the render target with red color.","breadcrumbs":"Rendering » Shaders » Pixel Shader","id":"298","title":"Pixel Shader"},"299":{"body":"Material defines a set of values for a shader. Materials usually contains textures (diffuse, normal, height, emission and other maps), numerical values (floats, integers), vectors, booleans, matrices and arrays of each type, except textures. Each parameter can be changed in runtime giving you the ability to create animated materials. However, in practice, most materials are static, this means that once it's created, it won't be changed anymore. Please keep in mind that the actual \"rules\" of drawing an entity are stored in the shader, material is only a storage for specific uses of the shader. Multiple materials can share the same shader, for example standard shader covers 95% of most common use cases, and it is shared across multiple materials. The only difference are property values, for example you can draw multiple cubes using the same shader, but with different textures. Material itself can be shared across multiple places as well as the shader. This gives you the ability to render multiple objects with the same material efficiently.","breadcrumbs":"Rendering » Materials » Materials","id":"299","title":"Materials"},"3":{"body":"The book is primarily focused on game development with Fyrox, not on its API. You can find API docs here .","breadcrumbs":"About the Book » API Documentation","id":"3","title":"API Documentation"},"30":{"body":"Let's talk a bit about design philosophy and goals of the engine. Development of the engine started in the beginning of 2019 as a hobby project to learn Rust, and it quickly showed that Rust can be a game changer in the game development industry. Initially, the engine was just a port of an engine that is written in C. At the beginning, it was very interesting to build such complex thing as game engine in such low level language without any safety guarantees. After a year of development it became annoying to fix memory related issues (memory corruption, leaks, etc.), luckily at that time Rust's popularity grew, and it showed on my radar. I ( @mrDIMAS ) was able to port the engine to it in less than a year. Stability has improved dramatically, no more random crashes, performance was at the same or better levels - time invested in learning new language was paid off. Development speed does not degrade over time as it was in C, it is very easy to manage growing project.","breadcrumbs":"Introduction » Design Philosophy and Goals » Design Philosophy and Goals","id":"30","title":"Design Philosophy and Goals"},"300":{"body":"It is very important re-use materials as much as possible, because the amount of materials used per frame significantly correlates with performance. The more unique materials you have per frame, the more work the renderer and video driver need in order to render a frame and more time the frame will require for rendering, thus lowering your FPS.","breadcrumbs":"Rendering » Materials » Performance","id":"300","title":"Performance"},"301":{"body":"The engine offers a standard PBR material, PBR stands for \"Physically-Based Rendering\" which gives you the quality of shading which is very close to materials in real world (to some extent of course). The standard material can cover 95% of use cases, and it is suitable for almost any kind of game, except maybe some cartoon-ish or stylized games. The standard material has quite a lot of properties that can be used to fully utilize the power of PBR rendering: diffuseColor - an RGBA color that will be used as a base color for you object. Caveat: the opacity value (alpha) will be used only with Forward render path! This means that you will need to switch render path on your mesh ( see below ) diffuseTexture - a 2D texture containing the unlit \"basic\" colors of your object, this is the most commonly used texture. For example, you can assign a brick wall texture to this property and your object will look like a brick wall. normalTexture - a 2D texture containing per-pixel normal vectors. metallicTexture - a 2D texture containing per-pixel metallic factor, where 0 - dielectric, 1 - metal. In simple words it defines whether your object reflects (1.0) the environment or not (0.0). roughnessTexture - a 2D texture containing per-pixel roughness factor, where 0 - completely flat, 1 - very rough. heightTexture - a 2D texture containing per-pixel displacement value, it is used with parallax mapping to crate an effect of volume on a flat surface. emissionTexture - a 2D texture containing per-pixel emission lighting. You could use this to create emissive surfaces like small lamps on wall of sci-fi ship, or to create glowing eyes for your monsters that will scare the player. lightmapTexture - a 2D texture containing per-pixel static lighting. It is used to apply precomputed light to your 3D models, and the most common use case is to lit a static object using a static light. Precomputed light is very cheap. The engine offers built-in lightmapper that can generate lightmaps for you. aoTexture - a 2D texture containing per-pixel shading values, allows you to \"bake\" shadows in for your 3D object. texCoordScale - a 2D vector that allows you to scale texture coordinates used to sample the textures mentioned above (except lightmaps, they're using separate texture coordinates) layerIndex - a natural number that is used for decals masking, a decal will only be applied to your mesh if and only if the decal has matching index. emissionStrength - a 3D vector that allows you to set the strength of emission per-channel (R, G, B) for your emissionTexture","breadcrumbs":"Rendering » Materials » Standard material","id":"301","title":"Standard material"},"302":{"body":"The standard material offers very basic transparency support, to use it you have to explicitly switch render path on your mesh object. It could be done in this way: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# scene::{mesh::RenderPath, node::Node, Scene},\n# };\n# # fn set_forward_render_path(scene: &mut Scene, mesh_handle: Handle) { scene.graph[mesh_handle] .as_mesh_mut() .set_render_path(RenderPath::Forward);\n# } After this, your mesh will be rendered using a specialized render pass called Forward which supports alpha-blending and transparent objects. Caveat: Current forward renderer implementation does not support any kind of lighting, if you need lighting, you will need to use custom shader for that!","breadcrumbs":"Rendering » Materials » Transparency","id":"302","title":"Transparency"},"303":{"body":"When you're loading a 3D model in the engine, the engine tries to convert the materials stored inside to standard material. In most cases there is no way to create 100% matching material on the fly, instead the engine tries to do its best to make sure the material will be imported as closely as possible to the original one. Various 3D modelling tools use different material system, but all of them allow you to export your 3D model in one of the commonly used formats (such as FBX).","breadcrumbs":"Rendering » Materials » Material import","id":"303","title":"Material import"},"304":{"body":"When using Blender, make sure you are using Principled BSDF material, it is the closest material that can be converted to engine's standard material at almost 100% fidelity.","breadcrumbs":"Rendering » Materials » Blender","id":"304","title":"Blender"},"305":{"body":"It highly depends on the version of the 3Ds max, but in general the default material should work fine.","breadcrumbs":"Rendering » Materials » 3Ds max","id":"305","title":"3Ds max"},"306":{"body":"","breadcrumbs":"Rendering » Light Maps (WIP) » Light Maps (WIP)","id":"306","title":"Light Maps (WIP)"},"307":{"body":"Renderer has a large set of settings, that allows you to tweak graphics quality to find optimal balance between rendering quality and performance. Quality settings are represented by the following structure: # extern crate fyrox;\n# use fyrox::renderer::{CsmSettings, ShadowMapPrecision};\nstruct QualitySettings { point_shadow_map_size: usize, point_soft_shadows: bool, point_shadows_enabled: bool, point_shadows_distance: f32, point_shadow_map_precision: ShadowMapPrecision, spot_shadow_map_size: usize, spot_soft_shadows: bool, spot_shadows_enabled: bool, spot_shadows_distance: f32, spot_shadow_map_precision: ShadowMapPrecision, csm_settings: CsmSettings, use_ssao: bool, ssao_radius: f32, light_scatter_enabled: bool, fxaa: bool, use_parallax_mapping: bool, use_bloom: bool,\n} point_shadow_map_size - size of a cube map face of shadow map texture (in pixels). The higher, the better quality, but lower performance. Typical values for medium GPU (GTX 1050) is 1024 pixels. point_soft_shadows - should the shadows from point lights be smooth (true) or blocky (false). The latter option has better performance, but lower quality. point_shadows_enabled - are the shadows from point lights enabled? point_shadows_distance - maximal distance from a camera to draw point light shadows. It is used to disable shadows on distant lights. The distance is given in meters. The lower the value, the better performance is. point_shadow_map_precision - defines bit-depth (u16 or u32) for shadow map pixels. Lower bit depth means better performance and lower quality. spot_shadow_map_size - size of a shadow map texture for spotlights. The higher, the better quality, but lower performance. Typical values for medium GPU (GTX 1050) is 1024 pixels. spot_soft_shadows - should the shadows from spotlights be smooth (true) or blocky (false). The latter option has better performance, but lower quality. spot_shadows_enabled - are the shadows from spotlights enabled? spot_shadows_distance - maximal distance from a camera to draw spotlight shadows. It is used to disable shadows on distant lights. The distance is given in meters. The lower the value, the better performance is. spot_shadow_map_precision - defines bit-depth (u16 or u32) for shadow map pixels. Lower bit depth means better performance and lower quality. csm_settings - settings for cascaded shadow maps for directional lights. enabled - whether cascaded shadow maps enabled or not. size - size of texture for each cascade. precision - defines bit-depth (u16 or u32) for shadow map pixels. Lower bit depth means better performance and lower quality. pcf - should the shadows from directional lights be smooth (true) or blocky (false). The latter option has better performance, but lower quality. use_ssao - defines whether the renderer should perform separate screen-space ambient occlusion pass. This option has relatively small performance impact. ssao_radius - radius of sampling hemisphere used in SSAO, it defines much ambient occlusion will be in your scene. has no performance impact. light_scatter_enabled - global switch to enable or disable light scattering. Each light have its own scatter switch, but this one is able to globally disable scatter. Light scattering has medium performance impact, it also depends on light count in your scene. fxaa - is full-screen anti-aliasing needed? This option has low performance impact. use_parallax_mapping - defines whether the renderer should use parallax mapping to simulate bumps and dents on flat surfaces using special textures. This option has low performance impact. use_bloom - defines whether the renderer should draw glowing pixels. This option has low performance impact.","breadcrumbs":"Rendering » Settings » Settings","id":"307","title":"Settings"},"308":{"body":"The renderer offers built-in presets for various graphics quality, use QualitySettings::ultra(), QualitySettings::high(), QualitySettings::medium() and QualitySettings::low() presets to quickly tune quality-performance balance.","breadcrumbs":"Rendering » Settings » Presets","id":"308","title":"Presets"},"309":{"body":"To apply the settings, use Renderer::set_quality_settings method: # extern crate fyrox;\n# use fyrox::{\n# core::log::Log, engine::GraphicsContext, plugin::PluginContext, renderer::QualitySettings,\n# };\n# fn set_quality_settings(context: &mut PluginContext) { // Keep in mind, that graphics context can be non-initialized. This could happen if you're trying to access it before // your game received `Event::Resumed` event. if let GraphicsContext::Initialized(ref mut graphics_context) = context.graphics_context { let mut settings = QualitySettings::high(); // Disable something. settings.use_ssao = false; settings.fxaa = false; // Apply. Log::verify(graphics_context.renderer.set_quality_settings(&settings)) }\n} Keep in mind, that graphics context can be non-initialized. This could happen if you're trying to access it before your game received Event::Resumed event. See the docs for Event::Resumed for more info. There is only one place, where graphics context is guaranteed to be initialized - Plugin::on_graphics_context_initialized method. Inside it, you can access the renderer by simple: context.graphics_context.as_initialized_mut().renderer, in other places you should always do a checked borrow.","breadcrumbs":"Rendering » Settings » How to apply","id":"309","title":"How to apply"},"31":{"body":"One of the main goals of in the development of the engine is to provide high level of safety. What does this mean? In short: protect from memory-unsafety bugs. This does not include any logic errors, but when your game is free of random crashes due to memory unsafety it is much easier to fix logic bugs, because you don't have to think about potentially corrupted memory. Safety is also dictates architecture design decisions of your game, typical callback hell, that is possible to do in many other languages, is very tedious to implement in Rust. It is possible, but it requires quite a lot of manual work and quickly tell you that you're doing it wrong.","breadcrumbs":"Introduction » Design Philosophy and Goals » Safety","id":"31","title":"Safety"},"310":{"body":"You can define your own render passes that extends the renderer, currently there are render passes only for scenes, so no custom post-effects (this is planned to be improved in Fyrox 0.28). Render pass has full access to graphics framework (which is a thin wrapper around OpenGL) so it can utilize full power of it to implement various graphical effects.","breadcrumbs":"Rendering » Render Pass » Render Pass","id":"310","title":"Render Pass"},"311":{"body":"Render pass is a complex thing, that requires relatively deep knowledge in computer graphics. It is intended to be used by experienced graphics programmers. Here's the simplest render pass that renders unit quad without any textures. # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Matrix4, pool::Handle, sstorage::ImmutableString},\n# renderer::{\n# framework::{\n# error::FrameworkError,\n# framebuffer::{DrawParameters},\n# geometry_buffer::{GeometryBuffer, ElementRange, GeometryBufferKind},\n# gpu_program::{GpuProgram, UniformLocation},\n# },\n# RenderPassStatistics, Renderer, SceneRenderPass, SceneRenderPassContext,\n# },\n# scene::{mesh::surface::SurfaceData, Scene},\n# };\n# use std::{cell::RefCell, rc::Rc};\n# struct MyRenderPass { enabled: bool, shader: GpuProgram, target_scene: Handle, quad: GeometryBuffer, world_view_proj: UniformLocation,\n} impl MyRenderPass { pub fn new( renderer: &mut Renderer, target_scene: Handle, ) -> Result { let vs = r#\" layout(location = 0) in vec3 vertexPosition; uniform mat4 c; void main() { gl_Position = worldViewProjectionMatrix * vertexPosition; } \"#; let fs = r#\" out vec4 FragColor; void main() { FragColor = vec4(1.0, 0.0, 0.0, 1.0); } \"#; let shader = GpuProgram::from_source(&mut renderer.state, \"MyShader\", vs, fs)?; Ok(Self { enabled: true, world_view_proj: shader.uniform_location( &renderer.state, &ImmutableString::new(\"worldViewProjectionMatrix\"), )?, target_scene, quad: GeometryBuffer::from_surface_data( &SurfaceData::make_quad(&Matrix4::identity()), GeometryBufferKind::StaticDraw, &mut renderer.state, ), shader, }) }\n} impl SceneRenderPass for MyRenderPass { fn on_ldr_render( &mut self, ctx: SceneRenderPassContext, ) -> Result { let mut stats = RenderPassStatistics::default(); // Make sure to render only to target scene. if self.enabled && ctx.scene_handle == self.target_scene { stats += ctx.framebuffer.draw( &self.quad, ctx.pipeline_state, ctx.viewport, &self.shader, &DrawParameters::default(), ElementRange::Full, |mut program| { program.set_matrix4(&self.world_view_proj, &Matrix4::identity()); }, )?; } Ok(stats) }\n} The code snippet shows how to create a shader, find its uniforms, and finally how to actually render something in target frame buffer.","breadcrumbs":"Rendering » Render Pass » Creating a render pass","id":"311","title":"Creating a render pass"},"312":{"body":"Every render pass must be registered in the renderer, otherwise it won't be used. You can register a render pass using add_render_pass method of the Renderer: # extern crate fyrox;\n# use fyrox::renderer::{Renderer, SceneRenderPass};\n# use std::{cell::RefCell, rc::Rc};\n# # struct MyRenderPass;\n# impl SceneRenderPass for MyRenderPass {}\n# fn usage_example(renderer: &mut Renderer, render_pass: MyRenderPass) { let shared_pass = Rc::new(RefCell::new(render_pass)); // You can share the pass across multiple places to be able to control it. renderer.add_render_pass(shared_pass);\n} Please notice that we've wrapped render pass in Rc>, this means that you can share it across multiple places and modify its data from the code of your game.","breadcrumbs":"Rendering » Render Pass » Registering a render pass","id":"312","title":"Registering a render pass"},"313":{"body":"This chapter explains how to use normal maps in the engine correctly and how to solve common issues with normal maps as well.","breadcrumbs":"Rendering » Normal Maps » Normal Maps","id":"313","title":"Normal Maps"},"314":{"body":"Fyrox uses so-called DirectX Y- normal maps, which means that it expects the origin of local coordinate system of a normal map to be at the left-top corner of the map, instead of OpenGL Y+ normal maps where the origin is at the left-bottom corner of the map. DirectX Y- normal maps are much more prevalent nowadays, especially when it comes to game-ready 3D models. Some software (like Substance Painter), by default has exporting settings to be set to DirectX Y- style normal maps. The difference between the two is quite obvious if you look at the lighting with both normal maps: difference The left one is DirectX Y- and the right one is OpenGL Y+. As you can see, the left one looks correctly - the screw head is convex as in reality and the lighting is also correct. On the other side, however, the screw head look to be concave and the lighting is opposite.","breadcrumbs":"Rendering » Normal Maps » Format","id":"314","title":"Format"},"315":{"body":"If you have these sort of issues in your project, all you need to do is to flip (G = 1 - G) green channel of you normal map. For now, this should be done manually in some pictures editor, future versions of the engine will have a switch to flip green channel for you automatically. A simple trick of how to understand which type of normal map you have: look at any obvious bump (convex part) on the normal map, if its top contains green-ish colors then you have OpenGL Y+ normal maps and its green channel should be flipped: y difference On the image above, the screw head (in the red circle) is an obviously convex and on the left side you can see, that the green-ish colors is at the bottom, while on the right side green-ish colors is at the top. You could also check the lighting results on your 3D model and see if they're correct (looks the same as in the 3D modelling software, for instance) while moving a light source around.","breadcrumbs":"Rendering » Normal Maps » Solving Issues","id":"315","title":"Solving Issues"},"316":{"body":"This chapter covers asset management in the engine. Asset management is performed by Asset Browser in the editor and by ResourceManager from API.","breadcrumbs":"Resource Management » Asset Management","id":"316","title":"Asset Management"},"317":{"body":"Assets loading is asynchronous, it is possible to load multiple assets in parallel or load until a specific asset is loaded.","breadcrumbs":"Resource Management » General Info","id":"317","title":"General Info"},"318":{"body":"It is strongly advised to specify all resources used by your game entities inside your scripts, instead of requesting resources directly from the resource manager on demand. This approach solves two common issues: It allows you to set resources directly from the editor by a simple drag'n'drop from the Asset Browser. The engine will be able to wait until all resources used by a scene are fully loaded. This is especially important, because this way can guarantee, that scene loading will be \"seamless\" and if the scene was loaded, it means that all resources used by it are loaded too. This can be achieved by adding a respective field in your script. For example, you may a have a weapon script that shoots some projectiles. In this case all you need to add a projectile: Option field in your script, assign it so some prefab in the editor and then instantiate it from code when shooting. Storing resource handle directly in your script helps the engine to gather all resources used by parent scene and preload them too while loading the scene itself. Such approach prevent lags when doing actual shooting, which is especially important if you're targeting a WebAssembly platform. On WebAssembly all the files accessed over network API which could work with unstable connection. In any case, even on PC it helps a lot. Requesting resources on demand could be useful in limited situations: You're loading a new game level - in this case it is perfectly fine to request the resource manually. You're doing some background work (level streaming for instance).","breadcrumbs":"Resource Management » Best Practices","id":"318","title":"Best Practices"},"319":{"body":"Asset browser allows you to preview your assets and edit their import properties. It looks something like this (keep in mind that the screenshot could be outdated). Asset Browser There are three main areas in it: Left directory tree - shows all directories starting from project root. It does not show any files, this is for what the center section is. Center asset previewer - shows all assets from selected directory. The path at the top of the section shows asset path. Right asset import options inspector - it shows import properties of selected asset. Typical workflow could look like this: Select desired directory from the left tree Select desired asset in the center previewer Edit import properties of selected asset and click \"Apply\" button to save import options and re-load the asset with new options. Check next chapters to learn how to manage specific asset types and what their import does what.","breadcrumbs":"Resource Management » Asset Browser","id":"319","title":"Asset Browser"},"32":{"body":"Game engines usually built using system-level programming languages, that provides peak performance levels. Fyrox is not an exception. One if its design goals is to provide high levels of performance by default, leaving an opportunity for adding custom solutions for performance-critical places.","breadcrumbs":"Introduction » Design Philosophy and Goals » Performance","id":"32","title":"Performance"},"320":{"body":"Please read API docs here","breadcrumbs":"Resource Management » API Docs","id":"320","title":"API Docs"},"321":{"body":"","breadcrumbs":"Resource Management » Models » Model resources","id":"321","title":"Model resources"},"322":{"body":"Fyrox supports these file formats for 3D models: FBX - standard game development industry 3D model exchange format RGS - native scenes format produced by Fyroxed (the editor) The list could be extended in the future.","breadcrumbs":"Resource Management » Models » Supported formats","id":"322","title":"Supported formats"},"323":{"body":"Model must be instantiated in your scene, there is no other way of using it. To do this, you can either use drag'n'drop from Asset Browser in the editor or instantiate the model dynamically from code: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::model::{Model, ModelResourceExtension},\n# scene::{node::Node, Scene},\n# };\n# use std::path::Path;\nasync fn instantiate_model( path: &Path, resource_manager: ResourceManager, scene: &mut Scene,\n) -> Handle { // Load model first. Alternatively, you can store resource handle somewhere and use it for // instantiation. let model = resource_manager.request::(path).await.unwrap(); model.instantiate(scene)\n}","breadcrumbs":"Resource Management » Models » Instantiation","id":"323","title":"Instantiation"},"324":{"body":"The engine tries to import materials as close as possible to originals in the model, however it is not always possible because some 3D modelling software could use different shading models. By default, the engine tries to convert everything to PBR materials, so if you have a 3D model with a special material made for cartoon shading, the engine will still import it as PBR material (with lots of missing textures of course). You should take this into account when working with something other than PBR materials. In cases when your 3D model have some weird materials, you should create appropriate materials and shaders manually , the engine is not a magic tool, it has some defaults that do not cover all possible cases. It is also possible to specify how to resolve textures while loading a 3D model, select your model in the Asset Browser and there will be import options right below the model preview: model import It is also possible to specify such options manually. To do that, you need to create import options file with the following content near your 3D model (this is what the editor does for you): ( material_search_options: RecursiveUp\n) The file must have the .options additional extension. For example, if you have a foo.fbx model, the options file should have foo.fbx.options name. Even if it is possible to modify it by hand, it is strongly advised to use the editor to edit import options, because it reduces the chance of messing up.","breadcrumbs":"Resource Management » Models » Material import","id":"324","title":"Material import"},"325":{"body":"Blender's FBX exporter has exporting scale properties usually set to 100%, this may lead to incorrect scale of your model in the engine. It will have (100.0, 100.0, 100.0) scale which is very huge. To fix that, set the scale in the exporter to 0.01.","breadcrumbs":"Resource Management » Models » Tips for Blender","id":"325","title":"Tips for Blender"},"326":{"body":"Latest versions of 3Ds max have node-based material editor which creates some \"junk\" nodes which may mess up material import. To prevent any issues with that, you should clean all assignments to material slots to use maps directly.","breadcrumbs":"Resource Management » Models » Tips for 3Ds Max","id":"326","title":"Tips for 3Ds Max"},"327":{"body":"Texture is an image that is used to fill faces to add details to them. In most cases textures are just 2D images, however there are some exclusions to that - for example cube maps, that may be used for environment mapping. Fyrox supports 1D, 2D, 3D and Cube textures.","breadcrumbs":"Resource Management » Textures » Textures","id":"327","title":"Textures"},"328":{"body":"To load images and decode them, Fyrox uses image and ddsfile crates. Here is the list of supported formats: png, tga, bmp, dds, jpg, gif, tiff, dds.","breadcrumbs":"Resource Management » Textures » Supported formats","id":"328","title":"Supported formats"},"329":{"body":"Fyrox supports most commonly used formats of compressed textures: DXT1, DXT3, DXT5. Such textures can be loaded only from DDS files. You can specify on-demand texture compression in import options (see below), it works for every texture format except DDS. It is meant to be used when you don't want to bother with DDS format, there are two compression methods: Quality - has 4:1 compression ratio, supports full 8-bit alpha channel. Textures with gradients will most likely suffer from noticeable banding. Speed - has lower quality compared to Quality mode, but it has 8:1 compression ratio for texture without alpha channel and 6:1 with alpha channel. Keep in mind, that alpha channel in this mode supports only 1 bit - it is either enabled or not. Compressed textures usually does not support color gradient very well, if you have a texture with a lot of colors and gradients, then you'll most likely get compressed texture with lots of graphical artifacts such as banding. It is also worth mentioning, that you should never use compression with normal maps, it can significantly distort normals because normal maps usually have lots of color gradients.","breadcrumbs":"Resource Management » Textures » Compressed textures","id":"329","title":"Compressed textures"},"33":{"body":"Other very important part is that the engine should be friendly to newcomers. It should lower entry threshold, not make it worse. Fyrox uses well known and battle-tested concepts, thus making it easier to make games with it. On other hand, it still can be extended with anything you need - it tries to be as good for veterans of the game industry as for newcomers.","breadcrumbs":"Introduction » Design Philosophy and Goals » Ease of use","id":"33","title":"Ease of use"},"330":{"body":"It is possible to define custom import options. Using import options you could set desired compression quality, filtering, wrapping, etc. Import options should be defined using Asset Browser in the editor: texture import It is also possible to define import options manually in a separate file with the same name as the source texture, but with additional extension options, this is what the editor does for you. For example, you have a foo.jpg texture, a file with import options should be called foo.jpg.options. Its content may look something like this: ( minification_filter: Linear, magnification_filter: Linear, s_wrap_mode: Repeat, t_wrap_mode: ClampToEdge, anisotropy: 8.0, compression: NoCompression, ) Even if it is possible to modify it by hand, it is strongly advised to use the editor to edit import options, because it reduces chances of messing up.","breadcrumbs":"Resource Management » Textures » Import options","id":"330","title":"Import options"},"331":{"body":"Texture can be used as a render target to render a scene in it. To do this you should use new_render_target method and pass its result to scene's render target property. Renderer will automatically provide you info about metrics of texture, but it won't give you access to pixels of render target.","breadcrumbs":"Resource Management » Textures » Render target","id":"331","title":"Render target"},"332":{"body":"","breadcrumbs":"Resource Management » Sound Buffers (WIP) » Sound (WIP)","id":"332","title":"Sound (WIP)"},"333":{"body":"","breadcrumbs":"Resource Management » Curves (WIP) » Curve (WIP)","id":"333","title":"Curve (WIP)"},"334":{"body":"In Fyrox, you can create your own, custom resource type that can be embedded in the standard resource management pipeline. It could be useful to access specific data using engine's resource manager. Custom resources has a few major advantages over manual resource management via direct files access: Since Fyrox resource system is asynchronous, your resource can be loaded in separate worker thread which speeds up loading (since it may run on a separate CPU core). You can access your resources from the Asset Browser and assign their handles to scripts directly from the editor. File access for resource management has an abstraction, that unifies the access over all supported platforms. This means that you don't need to use fetch API directly, if you're targeting WebAssembly platform, or use AssetManager on Android. To create a custom resource, you need to do three major steps: Define your resource structure with all required traits implemented. Add a custom resource loader, that will be used by the resource manager to load your custom resource. Register the resource loader in the resource manager. See the code snippet in the next section as a guide.","breadcrumbs":"Resource Management » Custom Resource » Custom Resources","id":"334","title":"Custom Resources"},"335":{"body":"Custom resource is just an ordinary struct with some data. It must implement Debug, Reflect, Visit, ResourceData traits. Also, it must contain at least path to external file with the content. Here's the simplest custom resource, that contains some string data. # extern crate fyrox;\n# use fyrox::{\n# asset::{\n# event::ResourceEventBroadcaster,\n# loader::{BoxedLoaderFuture, ResourceLoader},\n# untyped::UntypedResource,\n# ResourceData,\n# },\n# core::{\n# io::{self},\n# reflect::prelude::*,\n# uuid::{uuid, Uuid},\n# visitor::prelude::*,\n# TypeUuidProvider,\n# },\n# };\n# use std::{\n# any::Any,\n# borrow::Cow,\n# path::{Path, PathBuf},\n# };\n# #[derive(Debug, Visit, Reflect)]\nstruct CustomResource { // You resource must store the path. path: PathBuf, some_data: String,\n} impl TypeUuidProvider for CustomResource { // Every resource must provide a unique identifier, that is used for dynamic type // casting, serialization, etc. fn type_uuid() -> Uuid { uuid!(\"15551157-651b-4f1d-a5fb-6874fbfe8637\") }\n} impl ResourceData for CustomResource { fn path(&self) -> Cow { Cow::Borrowed(&self.path) } fn set_path(&mut self, path: PathBuf) { self.path = path; } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn type_uuid(&self) -> Uuid { ::type_uuid() } fn is_procedural(&self) -> bool { false }\n} struct CustomResourceLoader; impl ResourceLoader for CustomResourceLoader { fn extensions(&self) -> &[&str] { // An array of extensitions, supported by this loader. There could be any number of extensions // since sometimes multiple extensions map to a single resource (for instance, jpg, png, bmp, are // all images). &[\"my_resource\"] } fn data_type_uuid(&self) -> Uuid { ::type_uuid() } fn load( &self, resource: UntypedResource, event_broadcaster: ResourceEventBroadcaster, reload: bool, ) -> BoxedLoaderFuture { Box::pin(async move { let path = resource.path(); match io::load_file(&path).await { Ok(content) => { let my_resource = CustomResource { path, some_data: String::from_utf8(content).unwrap(), }; resource.commit_ok(my_resource); // Notify potential subscribers that the resource was loader. event_broadcaster.broadcast_loaded_or_reloaded(resource, reload); } Err(err) => { resource.commit_error(path, err); } } }) }\n} Keep in mind, that you must provide unique UUID for every resource type that you're creating. Otherwise, using existing id multiple times will cause incorrect serialization and type casting. The next step is to register the new resource in the resource manager. This can be done by adding the following code to the register method for impl PluginConstructor for GameConstructor: # extern crate fyrox;\n# use fyrox::{\n# asset::{\n# event::ResourceEventBroadcaster,\n# loader::{BoxedLoaderFuture, ResourceLoader},\n# untyped::UntypedResource,\n# },\n# core::{pool::Handle, uuid::Uuid},\n# plugin::{Plugin, PluginConstructor, PluginContext, PluginRegistrationContext},\n# scene::Scene,\n# };\n# # struct CustomResourceLoader;\n# # impl ResourceLoader for CustomResourceLoader {\n# fn extensions(&self) -> &[&str] {\n# todo!()\n# }\n# # fn data_type_uuid(&self) -> Uuid {\n# todo!()\n# }\n# # fn load(\n# &self,\n# resource: UntypedResource,\n# event_broadcaster: ResourceEventBroadcaster,\n# reload: bool,\n# ) -> BoxedLoaderFuture {\n# todo!()\n# }\n# }\n# # pub struct GameConstructor;\n# impl PluginConstructor for GameConstructor { fn register(&self, context: PluginRegistrationContext) { context .resource_manager .state() .loaders .set(CustomResourceLoader); // ... }\n# # fn create_instance(\n# &self,\n# scene_path: Option<&str>,\n# context: PluginContext,\n# ) -> Box {\n# todo!()\n# }\n} After doing so, any attempt to load a resource with my_resource extension will call the load method of your resource loader.","breadcrumbs":"Resource Management » Custom Resource » Example","id":"335","title":"Example"},"336":{"body":"There's one more step before your custom resource is fully usable - you need to register a property editor for it, so any fields in your scripts that has my_resource: Option> fields can be editable in the editor. Otherwise, you'll see an error message in the Inspector instead of resource selector field. To register a property editor, add the following lines to editor/src/main.rs file, somewhere after the editor instance is created: editor.inspector.property_editors.insert( ResourceFieldPropertyEditorDefinition::::new( Rc::new(|resource_manager, path| { resource_manager .try_request::(path) .map(|r| block_on(r)) }), editor.message_sender.clone(), ),\n); After this, the editor will create this property editor for my_resource field and will allow you to set its value by drag'n'dropping an asset from the Asset Browser.","breadcrumbs":"Resource Management » Custom Resource » Editor Support","id":"336","title":"Editor Support"},"337":{"body":"Fyrox features an extremely powerful and flexible node-based user interface system. Power and flexibility comes with a certain price: it has a steep learning curve. This chapter will cover user interface usage in the engine, explain basic concepts, provide information about most commonly used widgets, and so on.","breadcrumbs":"User Interface » User Interface","id":"337","title":"User Interface"},"338":{"body":"You can explore UI system capabilities in this web demo . Keep in mind, that it was designed to run on PC and wasn't tested on mobile devices.","breadcrumbs":"User Interface » Web Demo","id":"338","title":"Web Demo"},"339":{"body":"This chapter should help you understand basic concepts lying in the foundation of the GUI in the engine.","breadcrumbs":"User Interface » Basic concepts » Basic concepts","id":"339","title":"Basic concepts"},"34":{"body":"Fyrox has large projects built on it, that helps to understand real needs for general-purpose game engine. This helps in revealing weak spots in design and fix them.","breadcrumbs":"Introduction » Design Philosophy and Goals » Battle-tested","id":"34","title":"Battle-tested"},"340":{"body":"* Stateful UI means that we can create and destroy widgets when we need to, it is the opposite approach of immediate-mode or stateless UIs when you don't have long-lasting state for your widgets (usually stateless UI hold its state only for one or two frames). Stateful UI is much more powerful and flexible, it allows you to have complex layout system without having to create hacks to create complex layout as you'd do in immediate-mode UIs. It is also much faster in terms of performance. Stateful UI is a must for complex user interfaces that requires rich layout and high performance. I'm not telling that you can't do it in immediate mode UI, you can, but using tons of hacks. See Layout section for more info.","breadcrumbs":"User Interface » Basic concepts » Stateful","id":"340","title":"Stateful"},"341":{"body":"Every user interface could be represented as a set of small blocks that have hierarchical bonding between each other. For example a button could be represented using two parts: a background and a foreground. Usually the background is just a simple rectangle (either a vector or bitmap), and a foreground is a text. The text (the foreground widget) is a child object of the rectangle (the background widget). These two widgets forms another, more complex widget that we call button. Graphically it will look like this: Button On the right side of the image we can see the generic button and on the left side, we can see its hierarchical structure. Such approach allows us to modify the look of the button as we wish, we can create a button with image background, or with any vector image, or even other widgets. The foreground can be anything too, it can also contain its own complex hierarchy, like a pair of an icon with a text and so on.","breadcrumbs":"User Interface » Basic concepts » Node-based architecture","id":"341","title":"Node-based architecture"},"342":{"body":"Every widget in the engine uses composition to build more complex widgets. All widgets (and respective builders) contains Widget instance inside, it provides basic functionality the widget such as layout information, hierarchy, default foreground and background brushes (their usage depends on derived widget), render and layout transform and so on.","breadcrumbs":"User Interface » Basic concepts » Composition","id":"342","title":"Composition"},"343":{"body":"Many widgets provide component querying functionality - you can get an immutable reference to inner component by its type. It is used instead of type casting in many places. Component querying is much more flexible compared to direct type casting. For example, you may want to build a custom Tree widget, you want your CustomTree to inherit all the functionality from the Tree, but add something new. The Tree widget can manage its children subtrees, but it needs to somehow get required data from subtree. Direct type casting would fail in this case, because now you have something like this: # extern crate fyrox;\n# use fyrox::gui::tree::Tree;\nstruct CustomTree { tree: Tree, my_data: u32\n} On other hand, component querying will work fine, because you can query inner component (Tree in our case). Please note that this has nothing similar with ECS and stuff, it is made to circumvent Rust's lack of inheritance.","breadcrumbs":"User Interface » Basic concepts » Component Querying","id":"343","title":"Component Querying"},"344":{"body":"The engine uses message passing mechanism for any UI logic. What does that mean? Let's see at the button from the previous section and imagine we want to change its text. To do that we need to explicitly \"tell\" the button's text widget to change its content to something new. This is done by sending a message to the widget. There is no classic callbacks to handle various types of messages, which may come from widgets. Instead, you should write your own message dispatcher where you'll handle all messages. Why so? At first - decoupling, in this case business logic is decoupled from the UI. You just receive messages one-by-one and do specific logic. The next reason is that any callback would require context capturing which could be somewhat restrictive - since you need to share context with the UI, it would force you to wrap it in Rc>/Arc>. Message dispatcher is very easy to write, all you need to do is to handle UI messages in Plugin::on_ui_message method: # extern crate fyrox;\n# use fyrox::{\n# core::{pool::Handle},\n# event_loop::ControlFlow,\n# gui::{button::ButtonMessage, message::UiMessage, UiNode},\n# plugin::{Plugin, PluginContext},\n# };\n# struct MyPlugin { button: Handle,\n} impl Plugin for MyPlugin { fn on_ui_message( &mut self, _context: &mut PluginContext, message: &UiMessage, _control_flow: &mut ControlFlow, ) { if let Some(ButtonMessage::Click) = message.data() { if message.destination() == self.button { println!(\"The button was clicked!\"); } } }\n} As you can see, all you need to do is to check type of incoming message and message destination, which is a node handle from which a message was come from. Then you do any actions you want.","breadcrumbs":"User Interface » Basic concepts » Message passing","id":"344","title":"Message passing"},"345":{"body":"Message passing mechanism works in pair with various routing strategies that allows you to define how the message will \"travel\" across the tree of nodes. Bubble - a message starts its way from a widget and goes up on hierarchy until it reaches root node of hierarchy. Nodes that lies outside that path won't receive the message. This is the most important message routing strategy, that is used for every node by default. Direct - a message passed directly to every node that are capable to handle it. There is actual routing in this case. Direct routing is used in rare cases when you need to catch a message outside its normal \"bubble\" route.","breadcrumbs":"User Interface » Basic concepts » Message routing strategies","id":"345","title":"Message routing strategies"},"346":{"body":"The engine uses very complex, yet powerful layout system that allows you to build complex user interfaces with complex layout. Layout pass has two recursive sub-passes: Measurement - the sub-pass is used to fetch the desired size of each node in hierarchy. Arrangement - the sub-pass is used to set final position and size of each node in hierarchy. Such split is required because we need to know desired size of each node in hierarchy before we can actually do an arrangement.","breadcrumbs":"User Interface » Basic concepts » Layout","id":"346","title":"Layout"},"347":{"body":"UI system is fully decoupled from engine scenes, this means that you can't incorporate UI widgets in a scene graph. As a consequence, you don't have an ability to attach scripts to widgets. This limitation is intentional, and it is here only for one reason - decoupling of UI code from game logic. Currently, there's only one right approach to make UIs - to create widgets in your game plugin and sync the state of the widgets with game entities manually. Also, FyroxEd does not have a UI editor yet, which means that you need to create UI only from code. This limitation will be fixed in the future versions.","breadcrumbs":"User Interface » Basic concepts » Limitations","id":"347","title":"Limitations"},"348":{"body":"This chapter covers general rules that will help you to write code that will be easy to understand.","breadcrumbs":"User Interface » General rules » General rules","id":"348","title":"General rules"},"349":{"body":"Widget builders supports fluent syntax, this means that you can create your widget in series of nested call of other widget builders. In code, it looks something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle, resource::texture::Texture,\n# asset::manager::ResourceManager,\n# gui::{\n# button::ButtonBuilder, image::ImageBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# utils::into_gui_texture,\n# };\n# fn create_fancy_button(ui: &mut UserInterface, resource_manager: ResourceManager) -> Handle {\n# let ctx = &mut ui.build_ctx();\nButtonBuilder::new(WidgetBuilder::new()) .with_back( ImageBuilder::new(WidgetBuilder::new()) .with_texture(into_gui_texture( resource_manager.request::(\"path/to/your/texture\"), )) .build(ctx), ) .with_text(\"Click me!\") .build(ctx)\n# } This code snippet creates a button with an image and a text. Actually it creates three widgets, that forms complex hierarchy. The topmost widget in hierarchy is the Button widget itself, it has two children widgets: background image and a text. Background image is set explicitly by calling image widget builder with specific texture. The text is created implicitly, the button builder creates Text widget for you and attaches it to the button. The structure of the button can contain any amount of nodes, for example you can create a button that contains text with some icon. To do that, replace .with_text(\"My Button\") with this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::texture::Texture,\n# gui::{\n# button::ButtonBuilder,\n# grid::{Column, GridBuilder, Row},\n# image::ImageBuilder,\n# text::TextBuilder,\n# widget::WidgetBuilder,\n# UiNode, UserInterface,\n# },\n# utils::into_gui_texture,\n# };\n# # fn create_fancy_button(\n# ui: &mut UserInterface,\n# resource_manager: ResourceManager,\n# ) -> Handle {\n# let ctx = &mut ui.build_ctx();\n# # ButtonBuilder::new(WidgetBuilder::new()) .with_content( GridBuilder::new( WidgetBuilder::new() .with_child( ImageBuilder::new(WidgetBuilder::new().on_column(0)) .with_texture(into_gui_texture( resource_manager.request::(\"your_icon\"), )) .build(ctx), ) .with_child( TextBuilder::new(WidgetBuilder::new().on_column(1)) .with_text(\"My Button\") .build(ctx), ), ) .add_row(Row::stretch()) .add_column(Column::auto()) .add_column(Column::stretch()) .build(ctx), )\n# .build(ctx)\n# } Quite often you need to store a handle to a widget in a variable, there is one neat trick to do that preserving the fluent syntax: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::texture::Texture,\n# gui::{\n# button::ButtonBuilder, image::ImageBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# utils::into_gui_texture,\n# };\n# fn create_fancy_button(ui: &mut UserInterface, resource_manager: ResourceManager) -> Handle {\n# let ctx = &mut ui.build_ctx();\nlet image;\nButtonBuilder::new(WidgetBuilder::new()) .with_back({ image = ImageBuilder::new(WidgetBuilder::new()) .with_texture(into_gui_texture( resource_manager.request::(\"path/to/your/texture\"), )) .build(ctx); image }) .with_text(\"Click me!\") .build(ctx)\n# }\n// image now contains a handle of the Image widget","breadcrumbs":"User Interface » General rules » Fluent syntax","id":"349","title":"Fluent syntax"},"35":{"body":"This chapter contains answers for frequently asked questions.","breadcrumbs":"Introduction » Frequently Asked Questions » Frequently Asked Questions","id":"35","title":"Frequently Asked Questions"},"350":{"body":"The answer depends on the use case, but the general rules here is quite simple: If your widget exist in a single instance, then there is no need to create a custom widget for it. If you need to create multiple instances of your widget, and each widget will carry some specific data, then you definitely need a custom widget. Custom widgets have some limitations that could be limiting, one of them is that custom widgets do not have access to your code, since they're \"living\" inside UI and know nothing about the \"environment\" where they're being used.","breadcrumbs":"User Interface » General rules » Should I create a custom widget or use composition of other widgets?","id":"350","title":"Should I create a custom widget or use composition of other widgets?"},"351":{"body":"User interface is usually rendered directly on screen, and in most cases this is enough. However, there are some specific cases when you need to incorporate user interface in your game scene as an interactive screen (holographic, for example) or to render some scene inside some UI element (to create some sort of in-game CCTV, for example). This chapter covers these specific cases and rendering nuances in general.","breadcrumbs":"User Interface » Rendering » Rendering","id":"351","title":"Rendering"},"352":{"body":"Offscreen rendering is used to render a UI into a texture, so it can be later used in your game scene. Here's a simple example - holographic inventory in sci-fi games: offscreen ui Default engine's user interface instance (accessible in context.user_interface from plugin methods) can't be rendered offscreen due to engine design. However, you can create a new user interface instance, populate it with widgets, and then render it to a texture. This could be done like so: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector2, log::Log, pool::Handle, sstorage::ImmutableString},\n# engine::GraphicsContext,\n# event::Event,\n# event_loop::ControlFlow,\n# gui::{button::ButtonBuilder, widget::WidgetBuilder, UserInterface},\n# material::{Material, PropertyValue},\n# plugin::{Plugin, PluginContext},\n# resource::texture::{TextureResource, TextureResourceExtension},\n# scene::Scene,\n# utils,\n# };\n# struct Game { // Add these fields to your game. my_ui: UserInterface, render_target: TextureResource, screen_size: Vector2,\n} impl Game { pub fn new(override_scene: Handle, context: PluginContext) -> Self { // Add this code to your Game::new // Define the desired render target size. let width = 128; let height = 128; // Create render target and user interface. let render_target = TextureResource::new_render_target(width, height); let screen_size = Vector2::new(width as f32, height as f32); let mut my_ui = UserInterface::new(screen_size); // Create some widgets as usual. ButtonBuilder::new(WidgetBuilder::new()) .with_text(\"Click Me!\") .build(&mut my_ui.build_ctx()); // Use render_target as an ordinary texture - it could be applied to any material like so: let mut material = Material::standard(); Log::verify(material.set_property( &ImmutableString::new(\"diffuseTexture\"), PropertyValue::Sampler { value: Some(render_target.clone()), fallback: Default::default(), }, )); // This material **must** be assigned to some mesh in your scene! Self { my_ui, render_target, screen_size, } }\n} impl Plugin for Game { fn on_os_event( &mut self, event: &Event<()>, context: PluginContext, control_flow: &mut ControlFlow, ) { // This is the tricky part. Event OS event handling will be different depending on the use case. // In cases if your UI just shows some information, this method can be fully removed. In case when // you need to interact with the UI, there are two different ways. // 1) If your UI will be incorporated in 2D scene, you need to patch mouse events - mostly positions // of the cursor so it will be in local coordinates. // 2) In 3D, it is much more complex - you need to patch mouse events as well, but use mouse OS events // to produce a picking ray and do an intersection test with a quad that will serve as a canvas for your // UI to obtain the local mouse coordinates. if let Event::WindowEvent { event, .. } = event { if let Some(event) = utils::translate_event(event) { self.my_ui.process_os_event(&event); } } } fn update(&mut self, context: &mut PluginContext, control_flow: &mut ControlFlow) { // It is very important to update the UI every frame and process all events that // comes from it. self.my_ui.update(self.screen_size, context.dt); while let Some(message) = self.my_ui.poll_message() { // Do something with the events coming from the custom UI. } } fn before_rendering(&mut self, context: PluginContext, control_flow: &mut ControlFlow) { // Render the UI before every other rendering operation, this way the texture will be ready for use immediately. if let GraphicsContext::Initialized(ref mut graphics_context) = context.graphics_context { Log::verify( graphics_context .renderer .render_ui_to_texture(self.render_target.clone(), &mut self.my_ui), ); } }\n} There's quite a lot of code, but it is quite simple and the comments should help you to understand which part does what. It uses standard plugin structure and contents of each method should be placed in the according methods in your game. This code creates a new user interface, a render target of appropriate size and renders the UI into the render target. The render target then could be used as a diffuse texture in one of your materials, which in its turn, can be assigned to pretty much any mesh in your game.","breadcrumbs":"User Interface » Rendering » Offscreen Rendering","id":"352","title":"Offscreen Rendering"},"353":{"body":"It is possible to \"embed\" a scene into arbitrary user interface. This could be useful if you need to create some sort of CCTV, or to show 3D graphics in 2D user interface and so on. To do so, you need to specify a render target for your scene and then use the texture (render target) in an Image widget. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{image::ImageBuilder, widget::WidgetBuilder, UiNode},\n# plugin::PluginContext,\n# resource::texture::{TextureResource, TextureResourceExtension},\n# scene::Scene,\n# utils,\n# };\n# fn reroute_scene_rendering( width: u32, height: u32, scene: &mut Scene, context: &mut PluginContext,\n) -> Handle { // Create render target first. let render_target = TextureResource::new_render_target(width, height); // Specify render target for the scene. scene.rendering_options.render_target = Some(render_target.clone()); // The scene will be drawn in this image widget. ImageBuilder::new( WidgetBuilder::new() .with_width(width as f32) .with_height(height as f32), ) .with_texture(utils::into_gui_texture(render_target.clone())) .build(&mut context.user_interface.build_ctx())\n} This function could be used as-is to re-route rendering of a scene to an Image widget. It creates a new render target first, then it assigns it to the scene, and also it creates a new Image widget with the render target as a texture. A simple example of what this code does is the scene previewer in the editor: rerouting If you set width and height to match your screen size, you'll create a simple \"overlay\" that will allow you to render scene entities on top of the UI. In this case, however, you also need to configure scene camera accordingly, and probably use orthographic projection so the coordinates would match.","breadcrumbs":"User Interface » Rendering » Embedding Scene into UI","id":"353","title":"Embedding Scene into UI"},"354":{"body":"Font is used to store graphical representation of characters. The engine supports TTF and OTF fonts, you can load pretty much any font from the internet and use it as is.","breadcrumbs":"User Interface » Fonts » Font","id":"354","title":"Font"},"355":{"body":"There are two ways to create font instance - either load font from file, or load it directly from memory.","breadcrumbs":"User Interface » Fonts » Create New Font","id":"355","title":"Create New Font"},"356":{"body":"The easiest way to create a font is load it from file: # extern crate fyrox;\n# use fyrox::gui::ttf::Font;\nasync fn load_font_from_file() -> Font { let character_set = Font::default_char_set(); // ASCII character set. Font::from_file(\"path/to/your/font.ttf\", 20.0, character_set) .await .unwrap()\n} Please note, that this function is async due to the fact that it supports fetch API on WebAssembly, which does remote file fetching which is asynchronous by its nature. If you don't plan to support WebAssembly and don't want to use async, then the next section is for you.","breadcrumbs":"User Interface » Fonts » Loading Font From File","id":"356","title":"Loading Font From File"},"357":{"body":"This option could be useful, if you already have your font loaded into memory. Loading font from data buffer is very simple: # extern crate fyrox;\n# use fyrox::gui::ttf::Font;\nfn load_font_from_memory(data: &[u8]) -> Font { let character_set = Font::default_char_set(); // ASCII character set. Font::from_memory(data, 20.0, character_set).unwrap()\n} data input parameter could be a buffer that contains any valid TTF/OTF font. For example, you can load TTF file into a data buffer and create font using the data buffer: # extern crate fyrox;\n# use fyrox::gui::ttf::Font;\n# use std::{fs::File, io::Read};\nfn load_font_from_memory() -> Font { let character_set = Font::default_char_set(); // ASCII character set. let mut data = Vec::new(); File::open(\"path/to/your/font.ttf\") .unwrap() .read_to_end(&mut data) .unwrap(); Font::from_memory(data, 20.0, character_set).unwrap()\n}","breadcrumbs":"User Interface » Fonts » Creating Font From Memory","id":"357","title":"Creating Font From Memory"},"358":{"body":"User interface provides its own font of fixed size, it is enough to cover most of the use cases, but the default font includes only ASCII characters, if you need extended character set, you can replace default font using the following code snippet: # extern crate fyrox;\n# use fyrox::gui::{ttf::Font, UserInterface};\nasync fn set_default_font(ui: &mut UserInterface) { // Select character set. let character_set = Font::korean_char_set(); // Load font. let new_font = Font::from_file(\"path/to/your/font.ttf\", 20.0, character_set) .await .unwrap(); // Set as default font. ui.default_font.set(new_font)\n}","breadcrumbs":"User Interface » Fonts » Default Font","id":"358","title":"Default Font"},"359":{"body":"Unfortunately, you need to create new font instance for that, there is a tracking issue for that. Use any method from above paragraphs.","breadcrumbs":"User Interface » Fonts » How to Change Font Size","id":"359","title":"How to Change Font Size"},"36":{"body":"Fyrox uses OpenGL 3.3 on PC and OpenGL ES 3.0 on WebAssembly. Why? Mainly due to historical reasons. Back in the day (Q4 of 2018), there weren't any good alternatives to it with a wide range of supported platforms. For example, wgpu didn't even exist , as its first version was released in January 2019. Other crates were taking their first baby steps and weren't ready for production.","breadcrumbs":"Introduction » Frequently Asked Questions » Which graphics API does the engine use?","id":"36","title":"Which graphics API does the engine use?"},"360":{"body":"Current font implementation requires you to define fixed character set while creating an instance. What is character set and how it can be extended? Character set is just a set of ranges from Unicode, for example here's korean character set: # use std::ops::Range;\npub fn korean_char_set() -> &'static [Range] { &[ // Basic Latin + Latin Supplement 0x0020..0x00FF, // Korean alphabets 0x3131..0x3163, // Korean characters 0xAC00..0xD7A3, // Invalid 0xFFFD..0xFFFD, ]\n} You can create custom character set yourself by defining desired ranges from Unicode.","breadcrumbs":"User Interface » Fonts » Character Set","id":"360","title":"Character Set"},"361":{"body":"The engine has an ability to customize the appearance of widgets, however it is not centralized, and has to be done per widget. Check Style section of each widget (keep in mind that some of the widgets does not support custom styles, mainly because they were made in a hurry). In general, styling of widgets could be performed by replacing parts of a widget with your own. For example, a button by default uses Decorator widget as its background, which in its turn uses simple set of brushes to control internal Border widget's parameters, such as background and foreground colors. This is ok if you're creating some tools, where you don't need bells and whistles. However, buttons in games could be of any shape, color, have any kind of animations and so on. For this reason, Button widget allows you to change background widget with your own. So, imagine that you have a button template with two images that represents its state - Pressed and Normal. In this case you could create a custom widget, that will render different images depending on pressed state and use this widget as a background widget of your button. The same applies to pretty much every other widget, for example CheckBox allows you to change check marks for every of three states as well as a widget that is used as a background.","breadcrumbs":"User Interface » Style » Styles","id":"361","title":"Styles"},"362":{"body":"The subsections of this chapter explains how to use every widget built into Fyrox. The widgets in the table of contents to the left are ordered in alphebetical order. However, below we will order them by primary function to help introduce them to new users.","breadcrumbs":"User Interface » Widgets » Widgets","id":"362","title":"Widgets"},"363":{"body":"The Container widgets primary purpose is to contain other widgets. They are mostly used as a tool to layout the UI in visually different ways. Stack panel : The Stack Panel arranges widgets in a linear fashion, either vertically or horizontally depending on how it's setup. Wrap Panel : The Wrap Panel arranges widgets in a linear fashion but if it overflows the widgets are continued adjacent to the first line. Can arrange widgets either vertically or horizontally depending on how it's setup. Grid : The Grid arranges widgets into rows and columns with given size constraints. Canvas : The Canvas arranges widgets with pixel perfect precision. Window : The Window holds other widgets in a panel that can be configured at setup to be move-able, expanded and contracted via user input, exited, and have a displayed label. The window has a title bar to assist with these features. Message Box : The Message Box is a Window that has been streamlined to show standard confirmation/information dialogues, for example, closing a document with unsaved changes. It has a title, some text, and a fixed set of buttons (Yes, No, Cancel in different combinations). Menu : The Menu is a root container for Menu Items, an example could be a menu strip with File, Edit, View, etc items. Popup : The Popup is a panel that locks input to its content while it is open. A simple example of it could be a context menu. Scroll Viewer : The ScrollViewer is a wrapper for Scroll Panel that adds two scroll bars to it. Scroll Panel : The Scroll Panel is a panel that allows you apply some offset to children widgets. It is used to create \"scrollable\" area in conjunction with the Scroll Viewer. Expander : The Expander handles hiding and showing multiple panels of widgets in an accordian style UI element. Multiple panels can be shown or hidden at any time based on user input. Tab Control : The Tab Control handles hiding several panels of widgets, only showing the one that the user has selected. Docking Manager : The Docking manager allows you to dock windows and hold them in-place. Tree : The Tree allows you to create views for hierarchical data.","breadcrumbs":"User Interface » Widgets » Containers","id":"363","title":"Containers"},"364":{"body":"The Visual widgets primary purpose is to provide the user feedback generally without the user directly interacting with them. Text : The Text widget is used to display a string to the user. Image : The Image widget is used to display a pixel image to the user. Vector Image : The Vector Image is used to render vector instructions as a graphical element. Rect : The Rect allows you to specify numeric values for X, Y, Width, and Height of a rectangle. Progress Bar : The Progress Bar shows a bar whoes fill state can be adjusted to indicate visually how full something is, for example how close to 100% is a loading process. Decorator : The Decorator is used to style any widget. It has support for different styles depending on various events like mouse hover or click. Border : The Border widget is used in conjunction with the Decorator widget to provide configurable boarders to any widget for styling purposes.","breadcrumbs":"User Interface » Widgets » Visual","id":"364","title":"Visual"},"365":{"body":"Control widgets primary purpose is to provide users with intractable UI elements to control some aspect of the program. Button : The Button provides a press-able control that can contain other UI elements, for example a Text or Image Widget. Check Box : The Check Box is a toggle-able control that can contain other UI elements, for example a Text or Image Widget. Text Box : The Text Box is a control that allows the editing of text. Scroll Bar : The Scroll Bar provides a scroll bar like control that can be used on it's own as a data input or with certain other widgets to provide content scrolling capabilities. Numeric Field : The Numeric Field provides the ability to adjust a number via increment and decrement buttons or direct input. The number can be constrained to remain inside a specific range or have a specific step. Range : The Range allows the user to edit a numeric range - specify its begin and end values. List View : The List View provides a control where users can select from a list of items. Dropdown List : The Drop-down List is a control which shows the currently selected item and provides a drop-down list to select an item. File Browser : The File Browser is a tree view of the file system allowing the user to select a file or folder. Curve Editor : The CurveEditor allows editing parametric curves - adding points, and setting up transitions (constant, linear, cubic) between them. Inspector : The Inspector automatically creates and handles the input of UI elements based on a populated Inspector Context given to it allowing the user to adjust values of a variety of models without manually creating UI's for each type.","breadcrumbs":"User Interface » Widgets » Controls","id":"365","title":"Controls"},"366":{"body":"buttons","breadcrumbs":"User Interface » Widgets » Button » Button","id":"366","title":"Button"},"367":{"body":"To create a simple button with text you should do something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{button::ButtonBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\nfn create_button(ui: &mut UserInterface) -> Handle { ButtonBuilder::new(WidgetBuilder::new()) .with_text(\"Click me!\") .build(&mut ui.build_ctx())\n} How to create a button using custom dimensions (100x100) and custom text alignment (Vertical centered and Horizontal right aligned): # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{button::ButtonBuilder, widget::WidgetBuilder, UiNode, UserInterface, HorizontalAlignment, VerticalAlignment, text::TextBuilder},\n# };\nfn create_button(ui: &mut UserInterface) -> Handle { ButtonBuilder::new( WidgetBuilder::new() .with_width(100.0) .with_height(100.0), ) .with_content( TextBuilder::new(WidgetBuilder::new()) .with_text(\"Click me!\") .with_horizontal_text_alignment(HorizontalAlignment::Right) .with_vertical_text_alignment(VerticalAlignment::Center) .build(&mut ui.build_ctx()), ) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Button » Simple button with text","id":"367","title":"Simple button with text"},"368":{"body":"More fancy-looking button with an image as a background could be created using this code snippet: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle, resource::texture::Texture,\n# asset::manager::ResourceManager,\n# gui::{\n# button::ButtonBuilder, image::ImageBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# utils::into_gui_texture,\n# };\nfn create_fancy_button(ui: &mut UserInterface, resource_manager: ResourceManager) -> Handle { let ctx = &mut ui.build_ctx(); ButtonBuilder::new(WidgetBuilder::new()) .with_back( ImageBuilder::new(WidgetBuilder::new()) .with_texture(into_gui_texture( resource_manager.request::(\"path/to/your/texture\"), )) .build(ctx), ) .with_text(\"Click me!\") .build(ctx)\n}","breadcrumbs":"User Interface » Widgets » Button » A button with image","id":"368","title":"A button with image"},"369":{"body":"When clicked, a button sends a ButtonMessage::Click message, you can catch it in your code and do something useful: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# event_loop::ControlFlow,\n# gui::{button::ButtonMessage, message::UiMessage, UiNode},\n# plugin::{PluginContext, Plugin},\n# };\n# struct Game { button: Handle,\n} impl Plugin for Game { fn on_ui_message( &mut self, context: &mut PluginContext, message: &UiMessage, control_flow: &mut ControlFlow, ) { if let Some(ButtonMessage::Click) = message.data() { if message.destination() == self.button { // // Insert your code clicking handling code here. // } } }\n}","breadcrumbs":"User Interface » Widgets » Button » Message handling","id":"369","title":"Message handling"},"37":{"body":"There is no need for it. The current implementation works and is more than good enough. So instead of focusing on replacing something that works for little to no benefit, the current focus is on adding features that are missing as well as improving existing features when needed.","breadcrumbs":"Introduction » Frequently Asked Questions » Why not use alternatives now?","id":"37","title":"Why not use alternatives now?"},"370":{"body":"This example shows how to create a button that will close your game. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# event_loop::ControlFlow,\n# gui::{\n# button::{ButtonBuilder, ButtonMessage},\n# message::UiMessage,\n# text::TextBuilder,\n# widget::WidgetBuilder,\n# UiNode, UserInterface,\n# },\n# plugin::PluginContext,\n# };\n# struct Game { quit_button_handle: Handle,\n} fn create_button(ui: &mut UserInterface) -> Handle { ButtonBuilder::new(WidgetBuilder::new()) .with_content( TextBuilder::new(WidgetBuilder::new()) .with_text(\"Quit\") .build(&mut ui.build_ctx()), ) .build(&mut ui.build_ctx())\n} impl Game { fn new(ctx: PluginContext) -> Self { Self { quit_button_handle: create_button(ctx.user_interface), } } fn on_ui_message( &mut self, context: &mut PluginContext, message: &UiMessage, control_flow: &mut ControlFlow, ) { if let Some(ButtonMessage::Click) = message.data() { if message.destination() == self.quit_button_handle { *control_flow = ControlFlow::Exit; } } }\n}","breadcrumbs":"User Interface » Widgets » Button » Using a button to exit the game","id":"370","title":"Using a button to exit the game"},"371":{"body":"The Border widget provides a stylized, static border around its child widget. Below is an example of creating a 1 pixel thick border around a button widget: # extern crate fyrox;\nuse fyrox::gui::{ UserInterface, widget::WidgetBuilder, border::BorderBuilder, Thickness, text::TextBuilder,\n}; fn create_border_with_button(ui: &mut UserInterface) { BorderBuilder::new( WidgetBuilder::new() .with_child( TextBuilder::new(WidgetBuilder::new()) .with_text(\"I'm boxed in!\") .build(&mut ui.build_ctx()) ) ) //You can also use Thickness::uniform(1.0) .with_stroke_thickness(Thickness {left: 1.0, right: 1.0, top: 1.0, bottom: 1.0}) .build(&mut ui.build_ctx());\n} As with other UI elements, we create the border using the BorderBuilder helper struct. The widget that should have a border around it is added as a child of the base WidgetBuilder, and the border thickness can be set by providing a Thickness struct to the BorderBuilder's with_stroke_thickness function. This means you can set different thicknesses for each edge of the border. You can style the border by creating a Brush and setting the border's base WidgetBuilder's foreground or background. The foreground will set the style of the boarder itself, while setting the background will color the whole area within the border. Below is an example of a blue border and a red background with white text inside. # extern crate fyrox;\n# use fyrox::gui::{\n# brush::Brush,\n# core::color::Color,\n# widget::WidgetBuilder, # text::TextBuilder,\n# border::BorderBuilder,\n# UserInterface,\n# Thickness, # }; # let mut ui = UserInterface::new(Default::default()); BorderBuilder::new( WidgetBuilder::new() .with_foreground(Brush::Solid(Color::opaque(0, 0, 200))) .with_background(Brush::Solid(Color::opaque(200, 0, 0))) .with_child( TextBuilder::new(WidgetBuilder::new()) .with_text(\"I'm boxed in Blue and backed in Red!\") .build(&mut ui.build_ctx()) )\n)\n.with_stroke_thickness(Thickness {left: 2.0, right: 2.0, top: 2.0, bottom: 2.0})\n.build(&mut ui.build_ctx());","breadcrumbs":"User Interface » Widgets » Border » Border","id":"371","title":"Border"},"372":{"body":"canvas Canvas is a panel widget that allows you to explicitly set coordinates for children widgets. It is useful when you need to manually control position of children widgets (like potions on the image above). As any other panel widget, it does not have its own graphical representation, so the image above shows only its positioning capabilities. Root UI node is also canvas, so any widgets that are not attached to any other widgets can have explicit position.","breadcrumbs":"User Interface » Widgets » Canvas » Canvas","id":"372","title":"Canvas"},"373":{"body":"Use CanvasBuilder to create Canvas instance: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{canvas::CanvasBuilder, widget::WidgetBuilder, BuildContext, UiNode},\n# };\n# fn create_canvas(ctx: &mut BuildContext) -> Handle { CanvasBuilder::new(WidgetBuilder::new()).build(ctx)\n} Canvas does not have any specific options, so its creation is probably simplest of all widgets.","breadcrumbs":"User Interface » Widgets » Canvas » How to create","id":"373","title":"How to create"},"374":{"body":"Use .with_desired_position on children widgets to set specific position: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector2, pool::Handle},\n# gui::{\n# button::ButtonBuilder, canvas::CanvasBuilder, text::TextBuilder, widget::WidgetBuilder,\n# BuildContext, UiNode,\n# },\n# };\n# fn create_canvas_with_children_widgets(ctx: &mut BuildContext) -> Handle { CanvasBuilder::new( WidgetBuilder::new() .with_child( TextBuilder::new( WidgetBuilder::new().with_desired_position(Vector2::new(100.0, 200.0)), ) .with_text(\"Simple Text at (100.0, 200.0)\") .build(ctx), ) .with_child( ButtonBuilder::new( WidgetBuilder::new().with_desired_position(Vector2::new(200.0, 100.0)), ) .with_text(\"Simple Button at (200.0, 100.0)\") .build(ctx), ), ) .build(ctx)\n} The code snippet will create a canvas with a text widget located at (100.0, 200.0) relative to top-left corner of the canvas and a button located at (200.0, 100.0).","breadcrumbs":"User Interface » Widgets » Canvas » How to position children nodes","id":"374","title":"How to position children nodes"},"375":{"body":"Canvas provides infinite bounds for children widgets, this means that children nodes will not be stretched, instead they'll shrink to fit their content. For example, a button with a text will take slightly bigger rectangle than the text bounds.","breadcrumbs":"User Interface » Widgets » Canvas » Tips","id":"375","title":"Tips"},"376":{"body":"Checkbox is a UI widget that have three states - Checked, Unchecked and Undefined. In most cases it is used only with two values which fits in bool type. Third, undefined, state is used for specific situations when your data have such state.","breadcrumbs":"User Interface » Widgets » Check box » Check box","id":"376","title":"Check box"},"377":{"body":"Checkbox in Checked state: Checked Checkbox in Unchecked state: Unchecked","breadcrumbs":"User Interface » Widgets » Check box » How it looks","id":"377","title":"How it looks"},"378":{"body":"To create a checkbox you should do something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{check_box::CheckBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\nfn create_checkbox(ui: &mut UserInterface) -> Handle { CheckBoxBuilder::new(WidgetBuilder::new()) // A custom value can be set during initialization. .checked(Some(true)) .build(&mut ui.build_ctx())\n} The above code will create a checkbox without any textual info, but usually checkboxes have some useful info near them. To create such checkbox, you could use .with_content(..) method which accepts any widget handle. For checkbox with text, you could use TextBuilder to create textual content, for checkbox with text - use ImageBuilder. As already said, you're free to use any widget handle there. Here's an example of checkbox with textual content. # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# check_box::CheckBoxBuilder, text::TextBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# };\nfn create_checkbox(ui: &mut UserInterface) -> Handle { let ctx = &mut ui.build_ctx(); CheckBoxBuilder::new(WidgetBuilder::new()) // A custom value can be set during initialization. .checked(Some(true)) .with_content( TextBuilder::new(WidgetBuilder::new()) .with_text(\"This is a checkbox\") .build(ctx), ) .build(ctx)\n}","breadcrumbs":"User Interface » Widgets » Check box » How to create","id":"378","title":"How to create"},"379":{"body":"Checkboxes are not static widget and have multiple states. To handle a message from a checkbox, you need to handle a CheckBoxMessage::Check message. To do so, you can do something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# engine::Engine,\n# event_loop::ControlFlow,\n# gui::{check_box::CheckBoxMessage, message::UiMessage, UiNode},\n# plugin::PluginContext,\n# };\n# # struct Foo {\n# checkbox: Handle,\n# }\n# # impl Foo {\nfn on_ui_message( &mut self, context: &mut PluginContext, message: &UiMessage, control_flow: &mut ControlFlow,\n) { if let Some(CheckBoxMessage::Check(value)) = message.data() { if message.destination() == self.checkbox { // // Insert your clicking handling code here. // } }\n}\n# } Keep in mind that checkbox (as any other widget) generates WidgetMessage instances. You can catch them too and do a custom handling if you need.","breadcrumbs":"User Interface » Widgets » Check box » Message handling","id":"379","title":"Message handling"},"38":{"body":"No, the engine uses a mixed composition-based, object-oriented design with message passing and other different approaches that fit the most for a particular task. Why not use ECS for everything, though? Pragmatism. Use the right tool for the job. Don't use a microscope to hammer nails.","breadcrumbs":"Introduction » Frequently Asked Questions » Is the engine based on ECS?","id":"38","title":"Is the engine based on ECS?"},"380":{"body":"Checkbox can be fully customized to have any look you want, there are few methods that will help you with customization: .with_content(..) - sets the content that will be shown near the checkbox. .with_check_mark(..) - sets the widget that will be used as checked icon. .with_uncheck_mark(..) - sets the widget that will be used as unchecked icon. .with_undefined_mark(..) - sets the widget that will be used as undefined icon.","breadcrumbs":"User Interface » Widgets » Check box » Theme","id":"380","title":"Theme"},"381":{"body":"curve editor","breadcrumbs":"User Interface » Widgets » Curve editor (WIP) » Curve editor (WIP)","id":"381","title":"Curve editor (WIP)"},"382":{"body":"A visual element that changes its appearance by listening specific events. It can have \"pressed\", \"hover\", \"selected\" or normal appearance: Pressed - enables on mouse down message. Selected - whether decorator selected or not. Hovered - mouse is over the decorator. Normal - not selected, pressed or hovered. This element is widely used to provide some generic visual behaviour for various widgets. For example, it used in buttons, tree items, dropdown list items, etc.; in other words - everywhere where a widget needs to give visual feedback the user.","breadcrumbs":"User Interface » Widgets » Decorator (WIP) » Decorator","id":"382","title":"Decorator"},"383":{"body":"docking manager Docking manager allows you to dock windows and hold them in-place. Docking manager can hold any types of UI elements, but dragging works only for windows.","breadcrumbs":"User Interface » Widgets » Docking manager (WIP) » Docking manager (WIP)","id":"383","title":"Docking manager (WIP)"},"384":{"body":"dropdown list Drop-down list. This is control which shows currently selected item and provides drop-down list to select its current item. It is build using composition with standard list view.","breadcrumbs":"User Interface » Widgets » Dropdown list (WIP) » Dropdown list (WIP)","id":"384","title":"Dropdown list (WIP)"},"385":{"body":"expander Expander is a collapsible container for child widgets with a field that describes a content.","breadcrumbs":"User Interface » Widgets » Expander (WIP) » Expander (WIP)","id":"385","title":"Expander (WIP)"},"386":{"body":"file browser FileBrowser widget is a simple file system tree, FileSelector is a window with FileBrowser and few buttons.","breadcrumbs":"User Interface » Widgets » File browser (WIP) » File browser (WIP)","id":"386","title":"File browser (WIP)"},"387":{"body":"Grids are one of several methods to position multiple widgets in relation to each other. A Grid Widget, as the name implies, is able to position children widgets into a grid of specifically sized rows and columns. Here is a simple example that positions several text widgets into a 2 by 2 grid: # extern crate fyrox;\n# use fyrox::gui::{\n# UiNode,\n# BuildContext,\n# widget::WidgetBuilder,\n# text::TextBuilder,\n# grid::{GridBuilder, GridDimension},\n# }; fn create_text_grid(ctx: &mut BuildContext) -> fyrox::core::pool::Handle { GridBuilder::new( WidgetBuilder::new() .with_child( TextBuilder::new(WidgetBuilder::new()) .with_text(\"top left \") .build(ctx) ) .with_child( TextBuilder::new( WidgetBuilder::new() .on_column(1) ) .with_text(\" top right\") .build(ctx) ) .with_child( TextBuilder::new( WidgetBuilder::new() .on_row(1) ) .with_text(\"bottom left \") .build(ctx) ) .with_child( TextBuilder::new( WidgetBuilder::new() .on_row(1) .on_column(1) ) .with_text(\" bottom right\") .build(ctx) ) ) .add_row(GridDimension::auto()) .add_row(GridDimension::auto()) .add_column(GridDimension::auto()) .add_column(GridDimension::auto()) .build(ctx)\n} As with other UI widgets, Grids are created via the GridBuilder struct. Each widget whose position should be controlled by the Grid should be added as a child of the GridBuilder's base widget. You then need to tell each child what row and column it belongs to via the on_column and on_row functions of their base widget. By default, all children will be placed into row 0, column 0. After that you need to provide sizing constraints for each row and column to the GridBuilder by using the add_row and add_column functions while providing a GridDimension instance to the call. GridDimension can be constructed with the following functions: GridDimension::auto() - Sizes the row or column so it's just large enough to fit the largest child's size. GridDimension::stretch() - Stretches the row or column to fill the parent's available space, if multiple rows or columns have this option the size is evenly distributed between them. GridDimension::strict(f32) - Sets the row or column to be exactly the given value of pixels long. So a row will only be the given number of pixels wide, while a column will be that many pixels tall. You can add any number of rows and columns to a grid widget, and each grid cell does not need to have a UI widget in it to be valid. For example you can add a column and set it to a specific size via strict to provide spacing between two other columns.","breadcrumbs":"User Interface » Widgets » Grid » Grid","id":"387","title":"Grid"},"388":{"body":"image Image widget is a rectangle with a texture, it is used draw custom bitmaps. The UI in the engine is vector-based, Image widget is the only way to draw a bitmap. Usage of the Image is very simple:","breadcrumbs":"User Interface » Widgets » Image » Image","id":"388","title":"Image"},"389":{"body":"# extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::texture::Texture,\n# gui::{image::ImageBuilder, widget::WidgetBuilder, BuildContext, UiNode},\n# utils::into_gui_texture,\n# }; fn create_image(ctx: &mut BuildContext, resource_manager: ResourceManager) -> Handle { // You must explicitly set width and height of the image, otherwise it will collapse to a // point and you won't see anything. let width = 100.0; let height = 100.0; ImageBuilder::new(WidgetBuilder::new().with_width(width).with_height(height)) .with_texture(into_gui_texture( // Ask resource manager to load a texture. resource_manager.request::(\"path/to/your/texture.png\"), )) .build(ctx)\n} There are one common pitfall when using Image widget - you must explicitly set width and height of the image if it is not placed to some panel, that will stretch it automatically. In other words if you created an image with undefined width and height, then putting it to some container like Grid' cell will stretch the image to fit cell bounds.","breadcrumbs":"User Interface » Widgets » Image » Usage","id":"389","title":"Usage"},"39":{"body":"Pretty much any kind of games, except maybe games with vast open-worlds (since there's no built-in world streaming). In general, it depends on your game development experience.","breadcrumbs":"Introduction » Frequently Asked Questions » What kinds of games can I make using Fyrox?","id":"39","title":"What kinds of games can I make using Fyrox?"},"390":{"body":"Sometimes you need your image to have equal size with the texture it uses, in this case you should fetch texture bounds first and then create an Image width these bounds: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::texture::Texture,\n# gui::{image::ImageBuilder, widget::WidgetBuilder, BuildContext, UiNode},\n# resource::texture::TextureKind,\n# utils::into_gui_texture,\n# }; async fn create_image( ctx: &mut BuildContext<'_>, resource_manager: ResourceManager,\n) -> Handle { // Ask resource manager to load the texture and wait while it loads using `.await`. if let Ok(texture) = resource_manager .request::(\"path/to/your/texture.png\") .await { // A texture can be not only rectangular, so we must check that. let texture_kind = texture.data_ref().kind(); if let TextureKind::Rectangle { width, height } = texture_kind { return ImageBuilder::new( WidgetBuilder::new() .with_width(width as f32) .with_height(height as f32), ) .with_texture(into_gui_texture(texture)) .build(ctx); } } // Image wasn't created. Handle::NONE\n} This function can be used as-is whenever you need to create an Image that have same size as the source file. It is marked as async because resource loading (texture is a resource) happens in separate thread and to get actual texture data we must wait it. If you don't want to use async, then use any executor to block current thread and execute the promise immediately: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager,\n# gui::{BuildContext, UiNode},\n# };\n# # async fn create_image(\n# ctx: &mut BuildContext<'_>,\n# resource_manager: ResourceManager,\n# ) -> Handle {\n# Handle::NONE\n# }\nfn create_image_sync( ctx: &mut BuildContext<'_>, resource_manager: ResourceManager,\n) -> Handle { fyrox::core::futures::executor::block_on(create_image(ctx, resource_manager))\n}","breadcrumbs":"User Interface » Widgets » Image » Equal Size to Source","id":"390","title":"Equal Size to Source"},"391":{"body":"In some rare cases you need to flip your source image before showing it, there is .with_flip option for that: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# asset::manager::ResourceManager, resource::texture::Texture,\n# gui::{image::ImageBuilder, widget::WidgetBuilder, BuildContext, UiNode},\n# utils::into_gui_texture,\n# }; fn create_image(ctx: &mut BuildContext, resource_manager: ResourceManager) -> Handle { ImageBuilder::new(WidgetBuilder::new().with_width(100.0).with_height(100.0)) .with_flip(true) // Flips an image vertically .with_texture(into_gui_texture( resource_manager.request::(\"path/to/your/texture.png\"), )) .build(ctx)\n} There are few places where it can be helpful: You're using render target as a source texture for your Image instance, render targets are vertically flipped due to mismatch of coordinates of UI and graphics API. The UI has origin at left top corner, the graphics API - bottom left. Your source image is vertically mirrored.","breadcrumbs":"User Interface » Widgets » Image » Vertical Flip","id":"391","title":"Vertical Flip"},"392":{"body":"inspector A widget that allows you to generate visual representation for arbitrary structures, that implement Reflect trait.","breadcrumbs":"User Interface » Widgets » Inspector (WIP) » Inspector (WIP)","id":"392","title":"Inspector (WIP)"},"393":{"body":"list view","breadcrumbs":"User Interface » Widgets » List view (WIP) » List view (WIP)","id":"393","title":"List view (WIP)"},"394":{"body":"","breadcrumbs":"User Interface » Widgets » Menu (WIP) » Menu (WIP)","id":"394","title":"Menu (WIP)"},"395":{"body":"message box","breadcrumbs":"User Interface » Widgets » Message box (WIP) » Message box (WIP)","id":"395","title":"Message box (WIP)"},"396":{"body":"numeric up down A widget that handles numbers of any machine type. Use this widget if you need to provide input field for a numeric type.","breadcrumbs":"User Interface » Widgets » Numeric field » NumericUpDown Widget","id":"396","title":"NumericUpDown Widget"},"397":{"body":"Use NumericUpDownBuilder to create a new instance of the NumericUpDown widget: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle, gui::{numeric::NumericUpDownBuilder, widget::WidgetBuilder, BuildContext, UiNode}\n# };\nfn create_numeric_widget(ctx: &mut BuildContext) -> Handle { NumericUpDownBuilder::new(WidgetBuilder::new()) .with_value(123.0f32) .build(ctx)\n} Keep in mind, that this widget is generic and can work with any numeric types. Sometimes you might get an \"unknown type\" error message from the compiler (especially if your use 123.0 ambiguous numeric literals), in this case you need to specify the type explicitly (NumericUpDownBuilder::::new...).","breadcrumbs":"User Interface » Widgets » Numeric field » How to create","id":"397","title":"How to create"},"398":{"body":"This widget supports lower and upper limits for the values. It can be specified by NumericUpDownBuilder::with_min_value and NumericUpDownBuilder::with_max_value (or changed at runtime using NumericUpDownMessage::MinValue and NumericUpDownMessage::MaxValue messages): # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle, gui::{numeric::NumericUpDownBuilder, widget::WidgetBuilder, BuildContext, UiNode}\n# };\nfn create_numeric_widget(ctx: &mut BuildContext) -> Handle { NumericUpDownBuilder::new(WidgetBuilder::new()) .with_value(123.0f32) .with_min_value(42.0) .with_max_value(666.0) .build(ctx)\n} The default limits for min and max are NumericType::min_value and NumericType::max_value respectively.","breadcrumbs":"User Interface » Widgets » Numeric field » Limits","id":"398","title":"Limits"},"399":{"body":"Since the value of the widget can be changed via up/down arrow buttons (also by dragging the cursor up or down on them), the widget provides a way to set the step of the value (for increment and decrement at the same time): # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle, gui::{numeric::NumericUpDownBuilder, widget::WidgetBuilder, BuildContext, UiNode}\n# };\nfn create_numeric_widget(ctx: &mut BuildContext) -> Handle { NumericUpDownBuilder::new(WidgetBuilder::new()) .with_value(125.0f32) .with_step(5.0) .build(ctx)\n} The default value of the step is NumericType::one.","breadcrumbs":"User Interface » Widgets » Numeric field » Step","id":"399","title":"Step"},"4":{"body":"We're expecting that you know basics of Rust programming language, its package manager Cargo. It is also necessary to know the basics of the game development, linear algebra, principles of software development and patterns, otherwise the book will probably be very hard for you.","breadcrumbs":"About the Book » Required knowledge","id":"4","title":"Required knowledge"},"40":{"body":"This section of the book will guide you through the basics of the engine. You will learn how to create a project, use plugins, scripts, assets, and the editor. Fyrox is a modern game engine with its own scene editor, that helps you to edit game worlds, manage assets, and many more. At the end of reading this section, you'll also learn how to manage game and engine entities, how they're structured and what are the basics of data management in the engine. Next chapter will guide you through major setup of the engine - creating a game project using special project generator tool.","breadcrumbs":"Getting started » Getting Started","id":"40","title":"Getting Started"},"400":{"body":"It is possible to specify visual rounding of the value up to desired decimal place (it does not change the way how the actual value is rounded). For example, in some cases you might get irrational values such as 1/3 ~= 0.33333333, but you interested in only first two decimal places. In this case you can set the precision to 2: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle, gui::{numeric::NumericUpDownBuilder, widget::WidgetBuilder, BuildContext, UiNode}\n# };\nfn create_numeric_widget(ctx: &mut BuildContext) -> Handle { NumericUpDownBuilder::new(WidgetBuilder::new()) .with_value(0.3333333f32) .with_precision(2) .build(ctx)\n}","breadcrumbs":"User Interface » Widgets » Numeric field » Precision","id":"400","title":"Precision"},"401":{"body":"","breadcrumbs":"User Interface » Widgets » Popup (WIP) » Popup (WIP)","id":"401","title":"Popup (WIP)"},"402":{"body":"progress bar","breadcrumbs":"User Interface » Widgets » Progress bar (WIP) » Progress bar (WIP)","id":"402","title":"Progress bar (WIP)"},"403":{"body":"range editor","breadcrumbs":"User Interface » Widgets » Range (WIP) » Range (WIP)","id":"403","title":"Range (WIP)"},"404":{"body":"rect","breadcrumbs":"User Interface » Widgets » Rect (WIP) » Rect editor (WIP)","id":"404","title":"Rect editor (WIP)"},"405":{"body":"scroll bar Scroll bar is used to represent a value on a finite range. It has a thumb that shows the current value on on the bar. Usually it is used in pair with ScrollPanel to create something like ScrollViewer widget. However, it could also be used to create sliders to show some value that lies within some range.","breadcrumbs":"User Interface » Widgets » Scroll bar » Scroll bar","id":"405","title":"Scroll bar"},"406":{"body":"A simple example of how to create a new ScrollBar could be something like this: # extern crate fyrox;\n# use fyrox::gui::{\n# core::pool::Handle, scroll_bar::ScrollBarBuilder, widget::WidgetBuilder, BuildContext,\n# UiNode,\n# };\nfn create_scroll_bar(ctx: &mut BuildContext) -> Handle { ScrollBarBuilder::new(WidgetBuilder::new()) .with_min(0.0) .with_max(200.0) .with_value(123.0) .build(ctx)\n} It creates a horizontal scroll bar with 123.0 value and a range of [0.0..200.0]. To fetch the new value of the scroll bar, use ScrollBarMessage::Value message: # extern crate fyrox;\n# use fyrox::gui::{\n# core::pool::Handle,\n# message::{MessageDirection, UiMessage},\n# scroll_bar::ScrollBarMessage,\n# UiNode,\n# };\n# fn foo(scroll_bar: Handle, message: &mut UiMessage) {\nif message.destination() == scroll_bar && message.direction() == MessageDirection::FromWidget\n{ if let Some(ScrollBarMessage::Value(value)) = message.data() { println!(\"{}\", value); }\n}\n# } Please note, that you need to explicitly filter messages by MessageDirection::FromWidget, because it's the only direction that is used as an \"indicator\" that the value was accepted by the scroll bar.","breadcrumbs":"User Interface » Widgets » Scroll bar » Example","id":"406","title":"Example"},"407":{"body":"Scroll bar could be either horizontal (default) or vertical. You can select the orientation when building a scroll bar using ScrollBarBuilder::with_orientation method and provide a desired value from Orientation enum there.","breadcrumbs":"User Interface » Widgets » Scroll bar » Orientation","id":"407","title":"Orientation"},"408":{"body":"By default, scroll bar does not show its actual value, you can turn it on using ScrollBarBuilder::show_value method with true as the first argument. To change rounding of the value, use ScrollBarBuilder::with_value_precision and provide the desired amount of decimal places there.","breadcrumbs":"User Interface » Widgets » Scroll bar » Show values","id":"408","title":"Show values"},"409":{"body":"Scroll bar provides arrows to change the current value using a fixed step value. You can change it using ScrollBarBuilder::with_step method.","breadcrumbs":"User Interface » Widgets » Scroll bar » Step","id":"409","title":"Step"},"41":{"body":"Every Fyrox game is just a plugin for both the engine and the editor, such approach allows you to run your game from the editor and to be able to edit the game entities in it. Your game can define any number of scripts, which can be assigned to scene objects to run custom game logic on them. In this chapter you'll learn the basics: how to install the engine with its platform-specific dependencies, how to use the plugins and scripting system, how to run the editor.","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Editor, Plugins and Scripts","id":"41","title":"Editor, Plugins and Scripts"},"410":{"body":"","breadcrumbs":"User Interface » Widgets » Scroll panel (WIP) » Scroll panel (WIP)","id":"410","title":"Scroll panel (WIP)"},"411":{"body":"scroll viewer","breadcrumbs":"User Interface » Widgets » Scroll viewer (WIP) » Scroll viewer (WIP)","id":"411","title":"Scroll viewer (WIP)"},"412":{"body":"Stack Panels are one of several methods to position multiple widgets in relation to each other. A Stack Panel Widget orders it's children widgets linerarly, aka in a stack of widgets, based on the order the widgets were added as children. So the first widget added will be at the top or left most position, while each additional widget will decend from top to bottom or continue from left most to right most. The below example code places 3 text widgets into a vertical stack: # extern crate fyrox;\n# use fyrox::gui::{\n# UiNode,\n# BuildContext,\n# widget::WidgetBuilder,\n# text::TextBuilder,\n# stack_panel::StackPanelBuilder,\n# }; fn create_stack_panel(ctx: &mut BuildContext) -> fyrox::core::pool::Handle { StackPanelBuilder::new( WidgetBuilder::new() .with_child( TextBuilder::new(WidgetBuilder::new()) .with_text(\"Top\") .build(ctx) ) .with_child( TextBuilder::new(WidgetBuilder::new()) .with_text(\"Middle\") .build(ctx) ) .with_child( TextBuilder::new(WidgetBuilder::new()) .with_text(\"Bottom\") .build(ctx) ) ) .build(ctx) } As you can see from the example, creating a Stack Panel uses the standard method for creating widgets. Create a new StackPanelBuilder and provide it with a new WidgetBuilder. Adding widgets to the stack is done by adding childeren to the StackBuilder's WidgetBuilder.","breadcrumbs":"User Interface » Widgets » Stack panel » Stack Panel","id":"412","title":"Stack Panel"},"413":{"body":"As has been indicated, Stack Panels can be oriented to order it's children either Vertical, from top to bottom, or Horizontal, Left most to right most. This is done using the StackPanelBuilder's with_orientation function providing it with a gui::Orientation enum value. By default all Stack Panel's are Vertical. # extern crate fyrox;\n# use fyrox::gui::{\n# Orientation,\n# BuildContext,\n# widget::WidgetBuilder,\n# stack_panel::StackPanelBuilder,\n# }; # fn build(ctx: &mut BuildContext) {\nStackPanelBuilder::new( WidgetBuilder::new()\n) .with_orientation(Orientation::Horizontal) .build(ctx);\n# }","breadcrumbs":"User Interface » Widgets » Stack panel » Stack Panel Orientation","id":"413","title":"Stack Panel Orientation"},"414":{"body":"The Tab Control handles the visibility of several tabs, only showing a single tab that the user has selected via the tab header buttons. Each tab is defined via a Tab Definition struct which takes two widgets, one representing the tab header and the other representing the tab's contents. The following example makes a 2 tab, Tab Control containing some simple text widgets: # extern crate fyrox;\n# use fyrox::gui::{\n# tab_control::{TabControlBuilder, TabDefinition},\n# text::TextBuilder,\n# widget::WidgetBuilder,\n# BuildContext,\n# };\n# fn create_tab_control(ctx: &mut BuildContext) { TabControlBuilder::new(WidgetBuilder::new()) .with_tab(TabDefinition { header: TextBuilder::new(WidgetBuilder::new()) .with_text(\"First\") .build(ctx), content: TextBuilder::new(WidgetBuilder::new()) .with_text(\"First tab's contents!\") .build(ctx), can_be_closed: true, user_data: None, }) .with_tab(TabDefinition { header: TextBuilder::new(WidgetBuilder::new()) .with_text(\"Second\") .build(ctx), content: TextBuilder::new(WidgetBuilder::new()) .with_text(\"Second tab's contents!\") .build(ctx), can_be_closed: true, user_data: None, }) .build(ctx);\n} As usual, we create the widget via the builder TabControlBuilder. Tabs are added via the with_tab function in the order you want them to appear, passing each call to the function a directly constructed TabDefinition struct. Tab headers will appear from left to right at the top with tab contents shown directly below the tabs. As usual, if no constraints are given to the base WidgetBuilder of the TabControlBuilder, then the tab content area will resize to fit whatever is in the current tab. Each tab's content is made up of one widget, so to be useful you will want to use one of the container widgets to help arrange additional widgets within the tab.","breadcrumbs":"User Interface » Widgets » Tab Control » Tab Control","id":"414","title":"Tab Control"},"415":{"body":"Notice that you can put any widget into the tab header, so if you want images to denote each tab you can add an Image widget to each header, and if you want an image and some text you can insert a stack panel with an image on top and text below it. You will also likely want to style whatever widgets you add. As can be seen when running the code example above, the tab headers are scrunched when there are no margins provided to your text widgets. Simply add something like the below code example and you will get a decent look: # extern crate fyrox;\n# use fyrox::gui::{\n# BuildContext,\n# widget::WidgetBuilder,\n# text::TextBuilder,\n# Thickness, # tab_control::{TabDefinition},\n# }; # fn build(ctx: &mut BuildContext) {\n# TabDefinition{\nheader: TextBuilder::new( WidgetBuilder::new() .with_margin(Thickness::uniform(4.0)) ) .with_text(\"First\") .build(ctx),\n# content: Default::default(),\n# can_be_closed: true,\n# user_data: None,\n# };\n# }","breadcrumbs":"User Interface » Widgets » Tab Control » Tab Header Styling","id":"415","title":"Tab Header Styling"},"416":{"body":"Text is a simple widget that allows you to print text on screen. It has various options like word wrapping, text alignment, and so on.","breadcrumbs":"User Interface » Widgets » Text » Text","id":"416","title":"Text"},"417":{"body":"An instance of the Text widget could be created like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\nfn create_text(ui: &mut UserInterface, text: &str) -> Handle { TextBuilder::new(WidgetBuilder::new()) .with_text(text) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Text » How to create","id":"417","title":"How to create"},"418":{"body":"There are various text alignment options for both vertical and horizontal axes. Typical alignment values are: Left, Center, Right for horizontal axis, and Top, Center, Bottom for vertical axis. An instance of centered text could be created like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# text::TextBuilder, widget::WidgetBuilder, HorizontalAlignment, UiNode, UserInterface,\n# VerticalAlignment,\n# },\n# };\nfn create_centered_text(ui: &mut UserInterface, text: &str) -> Handle { TextBuilder::new(WidgetBuilder::new()) .with_horizontal_text_alignment(HorizontalAlignment::Center) .with_vertical_text_alignment(VerticalAlignment::Center) .with_text(text) .build(&mut ui.build_ctx())\n} Long text is usually needs to wrap on available bounds, there are three possible options for word wrapping: NoWrap, Letter, Word. An instance of text with word-based wrapping could be created like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# formatted_text::WrapMode, text::TextBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# };\nfn create_text_with_word_wrap(ui: &mut UserInterface, text: &str) -> Handle { TextBuilder::new(WidgetBuilder::new()) .with_wrap(WrapMode::Word) .with_text(text) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Text » Text alignment and word wrapping","id":"418","title":"Text alignment and word wrapping"},"419":{"body":"If you need to have a text with some background, you should use Border widget as a parent widget of your text. Caveat: Widget::background is ignored for Text widget! # extern crate fyrox;\n# use fyrox::{\n# core::{color::Color, pool::Handle},\n# gui::{\n# border::BorderBuilder, brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# };\n# fn create_text_with_background(ui: &mut UserInterface, text: &str) -> Handle { let text_widget = TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED))) .with_text(text) .build(&mut ui.build_ctx()); BorderBuilder::new( WidgetBuilder::new() .with_child(text_widget) // <-- Text is now a child of the border .with_background(Brush::Solid(Color::opaque(50, 50, 50))), ) .build(&mut ui.build_ctx())\n} Keep in mind that now the text widget is a child widget of the border, so if you need to position the text, you should position the border, not the text.","breadcrumbs":"User Interface » Widgets » Text » Background","id":"419","title":"Background"},"42":{"body":"Run the following commands to start using the engine as quick as possible. Read the next chapters if you want to know more or if you have any issues with this. cargo install fyrox-template\nfyrox-template init --name fyrox_test --style 2d\ncd fyrox_test\ncargo run --package editor --release","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Quick Start","id":"42","title":"Quick Start"},"420":{"body":"To set a color of the text just use .with_foreground(..) of the WidgetBuilder while building the text instance: # extern crate fyrox;\n# use fyrox::{\n# core::{color::Color, pool::Handle},\n# gui::{brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\nfn create_text(ui: &mut UserInterface, text: &str) -> Handle { // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED))) .with_text(text) .build(&mut ui.build_ctx())\n} By default, text is created with default font, however it is possible to set any custom font: # extern crate fyrox;\n# use fyrox::{\n# core::{futures::executor::block_on, pool::Handle},\n# gui::{\n# text::TextBuilder,\n# ttf::{Font, SharedFont},\n# widget::WidgetBuilder,\n# UiNode, UserInterface,\n# },\n# }; fn load_font() -> SharedFont { // Choose desired character set, default is Basic Latin + Latin Supplement. // Character set is a set of ranges with Unicode code points. let character_set = Font::default_char_set(); // Normally `block_on` should be avoided. let font = block_on(Font::from_file( \"path/to/your/font.ttf\", 24.0, character_set, )) .unwrap(); SharedFont::new(font)\n} fn create_text(ui: &mut UserInterface, text: &str) -> Handle { TextBuilder::new(WidgetBuilder::new()) .with_font(load_font()) .with_text(text) .build(&mut ui.build_ctx())\n} Please refer to Font chapter to learn more about fonts.","breadcrumbs":"User Interface » Widgets » Text » Fonts and colors","id":"420","title":"Fonts and colors"},"421":{"body":"There is no way to change font size without changing the entire font used by Text, it is known issue and there is tracking issue for that. Check Font chapter to learn how to create fonts.","breadcrumbs":"User Interface » Widgets » Text » Font size","id":"421","title":"Font size"},"422":{"body":"Text widget supports shadows effect to add contrast to your text, which could be useful to make text readable independent on the background colors. This effect could be used for subtitles. Shadows are pretty easy to add, all you need to do is to enable them, setup desired thickness, offset and brush (solid color or gradient). # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector2, color::Color, pool::Handle},\n# gui::{brush::Brush, text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\n# fn create_red_text_with_black_shadows(ui: &mut UserInterface, text: &str) -> Handle { TextBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED))) .with_text(text) // Enable shadows. .with_shadow(true) // Black shadows. .with_shadow_brush(Brush::Solid(Color::BLACK)) // 1px thick. .with_shadow_dilation(1.0) // Offset the shadow slightly to the right-bottom. .with_shadow_offset(Vector2::new(1.0, 1.0)) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Text » Shadows","id":"422","title":"Shadows"},"423":{"body":"Text widget can accept the following list of messages at runtime (respective constructors are name with small letter - TextMessage::Text -> TextMessage::text(widget_handle, direction, text)): TextMessage::Text - sets new text for a Text widget. TextMessage::Wrap - sets new wrapping mode . TextMessage::Font - sets new font TextMessage::VerticalAlignment and TextMessage::HorizontalAlignment sets vertical and horizontal text alignment respectively. TextMessage::Shadow - enables or disables shadow casting TextMessage::ShadowDilation - sets \"thickness\" of the shadows under the tex. TextMessage::ShadowBrush - sets shadow brush (allows you to change color and even make shadow with color gradients). TextMessage::ShadowOffset - sets offset of the shadows. An example of changing text at runtime could be something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# message::{MessageDirection},\n# UiNode, UserInterface,\n# text::TextMessage\n# },\n# };\nfn request_change_text(ui: &UserInterface, text_widget_handle: Handle, text: &str) { ui.send_message(TextMessage::text( text_widget_handle, MessageDirection::ToWidget, text.to_owned(), ))\n} Please keep in mind, that like any other situation when you \"changing\" something via messages, you should remember that the change is not immediate. The change will be applied on ui.poll_message(..) call somewhere in your code (or will be done automatically if you're using scripts or Framework (obsolete)).","breadcrumbs":"User Interface » Widgets » Text » Messages","id":"423","title":"Messages"},"424":{"body":"TextBox is a text widget that allows you to edit text and create specialized input fields. It has various options like word wrapping, text alignment, and so on.","breadcrumbs":"User Interface » Widgets » Text box » Text Box","id":"424","title":"Text Box"},"425":{"body":"An instance of the TextBox widget could be created like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\nfn create_text_box(ui: &mut UserInterface, text: &str) -> Handle { TextBoxBuilder::new(WidgetBuilder::new()) .with_text(text) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Text box » How to create","id":"425","title":"How to create"},"426":{"body":"There are various text alignment options for both vertical and horizontal axes. Typical alignment values are: Left, Center, Right for horizontal axis, and Top, Center, Bottom for vertical axis. An instance of centered text could be created like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# text_box::TextBoxBuilder, widget::WidgetBuilder, HorizontalAlignment, UiNode, UserInterface,\n# VerticalAlignment,\n# },\n# };\nfn create_centered_text(ui: &mut UserInterface, text: &str) -> Handle { TextBoxBuilder::new(WidgetBuilder::new()) .with_horizontal_text_alignment(HorizontalAlignment::Center) .with_vertical_text_alignment(VerticalAlignment::Center) .with_text(text) .build(&mut ui.build_ctx())\n} Long text is usually needs to wrap on available bounds, there are three possible options for word wrapping: NoWrap, Letter, Word. An instance of text with word-based wrapping could be created like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# formatted_text::WrapMode, text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode,\n# UserInterface,\n# },\n# };\nfn create_text_with_word_wrap(ui: &mut UserInterface, text: &str) -> Handle { TextBoxBuilder::new(WidgetBuilder::new()) .with_wrap(WrapMode::Word) .with_text(text) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Text box » Text alignment and word wrapping","id":"426","title":"Text alignment and word wrapping"},"427":{"body":"To set a color of the text just use .with_foreground(..) of the WidgetBuilder while building the text instance: # extern crate fyrox;\n# use fyrox::{\n# core::{color::Color, pool::Handle},\n# gui::{brush::Brush, text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\nfn create_text(ui: &mut UserInterface, text: &str) -> Handle { // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv TextBoxBuilder::new(WidgetBuilder::new().with_foreground(Brush::Solid(Color::RED))) .with_text(text) .build(&mut ui.build_ctx())\n} By default, text is created with default font, however it is possible to set any custom font: # extern crate fyrox;\n# use fyrox::{\n# core::{futures::executor::block_on, pool::Handle},\n# gui::{\n# text_box::TextBoxBuilder,\n# ttf::{Font, SharedFont},\n# widget::WidgetBuilder,\n# UiNode, UserInterface,\n# },\n# }; fn load_font() -> SharedFont { // Choose desired character set, default is Basic Latin + Latin Supplement. // Character set is a set of ranges with Unicode code points. let character_set = Font::default_char_set(); // Normally `block_on` should be avoided. let font = block_on(Font::from_file( \"path/to/your/font.ttf\", 24.0, character_set, )) .unwrap(); SharedFont::new(font)\n} fn create_text(ui: &mut UserInterface, text: &str) -> Handle { TextBoxBuilder::new(WidgetBuilder::new()) .with_font(load_font()) .with_text(text) .build(&mut ui.build_ctx())\n} Please refer to Font chapter to learn more about fonts.","breadcrumbs":"User Interface » Widgets » Text box » Fonts and colors","id":"427","title":"Fonts and colors"},"428":{"body":"There is no way to change font size without changing the entire font used by Text, it is known issue and there is tracking issue for that. Check Font chapter to learn how to create fonts.","breadcrumbs":"User Interface » Widgets » Text box » Font size","id":"428","title":"Font size"},"429":{"body":"TextBox widget accepts the following list of messages: TextBoxMessage::SelectionBrush - change the brush that is used to highlight selection. TextBoxMessage::CaretBrush - changes the brush of the caret (small blinking vertical line). TextBoxMessage::TextCommitMode - changes the text commit mode . TextBoxMessage::Multiline - makes the TextBox either multiline (true) or single line (false) TextBoxMessage::Editable - enables or disables editing of the text. Important: Please keep in mind, that TextBox widget also accepts Text widget messages . An example of changing text at runtime could be something like this: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# message::{MessageDirection},\n# UiNode, UserInterface,\n# text::TextMessage\n# },\n# };\nfn request_change_text(ui: &UserInterface, text_box_widget_handle: Handle, text: &str) { ui.send_message(TextMessage::text( text_box_widget_handle, MessageDirection::ToWidget, text.to_owned(), ))\n} Please keep in mind, that like any other situation when you \"changing\" something via messages, you should remember that the change is not immediate. The change will be applied on ui.poll_message(..) call somewhere in your code (or will be done automatically if you're using scripts or Framework (obsolete)).","breadcrumbs":"User Interface » Widgets » Text box » Messages","id":"429","title":"Messages"},"43":{"body":"Before you start using the engine, make sure you have all required platform-specific development dependencies installed, otherwise you'll get compilation errors. If you're on Windows or macOS, you don't need to install anything specific - all you need to have is the latest Rust installed with appropriate toolchain for your platform.","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Platform-specific Dependencies","id":"43","title":"Platform-specific Dependencies"},"430":{"body":"There are number of default shortcuts that can be used to speed up text editing: Ctrl+A - select all Ctrl+C - copy selected text Ctrl+V - paste text from clipboard Ctrl+Home - move caret to the beginning of the text Ctrl+End - move caret to the beginning of the text Shift+Home - select everything from current caret position until the beginning of current line Shift+End - select everything from current caret position until the end of current line Arrows - move caret accordingly Delete - deletes next character Backspace - deletes previous character Enter - new line (if multiline mode is set) or commit message","breadcrumbs":"User Interface » Widgets » Text box » Shortcuts","id":"430","title":"Shortcuts"},"431":{"body":"By default, text box will not add new line character to the text if you press Enter on keyboard. To enable this functionality use .with_multiline(true)","breadcrumbs":"User Interface » Widgets » Text box » Multiline Text Box","id":"431","title":"Multiline Text Box"},"432":{"body":"You can enable or disable content editing by using read-only mode. Use .with_readonly at build stage.","breadcrumbs":"User Interface » Widgets » Text box » Read-only Mode","id":"432","title":"Read-only Mode"},"433":{"body":"You can specify replacement character for every other characters, this is useful option for password fields. Use .with_mask_char at build stage. For example, you can set replacement character to asterisk * using .with_mask_char(Some('*'))","breadcrumbs":"User Interface » Widgets » Text box » Mask Character","id":"433","title":"Mask Character"},"434":{"body":"In many situations you don't need the text box to send new text message every new character, you either want this message if Enter key is pressed or TextBox has lost keyboard focus (or both). There is with_text_commit_mode on builder specifically for that purpose. Use one of the following modes: TextCommitMode::Immediate - text box will immediately send Text message after any change. TextCommitMode::LostFocus - text box will send Text message only when it loses focus. TextCommitMode::LostFocusPlusEnter - text box will send Text message when it loses focus or if Enter key was pressed. This is default behavior. In case of multiline text box hitting Enter key won't commit text!","breadcrumbs":"User Interface » Widgets » Text box » Text Commit Mode","id":"434","title":"Text Commit Mode"},"435":{"body":"It is possible specify custom input filter, it can be useful if you're creating special input fields like numerical or phone number. A filter can be specified at build stage like so: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{text_box::TextBoxBuilder, widget::WidgetBuilder, UiNode, UserInterface},\n# };\n# use std::{cell::RefCell, rc::Rc};\nfn create_text_box(ui: &mut UserInterface) -> Handle { TextBoxBuilder::new(WidgetBuilder::new()) // Specify a filter that will pass only digits. .with_filter(Rc::new(RefCell::new(|c: char| c.is_ascii_digit()))) .build(&mut ui.build_ctx())\n}","breadcrumbs":"User Interface » Widgets » Text box » Filtering","id":"435","title":"Filtering"},"436":{"body":"You can change brush of caret by using .with_caret_brush and also selection brush by using .with_selection_brush, it could be useful if you don't like default colors.","breadcrumbs":"User Interface » Widgets » Text box » Style","id":"436","title":"Style"},"437":{"body":"tree","breadcrumbs":"User Interface » Widgets » Tree (WIP) » Tree (WIP)","id":"437","title":"Tree (WIP)"},"438":{"body":"","breadcrumbs":"User Interface » Widgets » Vector image (WIP) » Vector image (WIP)","id":"438","title":"Vector image (WIP)"},"439":{"body":"window The Window widget provides a standared window that can contain another widget. Based on setting windows can be configured so users can do any of the following: Movable by the user. Not configurable. Have title text on the title bar. Set by the with_title function. Able to be exited by the user. Set by the can_close function. Able to be minimized to just the Title bar, and of course maximized again. Set by the can_minimize function. Able to resize the window. Set by the can_resize function. As with other UI elements, you create and configure the window using the WindowBuilder. # extern crate fyrox;\nuse fyrox::{ core::{pool::Handle, algebra::Vector2}, gui::{ window::{WindowBuilder, WindowTitle}, text::TextBuilder, widget::WidgetBuilder, UiNode, UserInterface },\n}; fn create_window(ui: &mut UserInterface) { WindowBuilder::new( WidgetBuilder::new() .with_desired_position(Vector2::new(300.0, 0.0)) .with_width(300.0), ) .with_content( TextBuilder::new(WidgetBuilder::new()) .with_text(\"Example Window content.\") .build(&mut ui.build_ctx()) ) .with_title(WindowTitle::text(\"Window\")) .can_close(true) .can_minimize(true) .open(true) .can_resize(false) .build(&mut ui.build_ctx());\n} You will likely want to constrain the initial size of the window to somethig as shown in the example by providing a set width and/or height to the base WidgetBuilder. Otherwise it will expand to fit it's content. You may also want to set an inital position with the with_desired_position function called on the base WidgetBuilder which sets the position of the window's top-left corner. Otherwise all your windows will start with it's top-left corner at 0,0 and be stacked on top of eachother. Windows can only contain a single direct child widget, set by using the with_content function. Additional calls to with_content replaces the widgets given in previous calls, and the old widgets exist outside the window, so you should delete old widgets before changing a window's widget. If you want multiple widgets, you need to use one of the layout container widgets like the Grid, Stack Panel, etc then add the additional widgets to that widget as needed. The Window is a user editable object, but can only be affected by UI Messages they trigger if the message's corresponding variable has been set to true aka what is set by the can_close , can_minimize , and can_resize functions.","breadcrumbs":"User Interface » Widgets » Window » Window","id":"439","title":"Window"},"44":{"body":"On Linux Fyrox needs the development files for the following libraries: libxcb-shape0, libxcb-xfixes0, libxcb1, libxkbcommon, libasound2. For Debian based distros like Ubuntu, they can be installed like below: sudo apt install libxcb-shape0-dev libxcb-xfixes0-dev libxcb1-dev libxkbcommon-dev libasound2-dev","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Linux","id":"44","title":"Linux"},"440":{"body":"By default, the window will be created in the open, or maximized, state. You can manually set this state via the open function providing a true or false as desired.","breadcrumbs":"User Interface » Widgets » Window » Initial Open State","id":"440","title":"Initial Open State"},"441":{"body":"The window close and minimise buttons can be configured with the with_close_button and with_minimize_button functions. You will want to pass them a button widget, but can do anything else you like past that.","breadcrumbs":"User Interface » Widgets » Window » Styling the Buttons","id":"441","title":"Styling the Buttons"},"442":{"body":"A Modal in UI design terms indicates a window or box that has forced focus. The user is not able to interact with anything else until the modal is dissmissed. Any window can be set and unset as a modal via the modal function.","breadcrumbs":"User Interface » Widgets » Window » Modal (AKA Forced Focus)","id":"442","title":"Modal (AKA Forced Focus)"},"443":{"body":"wrap panel Wrap panel is used to stack children widgets either in vertical or horizontal direction with overflow - every widget that does not have enough space on current line, will automatically be placed on the next line.","breadcrumbs":"User Interface » Widgets » Wrap panel » Wrap panel","id":"443","title":"Wrap panel"},"444":{"body":"Use WrapPanelBuilder to create new wrap panel instance: # extern crate fyrox;\n# use fyrox::{\n# core::pool::Handle,\n# gui::{\n# widget::WidgetBuilder, wrap_panel::WrapPanelBuilder, BuildContext, Orientation, UiNode,\n# },\n# };\n# fn create_wrap_panel(ctx: &mut BuildContext) -> Handle { WrapPanelBuilder::new(WidgetBuilder::new()) .with_orientation(Orientation::Horizontal) .build(ctx)\n}","breadcrumbs":"User Interface » Widgets » Wrap panel » How to create","id":"444","title":"How to create"},"445":{"body":"Wrap panel can stack your widgets either in vertical or horizontal direction. Use .with_orientation while building the panel to switch orientation to desired.","breadcrumbs":"User Interface » Widgets » Wrap panel » Orientation","id":"445","title":"Orientation"},"446":{"body":"One of many use case examples could be picture gallery, or asset browser in the Fyroxed: wrap panel","breadcrumbs":"User Interface » Widgets » Wrap panel » Use cases","id":"446","title":"Use cases"},"447":{"body":"Serialization is a process that converts arbitrary objects into a set of bytes that can be stored to disk or to send them over the network. An opposite to serialization - deserialization - is a process that restores objects from a given set of bytes. Serialization often used to make save/load functionality in games. Fyrox has built-in serializer that is used all over the place the engine and which is represented by a Visit trait. Visit name could be confusing, but it is called after well-known Visitor design pattern. Serialization and deserialization itself is handled by Visitor, it can be created in two modes: read and write. See mode info in respective sections below.","breadcrumbs":"Serialization » Serialization","id":"447","title":"Serialization"},"448":{"body":"There are two main ways to implement Visit trait, each way serves for specific cases. Let's understand which one to use when.","breadcrumbs":"Serialization » Usage","id":"448","title":"Usage"},"449":{"body":"The engine provides proc-macro, that uses code generation to implement Visit trait for you. All you need to do is to add #[derive(Visit)] to your struct/enum. Code generation in most cases is capable to generate typical implementation for serialization/deserialization. You should prefer proc-macro to manual implementation in most cases. The macro supports few very useful attributes, that can be added to fields of a struct/enum: #[visit(optional)] - forces the engine to ignore any errors that may occur during deserialization, leaving a field's value in default state. Very useful option if you're adding a new field to your structure, otherwise the engine will refuse to continue loading of your struct. In case of scripts, deserialization will stop on missing field, and it will be partially loaded. #[visit(rename = \"new_name\")] - replaces the name of a field with given value. Useful if you need to rename a field in the code, but leave backward compatibility with previous versions. #[visit(skip)] - ignores a field completely. Useful if you don't want to serialize a field at all, or a field is not serializable. To use the macro, you must import all types related to Visit trait by use fyrox::core::visitor::prelude::*;. Here's an example: # extern crate fyrox;\nuse fyrox::core::visitor::prelude::*; #[derive(Visit)]\nstruct MyStruct { foo: u32, #[visit(rename = \"baz\")] foobar: f32, #[visit(optional)] optional: String, #[visit(skip)] ignored: usize,\n}","breadcrumbs":"Serialization » Proc-macro #[derive(Visit)]","id":"449","title":"Proc-macro #[derive(Visit)]"},"45":{"body":"Fyrox plugins are static, this means that you must re-compile your game or editor if the source code of your game changes, such architecture requires some boilerplate code for any game. Fyrox offers a special tiny tool - fyrox-template - that helps you generate all this boilerplate with a single command. Install it by running the following command: cargo install fyrox-template Note for Linux: This installs it in $user/.cargo/bin. If you get errors about the fyrox-template command not found then you need to add this folder to your $PATH still. Navigate to the folder where you want the project to be created and run the following command: fyrox-template init --name my_game --style 3d Note that unlike cargo init, this will create a new folder with the given name. The tool accepts two arguments - a project name (--name) and a style (--style), which defines the contents of the default scene. Once you initialize your project, go to game/src/lib.rs - this is where your game logic is located, as you can see, the fyrox-template generated quite a bit of code for you. There are comments explaining what each place is for. For more info about each method, please refer to the docs . Once the project is generated, you should memorize the two commands that will help you to run your game in different modes: cargo run --package editor --release - launches the editor with your game attached. The editor allows you to run your game from it and edit its game entities. It is intended to be used only for development. cargo run --package executor --release - creates and runs the production binary of your game, which can be shipped (for example - to a store). Navigate to your project's directory and run cargo run --package editor --release, after some time you should see the editor: editor In the editor you can start building your game scene. Important note: your scene must have at least one camera, otherwise you won't see a thing. Read the next chapter to learn how to use the editor.","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Project Generator","id":"45","title":"Project Generator"},"450":{"body":"Manual implementation of the trait gives you an opportunity to fix compatibility issues, do some specific actions during serialization (logging, for instance). Typical manual implementation could look like this: # extern crate fyrox;\nuse fyrox::core::visitor::prelude::*; struct MyStruct { foo: u32, foobar: f32, optional: String, ignored: usize,\n} impl Visit for MyStruct { fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { // Create a region first. let mut region = visitor.enter_region(name)?; // Add fields to it. self.foo.visit(\"Foo\", &mut region)?; // Manually rename the field for serialization. self.foobar.visit(\"Baz\", &mut region)?; // Ignore result for option field. let _ = self.optional.visit(\"Baz\", &mut region); // Ignore `self.ignored` Ok(()) }\n} This code pretty much shows the result of macro expansion from the previous section. As you can see, proc-macro saves you from writing tons of boilerplate code. Implementing Visit trait is a first step, the next step is to either serialize an object or deserialize it. See the following section for more info.","breadcrumbs":"Serialization » Manual implementation","id":"450","title":"Manual implementation"},"451":{"body":"To serialize an object all you need to do is to create an instance of a Visitor in either read or write mode and use it like so: # extern crate fyrox;\nuse fyrox::core::visitor::prelude::*;\nuse std::path::Path; #[derive(Visit, Default)]\nstruct MyStruct { foo: u32, #[visit(rename = \"baz\")] foobar: f32, #[visit(optional)] optional: String, #[visit(skip)] ignored: usize,\n} async fn visit_my_structure(path: &Path, object: &mut MyStruct, write: bool) -> VisitResult { if write { let mut visitor = Visitor::new(); object.visit(\"MyObject\", &mut visitor)?; // Dump to the path. visitor.save_binary(path) } else { let mut visitor = Visitor::load_binary(path).await?; // Create default instance of an object. let mut my_object = MyStruct::default(); // \"Fill\" it with contents from visitor. my_object.visit(\"MyObject\", &mut visitor) }\n} The key function here is visit_my_structure which works in both serialization and deserialization modes depending on write flag value. When write is true (serialization), we're creating a new empty visitor and filling it with values from our object and then \"dump\" its content to binary file. When write is false (deserialization), we're loading contents of a file, creating the object in its default state and then \"filling\" it with values from the visitor.","breadcrumbs":"Serialization » Serialization and Deserialization","id":"451","title":"Serialization and Deserialization"},"452":{"body":"Sometimes there is a need to pass custom data to visit methods, one of the ways to do this is to use blackboard field of the visitor: # extern crate fyrox;\nuse fyrox::core::visitor::prelude::*;\nuse std::sync::Arc; struct MyStruct { // Fields are intentionally omitted.\n} struct MyEnvironment { some_data: String,\n} impl Visit for MyStruct { fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult { if let Some(environment) = visitor .blackboard .get::() { println!(\"{}\", environment.some_data); } Ok(()) }\n} fn serialize_with_environment() { let mut my_object = MyStruct {}; let mut visitor = Visitor::new(); visitor.blackboard.register(Arc::new(MyEnvironment { some_data: \"Foobar\".to_owned(), })); my_object.visit(\"MyObject\", &mut visitor).unwrap();\n}","breadcrumbs":"Serialization » Environment","id":"452","title":"Environment"},"453":{"body":"All fields of your structure must implement Default trait, this is essential limitation because deserialization must have a way to create an instance of an object for you.","breadcrumbs":"Serialization » Limitations","id":"453","title":"Limitations"},"454":{"body":"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.","breadcrumbs":"Serialization » Saved Games (WIP) » Saved Games (WIP)","id":"454","title":"Saved Games (WIP)"},"455":{"body":"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. 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 .","breadcrumbs":"Serialization » Saved Games (WIP) » Saved Game Structure","id":"455","title":"Saved Game Structure"},"456":{"body":"This section of the book covers various aspects of the editor. Keep in mind, that this section covers aspects of the editor, that does not have direct relations with engine entities. For example, this section covers Editor Settings , but does not cover Animation Editor . This is because, it is better to show how to use a thing from both sides at once (code and editor), than split it to separate sections. In this section, you'll know how to use editor-specific parts of the engine, how to use special tools it provides. Check next chapters to learn more about a part that interests you now.","breadcrumbs":"Editor » Editor","id":"456","title":"Editor"},"457":{"body":"The editor uses Inspector widget to show the contents of your scripts and when you're using custom structures inside your scripts the editor needs to know how to show them in the UI. Inspector widget has a special mechanism for this called property editors . Basically, it defines a pair TypeId -> Widget - a type has an associated widget that is responsible for showing the content of the type and (optionally) edit it. If there's no widget associated with a type, the editor will print an error message near this field, basically telling you that you need to fix this.","breadcrumbs":"Editor » Property Editors » Property Editors","id":"457","title":"Property Editors"},"458":{"body":"The engine has property editors for pretty much every case, all you need to do is to associate your type with one of them. The following sections covers the most common use cases, each of them should be added to editor/src/main.rs file, after editor's initialization.","breadcrumbs":"Editor » Property Editors » Adding Property Editors","id":"458","title":"Adding Property Editors"},"459":{"body":"This is the most common case when you need to associate your type with a property editor, and in this case the property editor will be InspectablePropertyEditorDefinition: # extern crate fyrox;\nuse fyrox::{ core::reflect::prelude::*, gui::inspector::editors::{ inspectable::InspectablePropertyEditorDefinition, PropertyEditorDefinitionContainer, },\n};\n# # struct Editor {\n# inspector: Inspector,\n# }\n# # struct Inspector {\n# property_editors: PropertyEditorDefinitionContainer,\n# } #[derive(Reflect, Debug)]\nstruct MyStruct { foo: u32, bar: String,\n} # fn add_property_editor(editor: &Editor) {\neditor .inspector .property_editors .insert(InspectablePropertyEditorDefinition::::new());\n# } Keep in mind, that your structure must implement Reflect trait, otherwise you'll get a compilation error.","breadcrumbs":"Editor » Property Editors » Structures","id":"459","title":"Structures"},"46":{"body":"Due to the nature of the software development, some bugs will inevitably sneak into the major releases, due to this, you may want to use the latest engine version from the repository on GitHub, since it is the most likely to have bugs fixed (you can also contribute by fixing any bugs you find or at least, by filing an issue ).","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Using the Latest Engine Version","id":"46","title":"Using the Latest Engine Version"},"460":{"body":"Enumerations are a bit trickier to support, than simple structures, because they require a bit more traits to be implemented for your enumeration. At first, make sure that your editor project has the following dependencies: #[dependencies]\nstrum = \"0.25.0\"\nstrum_macros = \"0.25.0\" These two crates responsible for enum to string (and vice versa) conversions which will be very useful for us. The following example shows a typical usage: # extern crate fyrox;\n# extern crate strum_macros;\n# extern crate strum;\nuse fyrox::{ core::reflect::prelude::*, gui::inspector::editors::{ enumeration::EnumPropertyEditorDefinition, PropertyEditorDefinitionContainer, },\n};\nuse strum_macros::{AsRefStr, EnumString, EnumVariantNames};\n# # struct Editor {\n# inspector: Inspector,\n# }\n# # struct Inspector {\n# property_editors: PropertyEditorDefinitionContainer,\n# } #[derive(Reflect, Default, Debug, AsRefStr, EnumString, EnumVariantNames, Clone)]\nenum MyEnum { #[default] Baz, Foo(u32), Bar { baz: String, foobar: u32 },\n} # fn add_property_editor(editor: &Editor) {\neditor .inspector .property_editors .insert(EnumPropertyEditorDefinition::::new());\n# } As you can see, your enumeration needs a decent amount of trait implementations, hopefully all of them can be derived.","breadcrumbs":"Editor » Property Editors » Enumerations","id":"460","title":"Enumerations"},"461":{"body":"If your structure or enumeration needs to be inheritable (see more info about property inheritance ), then you need one more step. In case of inheritable variables, your fields will be wrapped in InheritableVariable<> and this fact requires you to register an appropriate property editor for this: # extern crate fyrox;\nuse fyrox::{ core::{reflect::prelude::*, variable::InheritableVariable}, gui::inspector::editors::{ inherit::InheritablePropertyEditorDefinition, inspectable::InspectablePropertyEditorDefinition, PropertyEditorDefinitionContainer, },\n};\n# # struct Editor {\n# inspector: Inspector,\n# }\n# # struct Inspector {\n# property_editors: PropertyEditorDefinitionContainer,\n# } #[derive(Reflect, Debug)]\nstruct MyStruct { foo: u32, bar: String,\n} // An example script with inheritable field of custom structure.\nstruct MyScript { inheritable: InheritableVariable,\n} # fn add_property_editor(editor: &Editor) {\neditor .inspector .property_editors .insert(InspectablePropertyEditorDefinition::::new()); // This is responsible for supporting inheritable properties in scripts.\neditor .inspector .property_editors .insert(InheritablePropertyEditorDefinition::::new()); // Alternatively, the two previous insertions could be replaced by a single call of helper\n// method:\neditor .inspector .property_editors .register_inheritable_inspectable::();\n# }","breadcrumbs":"Editor » Property Editors » Inheritable Properties","id":"461","title":"Inheritable Properties"},"462":{"body":"If you have a vector of some custom structure (Vec), then you also need to register a property editor for it: # extern crate fyrox;\nuse fyrox::{ core::reflect::prelude::*, gui::inspector::editors::{ collection::VecCollectionPropertyEditorDefinition, inspectable::InspectablePropertyEditorDefinition, PropertyEditorDefinitionContainer, },\n};\n# # struct Editor {\n# inspector: Inspector,\n# }\n# # struct Inspector {\n# property_editors: PropertyEditorDefinitionContainer,\n# } #[derive(Reflect, Clone, Debug, Default)]\nstruct MyStruct { foo: u32, bar: String,\n} // An example script with Vec field of custom structure.\nstruct MyScript { inheritable: Vec,\n} # fn add_property_editor(editor: &Editor) {\neditor .inspector .property_editors .insert(InspectablePropertyEditorDefinition::::new()); // VecCollectionPropertyEditorDefinition is used to create a property editor for Vec,\n// internally it uses a registered property editor for its generic argument (MyStruct).\neditor .inspector .property_editors .insert(VecCollectionPropertyEditorDefinition::::new()); // Alternatively, you can use a special helper method to replace the two blocks above by a\n// single one.\neditor .inspector .property_editors .register_inheritable_vec_collection::();\n# }","breadcrumbs":"Editor » Property Editors » Collections","id":"462","title":"Collections"},"463":{"body":"See Inspector widget chapter to learn how to create custom property editors.","breadcrumbs":"Editor » Property Editors » Custom Property Editors","id":"463","title":"Custom Property Editors"},"464":{"body":"This chapter should help you to have better understanding of how to configure the editor and which settings are responsible for what. settings","breadcrumbs":"Editor » Settings » Settings","id":"464","title":"Settings"},"465":{"body":"This section contains options for objects selection. Ignore Back Faces - if set, forces mouse picking to ignore back faces of triangles, allowing you to \"click-thru\" triangles from back side. It is useful to pick objects in scenes where you have a ceiling, if the ceiling is one-sided, then all clicks will pass through it allowing you to select objects below the ceiling.","breadcrumbs":"Editor » Settings » Selection","id":"465","title":"Selection"},"466":{"body":"Options in this section defines quality settings for rendering. It directly affects performance and can be used to see how well your scene will be rendered with different options. Almost everything in this section is very well covered in Quality Settings section . The rest of the fields described below. Z Near - defines near clipping plane for main preview camera in the scene. Z Far - defines far clipping plane for main preview camera in the scene.","breadcrumbs":"Editor » Settings » Graphics","id":"466","title":"Graphics"},"467":{"body":"This section contains options for visual debugging, it helps you to see invisible geometry, such as bounding boxes, physical objects, etc. Show Physics - if set, shows physical entities in wireframe mode using debug renderer. It is useful to see where physical entities are, and what shape they have. Show Bounds - if set, shows bounding boxes of scene nodes. Show Tbn - if set, shows tangent-binormal-normal basis of every mesh in the scene. It can be useful to debug graphical issues related to incorrect tangent space.","breadcrumbs":"Editor » Settings » Debugging","id":"467","title":"Debugging"},"468":{"body":"Options in this section responsible for behaviour of Move interaction mode (a tool that allows you to move a node with a gizmo). Grid Snapping - if set, restricts movement to a 3D grid nodes with axes steps defined by Snap Step parameter for respective axis. X/Y/Z Snap Step - defines snapping step (in meters) on respective axis.","breadcrumbs":"Editor » Settings » Move Mode Settings","id":"468","title":"Move Mode Settings"},"469":{"body":"This section contains options for Rotate interaction mode (a tool that allows you to rotate a node with a gizmo). Angle Snapping - if set, restricts rotation around each axis to a series of marks with uniform angular step added to imaginary dial. X/Y/Z Snap Step - defines snapping step (in radians) around respective axis.","breadcrumbs":"Editor » Settings » Rotate Mode Settings","id":"469","title":"Rotate Mode Settings"},"47":{"body":"⚠️ fyrox-template has special sub-command - upgrade to quickly upgrade to desired engine version. To upgrade to the latest version (nightly) you should execute fyrox-template upgrade --version nightly command in your game's directory. There are three main variants for --version switch: nightly - uses latest nightly version of the engine from GitHub directly. This is the preferable version if you want to use the latest changes and bug fixes as they release. latest - uses latest stable version of the engine. major.minor.patch - uses specific stable version from crates.io (0.30.0 for example).","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Automatic","id":"47","title":"Automatic"},"470":{"body":"Options in this section affects how the editor handles Model assets. Instantiation Scale - defines a scale that will be applied to a root node of every Model resource being instantiated in the editor. It is useful if you have tons of Model resources that are either too large or too small, and you want to re-scale them automatically.","breadcrumbs":"Editor » Settings » Model","id":"470","title":"Model"},"471":{"body":"This section contains options of editor camera that is used in Scene Preview window. Speed - speed of camera in meters per second. Invert Dragging - if set, inverts dragging of the camera via middle mouse button. Drag Speed - defines how fast the camera will move while being dragged via middle mouse button.","breadcrumbs":"Editor » Settings » Camera","id":"471","title":"Camera"},"472":{"body":"This section contains information about miscellaneous things, which does not deserve separate section.","breadcrumbs":"Miscellaneous » Miscellaneous","id":"472","title":"Miscellaneous"},"473":{"body":"The engine has built-in logger that allows you to trace execution of your game by creating log entries when needed. Log The window allows you to select severity of the messages that will be put in the window: Info+ will show all messages with Info, Warning, Error severities. Warning+ will show all messages with Warning and Error severities. Error will show all messages with only Error severity. Each log entry can be copied to the clipboard by right-clicking on it and pressing Copy in the context menu. You can also clear the log using Clear button.","breadcrumbs":"Miscellaneous » Log » Logging","id":"473","title":"Logging"},"474":{"body":"You can use one of Log::info, Log::warn, Log::err methods, or use Log::writeln with severity specified. It is also possible to select desired severity level: # extern crate fyrox;\n# use fyrox::core::log::{Log, MessageKind};\n// These lines will be printed.\nLog::info(\"This is some info\");\nLog::warn(\"This is some warning\");\nLog::err(\"This is some error\"); Log::set_verbosity(MessageKind::Warning); Log::info(\"This is some info\"); // This won't be printed.\nLog::warn(\"This is some warning\");\nLog::err(\"This is some error\");","breadcrumbs":"Miscellaneous » Log » Writing to the log","id":"474","title":"Writing to the log"},"475":{"body":"The book offers a set of tutorials of how to write a game of specific genre using the engine. Every tutorial starts from mild difficulty and keep increasing the difficulty until the end. All tutorials are very well structured and you shouldn't be able to lost in them.","breadcrumbs":"Tutorials » Tutorials","id":"475","title":"Tutorials"},"476":{"body":"WARNING: This tutorial is using obsolete engine features, which are subject to be removed in future versions! In this tutorial we'll make a 3D shooter - something similar to rusty-shooter . Also, the series should help you to learn basic principles which lies in the foundation of the engine.","breadcrumbs":"Tutorials » FPS Tutorial » First-Person Shooter Tutorial","id":"476","title":"First-Person Shooter Tutorial"},"477":{"body":"Fyrox changes rapidly and tutorial's code could not compile with the newest versions of the engine, to prevent that and keep the code compilable over time the versions of both the engine and the editor are set to specific commits in the main repo. Fyrox version : 0.28 Source code : GitHub","breadcrumbs":"Tutorials » FPS Tutorial » Fyrox and Fyroxed version","id":"477","title":"Fyrox and Fyroxed version"},"478":{"body":"WARNING: This tutorial is using obsolete engine features, which are subject to be removed in future versions! Source code : GitHub","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » FPS Tutorial Part 1 - Character Controller.","id":"478","title":"FPS Tutorial Part 1 - Character Controller."},"479":{"body":"Introduction Creating a window Creating your first scene Using the scene Character controller Finishing touch Conclusion","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Table of contents","id":"479","title":"Table of contents"},"48":{"body":"Engine version can also be updated manually. The first step you need to take is to install the latest fyrox-template, this can be done with a single cargo command: cargo install fyrox-template --force --git https://github.com/FyroxEngine/Fyrox This will ensure you're using the latest project/script template generator, which is important, since old versions of the template generator will most likely generate outdated code, no longer be compatible with the engine. To switch existing projects to the latest version of the engine, you need to specify paths pointing to the remote repository for the fyrox and fyroxed_base dependencies. You need to do this in the game, executor, and editor projects. First, open game/Cargo.toml and change the fyrox dependency to the following: [dependencies]\nfyrox = { git = \"https://github.com/FyroxEngine/Fyrox\" } Do the same for executor/Cargo.toml. The editor has two dependencies we need to change: fyrox and fyroxed_base. Open the editor/Cargo.toml and set both dependencies to the following: [dependencies]\nfyrox = { git = \"https://github.com/FyroxEngine/Fyrox\" }\nfyroxed_base = { git = \"https://github.com/FyroxEngine/Fyrox\" } Now your game will use the latest engine and editor, but beware - new commits could bring some API breaks. You can avoid these by specifying a particular commit, just add rev = \"desired_commit_hash\" to every dependency like so: [dependencies]\nfyrox = { git = \"https://github.com/FyroxEngine/Fyrox\", rev = \"0195666b30562c1961a9808be38b5e5715da43af\" }\nfyroxed_base = { git = \"https://github.com/FyroxEngine/Fyrox\", rev = \"0195666b30562c1961a9808be38b5e5715da43af\" } To bring a local git repository of the engine to being up-to-date, just call cargo update at the root of the project's workspace. This will pull the latest changes from the remote, unless there is no rev specified. Learn more about dependency paths on the official cargo documentation, here .","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Manual","id":"48","title":"Manual"},"480":{"body":"Fyrox is a general purpose 3D engine, it allows creating any kind of 3D game, but today we'll focus on classic 3D shooter. In this tutorial we'll write a simple character controller. This is what we're aiming for: Let's start by creating a new cargo project, make a folder and execute this: cargo init --bin Open Cargo.toml and add fyrox dependency: [dependencies]\nfyrox = \"0.28.0\"","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Introduction","id":"480","title":"Introduction"},"481":{"body":"Great! Now we can start writing the game. Let's start from something very simple - a window and a main loop. Just copy and paste this code in the main.rs: # extern crate fyrox;\nuse fyrox::{ core::{ algebra::{UnitQuaternion, Vector3}, pool::Handle, }, engine::{Engine, EngineInitParams, SerializationContext}, asset::manager::ResourceManager, event::{DeviceEvent, ElementState, Event, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, resource::texture::TextureWrapMode, scene::{ base::BaseBuilder, camera::{CameraBuilder, SkyBox, SkyBoxBuilder}, collider::{ColliderBuilder, ColliderShape}, node::Node, rigidbody::RigidBodyBuilder, transform::TransformBuilder, Scene, }, window::WindowBuilder,\n};\nuse std::{sync::Arc, time};\nuse fyrox::window::WindowAttributes;\nuse fyrox::engine::{GraphicsContextParams, GraphicsContext}; // Our game logic will be updated at 60 Hz rate.\nconst TIMESTEP: f32 = 1.0 / 60.0; struct Game { // Empty for now.\n} impl Game { pub fn new() -> Self { Self {} } pub fn update(&mut self) { // Game logic will be placed here. }\n} fn main() { // Create event loop that will be used to \"listen\" events from the OS. let event_loop = EventLoop::new().unwrap(); // Finally create an instance of the engine. let graphics_context_params = GraphicsContextParams { window_attributes: WindowAttributes { title: \"3D Shooter Tutorial\".to_string(), resizable: true, ..Default::default() }, vsync: true, }; let serialization_context = Arc::new(SerializationContext::new()); let mut engine = Engine::new(EngineInitParams { graphics_context_params, resource_manager: ResourceManager::new(), serialization_context, }) .unwrap(); // Initialize game instance. It is empty for now. let mut game = Game::new(); // Run the event loop of the main window. which will respond to OS and window events and update // engine's state accordingly. Engine lets you to decide which event should be handled, // this is a minimal working example of how it should be. let mut previous = time::Instant::now(); let mut lag = 0.0; event_loop.run(move |event, _, control_flow| { match event { Event::MainEventsCleared => { // This main game loop - it has fixed time step which means that game // code will run at fixed speed even if renderer can't give you desired // 60 fps. let elapsed = previous.elapsed(); previous = time::Instant::now(); lag += elapsed.as_secs_f32(); while lag >= TIMESTEP { lag -= TIMESTEP; // Run our game's logic. game.update(); // Update engine each frame. engine.update(TIMESTEP, control_flow, &mut lag, Default::default()); } // Rendering must be explicitly requested and handled after RedrawRequested event is received. if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context { ctx.window.request_redraw(); } } Event::RedrawRequested(_) => { // Render at max speed - it is not tied to the game code. engine.render().unwrap(); } Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { input, .. } => { // Exit game by hitting Escape. if let Some(VirtualKeyCode::Escape) = input.virtual_keycode { *control_flow = ControlFlow::Exit } } WindowEvent::Resized(size) => { // It is very important to handle Resized event from window, because // renderer knows nothing about window size - it must be notified // directly when window size has changed. engine.set_frame_size(size.into()).unwrap(); } _ => (), }, _ => *control_flow = ControlFlow::Poll, } });\n} Wow! There is lots of code for such a simple task. Fear not, everything here is pretty straightforward, let's dive into this code and disassemble it line by line. Just skip imports, it's too boring. Let's look at this line: const TIMESTEP: f32 = 1.0 / 60.0; Here we define a rate of update for logic of our future game, just sticking to common 60 FPS. Next goes the skeleton of the game, just a struct with two methods. It will be filled later in this tutorial. struct Game { // Empty for now.\n} impl Game { pub fn new() -> Self { Self {} } pub fn update(&mut self) { // Game logic will be placed here. }\n} Finally, we at the point where the interesting stuff happens - fn main(). We're starting by creating our event loop: # extern crate fyrox;\n# use fyrox::event_loop::EventLoop;\nlet event_loop = EventLoop::new().unwrap(); The event loop is a \"magic\" thing that receives events from the operating system and feeds your application, this is a very important part which makes the application work. Finally, we're creating an instance of the engine: let graphics_context_params = GraphicsContextParams { window_attributes: WindowAttributes { title: \"3D Shooter Tutorial\".to_string(), resizable: true, ..Default::default() }, vsync: true,\n}; let serialization_context = Arc::new(SerializationContext::new());\nlet mut engine = Engine::new(EngineInitParams { graphics_context_params, resource_manager: ResourceManager::new(serialization_context.clone()), serialization_context,\n})\n.unwrap(); At first, we're creating an instance of SerializationContext - it is used to store type constructors used for serialization needs. Next, we're filling EngineInitParams structure, there is nothing interesting there, except maybe a flag that is responsible for vertical synchronization (VSync). In this tutorial we'll have VSync disabled, because it requires specific platform-dependent extensions which are not always available and calling .unwrap() might result in panic on some platforms. Next we're creating an instance of the game, remember this line, it will be changed soon: let mut game = Game::new(); Next we define two variables for the game loop: let clock = time::Instant::now();\nlet mut elapsed_time = 0.0; At first, we \"remember\" the starting point of the game in time. The next variable is used to control the game loop. Finally, we run the event loop and start checking for events coming from the OS: event_loop.run(move |event, _, control_flow| { match event { ... }\n}); Let's look at each event separately starting from Event::MainEventsCleared: Event::MainEventsCleared => { // This main game loop - it has fixed time step which means that game // code will run at fixed speed even if renderer can't give you desired // 60 fps. let mut dt = clock.elapsed().as_secs_f32() - elapsed_time; while dt >= TIMESTEP { dt -= TIMESTEP; elapsed_time += TIMESTEP; // Run our game's logic. game.update(); // Update engine each frame. engine.update(TIMESTEP); } // Rendering must be explicitly requested and handled after RedrawRequested event is received. if let GraphicsContext::Initialized(ref ctx) = engine.graphics_context { ctx.window.request_redraw(); }\n} This is the heart of game loop - it stabilizes update rate of game logic by measuring time from last update call and performs a various amount of iterations based on an amount of time since last update. This makes the game logic update rate independent of FPS - it will be always 60 Hz for game logic even if FPS is 10. The while loop contains game.update() and engine.update(TIMESTEP) calls to update game's logic and engine internals respectively. After the loop we're asking the engine to render the next frame. In the next match arm Event::RedrawRequested we're handing our request: Event::RedrawRequested(_) => { // Render at max speed - it is not tied to the game code. engine.render().unwrap();\n} As you can see rendering happens in a single line of code. Next we need to handle window events: Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { input, .. } => { // Exit game by hitting Escape. if let Some(VirtualKeyCode::Escape) = input.virtual_keycode { *control_flow = ControlFlow::Exit } } WindowEvent::Resized(size) => { // It is very important to handle Resized event from window, because // renderer knows nothing about window size - it must be notified // directly when window size has changed. engine.set_frame_size(size.into()).unwrap(); } _ => (),\n}, Here we're just checking if the player has hit Escape button and exit game if so. Also, when WindowEvent::Resized is received, we're notifying renderer about that, so it's render targets will be resized too. The final match arm is for every other event, nothing fancy here - just asking engine to continue listening for new events. _ => *control_flow = ControlFlow::Poll, So far so good. This small piece of code just creates a new window and fills it with black color, now we can start writing the game. Window Let's start by creating a simple scene where we'll test our character controller. This is the time when Fyroxed comes into play - Fyroxed is a native scene editor of the engine. It is worth mentioning what \"scene editor\" means: unlike many other engines (Unity, UnrealEngine, etc.), Fyroxed does not allow you to run your game inside it, instead you just edit your scene, save it in the editor and load it in your game. Being able to run a game inside the editor was a very huge task for one person, and I just chose the easiest way. Alright, back to the interesting stuff. Build the editor first using instructions from its GitHub page using specific commit stated in the beginning of the article.","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Creating a window","id":"481","title":"Creating a window"},"482":{"body":"This section is completely optional, if you eager to make the game - just use a pre-made scene (download it and unpack in the folder of your game) and go to the next section . Open Fyroxed, it should look like this: Fyroxed It will ask you to choose a working directory. configurator The working directory is simply a path to your game's executable, in most cases it will be the root folder of your project. Next, click File -> CreateScene. Now you can start modifying your scene. All we need for now is a floor and maybe some decorations. To do that, you can either create everything from simple objects (cubes, cones, cylinders, etc.) or load some assets made in 3D editors (like Blender, 3Ds max, etc.). Here we combine two approaches: floor will be just a squashed cube and decorations will be 3D models. Let's start from the floor. Click Create -> Mesh -> Cube, select the cube and use Scale tool from the toolbar to squash it to form the floor. Floor Next we need to add physical body to the floor to not fall through it. This is very simple, click Create -> Physics -> Rigid Body then right-click on the rigid body in the World Viewer and click Create -> Physics -> Collider. Next we need to bind the floor 3D model with the rigid body, to do that drag'n'drop the floor entity to the rigid body. Now we need to configure the collider of the rigid body. Select it and go to Inspector, find Shape property and select Trimesh from the dropdown list. Next, click + sign in Sources and then drag'n'drop floor entity to Unassigned entry while holding Alt on the keyboard. By doing this, we've added a source of geometry for triangle mesh collider. Also, we need to make the rigid body static, so it won't be affected by gravity and external forces, otherwise the floor will fall as any other dynamic rigid body. To do that, simply select the body and change its Body Type property to Static. Floor Body Ok, good, but it looks awful, let's add some texture to it, to do that, download floor texture , place it to data/textures and apply it to the floor. To do that, use the asset browser: at its left side it shows file system of your project, locate data/textures folder and select floor.jpg. Now just drag-n-drop the texture to the floor, this is what you should get. Floor Texture Now let's add some decorations, to do that download 3D model I prepared for this tutorial and unpack it in data/models. Now go to the data/models in the asset browser and just drag-n-drop the barrel.FBX to the scene. Now use the Scale and Move tools to adjust scale and position of the barrel, it should look like this: Barrel Body Barrel does not have any rigid body yet, and it won't interact with world. Let's fix this. As usual, click Create -> Physics -> Rigid Body then click on the added rigid body and add a cylinder collider by right-click on it and selecting Create -> Physics -> Colider. Now select the collider and set its shape to Cylinder adjust its height and radius. As a final step drag'n'drop the barrel.FBX scene node on the rigid body node. Now clone some barrels, to do that select a parent rigid body of some barrel.FBX in the World Outliner, right-click on the scene preview and press Ctrl+C to copy the barrel and Ctrl+V to paste. Repeat multiple times. Barrel Also add a light source, to do that go to Create -> Light -> Point and adjust its position using the Move tool. Barrel The final step: save your scene in data/models, to do that go to File -> Save and select the folder and type name of the scene in the field it should be scene.rgs.","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Creating your first scene","id":"482","title":"Creating your first scene"},"483":{"body":"Now it's the time to load the scene we've made earlier in the game. This is very simple, all we need to do is to load scene as resource and create its instance. Change fn new() body to: # extern crate fyrox;\n# use fyrox::{\n# core::{algebra::Vector3, pool::Handle},\n# engine::Engine, resource::model::{Model, ModelResourceExtension},\n# scene::{\n# base::BaseBuilder, camera::CameraBuilder, node::Node, transform::TransformBuilder,\n# Scene,\n# },\n# };\n# struct Stub {\n# camera: Handle,\n# scene: Handle,\n# }\n# impl Stub {\npub async fn new(engine: &mut Engine) -> Self { let mut scene = Scene::new(); // Load a scene resource and create its instance. engine .resource_manager .request::(\"data/models/scene.rgs\") .await .unwrap() .instantiate(&mut scene); // Next create a camera, it is our \"eyes\" in the world. // This can also be made in editor, but for educational purpose we'll made it by hand. let camera = CameraBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(0.0, 1.0, -3.0)) .build(), ), ) .build(&mut scene.graph); Self { camera, scene: engine.scenes.add(scene), }\n}\n# } You may have noticed that the Game structure now has two new fields: struct Game { scene: Handle, // A handle to the scene camera: Handle, // A handle to the camera\n} These fields are just handles to the \"entities\" we've created in the Game::new(). Also, change let mut game = Game::new(); to this: let mut game = fyrox::core::futures::executor::block_on(Game::new(&mut engine)); Here we execute async function Game::new() and it creates game's instance with the scene we've made previously. Run the game and you should see this: Barrel Cool! Now let's disassemble fn new() line by line. First, we're creating an empty scene: let mut scene = Scene::new(); The next few lines are the most interesting: engine .resource_manager .request::(\"data/models/scene.rgs\") .await .unwrap() .instantiate(&mut scene); Here we're asking the resource manager to load the scene we've made previously, awaiting while it loads and then instantiating it on the scene. What does \"instantiation\" mean? In short, it means that we're creating a copy of a scene and adding the copy to some other scene, the engine remembers connections between clones and original entities and is capable of restoring data from resource for the instance. At this point we've successfully instantiated the scene. However, we won't see anything yet - we need a camera: let camera = CameraBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(0.0, 1.0, -3.0)) .build(), ),\n)\n.build(&mut scene.graph); Camera is our \"eyes\" in the world, here we're just creating a camera and moving it a bit up and back to be able to see the scene. Finally, we're adding the scene to the engine's container for scenes, and it gives us a handle to the scene. Later we'll use the handle to borrow scene and modify it. Self { camera, scene: engine.scenes.add(scene),\n}","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Using the scene","id":"483","title":"Using the scene"},"484":{"body":"We've made a lot of things already, but still can't move in the scene. Let's fix this! We'll start writing the character controller which will allow us to walk in our scene. Let's start with a chunk of code as usual: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{UnitQuaternion, Vector3},\n# pool::Handle,\n# },\n# engine::{Engine},\n# asset::manager::ResourceManager,\n# event::{DeviceEvent, ElementState, Event, VirtualKeyCode, WindowEvent},\n# event_loop::{ControlFlow, EventLoop},\n# resource::texture::TextureWrapMode,\n# scene::{\n# base::BaseBuilder,\n# camera::{CameraBuilder, SkyBox, SkyBoxBuilder},\n# collider::{ColliderBuilder, ColliderShape},\n# node::Node,\n# rigidbody::RigidBodyBuilder,\n# transform::TransformBuilder,\n# Scene,\n# },\n# window::WindowBuilder,\n# };\n# use std::time; #[derive(Default)]\nstruct InputController { move_forward: bool, move_backward: bool, move_left: bool, move_right: bool, pitch: f32, yaw: f32,\n} struct Player { camera: Handle, rigid_body: Handle, controller: InputController,\n} impl Player { fn new(scene: &mut Scene) -> Self { // Create rigid body with a camera, move it a bit up to \"emulate\" head. let camera; let rigid_body_handle = RigidBodyBuilder::new( BaseBuilder::new() .with_local_transform( TransformBuilder::new() // Offset player a bit. .with_local_position(Vector3::new(0.0, 1.0, -1.0)) .build(), ) .with_children(&[ { camera = CameraBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(0.0, 0.25, 0.0)) .build(), ), ) .build(&mut scene.graph); camera }, // Add capsule collider for the rigid body. ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::capsule_y(0.25, 0.2)) .build(&mut scene.graph), ]), ) // We don't want the player to tilt. .with_locked_rotations(true) // We don't want the rigid body to sleep (be excluded from simulation) .with_can_sleep(false) .build(&mut scene.graph); Self { camera, rigid_body: rigid_body_handle, controller: Default::default(), } } fn update(&mut self, scene: &mut Scene) { // Set pitch for the camera. These lines responsible for up-down camera rotation. scene.graph[self.camera].local_transform_mut().set_rotation( UnitQuaternion::from_axis_angle(&Vector3::x_axis(), self.controller.pitch.to_radians()), ); // Borrow rigid body node. let body = scene.graph[self.rigid_body].as_rigid_body_mut(); // Keep only vertical velocity, and drop horizontal. let mut velocity = Vector3::new(0.0, body.lin_vel().y, 0.0); // Change the velocity depending on the keys pressed. if self.controller.move_forward { // If we moving forward then add \"look\" vector of the body. velocity += body.look_vector(); } if self.controller.move_backward { // If we moving backward then subtract \"look\" vector of the body. velocity -= body.look_vector(); } if self.controller.move_left { // If we moving left then add \"side\" vector of the body. velocity += body.side_vector(); } if self.controller.move_right { // If we moving right then subtract \"side\" vector of the body. velocity -= body.side_vector(); } // Finally new linear velocity. body.set_lin_vel(velocity); // Change the rotation of the rigid body according to current yaw. These lines responsible for // left-right rotation. body.local_transform_mut() .set_rotation(UnitQuaternion::from_axis_angle( &Vector3::y_axis(), self.controller.yaw.to_radians(), )); } fn process_input_event(&mut self, event: &Event<()>) { match event { Event::WindowEvent { event, .. } => { if let WindowEvent::KeyboardInput { input, .. } = event { if let Some(key_code) = input.virtual_keycode { match key_code { VirtualKeyCode::W => { self.controller.move_forward = input.state == ElementState::Pressed; } VirtualKeyCode::S => { self.controller.move_backward = input.state == ElementState::Pressed; } VirtualKeyCode::A => { self.controller.move_left = input.state == ElementState::Pressed; } VirtualKeyCode::D => { self.controller.move_right = input.state == ElementState::Pressed; } _ => (), } } } } Event::DeviceEvent { event, .. } => { if let DeviceEvent::MouseMotion { delta } = event { self.controller.yaw -= delta.0 as f32; self.controller.pitch = (self.controller.pitch + delta.1 as f32).clamp(-90.0, 90.0); } } _ => (), } }\n} This is all the code we need for character controller, quite a lot actually, but as usual everything here is pretty straightforward. # extern crate fyrox;\n# use fyrox::core::pool::Handle;\n# use fyrox::engine::Engine;\n# use fyrox::scene::Scene;\n# use fyrox::resource::model::{Model, ModelResourceExtension};\n# struct Player;\n# impl Player {\n# fn new(_scene: &mut Scene) -> Self {\n# Self\n# }\n# fn update(&mut self, _scene: &mut Scene) {}\n# }\n// Also we must change Game structure a bit too and the new() code.\nstruct Game { scene: Handle, player: Player, // New\n} impl Game { pub async fn new(engine: &mut Engine) -> Self { let mut scene = Scene::new(); // Load a scene resource and create its instance. engine .resource_manager .request::(\"data/models/scene.rgs\") .await .unwrap() .instantiate(&mut scene); Self { player: Player::new(&mut scene), // New scene: engine.scenes.add(scene), } } pub fn update(&mut self, engine: &mut Engine) { self.player.update(&mut engine.scenes[self.scene]); // New }\n} We've moved camera creation to Player, because now the camera is attached to the player's body. Also, we must add this line in the beginning of event_loop.run(...) to let player handle input events: game.player.process_input_event(&event); So, let's try to understand what happens in this huge chunk of code. Let's start from the InputController struct, it holds the state of the input for a single frame and rotations of player \"parts\". #[derive(Default)]\nstruct InputController { move_forward: bool, move_backward: bool, move_left: bool, move_right: bool, pitch: f32, yaw: f32,\n} Next goes the Player::new() function. First, we're creating a simple chain of nodes of different kinds in the scene graph . let camera;\nlet rigid_body_handle = RigidBodyBuilder::new( BaseBuilder::new() .with_local_transform( TransformBuilder::new() // Offset player a bit. .with_local_position(Vector3::new(0.0, 1.0, -1.0)) .build(), ) .with_children(&[ { camera = CameraBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(0.0, 0.25, 0.0)) .build(), ), ) .build(&mut scene.graph); camera }, // Add capsule collider for the rigid body. ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::capsule_y(0.25, 0.2)) .build(&mut scene.graph), ]), ) // We don't want the player to tilt. .with_locked_rotations(true) // We don't want the rigid body to sleep (be excluded from simulation) .with_can_sleep(false) .build(&mut scene.graph); Basically we're making something like this: Graph As you can see, the camera is attached to the rigid body and has a relative position of (0.0, 0.25, 0.0). So when we'll move rigid body, the camera will move too (and rotate of course). Self { camera, rigid_body: rigid_body_handle, controller: Default::default(),\n} Next goes the fn update(...) function, it is responsible for movement of the player. It starts from these lines: // Set pitch for the camera. These lines responsible for up-down camera rotation.\nscene.graph[self.camera].local_transform_mut().set_rotation( UnitQuaternion::from_axis_angle(&Vector3::x_axis(), self.controller.pitch.to_radians()),\n); We're borrowing the camera from the graph (scene.graph[self.camera]) and modifying its local rotation, using a quaternion built from an axis, and an angle. This rotates camera in vertical direction. Let's talk about borrowing in the engine. Almost every object in the engine \"lives\" in generational arenas (pool in fyrox's terminology). Pool is a contiguous chunk of memory, to be able to \"reference\" an object in a pool Fyrox uses handles. Almost every entity has a single owner - the engine, so to mutate or read data from an entity your have to borrow it first, like this: // Borrow rigid body node.\nlet body = scene.graph[self.rigid_body].as_rigid_body_mut(); This piece of code scene.graph[self.rigid_body] borrows rigid_body as either mutable or shared, depending on the context (basically it is just an implementation of Index + IndexMut traits). Once we've borrowed objects, we can modify them. As the next step we calculate new horizontal speed for the player: // Keep only vertical velocity, and drop horizontal.\nlet mut velocity = Vector3::new(0.0, body.lin_vel().y, 0.0); // Change the velocity depending on the keys pressed.\nif self.controller.move_forward { // If we moving forward then add \"look\" vector of the body. velocity += body.look_vector();\n}\nif self.controller.move_backward { // If we moving backward then subtract \"look\" vector of the body. velocity -= body.look_vector();\n}\nif self.controller.move_left { // If we moving left then add \"side\" vector of the body. velocity += body.side_vector();\n}\nif self.controller.move_right { // If we moving right then subtract \"side\" vector of the body. velocity -= body.side_vector();\n} // Finally new linear velocity.\nbody.set_lin_vel(velocity); We don't need to modify vertical speed, because it should be controlled by the physics engine. Finally, we're setting rotation of the rigid body: // Change the rotation of the rigid body according to current yaw. These lines responsible for\n// left-right rotation.\nbody.local_transform_mut() .set_rotation(UnitQuaternion::from_axis_angle( &Vector3::y_axis(), self.controller.yaw.to_radians(), )); The next piece of code is a bit boring, but still should be addressed - it is input handling. In the process_input_event we check input events and configure input controller accordingly. Basically we're just checking if W, S, A, D keys were pressed or released. In the MouseMotion arm, we're modifying yaw and pitch of the controller according to mouse velocity. Nothing fancy, except this line: self.controller.pitch = (self.controller.pitch + delta.1 as f32).clamp(-90.0, 90.0); Here we're just restricting pitch to [-90; 90] degree range to not let flipping camera upside-down. Now let's run the game, you should see something like this and be able to walk and turn the camera. Controller","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Character controller","id":"484","title":"Character controller"},"485":{"body":"One more thing before we end the tutorial. Black \"void\" around us isn't nice, let's add skybox for the camera to improve that. Skybox is a very simple effect that significantly improves scene quality. To add a skybox, add this code first somewhere before impl Player: # extern crate fyrox;\n# use fyrox::{\n# asset::manager::{ResourceManager},\n# resource::texture::{Texture, TextureWrapMode},\n# scene::{\n# camera::{SkyBox, SkyBoxBuilder},\n# },\n# };\nasync fn create_skybox(resource_manager: ResourceManager) -> SkyBox { // Load skybox textures in parallel. let (front, back, left, right, top, bottom) = fyrox::core::futures::join!( resource_manager.request::(\"data/textures/skybox/front.jpg\"), resource_manager.request::(\"data/textures/skybox/back.jpg\"), resource_manager.request::(\"data/textures/skybox/left.jpg\"), resource_manager.request::(\"data/textures/skybox/right.jpg\"), resource_manager.request::(\"data/textures/skybox/up.jpg\"), resource_manager.request::(\"data/textures/skybox/down.jpg\") ); // Unwrap everything. let skybox = SkyBoxBuilder { front: Some(front.unwrap()), back: Some(back.unwrap()), left: Some(left.unwrap()), right: Some(right.unwrap()), top: Some(top.unwrap()), bottom: Some(bottom.unwrap()), } .build() .unwrap(); // Set S and T coordinate wrap mode, ClampToEdge will remove any possible seams on edges // of the skybox. let skybox_texture = skybox.cubemap().unwrap(); let mut data = skybox_texture.data_ref(); data.set_s_wrap_mode(TextureWrapMode::ClampToEdge); data.set_t_wrap_mode(TextureWrapMode::ClampToEdge); skybox\n} Then modify signature of Player::new to async fn new(scene: &mut Scene, resource_manager: ResourceManager) -> Self We just added resource manager parameter here, and made the function async, because we'll load a bunch of textures in the create_skybox function. Add following line at camera builder (before .build): .with_skybox(create_skybox(resource_manager).await) Also modify player creation in Game::new to this player: Player::new(&mut scene, engine.resource_manager.clone()).await, Next, download skybox textures from here and extract the archive in data/textures (all textures from the archive must be in data/textures/skybox). Now you can run the game, and you should see something like this: Controller This was the last step of this tutorial.","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Finishing touch","id":"485","title":"Finishing touch"},"486":{"body":"In this tutorial we've learned how to use the engine and the editor. Created simple character controller and walked on the scene we've made in the editor. I hope you liked this tutorial, and if so, please consider supporting the project on Patreon or LiberaPay . Source code is available on GitHub . In the next tutorial we'll start adding weapons. Discussion: Reddit , Discord .","breadcrumbs":"Tutorials » FPS Tutorial » Character controller » Conclusion","id":"486","title":"Conclusion"},"487":{"body":"WARNING: This tutorial is using obsolete engine features, which are subject to be removed in future versions! Source code : GitHub","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » FPS Tutorial Part 2 - Weapons","id":"487","title":"FPS Tutorial Part 2 - Weapons"},"488":{"body":"Introduction Adding weapons Game architecture Recoil Impact effects Conclusion","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Table of contents","id":"488","title":"Table of contents"},"489":{"body":"Of course for a shooter game we need weapons and targets to shoot at. In this tutorial we'll add weapons to the game. For simplicity, we'll add only one weapon, as you'll see later it is pretty easy to add more weapons yourself. This is the result we're aiming in the tutorial:","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Introduction","id":"489","title":"Introduction"},"49":{"body":"Any object-specific game logic should be added using scripts. A script is a \"container\" for data and code, that will be executed by the engine. Read the Scripts chapter to learn how to create, edit, and use scripts in your game.","breadcrumbs":"Getting started » Editor, Plugins and Scripts » Adding Game Logic","id":"49","title":"Adding Game Logic"},"490":{"body":"Add a new module weapon.rs near your main.rs and use it somewhere after other imports: pub mod weapon;\n...\nuse weapon::Weapon; Switch to weapon.rs and paste this code into it: # extern crate fyrox;\nuse fyrox::scene::graph::Graph;\nuse fyrox::{ core::{algebra::Vector3, math::Vector3Ext, pool::Handle}, asset::manager::ResourceManager, resource::model::{Model, ModelResourceExtension}, scene::{node::Node, Scene},\n}; pub struct Weapon { model: Handle, shot_point: Handle, shot_timer: f32,\n} impl Weapon { pub async fn new(scene: &mut Scene, resource_manager: ResourceManager) -> Self { // Yeah, you need only few lines of code to load a model of any complexity. let model = resource_manager .request::(\"data/models/m4.fbx\") .await .unwrap() .instantiate(scene); let shot_point = scene.graph.find_by_name(model, \"Weapon:ShotPoint\").unwrap().0; Self { model, shot_point, shot_timer: 0.0, } } pub fn model(&self) -> Handle { self.model } pub fn shot_point(&self) -> Handle { self.shot_point } pub fn update(&mut self, dt: f32) { self.shot_timer = (self.shot_timer - dt).min(0.0); } pub fn can_shoot(&self) -> bool { self.shot_timer <= 0.0 } pub fn shoot(&mut self) { self.shot_timer = 1.0; }\n} This piece of code just loads a weapon model and saves an instance handle for further use. Also, each weapon should contain a helper node that tells from where it will \"emit\" bullets, the node was added in a 3D editor and has name Weapon:ShotPoint. Weapon implementation has helper methods that provide read-only access to inner fields (model, shot_point). update method just decreases the timer's value which is used to change the pace of shooting. You may ask \"why shoot method just modifies timer's value and does not create bullets, etc.?\" - please be patient, I will explain this later in game architecture section of the tutorial. OK, now we need to make a point where every weapon will be \"mounted\" on, go to Player::new and add these lines in the BaseBuilder of the CameraBuilder instance (you also need to import PivotBuilder): .with_children(&[{ weapon_pivot = PivotBuilder::new( BaseBuilder::new().with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new( -0.1, -0.05, 0.015, )) .build(), ), ) .build(&mut scene.graph); weapon_pivot\n}]), What is going on here? We're just adding new child node to the camera and offset it by some vector. Every weapon will be attached to this pivot. Please keep in mind that the offset given in local coordinates, which means that weapon pivot will move with the camera, but with some offset relative to it. Also, do not forget to add this line after let camera;: let weapon_pivot; Finally, add the weapon pivot to Self { ... } (and also add weapon_pivot: Handle to the Player struct): Self { camera, weapon_pivot, // <- here rigid_body: rigid_body_handle.into(), controller: Default::default(),\n} Next we need a container for weapons, let's add it to the Game struct: struct Game { scene: Handle, player: Player, weapons: Pool // Weapons will live in a pool\n} Also do not forget to import Pool from fyrox::core::pool in main.rs: ...\nuse fyrox::core::pool::{Handle, Pool};\n... Now we need to change Game::new() a bit to add a weapon to the player: pub async fn new(engine: &mut Engine) -> Self { let mut scene = Scene::new(); // Load a scene resource and create its instance. engine .resource_manager .request::(\"data/models/scene.rgs\") .await .unwrap() .instantiate(&mut scene); // Create player first. let player = Player::new(&mut scene, engine.resource_manager.clone()).await; // Create weapon next. let weapon = Weapon::new(&mut scene, engine.resource_manager.clone()).await; // \"Attach\" the weapon to the weapon pivot of the player. scene.graph.link_nodes(weapon.model(), player.weapon_pivot); // Create a container for the weapons. let mut weapons = Pool::new(); // Put the weapon into it. weapons.spawn(weapon); Self { player, scene: engine.scenes.add(scene), weapons, }\n} At first, we're loading the scene, next we're creating player as usual. Next we're creating a weapon and attach it to the weapon pivot we've made earlier. Finally, we're creating a container for the weapons: we'll use Pool to be able to borrow weapon later on when we need. So, let's run the game, and you should see something like this: Weapon Alright, now we have a weapon, but it still can't shoot. Let's fix that.","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Adding weapons","id":"490","title":"Adding weapons"},"491":{"body":"We at the point now where we need to choose correct approach of interaction between parts of the game. We already have two kinds of entities: player and weapon. In naive approach to shoot a weapon, you'd pass a reference to a weapon in Player::update() and would call something like weapon.shoot(). Most likely that at some point you'll end up in a situation when you need too much of a context in a single method. This is so-called strong coupling, this is the thing that disappoints borrow checker too much, and it rejects your code because you're trying to borrow same things multiple times. So we need a way to change strong coupling to loose coupling. To do that we'll use messages to delay execution of some actions that require too much of a context. In general, we'll replace direct function call with a message that will be put in a common queue and executed later on one by one at the top of call hierarchy (in Game::update in our case). Let's begin by adding a MPSC (Multiple Producer Single Consumer) queue to the Game: struct Game { scene: Handle, player: Player, weapons: Pool, receiver: Receiver, // Single receiver, it cannot be cloned. sender: Sender, // Sender can be cloned and used from various places.\n} Now we need a Message enumeration, add message.rs module, import it in main.rs (pub mod message;) and fill it with the following code: use crate::weapon::Weapon;\nuse fyrox::core::pool::Handle; pub enum Message { ShootWeapon { weapons: Handle }\n} For now, we have only one message kind - ShootWeapon with a single parameter, a handle of a weapon to shoot. We need a place to handle messages, Game::update seems to be the most suitable - it is on top of \"call hierarchy\" and has most wide context. Let's change Game::update to this code: pub fn update(&mut self, engine: &mut Engine, dt: f32) { self.player.update(&mut engine.scenes[self.scene]); // v New code v // for weapon in self.weapons.iter_mut() { weapon.update(dt); } // We're using `try_recv` here because we don't want to wait until next message - // if the queue is empty just continue to next frame. while let Ok(message) = self.receiver.try_recv() { match message { Message::ShootWeapon { weapon } => { self.shoot_weapon(weapon, engine); } } }\n} So, these first three new lines updating every weapon in the game, for now update of a weapon just updates shooting timer. As you can see we've added new parameter to the function dt: f32, this is the time span from last frame. Do not forget to pass this parameter in fn main(): game.update(&mut engine, TIMESTEP);. Finally, at the end of the function we're handling messages from the queue one by one. As you can see we're handling ShootWeapon message, but there is a mysterious line self.shoot_weapon(weapon, engine) which is not yet defined, let's fix that, add these lines to impl Game: fn shoot_weapon(&mut self, weapon: Handle, engine: &mut Engine) { let weapon = &mut self.weapons[weapon]; if weapon.can_shoot() { weapon.shoot(); let scene = &mut engine.scenes[self.scene]; let weapon_model = &scene.graph[weapon.model()]; // Make a ray that starts at the weapon's position in the world and look toward // \"look\" vector of the weapon. let ray = Ray::new( scene.graph[weapon.shot_point()].global_position(), weapon_model.look_vector().scale(1000.0), ); let mut intersections = Vec::new(); scene.graph.physics.cast_ray( RayCastOptions { ray_origin: Point3::from(ray.origin), max_len: ray.dir.norm(), groups: Default::default(), sort_results: true, // We need intersections to be sorted from closest to furthest. ray_direction: ray.dir, }, &mut intersections, ); // Ignore intersections with player's capsule. let trail_length = if let Some(intersection) = intersections .iter() .find(|i| i.collider != self.player.collider) { // // TODO: Add code to handle intersections with bots. // // For now just apply some force at the point of impact. let colliders_parent = scene.graph[intersection.collider].parent(); let picked_rigid_body = scene.graph[colliders_parent].as_rigid_body_mut(); picked_rigid_body.apply_force_at_point( ray.dir.normalize().scale(10.0), intersection.position.coords, ); picked_rigid_body.wake_up(); // Trail length will be the length of line between intersection point and ray origin. (intersection.position.coords - ray.origin).norm() } else { // Otherwise trail length will be just the ray length. ray.dir.norm() }; create_shot_trail(&mut scene.graph, ray.origin, ray.dir, trail_length); }\n} Wow! Why is there so much code to shoot a weapon!? Actually, this is not all the code - check the last line create_shot_trail(&mut scene.graph, ray.origin, ray.dir, trail_length); This is yet another function we must add, it is a standalone helper function that creates a shot trail: # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{UnitQuaternion, Vector3},\n# color::Color,\n# parking_lot::Mutex,\n# sstorage::ImmutableString,\n# },\n# material::{Material, PropertyValue, SharedMaterial},\n# scene::{\n# base::BaseBuilder,\n# graph::Graph,\n# mesh::{\n# surface::{SurfaceBuilder, SurfaceData, SurfaceSharedData},\n# MeshBuilder, RenderPath,\n# },\n# transform::TransformBuilder,\n# },\n# };\nuse std::sync::Arc;\nfn create_shot_trail( graph: &mut Graph, origin: Vector3, direction: Vector3, trail_length: f32,\n) { let transform = TransformBuilder::new() .with_local_position(origin) // Scale the trail in XZ plane to make it thin, and apply `trail_length` scale on Y axis // to stretch is out. .with_local_scale(Vector3::new(0.0025, 0.0025, trail_length)) // Rotate the trail along given `direction` .with_local_rotation(UnitQuaternion::face_towards(&direction, &Vector3::y())) .build(); // Create unit cylinder with caps that faces toward Z axis. let shape = SurfaceSharedData::new(SurfaceData::make_cylinder( 6, // Count of sides 1.0, // Radius 1.0, // Height false, // No caps are needed. // Rotate vertical cylinder around X axis to make it face towards Z axis &UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 90.0f32.to_radians()).to_homogeneous(), )); // Create an instance of standard material for the shot trail. let mut material = Material::standard(); material .set_property( &ImmutableString::new(\"diffuseColor\"), // Set yellow-ish color. PropertyValue::Color(Color::from_rgba(255, 255, 0, 120)), ) .unwrap(); MeshBuilder::new( BaseBuilder::new() // Do not cast shadows. .with_cast_shadows(false) .with_local_transform(transform) // Shot trail should live ~0.25 seconds, after that it will be automatically // destroyed. .with_lifetime(0.25), ) .with_surfaces(vec![SurfaceBuilder::new(shape) .with_material(SharedMaterial::new(material)) .build()]) // Make sure to set Forward render path, otherwise the object won't be // transparent. .with_render_path(RenderPath::Forward) .build(graph);\n} Okay... Let's disassemble this heap of code line by line. At first, we're borrowing the weapon by its handle and check if it can shoot (if the timer has reached zero), and \"shoot\" (reset the timer) if so: let weapon = &mut self.weapons[weapon]; if weapon.can_shoot() { weapon.shoot(); ... Next we're using ray casting to find the target we're shooting at: // ... let scene = &mut engine.scenes[self.scene]; let weapon_model = &scene.graph[weapon.model()]; // Make a ray that starts at the weapon's position in the world and look toward\n// \"look\" vector of the weapon.\nlet ray = Ray::new( scene.graph[weapon.shot_point()].global_position(), weapon_model.look_vector().scale(1000.0),\n); let mut intersections = Vec::new(); scene.graph.physics.cast_ray( RayCastOptions { ray_origin: Point3::from(ray.origin), max_len: ray.dir.norm(), groups: Default::default(), sort_results: true, // We need intersections to be sorted from closest to furthest. ray_direction: ray.dir, }, &mut intersections,\n); ... To determine the \"target\", we're have to make a ray first. It starts from the \"shot point\" we've attached to the weapon. Direction of the ray is the \"look\" vector of the weapon model scaled by some large value which defines a \"length\" of the ray. Finally, we're casting the ray. Next we have to check each intersection and find the target: // Ignore intersections with player's capsule.\nlet trail_length = if let Some(intersection) = intersections .iter() .find(|i| i.collider != self.player.collider)\n{ // // TODO: Add code to handle intersections with bots. // // For now just apply some force at the point of impact. let colliders_parent = scene.graph[intersection.collider].parent(); let picked_rigid_body = scene.graph[colliders_parent].as_rigid_body_mut(); picked_rigid_body.apply_force_at_point( ray.dir.normalize().scale(10.0), intersection.position.coords, ); picked_rigid_body.wake_up(); // Trail length will be the length of line between intersection point and ray origin. (intersection.position.coords - ray.origin).norm()\n} else { // Otherwise trail length will be just the ray length. ray.dir.norm()\n}; First intersection most likely will be player's capsule, because shot point may be inside player's capsule. We're filtering such intersection in the first three lines. To do that, we have to remember the handle of player's capsule in Player: collider: Handle and fill the field in Player::new like this: let collider;\n...\n// Add capsule collider for the rigid body.\n{ collider = ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::capsule_y(0.25, 0.2)) .build(&mut scene.graph); collider\n} ... Self { ... collider, // <- ...\n} Next goes TODO comment, remember this line, it will be replaced when we'll add bots in future tutorial. Until we have no bots, we should somehow emulate shot impact, to do that we'll just apply some force at the point of impact. Finally, we're calculating desired shot trail length - it is just distance between point of impact and ray's origin. In the else branch we're setting the length to be the length of the ray. Finally, we're creating a shot trail: create_shot_trail(&mut scene.graph, ray.origin, ray.dir, trail_length); Now let's dive into this function. It starts from the definition of local transform of the trail: let transform = TransformBuilder::new() .with_local_position(origin) // Scale the trail in XZ plane to make it thin, and apply `trail_length` scale on Y axis // to stretch is out. .with_local_scale(Vector3::new(0.0025, 0.0025, trail_length)) // Rotate the trail along given `direction` .with_local_rotation(UnitQuaternion::face_towards(&direction, &Vector3::y())) .build(); Its purpose is to shrink cylinder in XZ plane and stretch it out on Y axis to the length of the trail. Next we're making geometry for the cylinder: let shape = SurfaceSharedData::new(SurfaceData::make_cylinder( 6, // Count of sides 1.0, // Radius 1.0, // Height false, // No caps are needed. // Rotate vertical cylinder around X axis to make it face towards Z axis &UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 90.0f32.to_radians()).to_homogeneous(),\n)); Here we're creating unit vertical cylinder, rotate it to make it face towards Z axis. Finally, we're creating mesh node: // Create an instance of standard material for the shot trail.\nlet mut material = Material::standard();\nmaterial .set_property( &ImmutableString::new(\"diffuseColor\"), // Set yellow-ish color. PropertyValue::Color(Color::from_rgba(255, 255, 0, 120)), ) .unwrap(); MeshBuilder::new( BaseBuilder::new() .with_local_transform(transform) // Shot trail should live ~0.25 seconds, after that it will be automatically // destroyed. .with_lifetime(0.25),\n)\n.with_surfaces(vec![SurfaceBuilder::new(shape) .with_material(SharedMaterial::new(material)) .build()])\n// Do not cast shadows.\n.with_cast_shadows(false)\n// Make sure to set Forward render path, otherwise the object won't be\n// transparent.\n.with_render_path(RenderPath::Forward)\n.build(graph); The trail will live just 0.25 seconds, Fyrox has built-in functionality for short-living objects, you have to just set desired lifetime, and an object will be removed at the time. Also, we don't want the trail to cast shadows, and we want the trail to be transparent yellow, to do that we have to change render path of the mesh to Forward. In short, every mesh by default rendered using Deferred renderer which does not support transparent objects due to technical reasons, but Fyrox has Forward renderer for such objects. Check this article to learn more about Deferred and Forward rendering techniques. Ok, now we have to \"teach\" player how to shoot. At first, let's add new field to InputController: shoot: bool. We'll be changing this flag by left mouse click, to do that let's add these lines at the end of Event::WindowEvent match arm before _ => {} in Player::process_input_event: &WindowEvent::MouseInput { button, state, .. } => { if button == MouseButton::Left { self.controller.shoot = state == ElementState::Pressed; }\n} Now we need a way to send messages to the game from the player. We need a new field in Player struct sender: Sender. Also, we must change signature of Player::new() to this: async fn new( scene: &mut Scene, resource_manager: ResourceManager, sender: Sender,\n) -> Self Just pass the sender in the Self { .. } like this: Self { ... sender, // <- Pass sender ...\n} Player must know about its weapons, let's fix this by adding new field to the Player: weapon: Handle, we'll fill this field in the Game::new(), not in Player::new() because at the moment of creation of the player there are no weapons: ... // Put the weapon into it - this operation moves the weapon in the pool and returns handle.\nlet weapon = weapons.spawn(weapon); // \"Give\" the weapon to the player.\nplayer.weapon = weapon; ... Finally, at the end of Player::update we will handle the state of the input controller: if self.controller.shoot { self.sender .send(Message::ShootWeapon { weapon: self.weapon, }) .unwrap();\n} Ok, at this momemt you should be able to compile the game and as you run it, you should see something like this when you'll press left mouse button: Weapon Barrels should react to shots as in the video at the beginning of the tutorial.","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Game architecture","id":"491","title":"Game architecture"},"492":{"body":"Ok, the weapon shoots but looks kinda unnatural, to fix that we need to add a recoil. This is very simple to do, let's start by adding these fields in the Weapon struct: recoil_offset: Vector3,\nrecoil_target_offset: Vector3, Initialize these fields with default values (zero vector): recoil_offset: Default::default(),\nrecoil_target_offset: Default::default(), First vector is the actual recoil offset that will be applied to the weapon model, second - target recoil value which first vector will follow. Why do we need two vectors instead just one? The answer is very simple, we need to smoothly change offset over time, and to do that offset will just \"follow\" target offset which will give us desired smooth movement. Let's add the code responsible for the recoil to the Weapon::update: // Notice new `graph` parameter\npub fn update(&mut self, dt: f32, graph: &mut Graph) { self.shot_timer = (self.shot_timer - dt).max(0.0); // v New code v // `follow` method defined in Vector3Ext trait and it just increases or // decreases vector's value in order to \"follow\" the target value with // given speed. self.recoil_offset.follow(&self.recoil_target_offset, 0.5); // Apply offset to weapon's model. graph[self.model] .local_transform_mut() .set_position(self.recoil_offset); // Check if we've reached target recoil offset. if self .recoil_offset .metric_distance(&self.recoil_target_offset) < 0.001 { // And if so, reset offset to zero to return weapon at // its default position. self.recoil_target_offset = Default::default(); }\n} At first, in the first line we force the recoil_offset to follow recoil_target_offset with given speed. Next we're applying offset to weapon's model. Finally, we're checking if we've reached target offset and if so, just reset target offset to return a weapon to its default position. Also we have to slightly modify Weapon::shoot to modify target offset: pub fn shoot(&mut self) { self.shot_timer = 0.1; // Set new offset after each shot. self.recoil_target_offset = Vector3::new(0.0, 0.0, -0.025); } The last thing we need to do is to slightly modify code at the Game::update to pass new parameter to Weapon::update: pub fn update(&mut self, engine: &mut GameEngine, dt: f32) { let scene = &mut engine.scenes[self.scene]; self.player.update(scene); for weapon in self.weapons.iter_mut() { weapon.update(dt, &mut scene.graph); } ... Ok, run the game, and the weapon should feel more natural now.","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Recoil","id":"492","title":"Recoil"},"493":{"body":"Shooting have become much better after we've added a recoil, but there is still no impact effects like sparks. Let's fix that! This is the first time when we'll use particle systems. Let's add this function somewhere in main.rs # extern crate fyrox;\n# use fyrox::{\n# core::{\n# algebra::{UnitQuaternion, Vector3},\n# color::Color,\n# color_gradient::{ColorGradient, GradientPoint},\n# pool::Handle,\n# },\n# asset::manager::ResourceManager, resource::texture::Texture,\n# scene::{\n# base::BaseBuilder,\n# graph::Graph,\n# node::Node,\n# particle_system::{\n# emitter::{base::BaseEmitterBuilder, sphere::SphereEmitterBuilder},\n# ParticleSystemBuilder,\n# },\n# transform::TransformBuilder,\n# },\n# };\n# use std::path::Path;\nfn create_bullet_impact( graph: &mut Graph, resource_manager: ResourceManager, pos: Vector3, orientation: UnitQuaternion,\n) -> Handle { // Create sphere emitter first. let emitter = SphereEmitterBuilder::new( BaseEmitterBuilder::new() .with_max_particles(200) .with_spawn_rate(3000) .with_size_modifier_range(-0.01..-0.0125) .with_size_range(0.0075..0.015) .with_lifetime_range(0.05..0.2) .with_x_velocity_range(-0.0075..0.0075) .with_y_velocity_range(-0.0075..0.0075) .with_z_velocity_range(0.025..0.045) .resurrect_particles(false), ) .with_radius(0.01) .build(); // Color gradient will be used to modify color of each particle over its lifetime. let color_gradient = { let mut gradient = ColorGradient::new(); gradient.add_point(GradientPoint::new(0.00, Color::from_rgba(255, 255, 0, 0))); gradient.add_point(GradientPoint::new(0.05, Color::from_rgba(255, 160, 0, 255))); gradient.add_point(GradientPoint::new(0.95, Color::from_rgba(255, 120, 0, 255))); gradient.add_point(GradientPoint::new(1.00, Color::from_rgba(255, 60, 0, 0))); gradient }; // Create new transform to orient and position particle system. let transform = TransformBuilder::new() .with_local_position(pos) .with_local_rotation(orientation) .build(); // Finally create particle system with limited lifetime. ParticleSystemBuilder::new( BaseBuilder::new() .with_lifetime(1.0) .with_local_transform(transform), ) .with_acceleration(Vector3::new(0.0, 0.0, 0.0)) .with_color_over_lifetime_gradient(color_gradient) .with_emitters(vec![emitter]) // We'll use simple spark texture for each particle. .with_texture(resource_manager.request::(Path::new(\"data/textures/spark.png\"))) .build(graph)\n} Ok, again a heap of code... As usual, everything here is pretty straightforward. At first, we're creating spherical emitter - it is a spherical volume that responsible for spawning particles. It has a radius, and a bunch of basic properties such as spawn rate, maximum amount of particles, initial velocity range, etc. Next we're creating color gradient to make particles fade over their lifetime. Next, we're creating simple transform that will orient and position particle system in world space. Finally, we're creating particle system itself, using pre-made parts. For each particle we'll use simple spark texture , it should be placed in data/textures. Now we need to find correct place to create this particle system. It should be placed right after we're applying force to target we've hit in Game::shoot_weapon: // Add bullet impact effect.\nlet effect_orientation = vector_to_quat(intersection.normal); create_bullet_impact( &mut scene.graph, engine.resource_manager.clone(), intersection.position.coords, effect_orientation,\n); Ok, now run the game, and you should see something like this: Weapon One more thing that could be added is bullet shells that should come from a weapon when we're shooting, but I think the tutorial is already pretty big already, and I'll leave this as a \"homework\", but I'll just give some hints of how to do this. Make or download a shell model in FBX format, then write a function that will load it via resource manager, instantiate it and create a physical body for it (with cylinder collider for example), link a model with a body and call this function when shooting a weapon.","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Impact effects","id":"493","title":"Impact effects"},"494":{"body":"In this tutorial we added weapon to the game, added recoil to make shooting more natural, and added impact effects. I hope you liked this tutorial, and if so, please consider supporting the project on Patreon or LiberaPay . Source code is available on GitHub . In the next tutorial we'll add sounds to the game and something special (let it be a surprise). Discussion: Reddit , Discord .","breadcrumbs":"Tutorials » FPS Tutorial » Weapons » Conclusion","id":"494","title":"Conclusion"},"495":{"body":"WARNING: This tutorial is using obsolete engine features, which are subject to be removed in future versions! Source code : GitHub","breadcrumbs":"Tutorials » FPS Tutorial » Bots and AI » FPS Tutorial Part 1 - Bots and AI","id":"495","title":"FPS Tutorial Part 1 - Bots and AI"},"496":{"body":"Introduction Bots Animations Simple AI Conclusion","breadcrumbs":"Tutorials » FPS Tutorial » Bots and AI » Table of contents","id":"496","title":"Table of contents"},"497":{"body":"In the previous tutorial we've added weapons, but we still have no bots to shoot at. Let's fix that! In this tutorial we'll add bots and a very simple AI. Bots will be a bit dumb, but it will be fixed in future tutorials. This is the result we're aiming in the tutorial:","breadcrumbs":"Tutorials » FPS Tutorial » Bots and AI » Introduction","id":"497","title":"Introduction"},"498":{"body":"Previous tutorials were children's play in comparison to this, prepare for some advanced stuff. Let's begin by adding a separate module for bots - add bot.rs and fill it with following code: use fyrox::engine::resource_manager::MaterialSearchOptions;\nuse fyrox::{ animation::{ machine::{Machine, Parameter, PoseNode, State, Transition}, Animation, }, core::{ algebra::{UnitQuaternion, Vector3}, pool::Handle, }, asset::manager::ResourceManager, physics3d::{ rapier::dynamics::RigidBodyBuilder, rapier::geometry::ColliderBuilder, ColliderHandle, RigidBodyHandle, }, resource::model::Model, scene::{base::BaseBuilder, node::Node, Scene},\n}; pub struct Bot { rigid_body: Handle, collider: Handle,\n} impl Bot { pub async fn new( scene: &mut Scene, position: Vector3, resource_manager: ResourceManager, ) -> Self { // Load bot 3D model as usual. let model = resource_manager .request::(\"data/models/zombie.fbx\") .await .unwrap() .instantiate(scene); scene.graph[model] .local_transform_mut() // Move the model a bit down to make sure bot's feet will be on ground. .set_position(Vector3::new(0.0, -0.45, 0.0)) // Scale the model because it is too big. .set_scale(Vector3::new(0.0047, 0.0047, 0.0047)); let collider; let rigid_body = RigidBodyBuilder::new( BaseBuilder::new() .with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(position.x, position.y, position.z)) .build(), ) .with_children(&[ // Attach model to the rigid body. model, // Add capsule collider for the rigid body. { collider = ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::capsule_y(0.25, 0.2)) .build(&mut scene.graph); collider }, ]), ) // We don't want a bot to tilt. .with_locked_rotations(true) .with_can_sleep(false) .build(&mut scene.graph); Self { rigid_body, collider, } }\n} Ok, before we dive into the code, we need to add some more code in main.rs, let's begin by adding a container for bots in Game: bots: Pool,. Next we need to add some more code to Game::new where we'll add a bot instance in the game. Also, you can add more than one bot at different positions if you want. Do not forget to download bot model with textures from here and unpack fbx file in data/models and other files (textures), in data/textures. // Add some bots.\nlet mut bots = Pool::new(); bots.spawn( Bot::new( &mut scene, Vector3::new(-1.0, 1.0, -1.0), engine.resource_manager.clone(), ) .await,\n); ... Self { ... bots\n} As usual, let's disassemble the code line-by-line. Creation of bot begins from loading its 3D model in the scene: let model = resource_manager .request::(\"data/models/zombie.fbx\") .await .unwrap() .instantiate(scene); Nothing really new here, loading and instantiation of a 3D model of any complexity is the same as before. Next we have to slightly modify the model, shift it a bit down and shrink: scene.graph[model] .local_transform_mut() // Move the model a bit down to make sure bot's feet will be on ground. .set_position(Vector3::new(0.0, -0.45, 0.0)) // Scale the model because it is too big. .set_scale(Vector3::new(0.0047, 0.0047, 0.0047)); Here we're borrow model in the scene graph, and modify its local transform. Next we're creating rigid body with a capsule collider, and attaching the model to the rigid body: let collider;\nlet rigid_body = RigidBodyBuilder::new( BaseBuilder::new() .with_local_transform( TransformBuilder::new() .with_local_position(Vector3::new(position.x, position.y, position.z)) .build(), ) .with_children(&[ // Attach model to the rigid body. model, // Add capsule collider for the rigid body. { collider = ColliderBuilder::new(BaseBuilder::new()) .with_shape(ColliderShape::capsule_y(0.25, 0.2)) .build(&mut scene.graph); collider }, ]),\n)\n// We don't want a bot to tilt.\n.with_locked_rotations(true)\n.with_can_sleep(false)\n.build(&mut scene.graph); Finally, we're returning bot's instance: Self { pivot, rigid_body, collider,\n} Ok, now for the bots instantiation, for simplicity we create a single bot in Game::new: let mut bots = Pool::new(); bots.spawn( Bot::new( &mut scene, Vector3::new(-1.0, 1.0, 1.5), engine.resource_manager.clone(), ) .await,\n); As you can see, we'll store bots in a pool as many other game entities, this will allow us to borrow bots later on when we'll be adding AI. Ok, now run the game, and you should see something like this: Weapon Wow... a T-posing bot... Is this some kind of Cyberpunk 2077 reference? This is boring, let's add some animations to make it more \"alive\".","breadcrumbs":"Tutorials » FPS Tutorial » Bots and AI » Bots","id":"498","title":"Bots"},"499":{"body":"Let's start from definition of animation. Animation is a set of tracks, where each track responsible for animation of a single node and contains key frames placed on a timeline with some transition rules between key frames. Animation usually changes just position/rotation/scale of a node. When animation is playing it calculates intermediate values for each parameter using interpolation techniques (linear and spherical interpolation). Animations in general is very simple and powerful technique, but at many times it still does not enough. If you will just switch multiple animations from one to one, it will look very bad and unnatural. This is where animation blending machines come into play. Weapon Fyrox provides very powerful mechanism for animations - animation blending machines. If you're already familiar with Mecanim in Unity game engine or similar thing in Unreal Engine, then you'll probably already understood what will be in this paragraph. Animation blending machine (ABM) is a state machine that allows you to blend multiple animations in one and apply it to a set of bones (or nodes in general). ABM is a graph where each node is a state, each state has a source of animation pose which in its turn may be another subgraph of pose emitting nodes. States connected between each other using transition edges which have some parameters like transition time, a rule for transition, etc. At each moment of time either state or transition can be active. In other words this means that ABM is either transitioning from a state to state or hold a state. This leads to another fact - ABM cannot jump from a state to state if they're not connected with transition edge. ABMs in general can be represented like this: Weapon States are marked yellow, animation nodes - blue, animations - green. As you can see there can be multiple transitions between states, each transition has corresponding Rule parameter which is a simple boolean value which tells a machine when it is possible to start transition. Each state is supplied with animation node (PlayAnimation, BlendAnimations, BlendAnimationsByIndex, etc.) which does exactly what the name states. PlayAnimation just plays given animation, BlendAnimations - blends multiple animations with different weights into one, BlendAnimationsByIndex - blends multiple animations as the BlendAnimations, but each animation has transition time, and the node uses an index parameter to switch between attached animations. Please keep in mind that BlendAnimations node can have various sources of pose, as you can see in the picture, it uses two PlayAnimation nodes as source of poses, but nothing keeps you from adding new pose source which will be yet another BlendAnimations. So everything depends on your needs and ABM just gives you enough flexibility. Ok, back to the game. Let's create a simple ABM for bots. For simplicity, it will contain only three states - Idle, Walk, Attack. Put this code somewhere at the end of bot.rs: // Simple helper method to create a state supplied with PlayAnimation node.\nfn create_play_animation_state( animation_resource: Model, name: &str, layer: &mut MachineLayer, scene: &mut Scene, model: Handle,\n) -> (Handle, Handle) { // Animations retargetting just makes an instance of animation and binds it to // given model using names of bones. let animation = *animation_resource .retarget_animations(model, &mut scene.graph) .get(0) .unwrap(); // Create new PlayAnimation node and add it to machine. let node = layer.add_node(PoseNode::make_play_animation(animation)); // Make a state using the node we've made. let state = layer.add_state(State::new(name, node)); (animation, state)\n} pub struct BotAnimationMachineInput { // Whether a bot is walking or not. pub walk: bool, // Whether a bot is attacking or not. pub attack: bool,\n} pub struct BotAnimationMachine { animation_player: Handle, machine: Machine,\n} impl BotAnimationMachine { // Names of parameters that will be used for transition rules in machine. const IDLE_TO_WALK: &'static str = \"IdleToWalk\"; const WALK_TO_IDLE: &'static str = \"WalkToIdle\"; const WALK_TO_ATTACK: &'static str = \"WalkToAttack\"; const IDLE_TO_ATTACK: &'static str = \"IdleToAttack\"; const ATTACK_TO_IDLE: &'static str = \"AttackToIdle\"; const ATTACK_TO_WALK: &'static str = \"AttackToWalk\"; pub async fn new( scene: &mut Scene, model: Handle, resource_manager: ResourceManager, ) -> Self { let animation_player = AnimationPlayerBuilder::new(BaseBuilder::new()).build(&mut scene.graph); scene.graph.link_nodes(animation_player, model); let mut machine = Machine::new(); let root = machine.layers_mut().first_mut().unwrap(); // Load animations in parallel. let (walk_animation_resource, idle_animation_resource, attack_animation_resource) = fyrox::core::futures::join!( resource_manager.request::(\"data/animations/zombie_walk.fbx\"), resource_manager.request::(\"data/animations/zombie_idle.fbx\"), resource_manager.request::(\"data/animations/zombie_attack.fbx\"), ); // Now create three states with different animations. let (_, idle_state) = create_play_animation_state( idle_animation_resource.unwrap(), \"Idle\", root, scene, model, ); let (walk_animation, walk_state) = create_play_animation_state( walk_animation_resource.unwrap(), \"Walk\", root, scene, model, ); let (attack_animation, attack_state) = create_play_animation_state( attack_animation_resource.unwrap(), \"Attack\", root, scene, model, ); // Next, define transitions between states. root.add_transition(Transition::new( // A name for debugging. \"Idle->Walk\", // Source state. idle_state, // Target state. walk_state, // Transition time in seconds. 0.4, // A name of transition rule parameter. Self::IDLE_TO_WALK, )); root.add_transition(Transition::new( \"Walk->Idle\", walk_state, idle_state, 0.4, Self::WALK_TO_IDLE, )); root.add_transition(Transition::new( \"Walk->Attack\", walk_state, attack_state, 0.4, Self::WALK_TO_ATTACK, )); root.add_transition(Transition::new( \"Idle->Attack\", idle_state, attack_state, 0.4, Self::IDLE_TO_ATTACK, )); root.add_transition(Transition::new( \"Attack->Idle\", attack_state, idle_state, 0.4, Self::ATTACK_TO_IDLE, )); root.add_transition(Transition::new( \"Attack->Walk\", attack_state, walk_state, 0.4, Self::ATTACK_TO_WALK, )); // Define entry state. root.set_entry_state(idle_state); Self { animation_player, machine, } } pub fn update(&mut self, scene: &mut Scene, dt: f32, input: BotAnimationMachineInput) { let animation_player = scene.graph[self.animation_player] .query_component_ref::