diff --git a/assets/shaders/mouse_shader.wgsl b/assets/shaders/mouse_shader.wgsl index 24deaf7..70055a8 100644 --- a/assets/shaders/mouse_shader.wgsl +++ b/assets/shaders/mouse_shader.wgsl @@ -1,20 +1,31 @@ #import bevy_sprite::mesh2d_vertex_output::VertexOutput @group(2) @binding(0) var cursor_position: vec2; -@group(2) @binding(1) var terrain_texture: texture_2d; -@group(2) @binding(2) var terrain_texture_sampler: sampler; -@group(2) @binding(3) var mask_texture: texture_2d; -@group(2) @binding(4) var mask_texture_sampler: sampler; +@group(2) @binding(1) var level_viewport: vec2; +@group(2) @binding(2) var terrain_texture: texture_2d; +@group(2) @binding(3) var terrain_texture_sampler: sampler; +@group(2) @binding(4) var mask_texture: texture_2d; +@group(2) @binding(5) var mask_texture_sampler: sampler; @fragment fn fragment(mesh: VertexOutput) -> @location(0) vec4 { var terrain_color = textureSample(terrain_texture, terrain_texture_sampler, mesh.uv); - let diff = mesh.position.xy - cursor_position; - if all(abs(diff) < vec2(36.0, 36.0)) { - // Convert the difference to UV coordinates for the mask (0 to 1 range) - // Add 0.5 to center the mask (moving from -36..36 to 0..1 range) - let mask_uv = (diff + vec2(36.0)) / 72.0; + // For centre of screen: + // level_viewport here is 640, 360 (centred) + // mesh.position.xy is 1280, 720 because it's the middle of a 2k screen + // cursor_position will be 640, 360 (centre of screen) + // adding level_viewport + mesh.position.xy == 1920, 1080 which makes little sense + // instead, our target value should be the centre of 2k which is 1280, 720 + // it also needs to work when viewport is 0, 0 or 1920, 720, the min and max possible for viewport + // let adjusted_position = mesh.position.xy + level_viewport; + // let diff = adjusted_position - cursor_position; + + let diff = mesh.uv - cursor_position; + + if all(abs(diff) < vec2(0.01, 0.01)) { + // TODO: should this be different to adjust for actual mesh position? + let mask_uv = (diff + vec2(1., 1.)) / 2.; var mask_color = textureSample(mask_texture, mask_texture_sampler, mask_uv); if terrain_color.a != 0. { diff --git a/assets/textures/blank.png b/assets/textures/blank.png new file mode 100644 index 0000000..9b37b5b Binary files /dev/null and b/assets/textures/blank.png differ diff --git a/assets/textures/level.png b/assets/textures/level.png index 274621b..abb4a9c 100644 Binary files a/assets/textures/level.png and b/assets/textures/level.png differ diff --git a/assets/textures/yup.png b/assets/textures/yup.png index 05df6bf..82b4de7 100644 Binary files a/assets/textures/yup.png and b/assets/textures/yup.png differ diff --git a/src/assets.rs b/src/assets.rs index e6724c1..fa8ea7b 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -17,6 +17,8 @@ pub fn plugin(app: &mut App) { pub struct Levels { #[asset(path = "textures/level.png")] pub level: Handle, + #[asset(path = "textures/blank.png")] + pub blank: Handle, } #[derive(AssetCollection, Resource)] diff --git a/src/game.rs b/src/game.rs index cb9adee..eedf4ad 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,3 +1,4 @@ +pub mod minimap; pub mod movement; pub mod yup; @@ -24,7 +25,7 @@ pub enum Game { pub fn plugin(app: &mut App) { app.init_state::(); app.enable_state_scoped_entities::(); - app.add_plugins((movement::plugin, yup::plugin)); + app.add_plugins((minimap::plugin, movement::plugin, yup::plugin)); app.add_systems(OnEnter(Game::Intro), init); } diff --git a/src/game/minimap.rs b/src/game/minimap.rs new file mode 100644 index 0000000..42fb053 --- /dev/null +++ b/src/game/minimap.rs @@ -0,0 +1,85 @@ +use bevy::{ + asset::RenderAssetUsages, + prelude::*, + render::{ + render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages}, + view::RenderLayers, + }, +}; + +use crate::screens::Screen; + +pub fn plugin(app: &mut App) { + app.init_resource::(); + app.add_systems(OnEnter(Screen::InGame), init); +} + +#[derive(Component)] +pub struct MinimapCamera; + +#[derive(Resource, Default)] +pub struct MinimapRenderTarget { + pub texture: Handle, +} + +fn get_minimap_transform(image_size: &Vec2, screen_size: &Vec2, scale_factor: f32) -> Transform { + let actual_size = image_size / 2. * scale_factor; + Transform::from_xyz( + 20. - (screen_size.x / 2.) + actual_size.x, + // TODO: fix + -150. + (screen_size.y / 2.) + actual_size.y, + 0., + ) + .with_scale(Vec3::splat(scale_factor)) +} + +fn init( + mut commands: Commands, + mut images: ResMut>, + mut minimap: ResMut, + window: Single<&Window>, +) { + // Render to image for minimap. + let mut minimap_image = Image::new_fill( + Extent3d { + width: 2560, + height: 1440, + ..default() + }, + TextureDimension::D2, + &[0, 0, 0, 0], + TextureFormat::Bgra8UnormSrgb, + RenderAssetUsages::default(), + ); + // TODO: feels like we need DST but not SRC here? Find out for sure. This even seems to work + // without COPY_DST. Ask Discord? + minimap_image.texture_descriptor.usage = + TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT; + minimap.texture = images.add(minimap_image); + + commands.spawn(( + Name::new("Minimap Camera"), + MinimapCamera, + Camera2d, + Camera { + clear_color: Color::WHITE.into(), + // Render this first. + order: -1, + target: minimap.texture.clone().into(), + ..default() + }, + StateScoped(Screen::InGame), + )); + + commands.spawn(( + Name::new("Minimap"), + RenderLayers::layer(1), + Sprite { + image: minimap.texture.clone(), + ..Default::default() + }, + StateScoped(Screen::InGame), + // TODO: magic numbers + get_minimap_transform(&Vec2::new(2560., 1440.), &window.size(), 0.1), + )); +} diff --git a/src/lib.rs b/src/lib.rs index ba2874c..1530a47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ use bevy::{ asset::AssetMetaCheck, audio::{AudioPlugin, Volume}, prelude::*, - render::view::RenderLayers, + render::{camera::Viewport, view::RenderLayers}, window::WindowResolution, }; @@ -42,7 +42,7 @@ impl Plugin for GamePlugin { fit_canvas_to_parent: true, prevent_default_event_handling: true, resizable: false, - resolution: WindowResolution::new(1920., 1080.) + resolution: WindowResolution::new(1280., 720.) .with_scale_factor_override(1.0), ..default() } diff --git a/src/screens/ingame/playing.rs b/src/screens/ingame/playing.rs index 098dff9..829554c 100644 --- a/src/screens/ingame/playing.rs +++ b/src/screens/ingame/playing.rs @@ -1,35 +1,26 @@ use bevy::{ - asset::RenderAssetUsages, prelude::*, - render::{ - render_resource::{ - AsBindGroup, Extent3d, ShaderRef, TextureDimension, TextureFormat, TextureUsages, - }, - view::RenderLayers, - }, + render::render_resource::{AsBindGroup, ShaderRef}, sprite::{Material2d, Material2dPlugin}, }; use tiny_bail::prelude::*; use crate::{ + MainCamera, assets::{Levels, Masks}, + game::minimap::MinimapRenderTarget, screens::Screen, }; const SHADER_ASSET_PATH: &str = "shaders/mouse_shader.wgsl"; pub fn plugin(app: &mut App) { + app.init_resource::(); app.add_systems(OnEnter(Screen::InGame), init); - app.init_resource::(); app.add_systems(Update, draw_alpha_gpu.run_if(in_state(Screen::InGame))); app.add_plugins(Material2dPlugin::::default()); } -#[derive(Resource, Default)] -pub struct MinimapRenderTarget { - pub texture: Handle, -} - #[derive(Component, Debug)] pub struct Level; @@ -39,22 +30,31 @@ pub struct Obstacle; #[derive(Component)] pub struct MovementSpeed(pub f32); -#[derive(Component)] -pub struct MinimapCamera; - #[derive(Asset, Default, TypePath, AsBindGroup, Debug, Clone)] pub struct LevelMaterial { #[uniform(0)] pub cursor_position: Vec2, + #[uniform(1)] + pub level_viewport: Vec2, // TODO: find out more about samplers! - #[texture(1)] - #[sampler(2)] + #[texture(2)] + #[sampler(3)] pub terrain_texture: Handle, - #[texture(3)] - #[sampler(4)] + #[texture(4)] + #[sampler(5)] pub mask_texture: Handle, } +#[derive(Resource, Deref, DerefMut)] +pub struct LevelViewport(Vec2); + +impl Default for LevelViewport { + fn default() -> Self { + // Start at the centre of the 2k mesh + Self(Vec2::new(640., 360.)) + } +} + impl Material2d for LevelMaterial { fn alpha_mode(&self) -> bevy::sprite::AlphaMode2d { bevy::sprite::AlphaMode2d::Blend @@ -67,11 +67,10 @@ impl Material2d for LevelMaterial { pub fn init( mut commands: Commands, - mut images: ResMut>, + level_viewport: Res, masks: Res, mut materials: ResMut>, mut meshes: ResMut>, - mut minimap: ResMut, textures: Res, window: Single<&Window>, ) { @@ -80,64 +79,20 @@ pub fn init( commands.spawn(( Name::new("Level"), Level, - Mesh2d(meshes.add(Rectangle::new(1920., 1080.))), + Mesh2d(meshes.add(Rectangle::new(2560., 1440.))), MeshMaterial2d(materials.add(LevelMaterial { cursor_position, + level_viewport: **level_viewport, mask_texture: masks.cursor.clone(), terrain_texture: textures.level.clone(), })), StateScoped(Screen::InGame), )); - - // Render to image for minimap. - // TODO: can we do the lemmings-like thing of displaying a viewport within the larger image? - let mut image = Image::new_fill( - Extent3d { - width: 1920, - height: 1080, - ..default() - }, - TextureDimension::D2, - &[0, 0, 0, 0], - TextureFormat::Bgra8UnormSrgb, - RenderAssetUsages::default(), - ); - // TODO: feels like we need DST but not SRC here? Find out for sure. This even seems to work - // without COPY_DST. Ask Discord? - image.texture_descriptor.usage = - TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT; - minimap.texture = images.add(image); - - // Source camera - commands.spawn(( - Name::new("Minimap Camera"), - MinimapCamera, - Camera2d, - Camera { - // Render this first. - order: -1, - target: minimap.texture.clone().into(), - clear_color: Color::WHITE.into(), - ..default() - }, - StateScoped(Screen::InGame), - )); - - // Debug image - commands.spawn(( - Name::new("Debug Terrain RenderTarget"), - RenderLayers::layer(1), - Sprite { - image: minimap.texture.clone(), - ..Default::default() - }, - Transform::from_xyz(-850., 450., 0.).with_scale(Vec3::splat(0.1)), - StateScoped(Screen::InGame), - )); } fn draw_alpha_gpu( level: Query<&MeshMaterial2d, With>, + level_viewport: Res, mut materials: ResMut>, mouse_button: Res>, window: Single<&Window>, @@ -148,7 +103,12 @@ fn draw_alpha_gpu( let l = r!(level.get_single()); let level_material = r!(materials.get_mut(&l.0)); - if let Some(cursor_pos) = window.physical_cursor_position() { - level_material.cursor_position = cursor_pos; + if let Some(cursor_pos) = window.cursor_position() { + let uv = Vec2::new( + cursor_pos.x / window.width(), + cursor_pos.y / window.height(), + ); + level_material.cursor_position = uv; + level_material.level_viewport = **level_viewport; } }