Add UI scaling and window example

main
Elijah Voigt 2 months ago
parent a201c2466b
commit 7130672c40

@ -14,6 +14,10 @@ codegen-units = 1
opt-level = "z" opt-level = "z"
lto = "thin" lto = "thin"
[profile.wasm-dev]
inherits = "dev"
codegen-backend = "llvm"
[profile.wasm-release] [profile.wasm-release]
inherits = "release" inherits = "release"
opt-level = "s" opt-level = "s"

@ -0,0 +1,2 @@
* setup CI template in web server running on port 8000
* setup mock bevy app running on port 8001 w/ first app running in iframe

@ -5,6 +5,7 @@ fn main() {
.add_plugins((BaseGamePlugin { .add_plugins((BaseGamePlugin {
name: "2d game example".into(), name: "2d game example".into(),
game_type: GameType::Two, game_type: GameType::Two,
..default()
},)) },))
.add_systems(Startup, spawn_guy) .add_systems(Startup, spawn_guy)
.run(); .run();

@ -0,0 +1,34 @@
use games::*;
fn main() {
App::new()
.add_plugins(BaseGamePlugin { target_resolution: (360.0, 640.0).into(), ..default() })
.add_systems(Startup, init_window_info)
.add_systems(Update, update_window_info.run_if(any_component_changed::<Window>))
.run();
}
#[derive(Component)]
struct WindowInfo;
fn init_window_info(mut commands: Commands) {
commands.spawn((
Node {
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
..default()
},
children![(Text::new("Placeholder"), WindowInfo,)],
));
}
fn update_window_info(mut text: Single<&mut Text, With<WindowInfo>>, window: Single<&Window>) {
text.0 = format!(
"Logical: {}x{}\nPhysical: {}x{}\nScale Factor: {}",
window.resolution.physical_width(),
window.resolution.physical_height(),
window.resolution.width(),
window.resolution.height(),
window.resolution.scale_factor(),
);
}

@ -1,26 +1,38 @@
VERSION := `git rev-parse --short HEAD` VERSION := `git rev-parse --short HEAD`
# Build the web version bindgen profile name:
web GAME: mkdir -p ./dist/{{name}}
# base directory
mkdir -p dist/{{GAME}}/assets/{{GAME}}
# wasm binary
cargo build --bin {{GAME}} --profile wasm-release --target wasm32-unknown-unknown
# wasm bindgen wasm-bindgen \
wasm-bindgen --no-typescript --target web \ --no-typescript \
--out-dir ./dist/{{GAME}} \ --target web \
--out-dir ./dist/{{name}} \
--out-name "bin" \ --out-name "bin" \
${CARGO_TARGET_DIR}/wasm32-unknown-unknown/release/{{GAME}}.wasm ${CARGO_TARGET_DIR}/wasm32-unknown-unknown/{{profile}}/{{name}}.wasm
optimize NAME:
# Size pass # Size pass
wasm-opt -Oz \ wasm-opt -Oz \
-o dist/{{GAME}}/bin_bg-tmp.wasm \ -o dist/{{NAME}}/bin_bg-tmp.wasm \
./dist/{{GAME}}/bin_bg.wasm ./dist/{{NAME}}/bin_bg.wasm
# Replace old bin with new (compressed) bin # Replace old bin with new (compressed) bin
mv ./dist/{{GAME}}/bin_bg-tmp.wasm ./dist/{{GAME}}/bin_bg.wasm mv ./dist/{{NAME}}/bin_bg-tmp.wasm ./dist/{{NAME}}/bin_bg.wasm
build-example EXAMPLE:
cargo build --example {{EXAMPLE}} --profile wasm-dev --target wasm32-unknown-unknown
build-bin GAME:
# wasm binary
cargo build --bin {{GAME}} --profile wasm-release --target wasm32-unknown-unknown
example NAME: (build-example NAME) (bindgen "wasm-dev" "examples"/NAME)
cp web/example.html ./dist/examples/{{NAME}}/index.html
# Build the web version
web GAME: (build-bin GAME) (bindgen "wasm-release" GAME) (optimize GAME)
# base directory
mkdir -p dist/{{GAME}}/assets/{{GAME}}
# index.html # index.html
cp ./web/{{GAME}}.html ./dist/{{GAME}}/index.html cp ./web/{{GAME}}.html ./dist/{{GAME}}/index.html

@ -1,5 +1,3 @@
use bevy::platform::hash::RandomState;
use super::*; use super::*;
/// A good starting place for creating a game building on top of the base Bevy app /// A good starting place for creating a game building on top of the base Bevy app
@ -7,7 +5,7 @@ pub struct BaseGamePlugin {
pub title: String, pub title: String,
pub name: String, pub name: String,
pub game_type: GameType, pub game_type: GameType,
pub window: Option<Window>, pub target_resolution: WindowResolution,
} }
pub enum GameType { pub enum GameType {
@ -21,7 +19,7 @@ impl Default for BaseGamePlugin {
title: "My Game".into(), title: "My Game".into(),
name: "mygame".into(), name: "mygame".into(),
game_type: GameType::Three, game_type: GameType::Three,
window: None, target_resolution: WindowResolution::default(),
} }
} }
} }
@ -33,19 +31,27 @@ impl Plugin for BaseGamePlugin {
.set(WindowPlugin { .set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
fit_canvas_to_parent: true, fit_canvas_to_parent: true,
canvas: Some(format!("#{}-canvas", self.name)), resolution: self.target_resolution.clone(),
..self.window.clone().unwrap_or_default() ..default()
}), }),
..default() ..default()
}) })
.set(ImagePlugin::default_nearest()), .set(ImagePlugin::default_nearest())
.set(AssetPlugin {
meta_check: AssetMetaCheck::Never,
..default()
}),
) )
.add_plugins(DebuggingPlugin) .add_plugins(DebuggingPlugin)
.add_plugins(MeshPickingPlugin) .add_plugins(MeshPickingPlugin)
.add_plugins(LoadingPlugin) .add_plugins(LoadingPlugin)
.add_plugins(BaseUiPlugin) .add_plugins(BaseUiPlugin)
.add_plugins(ParallaxPlugin) .add_plugins(ParallaxPlugin)
.init_resource::<Rand>(); .insert_resource(TargetResolution(self.target_resolution.clone()))
.init_resource::<Rand>()
.add_systems(
Update, scale_game.run_if(any_component_changed::<Window>)
);
match self.game_type { match self.game_type {
GameType::Two => app.add_systems(Startup, create_camera_2d), GameType::Two => app.add_systems(Startup, create_camera_2d),
@ -54,6 +60,9 @@ impl Plugin for BaseGamePlugin {
} }
} }
#[derive(Resource)]
struct TargetResolution(WindowResolution);
/// System to toggle the visibility of entities based on their state /// System to toggle the visibility of entities based on their state
pub fn toggle_state_visibility<S: States + Component>( pub fn toggle_state_visibility<S: States + Component>(
mut q: Query<(Entity, &mut Visibility, &S)>, mut q: Query<(Entity, &mut Visibility, &S)>,
@ -87,3 +96,22 @@ pub fn create_camera_2d(mut commands: Commands) {
#[derive(Default, Resource)] #[derive(Default, Resource)]
pub struct Rand(pub RandomState); pub struct Rand(pub RandomState);
/// Scale the game based on the difference between the target and real resolution
fn scale_game(
mut window: Single<&mut Window>,
target_resolution: Res<TargetResolution>,
mut last_scale_factor: Local<f32>,
) {
let current_resolution: Vec2 = window.resolution.size();
let scale_width = current_resolution.x.round() / target_resolution.0.width().round();
let scale_height = current_resolution.y.round() / target_resolution.0.height().round();
let scale_factor = f32::min(scale_width, scale_height);
if window.resolution.scale_factor() != scale_factor {
// Need to check the previously set scale factor because the system can flip-flop otherwise
if scale_factor != *last_scale_factor {
*last_scale_factor = window.resolution.scale_factor();
window.resolution.set_scale_factor(scale_factor);
}
}
}

@ -2,12 +2,11 @@
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use bevy::image::{ImageLoaderSettings, ImageSampler}; use bevy::image::{ImageLoaderSettings, ImageSampler};
use bevy::platform::time::Instant;
use bevy::render::view::ColorGrading; use bevy::render::view::ColorGrading;
use bevy::window::WindowResolution;
use games::physics2d::*; use games::physics2d::*;
use games::*; use games::*;
use std::hash::BuildHasher; use std::hash::BuildHasher;
use std::time::Instant;
fn main() { fn main() {
App::new() App::new()
@ -16,6 +15,7 @@ fn main() {
title: "flappy bird (with rewind)".into(), title: "flappy bird (with rewind)".into(),
name: "flappy".into(), name: "flappy".into(),
game_type: GameType::Two, game_type: GameType::Two,
target_resolution: (360.0, 640.0).into(),
..default() ..default()
}, },
Physics2dPlugin, Physics2dPlugin,
@ -687,8 +687,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
)); ));
commands commands
.spawn(( .spawn((Node {
Node {
align_self: AlignSelf::End, align_self: AlignSelf::End,
justify_self: JustifySelf::Center, justify_self: JustifySelf::Center,
flex_direction: FlexDirection::Row, flex_direction: FlexDirection::Row,
@ -696,8 +695,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
width: Val::Percent(100.0), width: Val::Percent(100.0),
min_height: Val::Percent(10.0), min_height: Val::Percent(10.0),
..default() ..default()
}, },))
))
.with_children(|parent| { .with_children(|parent| {
let rewind_image = server.load_with_settings( let rewind_image = server.load_with_settings(
"flappy/rewind.png", "flappy/rewind.png",
@ -1281,7 +1279,13 @@ fn shimmer_button<T: Component>(mut bg: Single<&mut BackgroundColor, With<T>>, t
let red = (((t / period) % 1.0) * std::f32::consts::PI).cos(); let red = (((t / period) % 1.0) * std::f32::consts::PI).cos();
let green = ((((t / period) + 0.3) % 1.0) * std::f32::consts::PI).cos(); let green = ((((t / period) + 0.3) % 1.0) * std::f32::consts::PI).cos();
let blue = ((((t / period) + 0.6) % 1.0) * std::f32::consts::PI).cos(); let blue = ((((t / period) + 0.6) % 1.0) * std::f32::consts::PI).cos();
bg.0 = Srgba { red, green, blue, alpha: bg.0.alpha() }.into(); bg.0 = Srgba {
red,
green,
blue,
alpha: bg.0.alpha(),
}
.into();
} }
fn reset_button<T: Component>(mut bg: Single<&mut BackgroundColor, With<T>>) { fn reset_button<T: Component>(mut bg: Single<&mut BackgroundColor, With<T>>) {

@ -18,7 +18,7 @@ pub use std::fmt::Display;
// Community libraries // Community libraries
pub use bevy::{ pub use bevy::{
asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader}, asset::{AssetLoader, LoadContext, LoadState, LoadedFolder, io::Reader, AssetMetaCheck},
color::palettes::css::*, color::palettes::css::*,
gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin}, gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin},
input::{ input::{
@ -29,11 +29,11 @@ pub use bevy::{
mouse::{MouseScrollUnit, MouseWheel}, mouse::{MouseScrollUnit, MouseWheel},
}, },
pbr::wireframe::{WireframeConfig, WireframePlugin}, pbr::wireframe::{WireframeConfig, WireframePlugin},
platform::collections::HashMap, platform::{collections::HashMap, hash::RandomState},
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
sprite::AlphaMode2d, sprite::AlphaMode2d,
window::WindowResized, window::{WindowResized, WindowResolution},
}; };
pub use serde::Deserialize; pub use serde::Deserialize;
pub use thiserror::Error; pub use thiserror::Error;

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<body style="margin: 0px;">
<script type="module">
import init from './bin.js'
init().catch((error) => {
if (!error.message.startsWith("Using exceptions for control flow, don't mind me. This isn't actually an error!")) {
throw error;
}
});
</script>
</body>
</html>

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<body>
<canvas id="flappy-canvas" ></canvas> <body style="margin: 0px;">
<script type="module"> <script type="module">
import init from './bin.js' import init from './bin.js'
@ -12,4 +12,5 @@
}); });
</script> </script>
</body> </body>
</html> </html>

Loading…
Cancel
Save