From 02f0f7f3145c9f0a9efbe4b7c7ffb6c26a21be9c Mon Sep 17 00:00:00 2001 From: Alula <6276139+alula@users.noreply.github.com> Date: Thu, 28 Jan 2021 23:33:43 +0100 Subject: [PATCH] initial sdl2 port --- Cargo.toml | 2 +- src/bmfont_renderer.rs | 2 - src/common.rs | 37 ++- src/engine_constants/mod.rs | 2 +- src/framework/backend.rs | 39 +++- src/framework/backend_sdl2.rs | 420 ++++++++++++++++++++++++++++++++++ src/framework/context.rs | 21 +- src/framework/error.rs | 2 - src/framework/filesystem.rs | 28 +-- src/framework/graphics.rs | 33 ++- src/framework/image.rs | 15 -- src/framework/keyboard.rs | 146 +++++++++++- src/framework/mod.rs | 3 +- src/lib.rs | 86 ++++++- src/scene/game_scene.rs | 32 +-- src/shared_game_state.rs | 15 +- src/text_script.rs | 6 +- src/texture_set.rs | 132 +++++++---- 18 files changed, 864 insertions(+), 157 deletions(-) delete mode 100644 src/framework/image.rs diff --git a/Cargo.toml b/Cargo.toml index ff66501..cf1fb71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" pretty_env_logger = "0.4.0" -sdl2 = { version = "0.34.3", optional = true } +sdl2 = { version = "0.34.3", optional = true, features = ["unsafe_textures"] } serde = { version = "1", features = ["derive"] } serde_derive = "1" serde_yaml = "0.8" diff --git a/src/bmfont_renderer.rs b/src/bmfont_renderer.rs index a4c68f6..396b886 100644 --- a/src/bmfont_renderer.rs +++ b/src/bmfont_renderer.rs @@ -28,7 +28,6 @@ impl BMFontRenderer { let font = BMFont::load_from(filesystem::open(ctx, &full_path)?)?; let mut pages = Vec::new(); - println!("stem: {:?}", stem); let (zeros, _, _) = FILE_TYPES .iter() .map(|ext| (1, ext, format!("{}_0{}", stem.to_string_lossy(), ext))) @@ -41,7 +40,6 @@ impl BMFontRenderer { for i in 0..font.pages { let page_path = format!("{}_{:02$}", stem.to_string_lossy(), i, zeros); - println!("x: {}", &page_path); pages.push(page_path); } diff --git a/src/common.rs b/src/common.rs index 41f2d05..b2acea1 100644 --- a/src/common.rs +++ b/src/common.rs @@ -234,6 +234,22 @@ impl Direction { } } +#[derive(Debug, Clone, Copy)] +pub struct Point { + pub x: T, + pub y: T, +} + +impl Point { + #[inline(always)] + pub fn new(x: T, y: T) -> Point { + Point { + x, + y, + } + } +} + #[derive(Debug, Clone, Copy)] pub struct Rect { pub left: T, @@ -243,6 +259,7 @@ pub struct Rect { } impl Rect { + #[inline(always)] pub fn new(left: T, top: T, right: T, bottom: T) -> Rect { Rect { left, @@ -252,6 +269,7 @@ impl Rect { } } + #[inline(always)] pub fn new_size(x: T, y: T, width: T, height: T) -> Rect { Rect { left: x, @@ -260,6 +278,7 @@ impl Rect { bottom: y.add(height), } } + pub fn width(&self) -> T { if let Some(Ordering::Greater) = self.left.partial_cmp(&self.right) { self.left.sub(self.right) @@ -392,30 +411,30 @@ impl Color { } /// Create a new `Color` from four `u8`'s in the range `[0-255]` - pub const fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Color { + pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Color { Color::from((r, g, b, a)) } /// Create a new `Color` from three u8's in the range `[0-255]`, /// with the alpha component fixed to 255 (opaque) - pub const fn from_rgb(r: u8, g: u8, b: u8) -> Color { + pub fn from_rgb(r: u8, g: u8, b: u8) -> Color { Color::from((r, g, b)) } /// Return a tuple of four `u8`'s in the range `[0-255]` with the `Color`'s /// components. - pub const fn to_rgba(self) -> (u8, u8, u8, u8) { + pub fn to_rgba(self) -> (u8, u8, u8, u8) { self.into() } /// Return a tuple of three `u8`'s in the range `[0-255]` with the `Color`'s /// components. - pub const fn to_rgb(self) -> (u8, u8, u8) { + pub fn to_rgb(self) -> (u8, u8, u8) { self.into() } /// Convert a packed `u32` containing `0xRRGGBBAA` into a `Color` - pub const fn from_rgba_u32(c: u32) -> Color { + pub fn from_rgba_u32(c: u32) -> Color { let c = c.to_be_bytes(); Color::from((c[0], c[1], c[2], c[3])) @@ -423,21 +442,21 @@ impl Color { /// Convert a packed `u32` containing `0x00RRGGBB` into a `Color`. /// This lets you do things like `Color::from_rgb_u32(0xCD09AA)` easily if you want. - pub const fn from_rgb_u32(c: u32) -> Color { + pub fn from_rgb_u32(c: u32) -> Color { let c = c.to_be_bytes(); Color::from((c[1], c[2], c[3])) } /// Convert a `Color` into a packed `u32`, containing `0xRRGGBBAA` as bytes. - pub const fn to_rgba_u32(self) -> u32 { + pub fn to_rgba_u32(self) -> u32 { let (r, g, b, a): (u8, u8, u8, u8) = self.into(); u32::from_be_bytes([r, g, b, a]) } /// Convert a `Color` into a packed `u32`, containing `0x00RRGGBB` as bytes. - pub const fn to_rgb_u32(self) -> u32 { + pub fn to_rgb_u32(self) -> u32 { let (r, g, b, _a): (u8, u8, u8, u8) = self.into(); u32::from_be_bytes([0, r, g, b]) @@ -515,4 +534,4 @@ impl From for [f32; 4] { fn from(color: Color) -> Self { [color.r, color.g, color.b, color.a] } -} \ No newline at end of file +} diff --git a/src/engine_constants/mod.rs b/src/engine_constants/mod.rs index b9d3a79..ad5f6fc 100644 --- a/src/engine_constants/mod.rs +++ b/src/engine_constants/mod.rs @@ -216,7 +216,7 @@ pub struct EngineConstants { pub world: WorldConsts, pub npc: NPCConsts, pub weapon: WeaponConsts, - pub tex_sizes: CaseInsensitiveHashMap<(usize, usize)>, + pub tex_sizes: CaseInsensitiveHashMap<(u16, u16)>, pub textscript: TextScriptConsts, pub title: TitleConsts, pub font_path: String, diff --git a/src/framework/backend.rs b/src/framework/backend.rs index c20c7da..1fcfda4 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -1,9 +1,38 @@ +use crate::common::{Color, Rect, Point}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; use crate::Game; -pub(crate) trait Backend { - fn create_event_loop(&self) -> Box; +pub trait Backend { + fn create_event_loop(&self) -> GameResult>; } -pub(crate) trait BackendEventLoop { - fn run(&self, game: &mut Game); -} \ No newline at end of file +pub trait BackendEventLoop { + fn run(&mut self, game: &mut Game, ctx: &mut Context); + + fn new_renderer(&self) -> GameResult>; +} + +pub trait BackendRenderer { + fn clear(&mut self, color: Color); + + fn present(&mut self) -> GameResult; + + fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult>; +} + +pub trait BackendTexture { + fn dimensions(&self) -> (u16, u16); + fn add(&mut self, command: SpriteBatchCommand); + fn clear(&mut self); + fn draw(&mut self) -> GameResult; +} + +pub fn init_backend() -> GameResult> { + crate::framework::backend_sdl2::SDL2Backend::new() +} + +pub enum SpriteBatchCommand { + DrawRect(Rect, Rect), + DrawRectTinted(Rect, Rect, Color), +} diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index e69de29..a3c0023 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -0,0 +1,420 @@ +use core::mem; +use std::cell::RefCell; +use std::rc::Rc; + +use sdl2::{EventPump, keyboard, pixels, Sdl}; +use sdl2::event::{Event, WindowEvent}; +use sdl2::keyboard::Scancode; +use sdl2::pixels::PixelFormatEnum; +use sdl2::render::{BlendMode, Texture, TextureCreator, WindowCanvas}; +use sdl2::video::WindowContext; + +use crate::common::Color; +use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; +use crate::framework::context::Context; +use crate::framework::error::{GameError, GameResult}; +use crate::framework::keyboard::ScanCode; +use crate::Game; + +pub struct SDL2Backend { + context: Sdl, +} + +impl SDL2Backend { + pub fn new() -> GameResult> { + let context = sdl2::init().map_err(|e| GameError::WindowError(e))?; + + let backend = SDL2Backend { + context, + }; + + Ok(Box::new(backend)) + } +} + +impl Backend for SDL2Backend { + fn create_event_loop(&self) -> GameResult> { + SDL2EventLoop::new(&self.context) + } +} + +struct SDL2EventLoop { + event_pump: EventPump, + refs: Rc>, +} + +struct SDL2Context { + canvas: WindowCanvas, + texture_creator: TextureCreator, +} + +impl SDL2EventLoop { + pub fn new(sdl: &Sdl) -> GameResult> { + let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; + let video = sdl.video().map_err(|e| GameError::WindowError(e))?; + let window = video.window("Cave Story (doukutsu-rs)", 640, 480) + .position_centered() + .resizable() + .build() + .map_err(|e| GameError::WindowError(e.to_string()))?; + + let canvas = window.into_canvas() + .accelerated() + .present_vsync() + .build() + .map_err(|e| GameError::RenderError(e.to_string()))?; + + let texture_creator = canvas.texture_creator(); + + let event_loop = SDL2EventLoop { + event_pump, + refs: Rc::new(RefCell::new(SDL2Context { + canvas, + texture_creator, + })), + }; + + Ok(Box::new(event_loop)) + } +} + +impl BackendEventLoop for SDL2EventLoop { + fn run(&mut self, game: &mut Game, ctx: &mut Context) { + let state = unsafe { &mut *game.state.get() }; + + loop { + for event in self.event_pump.poll_iter() { + match event { + Event::Quit { .. } => { + state.shutdown(); + } + Event::Window { win_event, .. } => { + match win_event { + WindowEvent::Shown => {} + WindowEvent::Hidden => {} + WindowEvent::SizeChanged(width, height) => { + ctx.screen_size = (width.max(1) as f32, height.max(1) as f32); + state.handle_resize(ctx); + } + _ => {} + } + } + Event::KeyDown { scancode, repeat, .. } => { + if let Some(scancode) = scancode { + if let Some(drs_scan) = conv_scancode(scancode) { + game.key_down_event(drs_scan, repeat); + ctx.keyboard_context.set_key(drs_scan, true); + } + } + } + Event::KeyUp { scancode, .. } => { + if let Some(scancode) = scancode { + if let Some(drs_scan) = conv_scancode(scancode) { + ctx.keyboard_context.set_key(drs_scan, false); + } + } + } + _ => {} + } + } + + game.update(ctx).unwrap(); + + if state.shutdown { + log::info!("Shutting down..."); + break; + } + + if state.next_scene.is_some() { + mem::swap(&mut game.scene, &mut state.next_scene); + state.next_scene = None; + + game.scene.as_mut().unwrap().init(state, ctx).unwrap(); + game.loops = 0; + state.frame_time = 0.0; + } + + game.draw(ctx).unwrap(); + } + } + + fn new_renderer(&self) -> GameResult> { + SDL2Renderer::new(self.refs.clone()) + } +} + +struct SDL2Renderer { + refs: Rc>, +} + +impl SDL2Renderer { + pub fn new(refs: Rc>) -> GameResult> { + Ok(Box::new(SDL2Renderer { + refs, + })) + } +} + +fn to_sdl(color: Color) -> pixels::Color { + let (r, g, b, a) = color.to_rgba(); + pixels::Color::RGBA(r, g, b, a) +} + +impl BackendRenderer for SDL2Renderer { + fn clear(&mut self, color: Color) { + let mut refs = self.refs.borrow_mut(); + + refs.canvas.set_draw_color(to_sdl(color)); + refs.canvas.clear(); + } + + fn present(&mut self) -> GameResult { + let mut refs = self.refs.borrow_mut(); + + refs.canvas.present(); + + Ok(()) + } + + fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult> { + let mut refs = self.refs.borrow_mut(); + + let mut texture = refs.texture_creator + .create_texture_streaming(PixelFormatEnum::RGBA32, width as u32, height as u32) + .map_err(|e| GameError::RenderError(e.to_string()))?; + + texture.set_blend_mode(BlendMode::Blend); + texture.with_lock(None, |buffer: &mut [u8], pitch: usize| { + for y in 0..(height as usize) { + for x in 0..(width as usize) { + let offset = y * pitch + x * 4; + let data_offset = (y * width as usize + x) * 4; + + buffer[offset] = data[data_offset]; + buffer[offset + 1] = data[data_offset + 1]; + buffer[offset + 2] = data[data_offset + 2]; + buffer[offset + 3] = data[data_offset + 3]; + } + } + }).map_err(|e| GameError::RenderError(e.to_string()))?; + + return Ok(Box::new(SDL2Texture { + refs: self.refs.clone(), + texture: Some(texture), + width, + height, + commands: vec![], + })); + } +} + +struct SDL2Texture { + refs: Rc>, + texture: Option, + width: u16, + height: u16, + commands: Vec, +} + +impl BackendTexture for SDL2Texture { + fn dimensions(&self) -> (u16, u16) { + (self.width, self.height) + } + + fn add(&mut self, command: SpriteBatchCommand) { + self.commands.push(command); + } + + fn clear(&mut self) { + self.commands.clear(); + } + + fn draw(&mut self) -> GameResult { + match self.texture.as_mut() { + None => Ok(()), + Some(texture) => { + let mut refs = self.refs.borrow_mut(); + for command in self.commands.iter() { + match command { + SpriteBatchCommand::DrawRect(src, dest) => { + texture.set_color_mod(255, 255, 255); + texture.set_alpha_mod(255); + + refs.canvas.copy(texture, + Some(sdl2::rect::Rect::new(src.left as i32, src.top as i32, src.width() as u32, src.height() as u32)), + Some(sdl2::rect::Rect::new(dest.left as i32, dest.top as i32, dest.width() as u32, dest.height() as u32))) + .map_err(|e| GameError::RenderError(e.to_string()))?; + } + SpriteBatchCommand::DrawRectTinted(src, dest, color) => { + let (r, g, b, a) = color.to_rgba(); + texture.set_color_mod(r, g, b); + texture.set_alpha_mod(a); + + refs.canvas.copy(texture, + Some(sdl2::rect::Rect::new(src.left as i32, src.top as i32, src.width() as u32, src.height() as u32)), + Some(sdl2::rect::Rect::new(dest.left as i32, dest.top as i32, dest.width() as u32, dest.height() as u32))) + .map_err(|e| GameError::RenderError(e.to_string()))?; + } + } + } + + Ok(()) + } + } + } +} + +impl Drop for SDL2Texture { + fn drop(&mut self) { + let mut texture_opt = None; + mem::swap(&mut self.texture, &mut texture_opt); + + if let Some(texture) = texture_opt { + unsafe { texture.destroy(); } + } + } +} + +fn conv_scancode(code: keyboard::Scancode) -> Option { + match code { + Scancode::A => Some(ScanCode::A), + Scancode::B => Some(ScanCode::B), + Scancode::C => Some(ScanCode::C), + Scancode::D => Some(ScanCode::D), + Scancode::E => Some(ScanCode::E), + Scancode::F => Some(ScanCode::F), + Scancode::G => Some(ScanCode::G), + Scancode::H => Some(ScanCode::H), + Scancode::I => Some(ScanCode::I), + Scancode::J => Some(ScanCode::J), + Scancode::K => Some(ScanCode::K), + Scancode::L => Some(ScanCode::L), + Scancode::M => Some(ScanCode::M), + Scancode::N => Some(ScanCode::N), + Scancode::O => Some(ScanCode::O), + Scancode::P => Some(ScanCode::P), + Scancode::Q => Some(ScanCode::Q), + Scancode::R => Some(ScanCode::R), + Scancode::S => Some(ScanCode::S), + Scancode::T => Some(ScanCode::T), + Scancode::U => Some(ScanCode::U), + Scancode::V => Some(ScanCode::V), + Scancode::W => Some(ScanCode::W), + Scancode::X => Some(ScanCode::X), + Scancode::Y => Some(ScanCode::Y), + Scancode::Z => Some(ScanCode::Z), + Scancode::Num1 => Some(ScanCode::Key1), + Scancode::Num2 => Some(ScanCode::Key2), + Scancode::Num3 => Some(ScanCode::Key3), + Scancode::Num4 => Some(ScanCode::Key4), + Scancode::Num5 => Some(ScanCode::Key5), + Scancode::Num6 => Some(ScanCode::Key6), + Scancode::Num7 => Some(ScanCode::Key7), + Scancode::Num8 => Some(ScanCode::Key8), + Scancode::Num9 => Some(ScanCode::Key9), + Scancode::Num0 => Some(ScanCode::Key0), + Scancode::Return => Some(ScanCode::Return), + Scancode::Escape => Some(ScanCode::Escape), + Scancode::Backspace => Some(ScanCode::Backspace), + Scancode::Tab => Some(ScanCode::Tab), + Scancode::Space => Some(ScanCode::Space), + Scancode::Minus => Some(ScanCode::Minus), + Scancode::Equals => Some(ScanCode::Equals), + Scancode::LeftBracket => Some(ScanCode::LBracket), + Scancode::RightBracket => Some(ScanCode::RBracket), + Scancode::Backslash => Some(ScanCode::Backslash), + Scancode::NonUsHash => Some(ScanCode::NonUsHash), + Scancode::Semicolon => Some(ScanCode::Semicolon), + Scancode::Apostrophe => Some(ScanCode::Apostrophe), + Scancode::Grave => Some(ScanCode::Grave), + Scancode::Comma => Some(ScanCode::Comma), + Scancode::Period => Some(ScanCode::Period), + Scancode::Slash => Some(ScanCode::Slash), + Scancode::CapsLock => Some(ScanCode::Capslock), + Scancode::F1 => Some(ScanCode::F1), + Scancode::F2 => Some(ScanCode::F2), + Scancode::F3 => Some(ScanCode::F3), + Scancode::F4 => Some(ScanCode::F4), + Scancode::F5 => Some(ScanCode::F5), + Scancode::F6 => Some(ScanCode::F6), + Scancode::F7 => Some(ScanCode::F7), + Scancode::F8 => Some(ScanCode::F8), + Scancode::F9 => Some(ScanCode::F9), + Scancode::F10 => Some(ScanCode::F10), + Scancode::F11 => Some(ScanCode::F11), + Scancode::F12 => Some(ScanCode::F12), + Scancode::PrintScreen => Some(ScanCode::Sysrq), + Scancode::ScrollLock => Some(ScanCode::Scrolllock), + Scancode::Pause => Some(ScanCode::Pause), + Scancode::Insert => Some(ScanCode::Insert), + Scancode::Home => Some(ScanCode::Home), + Scancode::PageUp => Some(ScanCode::PageUp), + Scancode::Delete => Some(ScanCode::Delete), + Scancode::End => Some(ScanCode::End), + Scancode::PageDown => Some(ScanCode::PageDown), + Scancode::Right => Some(ScanCode::Right), + Scancode::Left => Some(ScanCode::Left), + Scancode::Down => Some(ScanCode::Down), + Scancode::Up => Some(ScanCode::Up), + Scancode::NumLockClear => Some(ScanCode::Numlock), + Scancode::KpDivide => Some(ScanCode::NumpadDivide), + Scancode::KpMultiply => Some(ScanCode::NumpadMultiply), + Scancode::KpMinus => Some(ScanCode::NumpadSubtract), + Scancode::KpPlus => Some(ScanCode::NumpadAdd), + Scancode::KpEnter => Some(ScanCode::NumpadEnter), + Scancode::Kp1 => Some(ScanCode::Numpad1), + Scancode::Kp2 => Some(ScanCode::Numpad2), + Scancode::Kp3 => Some(ScanCode::Numpad3), + Scancode::Kp4 => Some(ScanCode::Numpad4), + Scancode::Kp5 => Some(ScanCode::Numpad5), + Scancode::Kp6 => Some(ScanCode::Numpad6), + Scancode::Kp7 => Some(ScanCode::Numpad7), + Scancode::Kp8 => Some(ScanCode::Numpad8), + Scancode::Kp9 => Some(ScanCode::Numpad9), + Scancode::Kp0 => Some(ScanCode::Numpad0), + Scancode::NonUsBackslash => Some(ScanCode::NonUsBackslash), + Scancode::Application => Some(ScanCode::Apps), + Scancode::Power => Some(ScanCode::Power), + Scancode::KpEquals => Some(ScanCode::NumpadEquals), + Scancode::F13 => Some(ScanCode::F13), + Scancode::F14 => Some(ScanCode::F14), + Scancode::F15 => Some(ScanCode::F15), + Scancode::F16 => Some(ScanCode::F16), + Scancode::F17 => Some(ScanCode::F17), + Scancode::F18 => Some(ScanCode::F18), + Scancode::F19 => Some(ScanCode::F19), + Scancode::F20 => Some(ScanCode::F20), + Scancode::F21 => Some(ScanCode::F21), + Scancode::F22 => Some(ScanCode::F22), + Scancode::F23 => Some(ScanCode::F23), + Scancode::F24 => Some(ScanCode::F24), + Scancode::Stop => Some(ScanCode::Stop), + Scancode::Cut => Some(ScanCode::Cut), + Scancode::Copy => Some(ScanCode::Copy), + Scancode::Paste => Some(ScanCode::Paste), + Scancode::Mute => Some(ScanCode::Mute), + Scancode::VolumeUp => Some(ScanCode::VolumeUp), + Scancode::VolumeDown => Some(ScanCode::VolumeDown), + Scancode::KpComma => Some(ScanCode::NumpadComma), + Scancode::SysReq => Some(ScanCode::Sysrq), + Scancode::Return2 => Some(ScanCode::NumpadEnter), + Scancode::LCtrl => Some(ScanCode::LControl), + Scancode::LShift => Some(ScanCode::LShift), + Scancode::LAlt => Some(ScanCode::LAlt), + Scancode::LGui => Some(ScanCode::LWin), + Scancode::RCtrl => Some(ScanCode::RControl), + Scancode::RShift => Some(ScanCode::RShift), + Scancode::RAlt => Some(ScanCode::RAlt), + Scancode::RGui => Some(ScanCode::RWin), + Scancode::AudioNext => Some(ScanCode::NextTrack), + Scancode::AudioPrev => Some(ScanCode::PrevTrack), + Scancode::AudioStop => Some(ScanCode::MediaStop), + Scancode::AudioPlay => Some(ScanCode::PlayPause), + Scancode::AudioMute => Some(ScanCode::Mute), + Scancode::MediaSelect => Some(ScanCode::MediaSelect), + Scancode::Mail => Some(ScanCode::Mail), + Scancode::Calculator => Some(ScanCode::Calculator), + Scancode::Sleep => Some(ScanCode::Sleep), + _ => None, + } +} diff --git a/src/framework/context.rs b/src/framework/context.rs index 1ee64f9..0868543 100644 --- a/src/framework/context.rs +++ b/src/framework/context.rs @@ -1,18 +1,33 @@ +use crate::framework::backend::{Backend, init_backend, BackendRenderer}; +use crate::framework::error::GameResult; use crate::framework::filesystem::Filesystem; use crate::Game; +use crate::framework::keyboard::KeyboardContext; pub struct Context { pub(crate) filesystem: Filesystem, + pub(crate) renderer: Option>, + pub(crate) keyboard_context: KeyboardContext, + pub(crate) screen_size: (f32, f32), } impl Context { pub fn new() -> Context { Context { filesystem: Filesystem::new(), + renderer: None, + keyboard_context: KeyboardContext::new(), + screen_size: (320.0, 240.0), } } - pub fn run(&mut self, game: &mut Game) { - loop {} + pub fn run(&mut self, game: &mut Game) -> GameResult { + let backend = init_backend()?; + let mut event_loop = backend.create_event_loop()?; + self.renderer = Some(event_loop.new_renderer()?); + + event_loop.run(game, self); + + Ok(()) } -} \ No newline at end of file +} diff --git a/src/framework/error.rs b/src/framework/error.rs index 88f53e7..52b19d5 100644 --- a/src/framework/error.rs +++ b/src/framework/error.rs @@ -64,9 +64,7 @@ impl fmt::Display for GameError { impl Error for GameError { fn cause(&self) -> Option<&dyn Error> { match *self { - GameError::WindowCreationError(ref e) => Some(&**e), GameError::IOError(ref e) => Some(&**e), - GameError::ShaderProgramError(ref e) => Some(e), _ => None, } } diff --git a/src/framework/filesystem.rs b/src/framework/filesystem.rs index 306f10e..aff2397 100644 --- a/src/framework/filesystem.rs +++ b/src/framework/filesystem.rs @@ -241,6 +241,11 @@ impl Filesystem { pub fn mount_vfs(&mut self, vfs: Box) { self.vfs.push_back(vfs); } + + + pub fn mount_user_vfs(&mut self, vfs: Box) { + self.user_vfs.push_back(vfs); + } } /// Opens the given path and returns the resulting `File` @@ -340,22 +345,6 @@ pub fn read_dir>( ctx.filesystem.read_dir(path) } -/// Prints the contents of all data directories. -/// Useful for debugging. -pub fn print_all(ctx: &mut Context) { - ctx.filesystem.print_all() -} - -/// Outputs the contents of all data directories, -/// using the "info" log level of the `log` crate. -/// Useful for debugging. -/// -/// See the [`logging` example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs) -/// for how to collect log information. -pub fn log_all(ctx: &mut Context) { - ctx.filesystem.log_all() -} - /// Adds the given (absolute) path to the list of directories /// it will search to look for resources. /// @@ -370,4 +359,9 @@ pub fn mount(ctx: &mut Context, path: &path::Path, readonly: bool) { /// Adds a VFS to the list of resource search locations. pub fn mount_vfs(ctx: &mut Context, vfs: Box) { ctx.filesystem.mount_vfs(vfs) -} \ No newline at end of file +} + +/// Adds a VFS to the list of user data search locations. +pub fn mount_user_vfs(ctx: &mut Context, vfs: Box) { + ctx.filesystem.mount_user_vfs(vfs) +} diff --git a/src/framework/graphics.rs b/src/framework/graphics.rs index 0c08fd8..a596e91 100644 --- a/src/framework/graphics.rs +++ b/src/framework/graphics.rs @@ -1,6 +1,7 @@ use crate::common::Color; use crate::framework::context::Context; -use crate::framework::error::GameResult; +use crate::framework::error::{GameResult, GameError}; +use crate::framework::backend::BackendTexture; pub enum FilterMode { Nearest, @@ -13,12 +14,32 @@ impl Canvas { } -pub fn clear(ctx: &mut Context, color: Color) {} +pub fn clear(ctx: &mut Context, color: Color) { + if let Some(renderer) = ctx.renderer.as_mut() { + renderer.clear(color) + } +} + +pub fn present(ctx: &mut Context) -> GameResult { + if let Some(renderer) = ctx.renderer.as_mut() { + renderer.present()?; + } -pub fn present(ctx: &mut Context) -> GameResult<()> { Ok(()) } -pub fn drawable_size(ctx: &mut Context) -> (f32, f32) { - (320.0, 240.0) -} \ No newline at end of file +pub fn renderer_initialized(ctx: &mut Context) -> bool { + ctx.renderer.is_some() +} + +pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) -> GameResult> { + if let Some(renderer) = ctx.renderer.as_mut() { + return renderer.create_texture(width, height, data); + } + + Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string())) +} + +pub fn screen_size(ctx: &mut Context) -> (f32, f32) { + ctx.screen_size +} diff --git a/src/framework/image.rs b/src/framework/image.rs deleted file mode 100644 index 84d14f1..0000000 --- a/src/framework/image.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::framework::context::Context; -use crate::framework::error::GameResult; - -pub struct Image {} - -impl Image { - pub fn from_rgba8( - context: &mut Context, - width: u16, - height: u16, - rgba: &[u8], - ) -> GameResult { - Ok(Image {}) - } -} \ No newline at end of file diff --git a/src/framework/keyboard.rs b/src/framework/keyboard.rs index 427808a..432f4ef 100644 --- a/src/framework/keyboard.rs +++ b/src/framework/keyboard.rs @@ -1,6 +1,12 @@ +use std::collections::HashSet; + +use serde::{Deserialize, Serialize}; + +use crate::bitfield; use crate::framework::context::Context; #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] +#[derive(Serialize, Deserialize)] #[repr(u32)] pub enum ScanCode { /// The '1' key over the letters. @@ -109,6 +115,8 @@ pub enum ScanCode { /// The "Compose" key on Linux. Compose, + NonUsHash, + NonUsBackslash, Caret, Numlock, @@ -141,6 +149,7 @@ pub enum ScanCode { Backslash, Calculator, Capital, + Capslock, Colon, Comma, Convert, @@ -176,6 +185,7 @@ pub enum ScanCode { RControl, RShift, RWin, + Scrolllock, Semicolon, Slash, Sleep, @@ -200,6 +210,136 @@ pub enum ScanCode { Cut, } -pub fn is_key_pressed(ctx: &mut Context, code: ScanCode) -> bool { - false -} \ No newline at end of file +bitfield! { + /// Bitflags describing the state of keyboard modifiers, such as `Control` or `Shift`. + #[derive(Debug, Copy, Clone)] + pub struct KeyMods(u8); + + /// No modifiers; equivalent to `KeyMods::default()` and + /// [`KeyMods::empty()`](struct.KeyMods.html#method.empty). + /// Left or right Shift key. + pub shift, set_shift: 0; + /// Left or right Control key. + pub ctrl, set_ctrl: 1; + /// Left or right Alt key. + pub alt, set_alt: 2; + /// Left or right Win/Cmd/equivalent key. + pub win, set_win: 3; +} + +#[derive(Clone, Debug)] +pub struct KeyboardContext { + active_modifiers: KeyMods, + pressed_keys_set: HashSet, + last_pressed: Option, + current_pressed: Option, +} + +impl KeyboardContext { + pub(crate) fn new() -> Self { + Self { + active_modifiers: KeyMods(0), + pressed_keys_set: HashSet::with_capacity(256), + last_pressed: None, + current_pressed: None, + } + } + + pub(crate) fn set_key(&mut self, key: ScanCode, pressed: bool) { + if pressed { + let _ = self.pressed_keys_set.insert(key); + self.last_pressed = self.current_pressed; + self.current_pressed = Some(key); + } else { + let _ = self.pressed_keys_set.remove(&key); + self.current_pressed = None; + } + + self.set_key_modifier(key, pressed); + } + + /// Take a modifier key code and alter our state. + /// + /// Double check that this edge handling is necessary; + /// winit sounds like it should do this for us, + /// see https://docs.rs/winit/0.18.0/winit/struct.KeyboardInput.html#structfield.modifiers + /// + /// ...more specifically, we should refactor all this to consistant-ify events a bit and + /// make winit do more of the work. + /// But to quote Scott Pilgrim, "This is... this is... Booooooring." + fn set_key_modifier(&mut self, key: ScanCode, pressed: bool) { + if pressed { + match key { + ScanCode::LShift | ScanCode::RShift => self.active_modifiers.set_shift(true), + ScanCode::LControl | ScanCode::RControl => self.active_modifiers.set_ctrl(true), + ScanCode::LAlt | ScanCode::RAlt => self.active_modifiers.set_alt(true), + ScanCode::LWin | ScanCode::RWin => self.active_modifiers.set_win(true), + _ => (), + } + } else { + match key { + ScanCode::LShift | ScanCode::RShift => self.active_modifiers.set_shift(false), + ScanCode::LControl | ScanCode::RControl => self.active_modifiers.set_ctrl(false), + ScanCode::LAlt | ScanCode::RAlt => self.active_modifiers.set_alt(false), + ScanCode::LWin | ScanCode::RWin => self.active_modifiers.set_win(false), + _ => (), + } + } + } + + pub(crate) fn set_modifiers(&mut self, keymods: KeyMods) { + self.active_modifiers = keymods; + } + + pub(crate) fn is_key_pressed(&self, key: ScanCode) -> bool { + self.pressed_keys_set.contains(&key) + } + + pub(crate) fn is_key_repeated(&self) -> bool { + if self.last_pressed.is_some() { + self.last_pressed == self.current_pressed + } else { + false + } + } + + pub(crate) fn pressed_keys(&self) -> &HashSet { + &self.pressed_keys_set + } + + pub(crate) fn active_mods(&self) -> KeyMods { + self.active_modifiers + } +} + +impl Default for KeyboardContext { + fn default() -> Self { + Self::new() + } +} + +/// Checks if a key is currently pressed down. +pub fn is_key_pressed(ctx: &Context, key: ScanCode) -> bool { + ctx.keyboard_context.is_key_pressed(key) +} + +/// Checks if the last keystroke sent by the system is repeated, +/// like when a key is held down for a period of time. +pub fn is_key_repeated(ctx: &Context) -> bool { + ctx.keyboard_context.is_key_repeated() +} + +/// Returns a reference to the set of currently pressed keys. +pub fn pressed_keys(ctx: &Context) -> &HashSet { + ctx.keyboard_context.pressed_keys() +} + +/// Checks if keyboard modifier (or several) is active. +pub fn is_mod_active(ctx: &Context, keymods: KeyMods) -> bool { + (ctx.keyboard_context.active_mods().0 & keymods.0) != 0 +} + +/// Returns currently active keyboard modifiers. +pub fn active_mods(ctx: &Context) -> KeyMods { + ctx.keyboard_context.active_mods() +} diff --git a/src/framework/mod.rs b/src/framework/mod.rs index bad713e..af03421 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -4,7 +4,6 @@ pub mod context; pub mod error; pub mod filesystem; pub mod vfs; -pub mod image; pub mod graphics; pub mod keyboard; -pub mod backend_null; \ No newline at end of file +pub mod backend_null; diff --git a/src/lib.rs b/src/lib.rs index a289b4a..8e6246c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,21 +7,28 @@ extern crate strum; #[macro_use] extern crate strum_macros; -use std::env; +use core::mem; use std::cell::UnsafeCell; +use std::env; +use std::path::PathBuf; use std::time::Instant; +use directories::ProjectDirs; use log::*; use pretty_env_logger::env_logger::Env; +use crate::builtin_fs::BuiltinFS; +use crate::framework::backend::init_backend; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; +use crate::framework::filesystem::{mount_user_vfs, mount_vfs}; use crate::framework::graphics; use crate::framework::keyboard::ScanCode; +use crate::framework::vfs::PhysicalFS; use crate::scene::loading_scene::LoadingScene; use crate::scene::Scene; use crate::shared_game_state::{SharedGameState, TimingMode}; -use crate::texture_set::G_MAG; +use crate::texture_set::{G_MAG, I_MAG}; use crate::ui::UI; mod bmfont; @@ -62,7 +69,7 @@ mod texture_set; mod ui; mod weapon; -struct Game { +pub struct Game { scene: Option>, state: UnsafeCell, ui: UI, @@ -147,7 +154,10 @@ impl Game { n1 / n2 } else { 1.0 }; } - unsafe { G_MAG = if state_ref.settings.subpixel_coords { state_ref.scale } else { 1.0 } }; + unsafe { + G_MAG = if state_ref.settings.subpixel_coords { state_ref.scale } else { 1.0 }; + I_MAG = state_ref.scale; + } self.loops = 0; graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into()); @@ -259,18 +269,35 @@ pub fn init() -> GameResult { pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info")) .init(); - let mut resource_dir = env::current_exe()?; - - // Ditch the filename (if any) - if resource_dir.file_name().is_some() { - let _ = resource_dir.pop(); - } - resource_dir.push("data"); + let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") { + PathBuf::from(data_dir) + } else { + let mut resource_dir = env::current_exe()?; + if resource_dir.file_name().is_some() { + let _ = resource_dir.pop(); + } + resource_dir.push("data"); + resource_dir + }; info!("Resource directory: {:?}", resource_dir); info!("Initializing engine..."); - let mut context: Context = Context::new(); + let mut context = Context::new(); + mount_vfs(&mut context, Box::new(BuiltinFS::new())); + mount_vfs(&mut context, Box::new(PhysicalFS::new(&resource_dir, true))); + + + #[cfg(not(target_os = "android"))] + let project_dirs = match ProjectDirs::from("", "", "doukutsu-rs") { + Some(dirs) => dirs, + None => { + return Err(GameError::FilesystemError(String::from( + "No valid home directory path could be retrieved.", + ))); + } + }; + mount_user_vfs(&mut context, Box::new(PhysicalFS::new(project_dirs.data_local_dir(), false))); #[cfg(target_os = "android")] { @@ -284,4 +311,39 @@ pub fn init() -> GameResult { } } } + + let mut game = Game::new(&mut context)?; + let state_ref = unsafe { &mut *game.state.get() }; + #[cfg(feature = "scripting")] + { + unsafe { + state_ref.lua.update_refs(game.state.get(), &mut context as *mut Context); + } + } + + state_ref.next_scene = Some(Box::new(LoadingScene::new())); + context.run(&mut game); + + /* loop { + game.update(&mut context)?; + + if state_ref.shutdown { + log::info!("Shutting down..."); + break; + } + + if state_ref.next_scene.is_some() { + mem::swap(&mut game.scene, &mut state_ref.next_scene); + state_ref.next_scene = None; + + game.scene.as_mut().unwrap().init(state_ref, &mut context).unwrap(); + game.loops = 0; + state_ref.frame_time = 0.0; + } + + std::thread::sleep(std::time::Duration::from_millis(10)); + game.draw(&mut context)?; + }*/ + + Ok(()) } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 3f8cfab..8c744af 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -298,10 +298,10 @@ impl GameScene { let mut frame = tick; for x in (0..(state.canvas_size.0 as i32 + 16)).step_by(16) { - if frame > 15 { frame = 15; } else { frame += 1; } + if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { - rect.left = frame as u16 * 16; + rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; for y in (0..(state.canvas_size.1 as i32 + 16)).step_by(16) { @@ -318,10 +318,10 @@ impl GameScene { let mut frame = tick; for y in (0..(state.canvas_size.1 as i32 + 16)).step_by(16) { - if frame > 15 { frame = 15; } else { frame += 1; } + if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { - rect.left = frame as u16 * 16; + rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; for x in (0..(state.canvas_size.0 as i32 + 16)).step_by(16) { @@ -339,20 +339,20 @@ impl GameScene { let center_y = (state.canvas_size.1 / 2.0 - 8.0) as i32; let mut start_frame = tick; - for x in (0..(center_x + 16)).step_by(16) { + for x in 0..(center_x / 16 + 2) { let mut frame = start_frame; - for y in (0..(center_y + 16)).step_by(16) { - if frame > 15 { frame = 15; } else { frame += 1; } + for y in 0..(center_y / 16 + 1) { + if frame >= 15 { frame = 15; } else { frame += 1; } if frame >= 0 { - rect.left = frame as u16 * 16; + rect.left = frame.abs() as u16 * 16; rect.right = rect.left + 16; - batch.add_rect((center_x - x) as f32, (center_y + y) as f32, &rect); - batch.add_rect((center_x - x) as f32, (center_y - y) as f32, &rect); - batch.add_rect((center_x + x) as f32, (center_y + y) as f32, &rect); - batch.add_rect((center_x + x) as f32, (center_y - y) as f32, &rect); + batch.add_rect((center_x - x * 16) as f32, (center_y + y * 16) as f32, &rect); + batch.add_rect((center_x - x * 16) as f32, (center_y - y * 16) as f32, &rect); + batch.add_rect((center_x + x * 16) as f32, (center_y + y * 16) as f32, &rect); + batch.add_rect((center_x + x * 16) as f32, (center_y - y * 16) as f32, &rect); } } @@ -1213,13 +1213,13 @@ impl Scene for GameScene { //graphics::set_canvas(ctx, Some(&state.game_canvas)); self.draw_background(state, ctx)?; self.draw_tiles(state, ctx, TileLayer::Background)?; - if state.settings.shader_effects + /*if state.settings.shader_effects && self.stage.data.background_type != BackgroundType::Black && self.stage.data.background_type != BackgroundType::Outside && self.stage.data.background_type != BackgroundType::OutsideWind && self.stage.data.background.name() != "bkBlack" { self.draw_light_map(state, ctx)?; - } + }*/ self.boss.draw(state, ctx, &self.frame)?; for npc in self.npc_list.iter_alive() { @@ -1242,11 +1242,11 @@ impl Scene for GameScene { self.draw_tiles(state, ctx, TileLayer::Foreground)?; self.draw_tiles(state, ctx, TileLayer::Snack)?; self.draw_carets(state, ctx)?; - if state.settings.shader_effects + /*if state.settings.shader_effects && (self.stage.data.background_type == BackgroundType::Black || self.stage.data.background.name() == "bkBlack") { self.draw_light_map(state, ctx)?; - } + }*/ /*graphics::set_canvas(ctx, None); state.game_canvas.draw(ctx, DrawParam::new() diff --git a/src/shared_game_state.rs b/src/shared_game_state.rs index c7f5111..284624f 100644 --- a/src/shared_game_state.rs +++ b/src/shared_game_state.rs @@ -119,11 +119,6 @@ pub struct SharedGameState { impl SharedGameState { pub fn new(ctx: &mut Context) -> GameResult { - let screen_size = (320.0, 240.0); - let scale = *screen_size.1.div(230.0).floor().max(&1.0); - - let canvas_size = (screen_size.0 / scale, screen_size.1 / scale); - let mut constants = EngineConstants::defaults(); let mut base_path = "/"; let settings = Settings::load(ctx)?; @@ -170,9 +165,9 @@ impl SharedGameState { npc_super_pos: (0, 0), stages: Vec::with_capacity(96), frame_time: 0.0, - scale, - screen_size, - canvas_size, + scale: 2.0, + screen_size: (640.0, 480.0), + canvas_size: (320.0, 240.0), next_scene: None, textscript_vm: TextScriptVM::new(), season, @@ -280,12 +275,10 @@ impl SharedGameState { } pub fn handle_resize(&mut self, ctx: &mut Context) -> GameResult { - self.screen_size = graphics::drawable_size(ctx); + self.screen_size = graphics::screen_size(ctx); self.scale = self.screen_size.1.div(230.0).floor().max(1.0); self.canvas_size = (self.screen_size.0 / self.scale, self.screen_size.1 / self.scale); - //graphics::set_screen_coordinates(ctx, graphics::Rect::new(0.0, 0.0, self.screen_size.0, self.screen_size.1))?; - Ok(()) } diff --git a/src/text_script.rs b/src/text_script.rs index c709d3d..58340a3 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -9,8 +9,6 @@ use std::ops::Not; use std::str::FromStr; use byteorder::ReadBytesExt; - - use itertools::Itertools; use num_derive::FromPrimitive; use num_traits::{clamp, FromPrimitive}; @@ -21,6 +19,9 @@ use crate::encoding::{read_cur_shift_jis, read_cur_wtf8}; use crate::engine_constants::EngineConstants; use crate::entity::GameEntity; use crate::frame::UpdateTarget; +use crate::framework::context::Context; +use crate::framework::error::GameError::{InvalidValue, ParseError}; +use crate::framework::error::GameResult; use crate::npc::NPC; use crate::player::{ControlMode, TargetPlayer}; use crate::scene::game_scene::GameScene; @@ -28,7 +29,6 @@ use crate::scene::title_scene::TitleScene; use crate::shared_game_state::SharedGameState; use crate::str; use crate::weapon::WeaponType; -use crate::framework::error::GameError::ParseError; /// Engine's text script VM operation codes. #[derive(EnumString, Debug, FromPrimitive, PartialEq)] diff --git a/src/texture_set.rs b/src/texture_set.rs index 9368941..5beb9e2 100644 --- a/src/texture_set.rs +++ b/src/texture_set.rs @@ -6,19 +6,22 @@ use itertools::Itertools; use log::info; use crate::common; -use crate::common::FILE_TYPES; +use crate::common::{FILE_TYPES, Point, Rect}; use crate::engine_constants::EngineConstants; +use crate::framework::backend::{BackendTexture, SpriteBatchCommand}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; use crate::framework::filesystem; -use crate::framework::image::Image; +use crate::framework::graphics::{create_texture, FilterMode}; use crate::settings::Settings; use crate::shared_game_state::Season; use crate::str; +pub static mut I_MAG: f32 = 1.0; pub static mut G_MAG: f32 = 1.0; pub struct SizedBatch { + batch: Box, width: usize, height: usize, real_width: usize, @@ -60,15 +63,30 @@ impl SizedBatch { #[inline(always)] pub fn clear(&mut self) { - /*self.batch.clear();*/ + self.batch.clear(); } - pub fn add(&mut self, x: f32, y: f32) { - /*let param = DrawParam::new() - .dest(Point2::new(x, y)) - .scale(Vector2::new(self.scale_x, self.scale_y)); + pub fn add(&mut self, mut x: f32, mut y: f32) { + unsafe { + x = (x * G_MAG).floor() / G_MAG; + y = (y * G_MAG).floor() / G_MAG; + } + let mag = unsafe { I_MAG }; - self.batch.add(param);*/ + self.batch.add(SpriteBatchCommand::DrawRect( + Rect { + left: 0 as f32, + top: 0 as f32, + right: self.real_width as f32, + bottom: self.real_height as f32, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + self.width() as f32) * mag, + bottom: (y + self.height() as f32) * mag, + }, + )); } #[inline(always)] @@ -81,7 +99,7 @@ impl SizedBatch { self.add_rect_scaled_tinted(x, y, color, self.scale_x, self.scale_y, rect) } - pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect) { + pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; } @@ -90,45 +108,61 @@ impl SizedBatch { x = (x * G_MAG).floor() / G_MAG; y = (y * G_MAG).floor() / G_MAG; } + let mag = unsafe { I_MAG }; - /*let param = DrawParam::new() - .src(Rect::new(rect.left as f32 / self.width as f32, - rect.top as f32 / self.height as f32, - (rect.right - rect.left) as f32 / self.width as f32, - (rect.bottom - rect.top) as f32 / self.height as f32)) - .dest(mint::Point2 { x, y }) - .scale(Vector2::new(scale_x, scale_y)); - - self.batch.add(param);*/ + self.batch.add(SpriteBatchCommand::DrawRect( + Rect { + left: rect.left as f32 / scale_x, + top: rect.top as f32 / scale_y, + right: rect.right as f32 / scale_x, + bottom: rect.bottom as f32 / scale_y, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + rect.width() as f32) * mag, + bottom: (y + rect.height() as f32) * mag, + }, + )); } - pub fn add_rect_scaled_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), scale_x: f32, scale_y: f32, rect: &common::Rect) { + pub fn add_rect_scaled_tinted(&mut self, mut x: f32, mut y: f32, color: (u8, u8, u8, u8), mut scale_x: f32, mut scale_y: f32, rect: &common::Rect) { if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 { return; } - /*let param = DrawParam::new() - .color(color.into()) - .src(Rect::new(rect.left as f32 / self.width as f32, - rect.top as f32 / self.height as f32, - (rect.right - rect.left) as f32 / self.width as f32, - (rect.bottom - rect.top) as f32 / self.height as f32)) - .dest(mint::Point2 { x, y }) - .scale(Vector2::new(scale_x, scale_y)); + unsafe { + x = (x * G_MAG).floor() / G_MAG; + y = (y * G_MAG).floor() / G_MAG; + } + let mag = unsafe { I_MAG }; - self.batch.add(param);*/ + self.batch.add(SpriteBatchCommand::DrawRectTinted( + Rect { + left: rect.left as f32 / scale_x, + top: rect.top as f32 / scale_y, + right: rect.right as f32 / scale_x, + bottom: rect.bottom as f32 / scale_y, + }, + Rect { + left: x * mag, + top: y * mag, + right: (x + rect.width() as f32) * mag, + bottom: (y + rect.height() as f32) * mag, + }, + color.into(), + )); } #[inline(always)] pub fn draw(&mut self, ctx: &mut Context) -> GameResult { - //self.draw_filtered(FilterMode::Nearest, ctx) - Ok(()) + self.draw_filtered(FilterMode::Nearest, ctx) } pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult { - /*self.batch.set_filter(filter); - self.batch.draw(ctx, DrawParam::new())?; - self.batch.clear();*/ + ///self.batch.set_filter(filter); + self.batch.draw()?; + self.batch.clear(); Ok(()) } } @@ -166,7 +200,7 @@ impl TextureSet { } } - fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult { + fn load_image(&self, ctx: &mut Context, path: &str) -> GameResult> { let img = { let mut buf = [0u8; 8]; let mut reader = filesystem::open(ctx, path)?; @@ -182,7 +216,7 @@ impl TextureSet { }; let (width, height) = img.dimensions(); - Image::from_rgba8(ctx, width as u16, height as u16, img.as_ref()) + create_texture(ctx, width as u16, height as u16, &img) } pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult { @@ -197,26 +231,26 @@ impl TextureSet { info!("Loading texture: {}", path); - let image = self.load_image(ctx, &path)?; - let size = image.dimensions(); + let batch = self.load_image(ctx, &path)?; + let size = batch.dimensions(); - assert_ne!(size.w as isize, 0, "size.w == 0"); - assert_ne!(size.h as isize, 0, "size.h == 0"); + assert_ne!(size.0 as isize, 0, "size.width == 0"); + assert_ne!(size.1 as isize, 0, "size.height == 0"); - let dim = (size.w as usize, size.h as usize); - let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &dim); - let scale_x = orig_dimensions.0 as f32 / size.w; - let scale_y = orig_dimensions.0 as f32 / size.w; - let width = (size.w * scale_x) as usize; - let height = (size.h * scale_y) as usize; + let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &size); + let scale = orig_dimensions.0 as f32 / size.0 as f32; + let width = (size.0 as f32 * scale) as usize; + let height = (size.1 as f32 * scale) as usize; + println!("{} {} {} {}", size.0, size.1, width, height); Ok(SizedBatch { + batch, width, height, - scale_x, - scale_y, - real_width: size.w as usize, - real_height: size.h as usize, + scale_x: scale, + scale_y: scale, + real_width: size.0 as usize, + real_height: size.1 as usize, }) }