diff --git a/README.md b/README.md index 242ce9ce..a3aa6809 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,15 @@ The completed ports are: -| Game | Part of | Source Language | Source Libraries | Port Libraries | Tested on | -| :------------------------------------: | :--------------------------------------------------------------------------: | :-------------: | :------------------------------------------: | :------------------------------------------------: | :-------: | -| [Boing][Boing] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [ggez][ggez] 0.7 | Linux | -| [Catacomb II (SDL)][Catacomb II (SDL)] | - | C | [SDL 2][SDL 2] | [Rust-SDL2][Rust-SDL2] 0.35 | Linux | -| [Cavern][Cavern] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [Macroquad][Macroquad] 0.3 | Linux | -| [Soccer][Soccer] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [Fyrox][Fyrox] 0.26 | Linux | -| [Rusty Roguelike][Rusty Roguelike] | [Hands-on Rust: Effective Learning...][Hands-on Rust: Effective Learning...] | Rust | [bracket-lib][bracket-lib], [Legion][Legion] | [bracket-lib][bracket-lib], [Bevy][Bevy] (ECS) 0.7 | Linux | -| [Rusty Roguelike][Rusty Roguelike] | [Hands-on Rust: Effective Learning...][Hands-on Rust: Effective Learning...] | Rust | [bracket-lib][bracket-lib], [Legion][Legion] | [Macroquad][Macroquad] 0.3, [Legion][Legion] 0.3 | Linux | -| [Bunner][Bunner] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [Macroquad][Macroquad] 0.3 | Linux | +| Game | Part of | Source Language | Source Libraries | Port Libraries | Tested on | +| :------------------------------------: | :--------------------------------------------------------------------------: | :-------------: | :------------------------------------------: | :------------------------------------------------: | :-----------: | +| [Boing][Boing] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [ggez][ggez] 0.9 | Linux/Windows | +| [Catacomb II (SDL)][Catacomb II (SDL)] | - | C | [SDL 2][SDL 2] | [Rust-SDL2][Rust-SDL2] 0.35 | Linux | +| [Cavern][Cavern] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [Macroquad][Macroquad] 0.3 | Linux | +| [Soccer][Soccer] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [Fyrox][Fyrox] 0.26 | Linux | +| [Rusty Roguelike][Rusty Roguelike] | [Hands-on Rust: Effective Learning...][Hands-on Rust: Effective Learning...] | Rust | [bracket-lib][bracket-lib], [Legion][Legion] | [bracket-lib][bracket-lib], [Bevy][Bevy] (ECS) 0.7 | Linux | +| [Rusty Roguelike][Rusty Roguelike] | [Hands-on Rust: Effective Learning...][Hands-on Rust: Effective Learning...] | Rust | [bracket-lib][bracket-lib], [Legion][Legion] | [Macroquad][Macroquad] 0.3, [Legion][Legion] 0.3 | Linux | +| [Bunner][Bunner] | [Code the Classics Vol. 1][Code the Classics Vol. 1] | Python | [PyGame Zero][PyGame Zero] | [Macroquad][Macroquad] 0.3 | Linux | @@ -147,9 +147,9 @@ Catacomb II: A very straightforward port 🙂 -This port suffers from one (Winit) bug: +This port suffers from one bug (it's currently unclear if it's a port or library problem): -- corruption when running on fullscreen (reported [here](https://github.com/ggez/ggez/issues/1066)). +- game running too fast (issue [here](https://github.com/rust-gamedev/rust-game-ports/issues/150)). ### Cavern/Macroquad diff --git a/boing-ggez/Cargo.toml b/boing-ggez/Cargo.toml index cacf2ad8..bdd94bb9 100644 --- a/boing-ggez/Cargo.toml +++ b/boing-ggez/Cargo.toml @@ -1,10 +1,10 @@ [package] authors = ["Saverio Miroddi "] -edition = "2018" +edition = "2021" name = "boing-ggez" version = "0.1.0" [dependencies] fastrand = "1.7.0" -ggez = "0.7.0" +ggez = "0.9.3" glam = {version = "0.20.5", features = ["mint"]} diff --git a/boing-ggez/src/ball.rs b/boing-ggez/src/ball.rs index 32f19013..3d92e51e 100644 --- a/boing-ggez/src/ball.rs +++ b/boing-ggez/src/ball.rs @@ -49,7 +49,7 @@ impl GraphicEntity for Ball { impl Ball { pub fn new(context: &mut Context, dx: f32) -> Self { - let image = Image::new(context, "/ball.png").unwrap(); + let image = Image::from_path(context, "/ball.png").unwrap(); let hit_sounds = (0..5) .map(|i| { let sound_name = format!("/hit{}.ogg", i); diff --git a/boing-ggez/src/bat.rs b/boing-ggez/src/bat.rs index 1c72a253..8ed7ebdb 100644 --- a/boing-ggez/src/bat.rs +++ b/boing-ggez/src/bat.rs @@ -59,7 +59,7 @@ impl Bat { (0..3) .map(|image_i| { let image_name = format!("/bat{}{}.png", player, image_i); - Image::new(context, image_name).unwrap() + Image::from_path(context, image_name).unwrap() }) .collect() }) diff --git a/boing-ggez/src/controls.rs b/boing-ggez/src/controls.rs index 346e1af2..817625f4 100644 --- a/boing-ggez/src/controls.rs +++ b/boing-ggez/src/controls.rs @@ -1,9 +1,7 @@ use ggez::{ - event::{Axis, Button, KeyCode}, - input::{ - gamepad::{self, Gamepad}, - keyboard, - }, + event::{Axis, Button}, + input::gamepad::Gamepad, + winit::event::VirtualKeyCode, Context, }; @@ -22,7 +20,7 @@ pub const ANALOG_STICK_TOLERANCE: f32 = 0.1; // The pad functions are for convenience. // pub fn pad_input(context: &Context, pad_number: PadNum, test: fn(&Gamepad) -> bool) -> bool { - let mut pad_iter = gamepad::gamepads(context); + let mut pad_iter = context.gamepad.gamepads(); let pad = match pad_number { PadNum::Zero => pad_iter.next(), @@ -74,14 +72,14 @@ pub fn p1_controls(context: &Context, _ball: &Ball, _ai_offset: f32, _bat: &Bat) pad.is_pressed(Button::DPadDown) || pad.value(Axis::LeftStickY) < -ANALOG_STICK_TOLERANCE }); - let keys_pressed = keyboard::pressed_keys(context); + let keys_pressed = context.keyboard.pressed_keys(); let move_down = pad_0_down_pressed - || keys_pressed.contains(&KeyCode::Z) - || keys_pressed.contains(&KeyCode::Down); + || keys_pressed.contains(&VirtualKeyCode::Z) + || keys_pressed.contains(&VirtualKeyCode::Down); let move_up = pad_0_up_pressed - || keys_pressed.contains(&KeyCode::A) - || keys_pressed.contains(&KeyCode::Up); + || keys_pressed.contains(&VirtualKeyCode::A) + || keys_pressed.contains(&VirtualKeyCode::Up); if move_down { PLAYER_SPEED @@ -100,10 +98,10 @@ pub fn p2_controls(context: &Context, _ball: &Ball, _ai_offset: f32, _bat: &Bat) pad.is_pressed(Button::DPadDown) || pad.value(Axis::LeftStickY) < -ANALOG_STICK_TOLERANCE }); - let keys_pressed = keyboard::pressed_keys(context); + let keys_pressed = context.keyboard.pressed_keys(); - let move_down = pad_1_down_pressed || keys_pressed.contains(&KeyCode::M); - let move_up = pad_1_up_pressed || keys_pressed.contains(&KeyCode::K); + let move_down = pad_1_down_pressed || keys_pressed.contains(&VirtualKeyCode::M); + let move_up = pad_1_up_pressed || keys_pressed.contains(&VirtualKeyCode::K); if move_down { PLAYER_SPEED diff --git a/boing-ggez/src/game.rs b/boing-ggez/src/game.rs index 8e4508ec..2831910f 100644 --- a/boing-ggez/src/game.rs +++ b/boing-ggez/src/game.rs @@ -1,4 +1,4 @@ -use ggez::graphics::{DrawParam, Drawable, Image}; +use ggez::graphics::{Canvas, DrawParam, Drawable, Image}; use ggez::{audio, Context, GameResult}; use glam::Vec2; @@ -39,11 +39,11 @@ impl Game { Option f32>, ), ) -> Self { - let table_image = Image::new(context, "/table.png").unwrap(); + let table_image = Image::from_path(context, "/table.png").unwrap(); let effect_images = (0..2) .map(|image_i| { let image_name = format!("/effect{}.png", image_i); - Image::new(context, image_name).unwrap() + Image::from_path(context, image_name).unwrap() }) .collect(); let digit_images = (0..3) @@ -51,7 +51,7 @@ impl Game { (0..=9) .map(|image_i| { let image_name = format!("/digit{}{}.png", player, image_i); - Image::new(context, image_name).unwrap() + Image::from_path(context, image_name).unwrap() }) .collect() }) @@ -126,14 +126,14 @@ impl Game { Ok(()) } - pub fn draw(&mut self, context: &mut Context) -> GameResult { + pub fn draw(&mut self, context: &mut Canvas) -> GameResult { // Draw background - self.table_image.draw(context, DrawParam::new())?; + self.table_image.draw(context, DrawParam::new()); // Draw 'just scored' effects, if required for (p, bat) in self.bats.iter().enumerate() { if bat.timer > 0 && self.ball.out() { - self.effect_images[p].draw(context, DrawParam::new())?; + self.effect_images[p].draw(context, DrawParam::new()); } } @@ -142,13 +142,13 @@ impl Game { // the objects together and iterate them, but for this simplification only, it's not worth. for bat in &mut self.bats { - bat.draw(context)?; + bat.draw(context); } - self.ball.draw(context)?; + self.ball.draw(context); for impact in &mut self.impacts { - impact.draw(context)?; + impact.draw(context); } // Display scores - outer loop goes through each player @@ -180,7 +180,7 @@ impl Game { self.digit_images[colour][score_char_val].draw( context, DrawParam::new().dest(Vec2::new((255 + (160 * p) + (i * 55)) as f32, 46.)), - )?; + ); } } diff --git a/boing-ggez/src/global_state.rs b/boing-ggez/src/global_state.rs index 27d34ac4..d171645d 100644 --- a/boing-ggez/src/global_state.rs +++ b/boing-ggez/src/global_state.rs @@ -1,7 +1,7 @@ use ggez::audio::{self, SoundSource}; -use ggez::event::{EventHandler, KeyCode}; -use ggez::graphics::{self, Image}; -use ggez::input::keyboard::is_key_pressed; +use ggez::event::EventHandler; +use ggez::graphics::{self, Image, Rect}; +use ggez::winit::event::VirtualKeyCode; use ggez::{timer, Context, GameResult}; use crate::ball::Ball; @@ -21,6 +21,9 @@ pub struct GlobalState { space_down: bool, fire_down: bool, + viewport_rect: Rect, + scissors_rect: Rect, + menu_images: Vec, game_over_image: Image, @@ -31,15 +34,15 @@ pub struct GlobalState { } impl GlobalState { - pub fn new(context: &mut Context) -> Self { + pub fn new(context: &mut Context, viewport_rect: Rect, scissors_rect: Rect) -> Self { let menu_images = (0..2) .map(|i| { let menu_image_filename = format!("/menu{}.png", i); - Image::new(context, menu_image_filename).unwrap() + Image::from_path(context, menu_image_filename).unwrap() }) .collect(); - let game_over_image = Image::new(context, "/over.png").unwrap(); + let game_over_image = Image::from_path(context, "/over.png").unwrap(); // For simplicity, we always assume that it's possible to play the music. let music = audio::Source::new(context, "/theme.ogg").unwrap(); @@ -55,6 +58,8 @@ impl GlobalState { num_players: 1, space_down: false, fire_down: false, + viewport_rect, + scissors_rect, menu_images, game_over_image, music, @@ -76,15 +81,16 @@ impl EventHandler for GlobalState { // Work out whether the space key has just been pressed - i.e. in the previous frame it wasn't // down, and in this frame it is. - let space_pressed = is_key_pressed(context, KeyCode::Space) && !self.space_down; - self.space_down = is_key_pressed(context, KeyCode::Space); + let space_pressed = + context.keyboard.is_key_pressed(VirtualKeyCode::Space) && !self.space_down; + self.space_down = context.keyboard.is_key_pressed(VirtualKeyCode::Space); // We mimick the source project structure for the pad. let fire_pressed = is_fire_button_pressed(context, PadNum::Zero) && !self.fire_down; self.fire_down = is_fire_button_pressed(context, PadNum::Zero); if is_quit_button_pressed(context, PadNum::Zero) { - ggez::event::quit(context); + context.request_quit(); } match self.state { @@ -106,9 +112,9 @@ impl EventHandler for GlobalState { self.game = Game::new(context, controls); } else { - let input_up = is_key_pressed(context, KeyCode::Up) + let input_up = context.keyboard.is_key_pressed(VirtualKeyCode::Up) || is_pad_up_pressed(context, PadNum::Zero); - let input_down = is_key_pressed(context, KeyCode::Down) + let input_down = context.keyboard.is_key_pressed(VirtualKeyCode::Down) || is_pad_down_pressed(context, PadNum::Zero); if self.num_players == 2 && input_up { @@ -147,23 +153,26 @@ impl EventHandler for GlobalState { } fn draw(&mut self, context: &mut Context) -> GameResult { - self.game.draw(context)?; + let mut canvas = graphics::Canvas::from_frame(context, graphics::Color::BLACK); + canvas.set_screen_coordinates(self.viewport_rect); + canvas.set_scissor_rect(self.scissors_rect)?; + + self.game.draw(&mut canvas)?; match self.state { State::Menu => { - graphics::draw( - context, + canvas.draw( &self.menu_images[self.num_players - 1], graphics::DrawParam::new(), - )?; + ); } State::GameOver => { - graphics::draw(context, &self.game_over_image, graphics::DrawParam::new())?; + canvas.draw(&self.game_over_image, graphics::DrawParam::new()); } State::Play => {} } - graphics::present(context)?; + canvas.finish(context)?; timer::yield_now(); diff --git a/boing-ggez/src/graphic_entity.rs b/boing-ggez/src/graphic_entity.rs index 5d7d0c20..cbe6b34d 100644 --- a/boing-ggez/src/graphic_entity.rs +++ b/boing-ggez/src/graphic_entity.rs @@ -1,7 +1,4 @@ -use ggez::{ - graphics::{self, DrawParam, Image}, - Context, GameResult, -}; +use ggez::graphics::{Canvas, DrawParam, Image}; use glam::Vec2; /// Trait for implementing the drawing part of an Actor. @@ -12,11 +9,11 @@ pub trait GraphicEntity { /// Draws an image, anchored to its center. /// This is due to ggez not supporting anchoring. - fn draw(&mut self, context: &mut Context) -> GameResult { + fn draw(&mut self, canvas: &mut Canvas) { let dest = Vec2::new( self.x() - (self.image().width() / 2) as f32, self.y() - (self.image().height() / 2) as f32, ); - graphics::draw(context, self.image(), DrawParam::new().dest(dest)) + canvas.draw(self.image(), DrawParam::new().dest(dest)); } } diff --git a/boing-ggez/src/impact.rs b/boing-ggez/src/impact.rs index 7e73f086..608bf68c 100644 --- a/boing-ggez/src/impact.rs +++ b/boing-ggez/src/impact.rs @@ -32,7 +32,7 @@ impl Impact { let images = (0..5) .map(|i| { let image_filename = format!("/impact{}.png", i / 2); - Image::new(context, image_filename).unwrap() + Image::from_path(context, image_filename).unwrap() }) .collect(); diff --git a/boing-ggez/src/main.rs b/boing-ggez/src/main.rs index 878ef512..62e612c9 100644 --- a/boing-ggez/src/main.rs +++ b/boing-ggez/src/main.rs @@ -14,7 +14,7 @@ mod state; use std::env; use std::path::PathBuf; -use ggez::{event, GameResult}; +use ggez::{event, graphics::Rect, winit::dpi::PhysicalSize, Context, GameResult}; use global_state::GlobalState; @@ -46,19 +46,72 @@ fn get_resource_dirs() -> Vec { .collect() } +// Returns the viewport and scissor coordinates. +// +fn compute_viewport(context: &Context) -> (Rect, Rect) { + // Assume that the pixels are square. + // + let PhysicalSize { + width: screen_physical_width, + height: screen_physical_height, + } = context.gfx.window().inner_size(); + + let window_ratio = WINDOW_WIDTH / WINDOW_HEIGHT; + let screen_physical_ratio = screen_physical_width as f32 / screen_physical_height as f32; + + let (screen_logical_width, screen_logical_height, logical_scale) = + if screen_physical_ratio >= window_ratio { + ( + WINDOW_HEIGHT * screen_physical_ratio, + WINDOW_HEIGHT, + screen_physical_height as f32 / WINDOW_HEIGHT, + ) + } else { + ( + WINDOW_WIDTH, + WINDOW_WIDTH / screen_physical_ratio, + screen_physical_width as f32 / WINDOW_WIDTH, + ) + }; + + let horizontal_bar_width = (screen_logical_width - WINDOW_WIDTH) / 2.; + let vertical_bar_height = (screen_logical_height - WINDOW_HEIGHT) / 2.; + + let viewport_rect = Rect::new( + -horizontal_bar_width, + -vertical_bar_height, + screen_logical_width, + screen_logical_height, + ); + + let scissors_rect = Rect::new( + (screen_physical_width as f32 - WINDOW_WIDTH * logical_scale) / 2., + (screen_physical_height as f32 - WINDOW_HEIGHT * logical_scale) / 2., + viewport_rect.w * logical_scale, + viewport_rect.h * logical_scale, + ); + + (viewport_rect, scissors_rect) +} + fn main() -> GameResult { let resource_dirs = get_resource_dirs(); let mut context_builder = ggez::ContextBuilder::new(GAME_ID, AUTHOR) .window_setup(ggez::conf::WindowSetup::default().title(WINDOW_TITLE)) - .window_mode(ggez::conf::WindowMode::default().dimensions(WINDOW_WIDTH, WINDOW_HEIGHT)); + .window_mode( + ggez::conf::WindowMode::default().fullscreen_type(ggez::conf::FullscreenType::Desktop), + ); for dir in resource_dirs { context_builder = context_builder.add_resource_path(dir); } let (mut context, event_loop) = context_builder.build()?; - let mut state = GlobalState::new(&mut context); + + let (viewport_rect, scissors_rect) = compute_viewport(&context); + + let mut state = GlobalState::new(&mut context, viewport_rect, scissors_rect); state.play_music(&mut context)?;