Fyrox Game Engine 0.33
I'm happy to announce that Fyrox 0.33 has been released! Fyrox is a modern game engine written in Rust, it helps you to create 2D and 3D games with low effort using native editor; it is like Unity, but in Rust. This release includes new UI editor, rendering improvements, various editor improvements, script API improvements, async tasks, book improvements and more.
# Introduction
Before we begin, check out demo projects (opens new window) of the engine - it is a set of applications and games made with the engine. You can also check games (opens new window) section of the website to see which games are made using the engine.
# How to Update
At first, update the fyrox-template
by using cargo install fyrox-template --force
and then execute the following
command in your project root directory: fyrox-template upgrade --version=latest
. There will be some compilation
errors, but Rust compiler should help you to fix them. There are a lot of small breaking changes that could be
fixed pretty easily. Data compatibility was preserved in 99% of cases, so your previous assets should load fine.
# UI Editor
This is the most important part of the new release. The UI editor was requested by the community quite a long time ago and now it's basic version is finally added. It still incomplete in some places, but despite that, very useful.
The editor can edit special kind of scene called UI-scene. UI scenes have pretty much nothing in common with standard game scenes and completely isolated from them. There's a number of reasons why UI widgets are not classic scene nodes and you can read about this here (opens new window).
To load a UI scene like in the video above and do some actions when buttons clicked, you can do the following:
pub struct Game {
new_game: Handle<UiNode>,
exit: Handle<UiNode>,
}
impl Game {
pub fn new(_scene_path: Option<&str>, ctx: PluginContext) -> Self {
// Spawn async task that will load the UI.
ctx.task_pool.spawn_plugin_task(
UserInterface::load_from_file("menu.ui", ctx.resource_manager.clone()),
|result, game: &mut Game, ctx| {
let ui = ctx.user_interface;
*ui = result.unwrap();
game.new_game = ui.find_by_name_down_from_root("NewGame");
game.exit = ui.find_by_name_down_from_root("Exit");
},
);
Self {
new_game: Handle::NONE,
exit: Handle::NONE,
}
}
}
impl Plugin for Game {
fn on_ui_message(&mut self, ctx: &mut PluginContext, message: &UiMessage) {
if let Some(ButtonMessage::Click) = message.data() {
if message.destination() == self.new_game {
ctx.user_interface.send_message(WidgetMessage::visibility(
ctx.user_interface.root(),
MessageDirection::ToWidget,
false,
));
} else if message.destination() == self.exit {
if let Some(window_target) = ctx.window_target {
window_target.exit();
}
}
}
}
}
This code spawns an async task in Game::new
that will load the UI scene in a separate thread and when it
is done, it will replace current UI instance in the context with the loaded one. To do something useful when
our buttons clicked we're using on_ui_message
method from plugin - it is called when there's any message
from the UI.
# Navigational Mesh
Navigation on navmeshes was improved using string pulling algorithm. This algorithm straightens path on the navmesh and makes it less jagged. It also possible now to specify radius for navmesh agent and it will use this radius to turn around the corners of the navmesh like so:
It is now possible to calculate paths on a navmesh using async tasks. It could be done like so in one of your scripts:
#[derive(Visit, Default, Reflect, Debug, Clone)]
struct MyScript {
navmesh: Handle<Node>,
path: Option<Vec<Vector3<f32>>>,
}
impl ScriptTrait for MyScript {
fn on_start(&mut self, ctx: &mut ScriptContext) {
// Borrow a navigational mesh scene node first.
if let Some(navmesh_node) = ctx
.scene
.graph
.try_get_of_type::<NavigationalMesh>(self.navmesh)
{
// Take a shared reference to the internal navigational mesh.
let shared_navmesh = navmesh_node.navmesh();
// Spawn a task, that will calculate a long path.
ctx.task_pool.spawn_script_task(
ctx.scene_handle,
ctx.handle,
async move {
let navmesh = shared_navmesh.read();
if let Some((_, begin_index)) =
navmesh.query_closest(Vector3::new(1.0, 0.0, 3.0))
{
if let Some((_, end_index)) =
navmesh.query_closest(Vector3::new(500.0, 0.0, 800.0))
{
let mut path = Vec::new();
if navmesh
.build_path(begin_index, end_index, &mut path)
.is_ok()
{
return Some(path);
}
}
}
None
},
|path, this: &mut MyScript, _ctx| {
this.path = path;
Log::info("Path is calculated!");
},
);
}
}
fn on_update(&mut self, ctx: &mut ScriptContext) {
// Draw the computed path.
if let Some(path) = self.path.as_ref() {
for segment in path.windows(2) {
ctx.scene.drawing_context.add_line(Line {
begin: segment[0],
end: segment[1],
color: Default::default(),
})
}
}
}
}
This could be useful for large game levels with complex navigational meshes where you can spawn multiple tasks per frame (or per bunch of frames) to do calculations asynchronously.
# Scripts
Scripting API was significantly improved and now you need to write much less code and you have much wider access to the engine entities from scripts. This is how a smallest possible script was look like in Fyrox 0.32:
#[derive(Visit, Reflect, Default, Debug, Clone)]
pub struct MyScript { }
impl_component_provider!(MyScript);
impl TypeUuidProvider for MyScript {
fn type_uuid() -> Uuid {
uuid!("948ba50d-0fd1-4947-b94c-0d6080bb6f74")
}
}
impl ScriptTrait for MyScript {
fn id(&self) -> Uuid {
Self::type_uuid()
}
}
And this is how it looks like in 0.33:
#[derive(Visit, Reflect, Default, Debug, Clone, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "4a6399ac-2f3e-4488-8ea9-743abb86ab36")]
#[visit(optional)]
pub struct MyScript { }
impl ScriptTrait for MyScript { }
The new version significantly smaller: 5 lines of code vs 13 in the old version. This greatly reduces any
distractions with insignificant portions of code and helps you to focus on your own code. What's previously was
implemented using macros, now is implemented using #[derive]
attribute. As you can see, TypeUuidProvider
and
ComponentProvider
now implemented this way. Also, please note a new attribute at the top of the script declaration:
#[visit(optional)]
it marks all fields of the struct optional, and the deserializer won't complain if some field
is missing on load. This is very useful, to be able to add new fields freely to your script.
Scripts now have access to current graphics context (which gives you access to the main window and renderer), task pool (allows you to spawn async tasks from scripts), user interface (now you can directly access widgets from scripts), current scene handle.
In Fyrox 0.32, ScriptContext
had plugins
field, which gives you access to plugins from scripts. It was
quite tedious to access plugins from scripts: ctx.plugins.iter().find_map(|p| p.cast::<MyPlugin>()).unwrap()
. Now
it is much easier: ctx.plugins.get::<MyPlugin>()
.
# Async Tasks
Fyrox now supports task-based programming for both scripts and plugins. Task is a closure that does something in a separate thread and then the result of it is returned back to the main thread. This is very useful technique, that allows you to perform heavy calculations using all available CPU power, not just one CPU core with a single main thread. Tasks could be used for pretty much anything, that can be done as a separate piece of work. Graphically it could represented like this:
For example, this is how you can spawn a task in your plugin that loads a file in a separate thread and when it's done, it stores the loaded data in the plugin itself on the main thread.
struct MyGameConstructor;
impl PluginConstructor for MyGameConstructor {
fn create_instance(
&self,
_scene_path: Option<&str>,
context: PluginContext,
) -> Box<dyn Plugin> {
Box::new(MyGame::new(context))
}
}
struct MyGame {
data: Option<Vec<u8>>,
}
impl MyGame {
pub fn new(context: PluginContext) -> Self {
context.task_pool.spawn_plugin_task(
// Emulate heavy task by reading a potentially large file. The game will be fully
// responsive while it runs.
async move {
let mut file = File::open("some/file.txt").unwrap();
let mut data = Vec::new();
file.read_to_end(&mut data).unwrap();
data
},
// This closure is called when the future above has finished, but not immediately - on
// the next update iteration.
|data, game: &mut MyGame, _context| {
// Store the data in the game instance.
game.data = Some(data);
},
);
// Immediately return the new game instance with empty data.
Self { data: None }
}
}
impl Plugin for MyGame {
fn update(&mut self, _context: &mut PluginContext) {
// Do something with the data.
if let Some(data) = self.data.take() {
println!("The data is: {:?}", data);
}
}
}
Tasks should be used to perform sensible amount of work, simply because each task has its own overhead and it could be larger than the actual task if the task is tiny.
# Dynamic Font Atlas
For a long time fonts in Fyrox had quite rudimentary support and it was quite tricky to change font size of a text at runtime. In this release this was finally fixed by adding support for dynamic font atlases. Dynamic atlas is a very simple concept: texture atlas of the font is split into multiple pages, where each page corresponds to its own character size and each pages could also be split into multiple chunks. Multiple chunks is needed, because there's is a hardware limit for the maximum texture size.
You can now easily change font size in the UI of Text and TextBox widgets, this could be done like so:
fn set_font_size(text: Handle<UiNode>, ui: &UserInterface, new_font_size: f32) {
ui.send_message(TextMessage::font_size(
text,
MessageDirection::ToWidget,
new_font_size,
))
}
# Batching
Some parts of renderer now uses batching for rendering that improves rendering performance quite a lot. It is now used in 2D rendering, particle systems, 3D sprites.
# Animation Refactoring
Animation-related code was isolated in a separate crate called fyrox-animation
. Despite the name of it you
can still use it without Fyrox at all. The crate provides quite powerful ready-to-use animation system, that
includes:
- Key frame animation.
- Animation blending with state machine.
- Timeline events.
- Root motion.
- Optional reflection-based property modification of an animatable object.
- Sprite sheet animation.
It was moved into the separate crate, because there's a plan to add animation support for the user interface scenes, that were introduced in this release.
# IO Abstraction
It is now possible to use new IO abstraction to add support for virtual file systems. It could be used to provide access to assets located in ZIP-archives for example.
# Material Rework
Material is was turned into resource, which means that you can save materials on disk, share them across multiple surfaces. Sprites (3D), rectangles (2D), particle systems are now using materials instead of dedicated renderer with limited functionality. This greatly improves rendering flexibility.
# Editor Improvements
The editor has a ton of various changes, improves and fixes. Here's the most significant changes.
# Edge Highlighting
The editor now highlights currently selected objects, which helps to find the selected object and see its actual shape. This is especially useful if you either have a dark area or a bunch of objects.
# Gizmo Improvements
Movement, rotation, scaling gizmos were improved in this release. The gizmos are now react to the cursor which makes it much more comfortable to work with - you definitely know that you're clicking on a gizmo, not something behind it. In general, all gizmos now are closer to the industry standard.
# Collider Fitting
It is now possible to fit colliders to actual object bounds in just one click. The editor now automatically opens
a collider control panel with a single Fit
button.
# Ability to Create Assets
Asset browser can now create new assets and save them to disk. This could be done using small +
button near the
search bar:
You can create not just built-in asset types from this window, but also your own resource types.
# Camera Visualization
Camera frustum is now visualized in the editor which makes it easier to position and find it in the scene.
# Ability to Move Assets
You can now move assets and folders with assets in the Asset Browser. This operation automatically fixes all the links in the assets that has references to the moved ones.
# Placeholder Texture
The editor now assigns a placeholder texture for objects without a texture to make them look nicer. This texture is also available in the asset browser and can be assigned to pretty much any material.
# Other
It is now possible to start the editor with multiple scenes at once. This saves a lot of time when you need to work with a bunch of scenes and restart the editor often.
You can now connect a state with every other in the ABSM editor like so:
# Network
This release also contains some experimental support for multiplayer games. fyrox-core
now have a net
module,
which contains a thin abstraction layer, that allows you to establish connection between devices over the network
and send various messages between them. You can see it in action in
this project (opens new window).
# Book Improvements
The book in this release has tons of improvements as well, there are the following changes:
- Tutorials rework (see below)
- Custom widgets
- Navmesh improvements
- Shaders usage example
- Built-in properties of shaders
- Drawing parameters
- Animation usage
- Animation signals
- ABSM chapter improvements (including events)
- Mesh data buffers
- Ui Editor
- Font chapter rework
- Changed default theme to dark
- Removed obsolete information
- Added chapters for
Screen
,ScrollViewer
,ScrollPanel
,VectorImage
widgets. - Chapter about root motion
- Async tasks
- Light maps
- Input handling (mouse, keyboard)
- Sprite animation
- Accessing other script's data
- Window management
- NixOS build instructions (kudos to @stillinbeta (opens new window))
The book is also slowly getting refactored - source code of snippets is now slowly moving from the book source to a separate cargo project, and the code snippets are referenced directly from this project.
# Tutorials
FPS tutorial was fully rewritten using the latest engine version. As before, it is split in three parts:
There's also a set of community tutorials, which you can find in the respective chapter (opens new window) of the book.
# Full List of Changes
The list is split into four sections for ease of reading and finding particular information.
# Added
- UI editor.
- Tasks system for scripts and plugins.
- Implemented dynamic font atlas.
- Batching for 2D graphics.
- Ability to move resources and folders in the Asset Browser.
- Edge highlighting for selection in the editor.
- Added an ability to create resources from asset browser.
- Added height parameter for
Text
andTextBox
widgets. - Ability to specify IO abstraction when loading user interface.
Utf32StringPropertyEditorDefinition
to editVec<char>
UTF32 strings.RefCellPropertyEditorDefinition
forRefCell<T>
types.- Enable reflection + serialization for formatted text and its instances.
- Built in font resource.
- Font resource property editor with font preview.
- Ability to assign fonts from asset browser.
- Reflection for resources.
- UI graph manipulation methods.
Screen
widget automatically fits to the current screen size.- Show type name in world viewer for widgets.
- Ability to specify ignored types for
Reflect::apply_recursively
. - Preview for curve and hrir resources.
- Ability to open a window at desired position.
- Ability to edit textures as UntypedResource in widgets.
- Implemented
Serialize + Deserialize + Display
traits forErasedHandle
. - Smart positioning for contextual floating panels in the editor.
WidgetMessage::Align
+WindowMessage::OpenAndAlign
messages.- Ability to invalidate layout for all widgets at once.
- Ability to mark all fields of a struct/enum optional when deserializing:
#[visit(optional)]
can now be added to a struct/enum directly, thus overriding all other such attributes on fields. - Added access to user interface, task pool, graphics context, current scene handle for scripts.
PluginsRefMut::get/get_mut/of_type_ref/of_type_mut
methods.- Added a bunch of
#[inline]
attributes forPool
for slight performance improvements. - Added
AtomicHandle
that can be modified using interrior mutability. - Ability to pass pixel kind to the
Renderer::render_ui_to_texture
method. - Show material resource state in the material field editor.
- Ability to scroll to the end of the content for
ScrollViewer
andScrollPanel
widgets. - Ability to save and load light maps into/from a file.
- Ability to repeat clicks of a button while it is pressed.
- Ability to open materials for editing from the asset browser.
- Ability to filter a list of types when using reflection for fields iteration.
- Implemented
PartialOrd + Ord
traits forHandle
type. - Added icon in the asset browser for material resources.
- Ability to pass generics to
uuid_provider
macro. - Ability to share navigational mesh across multiple threads.
- Implemented
Reflect
trait forRwLock
. UserInterface::find_by_name_down_from_root
method to search widgets by name.- Implemented
Send
trait for UI entities. - Added user interface resource.
- Collider control panel with ability to fit colliders to parent bounds.
- Property editor for vector image's primitives.
- Show warning in the log when there's script message with no subscribers.
- Implemented
TypeUuidProvider
trait for standard types. - Ability to specify clear color in
Renderer::render_ui_to_texture
. - Added icon in the asset browser for shader resources.
- Ability to copy widgets from UI to UI.
- Ability to create ragdolls from
Create
menu. - Added an ability to rebind tracks in the animation editor.
- Added a set of standard materials, exposed them in the editor.
- Added placeholder texture.
- Ability to fetch resource import options from their respective loaders.
- Implemented
Visit
andReflect
traits forchar
. - Ability to specify icons for assets in respective preview generators.
TypedResourceData
trait to be able to set correct default state of a resource.- Implemented
ResourceData::save
for built-in engine resource types. - Documentation for LODs.
- Color constants for the colors with names.
- Ability to save resources.
ResourceLoader::supports_extension
method.- Implemented
Error
trait forVisitError
. Material::set_texture
shortcut.- Implemented
From<&str>
trait forImmutableString
. - Added normalization option for vertex attribute descriptor.
- Added experimental network abstraction layer.
- Added frustum culling for rectangle node.
- Added camera view pyramid visualization (kudos to @dasimonde (opens new window)).
- Added IO abstraction for resource system (kudos to @jacobtread (opens new window)).
- Added
Reflect
,Debug
,Visit
trait implementations for UI widgets. - Added
Visit
trait implementation forusize/isize
. - Added
ResourceIo::move_file
method. - Added
ResourceManager::move_resource
method with filtering. - Added
Drop
message forFileBrowser
with dropped path. - Added
ResourceIo::canonicalize_path
. - Added
Pool::generate_free_handles
methods. - Added
InteractionMode::make_button
method that creates appropriate button for the mode.
# Changed
- Major editor refactoring to support new UI scenes.
- Moved low level animation modules into fyrox-animation crate.
- Type aliases for scene specific animation entities + preludes.
- Texture as generic parameter for sprite sheet animation.
- Turn font into resource + added
TextMessage::Height
. - Make standard built-in shaders non-procedural by default.
- Refactored internal structure of resources.
- All resource related data is now stored in
ResourceHeader
instead of being scattered all around inResourceState
variants and even in resource data itself. - Backward compatibility is preserved.
ResourceKind
instead of path+flag, refactored resource loader trait.
- All resource related data is now stored in
- Refactored interaction modes in the editor.
- Switched to UntypedResource from SharedTexture in ui
- Simplified usage of
ResourceManager::request/try_request
. No need to writerm.request<Type, _>
, justrm.request<Type>
. - Registered Light Panel in floating panels, so it can be docked.
- Made searching in the asset browser smarter.
- GPU resources cache refactoring.
- Speed up access to textures.
- Automatic implementation of
ScriptTrait::id()
method. This implementation now should be removed from your scripts. - Scroll to the end of build log in the editor.
- Prevented build window from closing when a build has failed.
- Tweaked node handle property editor to also work with ui widgets.
- Filter out texture bytes in the property selector to improve performance.
- Enabled clicks repetition mode for scroll bar increase/decrease buttons.
- Keep the editor active if there's any captured ui element.
- Increased scroll bar step for scroll viewer.
- Added filter argument for
aabb_of_descendants
. - Use abstract EntityId instead of ErasedHandle in animation entities.
- Optimized internals of navigation mesh.
- Prevented serialization of the data of external resources.
- Pass screen size to
Control::update
. - Ability to clone user interface entirely.
- Refactored scene saving dialogs in the editor to make them more stable.
- Made
Limb::iterate_recursive
method public. - Switch character rigid body to kinematic when a ragdoll is active.
- Keep menu items highlighted when opening a menu chain.
- Gizmo improvements for navmesh interaction mode.
- Open navmesh panel at the top right of the scene preview when selecting a navmesh scene node.
- Improved visualization in the dependency viewer.
- Made asset import options inspector generic.
- Provide access to material context in the renderer.
- Movement, scale, rotation gizmo improvements.
- Mutable access for ui nodes.
- Preload resources before generating preview for them.
- Made world viewer to accept data provider instead of scene directly.
- Replaced
Cow<Path>
with&Path
inResourceData
trait - Allow to set materials by drag'n'drop on material editor field.
- Made material fields in the inspector more clickable.
- Improved navigation on navmesh using string pulling algorithm.
- Improved performance of navigation mesh queries.
- Improved text box widget performance.
Plane
API improvements.- Made material editor wider a bit by default.
- Extend resource data constructor with type name.
- Turned material into resource, removed
SharedMaterial
struct. - Serialize vertex buffer data as a bytes slab.
- Use
Window::pre_present_notify
as recommended in thewinit
docs. - Refactored sprites rendering to use materials.
- Refactored particle system rendering to use forward renderer.
- More built-in shader variables for lighting.
- Triangle buffer API improvements.
- Use debug message callback instead of message queue for OpenGL errors.
- Enable OpenGL debugging in debug build profile.
- Customizable time-to-live for geometry buffers (allows to create temporary buffers that lives one frame (ttl = 0)).
- Allow to start multiple scenes at editor start up (kudos to @dasimonde (opens new window)).
push_vertices
+push_vertices_transform
method for vertex buffer.- Ability to connect a state with every other state in the ABSM editor (kudos to @Riddhiman007 (opens new window))
- Added UUIDs for scene nodes.
- Ability to set navmesh agent path recalculation threshold.
- Reset
modified
flags of inheritable variables when fixing node type. - Check for node type mismatch on graph resolve and auto-fix this.
- Use type names instead of type ids when reporting inheritance errors.
- Remove orphaned nodes when restoring graph's integrity.
- Code example for
Inspector
widget. - Pass node handle to surface instance data.
- Check for global
enabled
flag when filtering out cameras for rendering. - Serialize joints binding local frames.
- Support for touch events in the UI (kudos to @Bocksdin (opens new window)).
- A* pathfinding optimization (kudos to @TiltedTeapot (opens new window)).
# Fixed
- Fixed crash of the editor on Wayland.
- Fixed font rendering API.
- Fixed restoration of shallow resource handles of untyped resources.
- Prevent double saving of settings after modification.
- Keep aspect ratio when previewing a texture in the asset browser.
- Filter out non-savable resources in resource creation dialog.
- Fixed offscreen UI rendering in the UI editor.
- Fixed deep cloning of materials: make them embedded after cloning.
- Fixed path filters to correctly handle folders with "extensions".
- Save material when changing its shader property in the material editor.
- Fixed massive footgun with pointers to the graphics pipeline state scattered all around the renderer.
- Prevent creating of multiple thread pool across the engine.
- Fixed crash in the material editor if a material is failed to load.
- Prevent the editor from closing after saving a scene via Ctrl+S.
- Fixed position saving of maximized editor window.
- Fixed crash when assigning non-material resource in a material property.
- Fixed forward pass of standard shader for skinned meshes
- Fixed uuid formatting in visitor.
- Fixed resource extension comparison in the editor by making it case-insensitive.
- Fixed crash when drag'n'dropping assets in scene previewer.
- Fixed OpenGL error handling
- Fixed performance issues when modifying vertex/triangle buffers.
- Fixed crash when editing terrains (kudos to @Riddhiman007 (opens new window))
- Fixed a bug when vertex attribute divisor was ignored.
- Fixed colors for log messages.
- Fixed scene loading in derived mode.
- Fixed text coloring when sending a
WidgetMessage::Foreground
to text. - Fixed memory leaks on Linux (kudos to @LordCocoNut (opens new window))
- Fixed invalid GPU resource indexing bug, that caused crashes/quirks in graphics when switching scenes in the editor.
# Removed
- Removed implicit cloning when in
Reflect::into_any
impl for some types. - Removed redundant memory allocation when fetching fields using reflection.
- Removed redundant memory allocation when iterating over fields.
- Removed
Option
wrapper in typed resource to flatten the internal structure of resources. - Removed a bunch of redundant clones in the renderer.
- Removed lazy calculations in the navigational mesh.
- Removed unused
soft_boundary_sharpness_factor
param from particle systems (this property was moved to the standard particle system material). - Removed
InteractionModeKind
and replaced it with uuids.
# Support
If you want to support the development of the project, click one of the links below. Preferable way is to use Boosty (opens new window) - this way the money will be available for the development immediately. Alternatively you can use Patreon (opens new window), but in this case the money will be on-hold for unknown period of time (details are here (opens new window)).
Also, you can help by fixing one of the "good first issues" (opens new window), adding a desired feature to the engine, or making a contribution to the book (opens new window)