Oxygengine
The hottest HTML5 + WASM game engine for games written in Rust with web-sys
.
Table of contents
- Installation
- Project Setup
- Building for development and production
- Roadmap
- Hello World
Installation
- Make sure that you have latest
node.js
with npm
tools installed (https://nodejs.org/)
- Make sure that you have latest
wasm-pack
toolset installed (https://rustwasm.github.io/wasm-pack/installer/)
Project Setup
- Create Rust + WASM project with
npm init rust-webpack <path>
where path
is path to empty folder where your project will be created by this
command.
- Then add this record into your
/crate/Cargo.toml
file:
[dependencies]
oxygengine = { version = "0.3", features = ["web-composite-game"] }
where web-composite-game
means that you want to use those
modules of Oxygen game engine, that gives you all features needed to easly make
an HTML5 web game with composite renderer.
You may also select which exact features you need, excluding those which you're
not gonna use. For example: web-composite-game
feature by default enables
these features: composite-renderer
, input
, network
. So if you just want to
make a movie-like animation then you don't need any input or networking, so you
will want to add this record instead:
[dependencies.oxygengine]
version = "0.3"
features = [
"web",
"composite-renderer",
"oxygengine-composite-renderer-backend-web"
]
which means you want to use composite renderer with web backend and produce app
for web target.
Building for development and production
- Launch live development with hot reloading (app will be automatically
recompiled in background):
npm start
- Build production distribution (will be available in
/dist
folder):
npm run build
- Build just crate instead of running dev env:
cd /crate
cargo build --all --target wasm32-unknown-unknown
Roadmap
- Hardware renderer
- WebGL hardware renderer backend
- 2D physics
Hello World
/index.html
:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>Oxygen Engine demo</title>
</head>
<body style="margin: 0; padding: 0;">
<canvas
id="screen"
style="margin: 0; padding: 0; position: absolute; width: 100%; height: 100%;"
></canvas>
</body>
</html>
where screen
canvas is our target fullpage game screen where game will be
rendered onto.
/crate/src/lib.rs
:
extern crate oxygengine;
use oxygengine::prelude::*;
use wasm_bindgen::prelude::*;
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[derive(Debug, Default, Copy, Clone)]
pub struct KeyboardMovementTag;
impl Component for KeyboardMovementTag {
type Storage = NullStorage<Self>;
}
#[derive(Debug, Default, Copy, Clone)]
pub struct Speed(pub Scalar);
impl Component for Speed {
type Storage = VecStorage<Self>;
}
pub struct KeyboardMovementSystem;
impl<'s> System<'s> for KeyboardMovementSystem {
type SystemData = (
Read<'s, InputController>,
ReadExpect<'s, AppLifeCycle>,
ReadStorage<'s, Speed>,
ReadStorage<'s, KeyboardMovementTag>,
WriteStorage<'s, CompositeTransform>,
);
fn run(
&mut self,
(input, lifecycle, speed, keyboard_movement, mut transforms): Self::SystemData,
) {
let dt = lifecycle.delta_time_seconds() as Scalar;
let hor = -input.axis_or_default("move-left") + input.axis_or_default("move-right");
let ver = -input.axis_or_default("move-up") + input.axis_or_default("move-down");
let offset = Vec2::new(hor, ver);
for (_, speed, transform) in (&keyboard_movement, &speed, &mut transforms).join() {
transform.set_translation(transform.get_translation() + offset * speed.0 * dt);
}
}
}
pub struct GameState;
impl State for GameState {
fn on_enter(&mut self, world: &mut World) {
world
.create_entity()
.with(CompositeCamera::new(CompositeScalingMode::CenterAspect))
.with(CompositeTransform::scale(400.0.into()))
.build();
let player = world
.create_entity()
.with(CompositeRenderable(
Rectangle {
color: Color::red(),
rect: [-50.0, -50.0, 100.0, 100.0].into(),
}
.into(),
))
.with(CompositeTransform::default())
.with(KeyboardMovementTag)
.with(Speed(100.0))
.build();
world
.create_entity()
.with(CompositeRenderable(
Rectangle {
color: Color::yellow(),
rect: [-10.0, -10.0, 20.0, 20.0].into(),
}
.into(),
))
.with(CompositeTransform::translation((-20.0).into()))
.with(Parent(player))
.build();
world
.create_entity()
.with(CompositeRenderable(
Text {
color: Color::white(),
font: "Verdana".into(),
align: TextAlign::Center,
text: "Use WSAD to move".into(),
position: 0.0.into(),
size: 24.0,
}
.into(),
))
.with(CompositeTransform::translation([0.0, 100.0].into()))
.with(CompositeRenderDepth(-1.0))
.build();
}
}
#[wasm_bindgen]
pub fn run() -> Result<(), JsValue> {
set_panic_hook();
let app = App::build()
.with_bundle(
oxygengine::core::assets::bundle_installer,
(WebFetchEngine::default(), |assets| {
oxygengine::composite_renderer::protocols_installer(assets);
}),
)
.with_bundle(oxygengine::input::bundle_installer, |input| {
input.register(WebKeyboardInputDevice::new(get_event_target_document()));
input.map_axis("move-up", "keyboard", "KeyW");
input.map_axis("move-down", "keyboard", "KeyS");
input.map_axis("move-left", "keyboard", "KeyA");
input.map_axis("move-right", "keyboard", "KeyD");
})
.with_bundle(
oxygengine::composite_renderer::bundle_installer,
WebCompositeRenderer::with_state(
get_canvas_by_id("screen"), RenderState::new(Some(Color::black())),
),
)
.with_system(KeyboardMovementSystem, "keyboard_movement", &[])
.build(GameState, WebAppTimer::default());
AppRunner::new(app).run::<WebAppRunner, _>()?;
Ok(())
}
fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}