diff --git a/TODO.md b/TODO.md index 6c39ee1..93f5208 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,13 @@ +## Specific Jobs + - UI: health, shield, fuel, heat, energy bars + - UI: text arranger + - Sound system + - Particles, impact effects + - Debris on ship death + - Radar: dynamic colors, size, planets and suns + +---------------------------------- + ## Game & Story - Landmarks to determine speed? - How to keep player in system bounds, what to do if they fly far away diff --git a/assets b/assets index 8205d79..9137741 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8205d79e8aa9d7e4976fe8a7794e83819ec19688 +Subproject commit 91377416617d049acec68bb5a17647dcac7acd0e diff --git a/content/textures.toml b/content/textures.toml index 9bcbc3e..ce25a2c 100644 --- a/content/textures.toml +++ b/content/textures.toml @@ -24,3 +24,6 @@ path = "ui/radar.png" [texture."ui::blip"] path = "ui/blip.png" + +[texture."ui::radarframe"] +path = "ui/radarframe.png" diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index 283a5e2..d4bc2aa 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -356,28 +356,46 @@ impl GPUState { let width = s.dimensions.x; let height = s.dimensions.y; + // Compute square scale, since we must apply screen aspect ratio + // AFTER rotation. let scale = Matrix4::from_nonuniform_scale( - width / logical_size.width, + width / logical_size.height, height / logical_size.height, 1.0, ); let rotate = Matrix4::from_angle_z(s.angle); let translate = Matrix4::from_translation(match s.pos { - super::AnchoredUiPosition::NorthWest(p) => Vector3 { + super::AnchoredUiPosition::NwC(p) => Vector3 { // Note the signs. Positive y points north! + x: -1.0 + p.x / (logical_size.width / 2.0), + y: 1.0 + p.y / (logical_size.height / 2.0), + z: 0.0, + }, + super::AnchoredUiPosition::NwNw(p) => Vector3 { x: -1.0 + (width / 2.0 + p.x) / (logical_size.width / 2.0), y: 1.0 - (height / 2.0 - p.y) / (logical_size.height / 2.0), z: 0.0, }, - _ => Vector3 { - x: 0.0, - y: 0.0, + super::AnchoredUiPosition::NwNe(p) => Vector3 { + x: -1.0 - (width / 2.0 - p.x) / (logical_size.width / 2.0), + y: 1.0 - (height / 2.0 - p.y) / (logical_size.height / 2.0), + z: 0.0, + }, + super::AnchoredUiPosition::NwSw(p) => Vector3 { + x: -1.0 + (width / 2.0 + p.x) / (logical_size.width / 2.0), + y: 1.0 + (height / 2.0 + p.y) / (logical_size.height / 2.0), + z: 0.0, + }, + super::AnchoredUiPosition::NwSe(p) => Vector3 { + x: -1.0 - (width / 2.0 - p.x) / (logical_size.width / 2.0), + y: 1.0 + (height / 2.0 + p.y) / (logical_size.height / 2.0), z: 0.0, }, }); + let screen_aspect = Matrix4::from_nonuniform_scale(1.0 / self.window_aspect, 1.0, 1.0); instances.push(SpriteInstance { - transform: (OPENGL_TO_WGPU_MATRIX * translate * rotate * scale).into(), + transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(), texture_index: texture.index, }); } diff --git a/crates/render/src/sprite.rs b/crates/render/src/sprite.rs index 69f84ee..5b44fbd 100644 --- a/crates/render/src/sprite.rs +++ b/crates/render/src/sprite.rs @@ -8,13 +8,25 @@ use cgmath::{Deg, Point2, Point3}; /// positive X always points right. #[derive(Debug, Clone)] pub enum AnchoredUiPosition { + /// Position of this sprite's center, + /// relative to the nw corner of the window. + NwC(Point2), + /// Position of this sprite's nw corner, /// relative to the nw corner of the window. - NorthWest(Point2), + NwNw(Point2), + + /// Position of this sprite's ne corner, + /// relative to the nw corner of the window. + NwNe(Point2), /// Position of this sprite's sw corner, - /// relative to the sw corner of the window. - SouthWest(Point2), + /// relative to the nw corner of the window. + NwSw(Point2), + + /// Position of this sprite's se corner, + /// relative to the nw corner of the window. + NwSe(Point2), } /// A sprite that represents a ui element diff --git a/crates/ui/src/radar.rs b/crates/ui/src/radar.rs index f2e8517..7cb88e8 100644 --- a/crates/ui/src/radar.rs +++ b/crates/ui/src/radar.rs @@ -1,12 +1,15 @@ -use cgmath::{Deg, InnerSpace, Point2}; +use cgmath::{Deg, InnerSpace, Point2, Vector2}; use galactica_content as content; use galactica_render::{AnchoredUiPosition, UiSprite}; use galactica_world::{util, ShipPhysicsHandle, World}; +// TODO: camera as one unit pub fn build_radar( + ct: &content::Content, player: &ShipPhysicsHandle, physics: &World, - ct: &content::Content, + camera_zoom: f32, + camera_aspect: f32, ) -> Vec { let mut out = Vec::new(); @@ -15,7 +18,7 @@ pub fn build_radar( out.push(UiSprite { texture: ct.get_texture_handle("ui::radar"), - pos: AnchoredUiPosition::NorthWest(Point2 { x: 10.0, y: -10.0 }), + pos: AnchoredUiPosition::NwNw(Point2 { x: 10.0, y: -10.0 }), dimensions: Point2 { x: radar_size, y: radar_size, @@ -23,26 +26,98 @@ pub fn build_radar( angle: Deg(0.0), }); + // Draw viewport frame + let d = Vector2 { + x: (camera_zoom / 2.0) * camera_aspect, + y: camera_zoom / 2.0, + }; + let m = d.magnitude() / radar_range; + let d = d / radar_range * (radar_size / 2.0); + println!("{:?}", d); + if m < 0.8 { + let texture = ct.get_texture_handle("ui::radarframe"); + let dimensions = Point2 { + x: texture.aspect, + y: 1.0, + } * 7.0f32.min((0.8 - m) * 70.0); + out.push(UiSprite { + texture, + pos: AnchoredUiPosition::NwNw(Point2 { + x: (radar_size / 2.0 + 10.0) - d.x, + y: (radar_size / -2.0 - 10.0) + d.y, + }), + dimensions, + angle: Deg(0.0), + }); + + out.push(UiSprite { + texture, + pos: AnchoredUiPosition::NwSw(Point2 { + x: (radar_size / 2.0 + 10.0) - d.x, + y: (radar_size / -2.0 - 10.0) - d.y, + }), + dimensions, + angle: Deg(90.0), + }); + + out.push(UiSprite { + texture, + pos: AnchoredUiPosition::NwSe(Point2 { + x: (radar_size / 2.0 + 10.0) + d.x, + y: (radar_size / -2.0 - 10.0) - d.y, + }), + dimensions, + angle: Deg(180.0), + }); + + out.push(UiSprite { + texture, + pos: AnchoredUiPosition::NwNe(Point2 { + x: (radar_size / 2.0 + 10.0) + d.x, + y: (radar_size / -2.0 - 10.0) + d.y, + }), + dimensions, + angle: Deg(270.0), + }); + } + let (_, pr) = physics.get_ship_body(player).unwrap(); let pr = util::rigidbody_position(pr); + let texture = ct.get_texture_handle("ui::blip"); for (s, r) in physics.iter_ship_body() { - if s.physics_handle == *player { - continue; - } - let r = util::rigidbody_position(r); - let d = r - pr; + let p = util::rigidbody_position(r); + let d = p - pr; let m = d.magnitude() / radar_range; - if m < 0.8 { + let angle: Deg = util::rigidbody_rotation(r) + .angle(Vector2 { x: 0.0, y: 1.0 }) + .into(); + if s.physics_handle == *player { out.push(UiSprite { - texture: ct.get_texture_handle("ui::blip"), - pos: AnchoredUiPosition::NorthWest( + texture, + pos: AnchoredUiPosition::NwC(Point2 { + x: radar_size / 2.0 + 10.0, + y: radar_size / -2.0 - 10.0, + }), + dimensions: Point2 { + x: texture.aspect, + y: 1.0, + } * 5.0f32.min((0.8 - m) * 50.0), + angle: -angle, + }); + } else if m < 0.8 { + out.push(UiSprite { + texture, + pos: AnchoredUiPosition::NwC( Point2 { x: radar_size / 2.0 + 10.0, y: radar_size / -2.0 - 10.0, - } + (d / radar_range * 150.0), + } + (d / radar_range * (radar_size / 2.0)), ), - dimensions: Point2 { x: 1.0, y: 1.0 } * 5.0f32.min((0.8 - m) * 50.0), - angle: Deg(0.0), + dimensions: Point2 { + x: texture.aspect, + y: 1.0, + } * 5.0f32.min((0.8 - m) * 50.0), + angle: -angle, }); } } diff --git a/src/camera.rs b/src/camera.rs index afe19a7..42f6fdf 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -8,4 +8,7 @@ pub struct Camera { /// Camera zoom /// (How many game units tall is the viewport?) pub zoom: f32, + + /// Aspect ratio of viewport (width / height) + pub aspect: f32, } diff --git a/src/game.rs b/src/game.rs index 83d2b12..466f4e6 100644 --- a/src/game.rs +++ b/src/game.rs @@ -86,6 +86,7 @@ impl Game { camera: Camera { pos: (0.0, 0.0).into(), zoom: 500.0, + aspect: 1.0, }, system: object::System::new(&ct, SystemHandle { index: 0 }), @@ -179,6 +180,12 @@ impl Game { } pub fn get_ui_sprites(&self) -> Vec { - return ui::build_radar(&self.player, &self.physics, &self.content); + return ui::build_radar( + &self.content, + &self.player, + &self.physics, + self.camera.zoom, + self.camera.aspect, + ); } } diff --git a/src/main.rs b/src/main.rs index 23a1a8d..e745a9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ fn main() -> Result<()> { let mut game = game::Game::new(content); gpu.update_starfield_buffer(); + game.camera.aspect = gpu.window_size.width as f32 / gpu.window_size.height as f32; event_loop.run(move |event, _, control_flow| { match event { @@ -75,9 +76,13 @@ fn main() -> Result<()> { } WindowEvent::Resized(_) => { gpu.resize(); + game.camera.aspect = + gpu.window_size.width as f32 / gpu.window_size.height as f32; } WindowEvent::ScaleFactorChanged { .. } => { gpu.resize(); + game.camera.aspect = + gpu.window_size.width as f32 / gpu.window_size.height as f32; } _ => {} },