diff --git a/Cargo.toml b/Cargo.toml index c81ca1a..986a49b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,9 @@ glam = "0.24.0" ascii = "1.1.0" gilrs = "0.10.2" cpal = "0.15.2" - -[dependencies.skunk2d] -path = "../skunk2d" +winit = "0.28.6" +softbuffer = "0.3.0" +gif = "0.12.0" [profile.release-dani] inherits = "release" diff --git a/src/gsa_render_to_screen.rs b/src/gsa_render_to_screen.rs new file mode 100644 index 0000000..5f38e91 --- /dev/null +++ b/src/gsa_render_to_screen.rs @@ -0,0 +1,23 @@ +use crate::{Gsa, MAX_SPRITES, TILE_SIZE}; +use glam::IVec2; +use softbuffer::Buffer; + +pub(crate) fn render_to_window(target: &mut Buffer, gsa: &Gsa, window_size: IVec2) { + for (y, row) in target.chunks_exact_mut(window_size.x as usize).enumerate() { + let y = y as i32; + for x in 0..window_size.x { + let mut p = 0; + for sprite in &gsa.sprites { + if sprite.tile > 0 + && x >= sprite.pos.x + && y >= sprite.pos.y + && x < sprite.pos.x + TILE_SIZE as i32 + && y < sprite.pos.y + TILE_SIZE as i32 + { + p = 1; + } + } + row[x as usize] = gsa.palette[p].to_u32(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 50aaf08..8ddcbd1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,12 +22,14 @@ mod buttons; mod gsa; +mod gsa_render_to_screen; mod input; mod rgb; mod run; mod sprite; mod state; mod tilemap; +mod tileset; pub use crate::buttons::Buttons; pub use crate::gsa::Gsa; @@ -40,8 +42,23 @@ pub use crate::tilemap::Tilemap; /// Amount of sprites in [Gsa::sprites] pub const MAX_SPRITES: usize = 0xff; +/// Screen Width in pixels +pub const SCREEN_WIDTH: usize = 304; + +/// Screen Height in pixels +pub const SCREEN_HEIGHT: usize = 176; + /// X and y dimensions of maps in [Gsa::maps] pub const TILEMAP_MAX_SIZE: usize = 1024; +/// Width and height (in tiles) of tileset +pub const TILESET_SIZE: usize = 0x100; + +/// Width and height of a tile +pub const TILE_SIZE: usize = 16; + +/// Width and height of a tile in half-tile mode +pub const HALF_TILE_SIZE: usize = 8; + /// Amount of tile maps in [Gsa::maps] pub const MAX_TILEMAPS: usize = 4; diff --git a/src/rgb.rs b/src/rgb.rs index 6f540d0..0dd7c80 100644 --- a/src/rgb.rs +++ b/src/rgb.rs @@ -8,3 +8,9 @@ pub struct Rgb { /// Blue component 0-255 pub b: u8, } + +impl Rgb { + pub(crate) fn to_u32(self) -> u32 { + (self.r as u32) | ((self.g as u32) << 8) | ((self.b as u32) << 16) + } +} diff --git a/src/run.rs b/src/run.rs index 6c78c6b..433c930 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,7 +1,16 @@ +use crate::gsa_render_to_screen::render_to_window; use crate::state::State; -use crate::{Gsa, Rgb, Sprite, MAX_SPRITES}; +use crate::tileset::load_tileset; +use crate::{Gsa, Rgb, Sprite, MAX_SPRITES, SCREEN_HEIGHT, SCREEN_WIDTH}; use gilrs::Gilrs; -use skunk2d::Image8; +use glam::IVec2; +use std::cmp::min; +use std::num::NonZeroU32; +use std::time::{Duration, Instant}; +use winit::dpi::LogicalSize; +use winit::event::{Event, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; /// Creates main function, includes gfx.gif, and calls run /// @@ -25,17 +34,7 @@ pub fn run( update_fn: fn(game: &mut TGame, gsa: &mut Gsa), image_data: &[u8], ) { - let tileset = Image8::load_data(image_data); - let pal = Image8::load_data_palette(image_data); - let mut palette = [Rgb { r: 0, g: 0, b: 0 }; 256]; - - for i in 0..pal.len() / 3 { - palette[i] = Rgb { - r: pal[i * 3 + 2], - g: pal[i * 3 + 1], - b: pal[i * 3], - }; - } + let (tileset, palette) = load_tileset(image_data); let mut gsa = Gsa { sprites: [Sprite::default(); MAX_SPRITES], @@ -44,8 +43,8 @@ pub fn run( font: 0x1010, input: Default::default(), }; - let game = init_fn(&mut gsa); - let state = State:: { + let mut game = init_fn(&mut gsa); + /*let state = State:: { gsa, game, tileset, @@ -53,5 +52,83 @@ pub fn run( first: true, gilrs: Gilrs::new().unwrap(), }; - skunk2d::run_with::>(304, 176, 60, state); + + */ + + let event_loop = EventLoop::new(); + let size = LogicalSize::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32); + let window = WindowBuilder::new() + .with_title("Game Skunk Advance v0.1") + .with_inner_size(size) + .with_min_inner_size(size) + .build(&event_loop) + .unwrap(); + + let context = unsafe { softbuffer::Context::new(&window) }.unwrap(); + let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap(); + + let mut frames_since_last_second = 0; + let mut last_second = Instant::now(); + + let mut last_frame = last_second; + let target_frame_duration = Duration::from_secs(1).div_f64(6000.0); + + let mut scale = 1usize; + let mut off_x = 0usize; + let mut off_y = 0usize; + + event_loop.run(move |event, _, control_flow| { + let scale = &mut scale; + let off_x = &mut off_x; + let off_y = &mut off_y; + if Instant::now() - last_second > Duration::from_secs(1) { + println!("fps: {}", frames_since_last_second); + frames_since_last_second = 0; + last_second += Duration::from_secs(1); + } + match event { + Event::WindowEvent { + event: WindowEvent::KeyboardInput { input, .. }, + .. + } => match input.virtual_keycode { + Some(VirtualKeyCode::Escape) => { + *control_flow = ControlFlow::Exit; + } + _ => {} + }, + Event::MainEventsCleared {} => { + if Instant::now() - last_frame >= target_frame_duration { + update_fn(&mut game, &mut gsa); + let size = window.inner_size(); + surface + .resize( + NonZeroU32::new(size.width).unwrap(), + NonZeroU32::new(size.height).unwrap(), + ) + .unwrap(); + + *scale = min( + size.width / SCREEN_WIDTH as u32, + size.height / SCREEN_HEIGHT as u32, + ) as usize; + *off_x = (size.width as usize - SCREEN_WIDTH * *scale) / 2; + *off_y = (size.height as usize - SCREEN_HEIGHT * *scale) / 2; + + let mut buf = surface.buffer_mut().unwrap(); + render_to_window( + &mut buf, + &gsa, + IVec2 { + x: size.width as i32, + y: size.height as i32, + }, + ); + buf.present().unwrap(); + frames_since_last_second += 1; + last_frame += target_frame_duration; + } + } + _ => {} + } + }); } diff --git a/src/state.rs b/src/state.rs index 5f75783..7c57989 100644 --- a/src/state.rs +++ b/src/state.rs @@ -2,24 +2,19 @@ use crate::buttons::Buttons; use crate::{Gsa, TILEMAP_MAX_SIZE}; use gilrs::{Button, EventType, Gilrs}; use glam::IVec2; -use skunk2d::{Event, IRect, Image8, WindowState}; use std::rc::Rc; pub struct State { pub(crate) gsa: Gsa, pub(crate) game: TGame, pub(crate) update_fn: fn(game: &mut TGame, gsa: &mut Gsa), - pub(crate) tileset: Rc, + pub(crate) tileset: Vec, pub(crate) first: bool, pub(crate) gilrs: Gilrs, } -impl skunk2d::Game for State { - fn update(&mut self, window_state: &mut WindowState) { - if self.first { - window_state.toggle_fullscreen(); - self.first = false; - } +impl State { + fn update(&mut self) { let mut new_buttons = self.gsa.input.down; while let Some(event) = self.gilrs.next_event() { match event { @@ -72,6 +67,7 @@ impl skunk2d::Game for State { }; //todo: don't if not updated... how check? <_< + /* for i in 0..=255 { window_state.set_palette( i, @@ -80,13 +76,12 @@ impl skunk2d::Game for State { self.gsa.palette[i as usize].b, ); } + */ (self.update_fn)(&mut self.game, &mut self.gsa); } - fn on_event(&mut self, _window_state: &mut WindowState, _event: Event) {} - - fn draw(&self, target: &mut Image8) { - target.clear(); + fn draw(&self) { + //target.clear(); for map in &self.gsa.maps { let tcmult = if map.half_tile { 2 } else { 1 }; @@ -103,6 +98,7 @@ impl skunk2d::Game for State { if tile > 0 { let ty = tile / 0x100; let tx = tile % 0x100; + /* target.draw_image_partial( IVec2 { x: x * tilesize - map.scroll.x, @@ -120,6 +116,8 @@ impl skunk2d::Game for State { }, }, ) + + */ } } } @@ -129,6 +127,7 @@ impl skunk2d::Game for State { if sprite.tile > 0 { let ty = sprite.tile / 0x100; let tx = sprite.tile % 0x100; + /* target.draw_image_partial( sprite.pos, &self.tileset, @@ -140,6 +139,8 @@ impl skunk2d::Game for State { size: IVec2 { x: 16, y: 16 }, }, ); + + */ } } } diff --git a/src/tilemap.rs b/src/tilemap.rs index 21152ef..8ff4448 100644 --- a/src/tilemap.rs +++ b/src/tilemap.rs @@ -4,7 +4,7 @@ use glam::IVec2; /// Tilemap which will be rendered on screen pub struct Tilemap { /// Tiles in idx, accessible via \[x\]\[y\], x and y 0..[TILEMAP_MAX_SIZE] - pub tiles: Box<[[u16; TILEMAP_MAX_SIZE]; TILEMAP_MAX_SIZE]>, + pub tiles: Vec>, /// Camera scroll (negative draw offset) for rendering pub scroll: IVec2, /// Are tiles indices half-tile indices? @@ -13,8 +13,10 @@ pub struct Tilemap { impl Default for Tilemap { fn default() -> Self { + let row = vec![0u16; TILEMAP_MAX_SIZE]; + let tiles = vec![row; TILEMAP_MAX_SIZE]; Self { - tiles: [[0u16; TILEMAP_MAX_SIZE]; TILEMAP_MAX_SIZE].into(), + tiles, scroll: IVec2::ZERO, half_tile: false, } diff --git a/src/tileset.rs b/src/tileset.rs new file mode 100644 index 0000000..04e5c19 --- /dev/null +++ b/src/tileset.rs @@ -0,0 +1,25 @@ +use crate::{Rgb, TILESET_SIZE, TILE_SIZE}; + +pub(crate) fn load_tileset(data: &[u8]) -> (Vec, [Rgb; 256]) { + let mut options = gif::DecodeOptions::new(); + options.set_color_output(gif::ColorOutput::Indexed); + let mut decoder = options.read_info(data).unwrap(); + + assert_eq!(decoder.width(), (TILESET_SIZE * TILE_SIZE) as u16); + assert_eq!(decoder.height(), (TILESET_SIZE * TILE_SIZE) as u16); + + decoder.next_frame_info().unwrap(); + let mut tileset = Vec::new(); + tileset.resize(TILESET_SIZE * TILE_SIZE * TILESET_SIZE * TILE_SIZE, 0); + decoder.read_into_buffer(tileset.as_mut_slice()).unwrap(); + let pal = decoder.palette().unwrap(); + let mut palette = [Rgb { r: 0, g: 0, b: 0 }; 256]; + for i in 0..pal.len() / 3 { + palette[i] = Rgb { + r: pal[i * 3 + 2], + g: pal[i * 3 + 1], + b: pal[i * 3], + }; + } + (tileset, palette) +}