... did all the things
This commit is contained in:
parent
40d4ba4574
commit
41189401ab
|
@ -3,6 +3,7 @@
|
||||||
name = "gsa"
|
name = "gsa"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
description = "Game development library modelled after an imaginary console"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
@ -11,7 +12,6 @@ lazy_static = "1.4.0"
|
||||||
glam = "0.24.0"
|
glam = "0.24.0"
|
||||||
ascii = "1.1.0"
|
ascii = "1.1.0"
|
||||||
gilrs = "0.10.2"
|
gilrs = "0.10.2"
|
||||||
cpal = "0.15.2"
|
|
||||||
winit = "0.28.6"
|
winit = "0.28.6"
|
||||||
softbuffer = "0.3.0"
|
softbuffer = "0.3.0"
|
||||||
gif = "0.12.0"
|
gif = "0.12.0"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use gsa::{run, Gsa};
|
use gsa::{run, Gsa, FACE_DOWN};
|
||||||
|
|
||||||
struct Game {}
|
struct Game {}
|
||||||
|
|
||||||
fn init(gsa: &mut Gsa) -> Game {
|
fn init(gsa: &mut Gsa) -> Game {
|
||||||
gsa.sprites[0].tile = 0x0300;
|
gsa.sprites[0].tile = 0x0300;
|
||||||
gsa.sprites[1].tile = 0x0200;
|
gsa.sprites[1].tile = 0x0200;
|
||||||
gsa.maps[0].tiles[0][0] = 0x0300;
|
gsa.maps[0].tiles[1][1] = 0x0300;
|
||||||
gsa.maps[1].half_tile = true;
|
gsa.maps[1].half_tile = true;
|
||||||
gsa.write_string(1, IVec2::ONE, "Hello world nyaa~");
|
gsa.write_string(1, IVec2::ONE, "Hello world nyaa~");
|
||||||
Game {}
|
Game {}
|
||||||
|
@ -14,8 +14,9 @@ fn init(gsa: &mut Gsa) -> Game {
|
||||||
|
|
||||||
fn update(_game: &mut Game, gsa: &mut Gsa) {
|
fn update(_game: &mut Game, gsa: &mut Gsa) {
|
||||||
gsa.sprites[0].pos.x = (gsa.sprites[0].pos.x + 1) % 300;
|
gsa.sprites[0].pos.x = (gsa.sprites[0].pos.x + 1) % 300;
|
||||||
gsa.sprites[1].pos += gsa.input.dir;
|
gsa.sprites[1].pos += gsa.input_dir();
|
||||||
if gsa.input.pressed.face_down {
|
gsa.maps[1].scroll.x += 1;
|
||||||
|
if gsa.button_pressed(FACE_DOWN) {
|
||||||
gsa.sprites[1].tile += 1;
|
gsa.sprites[1].tile += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
examples/gfx.gif
BIN
examples/gfx.gif
Binary file not shown.
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
109
src/buttons.rs
109
src/buttons.rs
|
@ -1,47 +1,70 @@
|
||||||
/// State of all GSA buttons
|
use gilrs::Button;
|
||||||
#[derive(Default, Copy, Clone)]
|
use winit::event::ScanCode;
|
||||||
pub struct Buttons {
|
|
||||||
/// UP on dpad
|
/// UP on dpad
|
||||||
pub dpad_up: bool,
|
pub const DPAD_UP: Buttons = 0x0001;
|
||||||
/// DOWN on dpad
|
/// DOWN on dpad
|
||||||
pub dpad_down: bool,
|
pub const DPAD_DOWN: Buttons = 0x0002;
|
||||||
/// LEFT on dpad
|
/// LEFT on dpad
|
||||||
pub dpad_left: bool,
|
pub const DPAD_LEFT: Buttons = 0x0004;
|
||||||
/// RIGHT on dpad
|
/// RIGHT on dpad
|
||||||
pub dpad_right: bool,
|
pub const DPAD_RIGHT: Buttons = 0x008;
|
||||||
/// X on nintendo, Y on microsoft, TRIANGLE on sony
|
/// X on nintendo, Y on microsoft, TRIANGLE on sony
|
||||||
pub face_up: bool,
|
pub const FACE_UP: Buttons = 0x0010;
|
||||||
/// B on nintendo, A on microsoft, CROSS on sony
|
/// B on nintendo, A on microsoft, CROSS on sony
|
||||||
pub face_down: bool,
|
pub const FACE_DOWN: Buttons = 0x0020;
|
||||||
/// Y on nintendo, X on microsoft, SQUARE on sony
|
/// Y on nintendo, X on microsoft, SQUARE on sony
|
||||||
pub face_left: bool,
|
pub const FACE_LEFT: Buttons = 0x0040;
|
||||||
/// A on nintendo, B on microsoft, CIRCLE on sony
|
/// A on nintendo, B on microsoft, CIRCLE on sony
|
||||||
pub face_right: bool,
|
pub const FACE_RIGHT: Buttons = 0x0080;
|
||||||
/// left shoulder button
|
/// left shoulder button
|
||||||
pub l: bool,
|
pub const L: Buttons = 0x0100;
|
||||||
/// right shoulder button
|
/// right shoulder button
|
||||||
pub r: bool,
|
pub const R: Buttons = 0x0200;
|
||||||
/// start button
|
/// start button
|
||||||
pub start: bool,
|
pub const START: Buttons = 0x0400;
|
||||||
/// select button
|
/// select button
|
||||||
pub select: bool,
|
pub const SELECT: Buttons = 0x0800;
|
||||||
|
|
||||||
|
/// Bitmask of button states
|
||||||
|
///
|
||||||
|
/// GSA pretends all input is a snes-like gamepad
|
||||||
|
/// d-pad, 4 face buttons, l and r shoulder buttons, start and select
|
||||||
|
pub type Buttons = u16;
|
||||||
|
|
||||||
|
pub(crate) fn button_from_scancode(scancode: ScanCode) -> Buttons {
|
||||||
|
//todo: currently windows only? check
|
||||||
|
match scancode {
|
||||||
|
0x0010 => L,
|
||||||
|
0x0011 => FACE_UP,
|
||||||
|
0x0012 => R,
|
||||||
|
0x001C => START,
|
||||||
|
0x001E => FACE_LEFT,
|
||||||
|
0x001F => FACE_DOWN,
|
||||||
|
0x0020 => FACE_RIGHT,
|
||||||
|
0x0039 => SELECT,
|
||||||
|
0xE048 => DPAD_UP,
|
||||||
|
0xE04B => DPAD_LEFT,
|
||||||
|
0xE04D => DPAD_RIGHT,
|
||||||
|
0xE050 => DPAD_DOWN,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buttons {
|
pub(crate) fn button_from_gilrs(button: Button) -> Buttons {
|
||||||
pub(crate) fn pressed(old: &Buttons, new: &Buttons) -> Buttons {
|
match button {
|
||||||
Buttons {
|
Button::South => FACE_DOWN,
|
||||||
dpad_up: !old.dpad_up && new.dpad_up,
|
Button::East => FACE_RIGHT,
|
||||||
dpad_down: !old.dpad_down && new.dpad_down,
|
Button::North => FACE_UP,
|
||||||
dpad_left: !old.dpad_left && new.dpad_left,
|
Button::West => FACE_LEFT,
|
||||||
dpad_right: !old.dpad_right && new.dpad_right,
|
Button::LeftTrigger | Button::LeftTrigger2 => L,
|
||||||
face_up: !old.face_up && new.face_up,
|
Button::RightTrigger | Button::RightTrigger2 => R,
|
||||||
face_down: !old.face_down && new.face_down,
|
Button::Select => SELECT,
|
||||||
face_left: !old.face_left && new.face_left,
|
Button::Start => START,
|
||||||
face_right: !old.face_right && new.face_right,
|
Button::DPadUp => DPAD_UP,
|
||||||
l: !old.l && new.l,
|
Button::DPadDown => DPAD_DOWN,
|
||||||
r: !old.r && new.r,
|
Button::DPadLeft => DPAD_LEFT,
|
||||||
start: !old.start && new.start,
|
Button::DPadRight => DPAD_RIGHT,
|
||||||
select: !old.select && new.select,
|
_ => 0,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
src/gsa.rs
35
src/gsa.rs
|
@ -1,8 +1,9 @@
|
||||||
use crate::input::Input;
|
|
||||||
use crate::rgb::Rgb;
|
use crate::rgb::Rgb;
|
||||||
use crate::sprite::Sprite;
|
use crate::sprite::Sprite;
|
||||||
use crate::tilemap::Tilemap;
|
use crate::tilemap::Tilemap;
|
||||||
use crate::{MAX_SPRITES, MAX_TILEMAPS, TILEMAP_MAX_SIZE};
|
use crate::{
|
||||||
|
Buttons, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT, DPAD_UP, MAX_SPRITES, MAX_TILEMAPS, TILEMAP_MAX_SIZE,
|
||||||
|
};
|
||||||
use ascii::{AsciiChar, AsciiStr};
|
use ascii::{AsciiChar, AsciiStr};
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
|
|
||||||
|
@ -22,8 +23,9 @@ pub struct Gsa {
|
||||||
/// Chosen as half-size tile index, extends 16x16 half tiles in x and y
|
/// Chosen as half-size tile index, extends 16x16 half tiles in x and y
|
||||||
pub font: u16,
|
pub font: u16,
|
||||||
|
|
||||||
/// Current input state
|
pub(crate) pressed: Buttons,
|
||||||
pub input: Input,
|
pub(crate) released: Buttons,
|
||||||
|
pub(crate) down: Buttons,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gsa {
|
impl Gsa {
|
||||||
|
@ -41,6 +43,31 @@ impl Gsa {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if any of given buttons currently held down
|
||||||
|
pub fn button_down(&self, button: Buttons) -> bool {
|
||||||
|
self.down & button != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if any of given buttons been pressed in the current frame
|
||||||
|
pub fn button_pressed(&self, button: Buttons) -> bool {
|
||||||
|
self.pressed & button != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if any of given buttons been released in the current frame
|
||||||
|
pub fn button_released(&self, button: Buttons) -> bool {
|
||||||
|
self.released & button != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Directional vector (-1 to 1) from current dpad state
|
||||||
|
pub fn input_dir(&self) -> IVec2 {
|
||||||
|
IVec2 {
|
||||||
|
x: if self.button_down(DPAD_LEFT) { -1 } else { 0 }
|
||||||
|
+ if self.button_down(DPAD_RIGHT) { 1 } else { 0 },
|
||||||
|
y: if self.button_down(DPAD_UP) { -1 } else { 0 }
|
||||||
|
+ if self.button_down(DPAD_DOWN) { 1 } else { 0 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write given string on given map, at given position, with font [Gsa::font]
|
/// Write given string on given map, at given position, with font [Gsa::font]
|
||||||
pub fn write_string(&mut self, map: usize, pos: IVec2, str: &str) {
|
pub fn write_string(&mut self, map: usize, pos: IVec2, str: &str) {
|
||||||
let str = AsciiStr::from_ascii(str).unwrap();
|
let str = AsciiStr::from_ascii(str).unwrap();
|
||||||
|
|
|
@ -1,23 +1,158 @@
|
||||||
use crate::{Gsa, MAX_SPRITES, TILE_SIZE};
|
use crate::{
|
||||||
|
Gsa, Rgb, Tilemap, HALF_TILE_SIZE, MAX_TILEMAPS, SCREEN_HEIGHT, SCREEN_WIDTH, TILEMAP_MAX_SIZE,
|
||||||
|
TILESET_SIZE, TILE_SIZE,
|
||||||
|
};
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use softbuffer::Buffer;
|
use softbuffer::Buffer;
|
||||||
|
|
||||||
pub(crate) fn render_to_window(target: &mut Buffer, gsa: &Gsa, window_size: IVec2) {
|
fn draw_tile(target: &mut [u8], pos: IVec2, tile: u16, tileset: &[u8], half: bool) {
|
||||||
for (y, row) in target.chunks_exact_mut(window_size.x as usize).enumerate() {
|
let tilesize = if half { HALF_TILE_SIZE } else { TILE_SIZE };
|
||||||
let y = y as i32;
|
let tx = tile as usize % 0x100 * tilesize;
|
||||||
for x in 0..window_size.x {
|
let ty = tile as usize / 0x100 * tilesize;
|
||||||
let mut p = 0;
|
let startx = pos.x.max(0).min(SCREEN_WIDTH as i32);
|
||||||
for sprite in &gsa.sprites {
|
let starty = pos.y.max(0).min(SCREEN_HEIGHT as i32);
|
||||||
if sprite.tile > 0
|
let endx = (pos.x + tilesize as i32).max(0).min(SCREEN_WIDTH as i32);
|
||||||
&& x >= sprite.pos.x
|
let endy = (pos.y + tilesize as i32).max(0).min(SCREEN_HEIGHT as i32);
|
||||||
&& y >= sprite.pos.y
|
for y in (starty - pos.y)..(endy - pos.y) {
|
||||||
&& x < sprite.pos.x + TILE_SIZE as i32
|
for x in (startx - pos.x)..(endx - pos.x) {
|
||||||
&& y < sprite.pos.y + TILE_SIZE as i32
|
let p = tileset[(x as usize + tx) + (y as usize + ty) * (TILESET_SIZE * TILE_SIZE)];
|
||||||
{
|
if p > 0 {
|
||||||
p = 1;
|
target[(x as usize + pos.x as usize)
|
||||||
|
+ (y as usize + pos.y as usize) * SCREEN_WIDTH] = p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
row[x as usize] = gsa.palette[p].to_u32();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_map(target: &mut [u8], map: &Tilemap, tileset: &[u8]) {
|
||||||
|
let tcmult = if map.half_tile { 2 } else { 1 };
|
||||||
|
let tilesize = if map.half_tile {
|
||||||
|
HALF_TILE_SIZE
|
||||||
|
} else {
|
||||||
|
TILE_SIZE
|
||||||
|
} as i32;
|
||||||
|
let mut startx = map.scroll.x / tilesize;
|
||||||
|
let mut starty = map.scroll.y / tilesize;
|
||||||
|
let endx = (TILEMAP_MAX_SIZE as i32).min(startx + 20 * tcmult);
|
||||||
|
let endy = (TILEMAP_MAX_SIZE as i32).min(starty + 12 * tcmult);
|
||||||
|
startx = 0.max(startx);
|
||||||
|
starty = 0.max(starty);
|
||||||
|
for x in startx..endx {
|
||||||
|
for y in starty..endy {
|
||||||
|
let tile = map.tiles[x as usize][y as usize];
|
||||||
|
if tile > 0 {
|
||||||
|
draw_tile(
|
||||||
|
target,
|
||||||
|
IVec2 {
|
||||||
|
x: x * tilesize - map.scroll.x,
|
||||||
|
y: y * tilesize - map.scroll.y,
|
||||||
|
},
|
||||||
|
tile,
|
||||||
|
tileset,
|
||||||
|
map.half_tile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_to_screen(target: &mut [u8], gsa: &Gsa, tileset: &[u8]) {
|
||||||
|
for i in 0..MAX_TILEMAPS {
|
||||||
|
render_map(target, &gsa.maps[i], tileset);
|
||||||
|
for sprite in &gsa.sprites {
|
||||||
|
if sprite.tile > 0 && sprite.priority == i as u8 {
|
||||||
|
draw_tile(target, sprite.pos, sprite.tile, tileset, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render_to_window(
|
||||||
|
target: &mut Buffer,
|
||||||
|
src: &mut [u8],
|
||||||
|
palette: &[Rgb; 256],
|
||||||
|
window_size: IVec2,
|
||||||
|
scale: usize,
|
||||||
|
off_x: usize,
|
||||||
|
off_y: usize,
|
||||||
|
) {
|
||||||
|
let start = window_size.x as usize * off_y;
|
||||||
|
let end = start + window_size.x as usize * SCREEN_HEIGHT * scale;
|
||||||
|
for (y, row) in target[start..end]
|
||||||
|
.chunks_exact_mut(window_size.x as usize * scale)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
let y = y as i32;
|
||||||
|
for x in 0..SCREEN_WIDTH {
|
||||||
|
let p = src[x + y as usize * SCREEN_WIDTH];
|
||||||
|
|
||||||
|
for scaley in 0..scale {
|
||||||
|
for scalex in 0..scale {
|
||||||
|
let sx = x * scale + scalex;
|
||||||
|
row[sx + off_x + scaley * window_size.x as usize] =
|
||||||
|
palette[p as usize].to_u32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//unrolled is faster benchmarked... <_<
|
||||||
|
/*
|
||||||
|
let ww = window_size.x as usize;
|
||||||
|
match scale {
|
||||||
|
1 => {
|
||||||
|
row[x + off_x] = palette[p as usize].to_u32();
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
row[x * 2 + off_x] = palette[p as usize].to_u32();
|
||||||
|
row[x * 2 + off_x + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 2 + off_x + ww] = palette[p as usize].to_u32();
|
||||||
|
row[x * 2 + off_x + ww + 1] = palette[p as usize].to_u32();
|
||||||
|
}
|
||||||
|
6 => {
|
||||||
|
row[x * 6 + off_x + ww * 0] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 0 + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 0 + 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 0 + 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 0 + 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 0 + 5] = palette[p as usize].to_u32();
|
||||||
|
|
||||||
|
row[x * 6 + off_x + ww * 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 1 + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 1 + 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 1 + 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 1 + 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 1 + 5] = palette[p as usize].to_u32();
|
||||||
|
|
||||||
|
row[x * 6 + off_x + ww * 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 2 + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 2 + 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 2 + 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 2 + 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 2 + 5] = palette[p as usize].to_u32();
|
||||||
|
|
||||||
|
row[x * 6 + off_x + ww * 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 3 + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 3 + 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 3 + 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 3 + 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 3 + 5] = palette[p as usize].to_u32();
|
||||||
|
|
||||||
|
row[x * 6 + off_x + ww * 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 4 + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 4 + 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 4 + 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 4 + 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 4 + 5] = palette[p as usize].to_u32();
|
||||||
|
|
||||||
|
row[x * 6 + off_x + ww * 5] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 5 + 1] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 5 + 2] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 5 + 3] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 5 + 4] = palette[p as usize].to_u32();
|
||||||
|
row[x * 6 + off_x + ww * 5 + 5] = palette[p as usize].to_u32();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/input.rs
18
src/input.rs
|
@ -1,18 +0,0 @@
|
||||||
use crate::buttons::Buttons;
|
|
||||||
use glam::IVec2;
|
|
||||||
|
|
||||||
/// Input State
|
|
||||||
///
|
|
||||||
/// GSA pretends all input is a snes-like gamepad
|
|
||||||
/// d-pad, 4 face buttons, l and r shoulder buttons, start and select
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Input {
|
|
||||||
/// Buttons that are currentle held down
|
|
||||||
pub down: Buttons,
|
|
||||||
|
|
||||||
/// Buttons that have been pressed in the current frame
|
|
||||||
pub pressed: Buttons,
|
|
||||||
|
|
||||||
/// Directional vector (-1 to 1) from current dpad state
|
|
||||||
pub dir: IVec2,
|
|
||||||
}
|
|
22
src/lib.rs
22
src/lib.rs
|
@ -12,7 +12,14 @@
|
||||||
//! - Tilemaps: 4 of size 1024x1024, scrollable
|
//! - Tilemaps: 4 of size 1024x1024, scrollable
|
||||||
//!
|
//!
|
||||||
//! ## Features not yet implemented
|
//! ## Features not yet implemented
|
||||||
//! - Sound (no samples
|
//! - Tilemap effects
|
||||||
|
//! - Rotation? Scaling?
|
||||||
|
//! - Mosaic?
|
||||||
|
//! - Mode7?
|
||||||
|
//! - Sprite Effects
|
||||||
|
//! - Rotation? Scaling?
|
||||||
|
//! - Mosaic?
|
||||||
|
//! - Sound (no samples)
|
||||||
//! - Synth
|
//! - Synth
|
||||||
//! - Speech
|
//! - Speech
|
||||||
//! - Savegames
|
//! - Savegames
|
||||||
|
@ -23,21 +30,18 @@
|
||||||
mod buttons;
|
mod buttons;
|
||||||
mod gsa;
|
mod gsa;
|
||||||
mod gsa_render_to_screen;
|
mod gsa_render_to_screen;
|
||||||
mod input;
|
|
||||||
mod rgb;
|
mod rgb;
|
||||||
mod run;
|
mod run;
|
||||||
mod sprite;
|
mod sprite;
|
||||||
mod state;
|
|
||||||
mod tilemap;
|
mod tilemap;
|
||||||
mod tileset;
|
mod tileset;
|
||||||
|
|
||||||
pub use crate::buttons::Buttons;
|
pub use crate::buttons::*;
|
||||||
pub use crate::gsa::Gsa;
|
pub use crate::gsa::*;
|
||||||
pub use crate::input::Input;
|
pub use crate::rgb::*;
|
||||||
pub use crate::rgb::Rgb;
|
|
||||||
pub use crate::run::run;
|
pub use crate::run::run;
|
||||||
pub use crate::sprite::Sprite;
|
pub use crate::sprite::*;
|
||||||
pub use crate::tilemap::Tilemap;
|
pub use crate::tilemap::*;
|
||||||
|
|
||||||
/// Amount of sprites in [Gsa::sprites]
|
/// Amount of sprites in [Gsa::sprites]
|
||||||
pub const MAX_SPRITES: usize = 0xff;
|
pub const MAX_SPRITES: usize = 0xff;
|
||||||
|
|
122
src/run.rs
122
src/run.rs
|
@ -1,14 +1,15 @@
|
||||||
use crate::gsa_render_to_screen::render_to_window;
|
use crate::buttons::{button_from_gilrs, button_from_scancode};
|
||||||
use crate::state::State;
|
use crate::gsa_render_to_screen::{render_to_screen, render_to_window};
|
||||||
use crate::tileset::load_tileset;
|
use crate::tileset::load_tileset;
|
||||||
use crate::{Gsa, Rgb, Sprite, MAX_SPRITES, SCREEN_HEIGHT, SCREEN_WIDTH};
|
use crate::{Buttons, Gsa, Sprite, MAX_SPRITES, SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||||
use gilrs::Gilrs;
|
use gilrs::EventType;
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use winit::dpi::LogicalSize;
|
use winit::dpi::{LogicalSize, PhysicalPosition, PhysicalSize};
|
||||||
use winit::event::{Event, VirtualKeyCode, WindowEvent};
|
use winit::event;
|
||||||
|
use winit::event::{ElementState, Event, VirtualKeyCode, WindowEvent};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
use winit::window::WindowBuilder;
|
use winit::window::WindowBuilder;
|
||||||
|
|
||||||
|
@ -41,7 +42,9 @@ pub fn run<TGame: 'static>(
|
||||||
maps: Default::default(),
|
maps: Default::default(),
|
||||||
palette,
|
palette,
|
||||||
font: 0x1010,
|
font: 0x1010,
|
||||||
input: Default::default(),
|
pressed: 0,
|
||||||
|
released: 0,
|
||||||
|
down: 0,
|
||||||
};
|
};
|
||||||
let mut game = init_fn(&mut gsa);
|
let mut game = init_fn(&mut gsa);
|
||||||
/*let state = State::<TGame> {
|
/*let state = State::<TGame> {
|
||||||
|
@ -64,6 +67,23 @@ pub fn run<TGame: 'static>(
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let monitor_size = window.primary_monitor().unwrap().size();
|
||||||
|
let scale_factor = 0.9;
|
||||||
|
let window_scale = ((monitor_size.width as f32 * scale_factor) as u32
|
||||||
|
/ SCREEN_WIDTH as u32)
|
||||||
|
.min((monitor_size.height as f32 * scale_factor) as u32 / SCREEN_HEIGHT as u32);
|
||||||
|
window.set_inner_size(PhysicalSize {
|
||||||
|
width: window_scale * SCREEN_WIDTH as u32,
|
||||||
|
height: window_scale * SCREEN_HEIGHT as u32,
|
||||||
|
});
|
||||||
|
let outer_size = window.outer_size();
|
||||||
|
window.set_outer_position(PhysicalPosition {
|
||||||
|
x: (monitor_size.width - outer_size.width) / 2,
|
||||||
|
y: (monitor_size.height - outer_size.height) / 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let context = unsafe { softbuffer::Context::new(&window) }.unwrap();
|
let context = unsafe { softbuffer::Context::new(&window) }.unwrap();
|
||||||
let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap();
|
let mut surface = unsafe { softbuffer::Surface::new(&context, &window) }.unwrap();
|
||||||
|
|
||||||
|
@ -71,34 +91,97 @@ pub fn run<TGame: 'static>(
|
||||||
let mut last_second = Instant::now();
|
let mut last_second = Instant::now();
|
||||||
|
|
||||||
let mut last_frame = last_second;
|
let mut last_frame = last_second;
|
||||||
let target_frame_duration = Duration::from_secs(1).div_f64(6000.0);
|
let target_frame_duration = Duration::from_secs(1).div_f64(60.0);
|
||||||
|
|
||||||
let mut scale = 1usize;
|
let mut scale = 1usize;
|
||||||
let mut off_x = 0usize;
|
let mut off_x = 0usize;
|
||||||
let mut off_y = 0usize;
|
let mut off_y = 0usize;
|
||||||
|
|
||||||
|
let mut new_buttons = Buttons::default();
|
||||||
|
// in case button gets pressed and released within the same frame need to catch them
|
||||||
|
// seperately from the last-now comparison to catch it
|
||||||
|
let mut new_pressed = Buttons::default();
|
||||||
|
let mut new_released = Buttons::default();
|
||||||
|
let mut gilrs = gilrs::Gilrs::new().unwrap();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
let scale = &mut scale;
|
let scale = &mut scale;
|
||||||
let off_x = &mut off_x;
|
let off_x = &mut off_x;
|
||||||
let off_y = &mut off_y;
|
let off_y = &mut off_y;
|
||||||
|
let new_buttons = &mut new_buttons;
|
||||||
|
let new_pressed = &mut new_pressed;
|
||||||
|
let new_released = &mut new_released;
|
||||||
|
let gilrs = &mut gilrs;
|
||||||
|
|
||||||
if Instant::now() - last_second > Duration::from_secs(1) {
|
if Instant::now() - last_second > Duration::from_secs(1) {
|
||||||
println!("fps: {}", frames_since_last_second);
|
println!("fps: {}", frames_since_last_second);
|
||||||
frames_since_last_second = 0;
|
frames_since_last_second = 0;
|
||||||
last_second += Duration::from_secs(1);
|
last_second += Duration::from_secs(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut screen_buffer = [0u8; SCREEN_WIDTH * SCREEN_HEIGHT];
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event:
|
||||||
|
WindowEvent::KeyboardInput {
|
||||||
|
input:
|
||||||
|
event::KeyboardInput {
|
||||||
|
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
},
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
}
|
||||||
|
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event: WindowEvent::KeyboardInput { input, .. },
|
event: WindowEvent::KeyboardInput { input, .. },
|
||||||
..
|
..
|
||||||
} => match input.virtual_keycode {
|
} => {
|
||||||
Some(VirtualKeyCode::Escape) => {
|
let button = button_from_scancode(input.scancode);
|
||||||
*control_flow = ControlFlow::Exit;
|
match input.state {
|
||||||
|
ElementState::Pressed => {
|
||||||
|
*new_buttons |= button;
|
||||||
|
*new_pressed |= button;
|
||||||
}
|
}
|
||||||
_ => {}
|
ElementState::Released => {
|
||||||
},
|
*new_buttons &= !button;
|
||||||
|
*new_released |= button;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Event::MainEventsCleared {} => {
|
Event::MainEventsCleared {} => {
|
||||||
if Instant::now() - last_frame >= target_frame_duration {
|
if Instant::now() - last_frame >= target_frame_duration {
|
||||||
|
//input
|
||||||
|
while let Some(event) = gilrs.next_event() {
|
||||||
|
match event.event {
|
||||||
|
EventType::ButtonPressed(button, ..) => {
|
||||||
|
let button = button_from_gilrs(button);
|
||||||
|
*new_buttons |= button;
|
||||||
|
*new_pressed |= button;
|
||||||
|
}
|
||||||
|
EventType::ButtonReleased(button, ..) => {
|
||||||
|
let button = button_from_gilrs(button);
|
||||||
|
*new_buttons &= !button;
|
||||||
|
*new_released |= button;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gsa.down = *new_buttons;
|
||||||
|
gsa.released = *new_released;
|
||||||
|
gsa.pressed = *new_pressed;
|
||||||
|
*new_released = 0;
|
||||||
|
*new_pressed = 0;
|
||||||
|
|
||||||
|
//update
|
||||||
update_fn(&mut game, &mut gsa);
|
update_fn(&mut game, &mut gsa);
|
||||||
|
|
||||||
|
//graphics
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
surface
|
surface
|
||||||
.resize(
|
.resize(
|
||||||
|
@ -114,16 +197,21 @@ pub fn run<TGame: 'static>(
|
||||||
*off_x = (size.width as usize - SCREEN_WIDTH * *scale) / 2;
|
*off_x = (size.width as usize - SCREEN_WIDTH * *scale) / 2;
|
||||||
*off_y = (size.height as usize - SCREEN_HEIGHT * *scale) / 2;
|
*off_y = (size.height as usize - SCREEN_HEIGHT * *scale) / 2;
|
||||||
|
|
||||||
let mut buf = surface.buffer_mut().unwrap();
|
let mut window_buffer = surface.buffer_mut().unwrap();
|
||||||
|
render_to_screen(&mut screen_buffer, &gsa, &tileset);
|
||||||
render_to_window(
|
render_to_window(
|
||||||
&mut buf,
|
&mut window_buffer,
|
||||||
&gsa,
|
&mut screen_buffer,
|
||||||
|
&palette,
|
||||||
IVec2 {
|
IVec2 {
|
||||||
x: size.width as i32,
|
x: size.width as i32,
|
||||||
y: size.height as i32,
|
y: size.height as i32,
|
||||||
},
|
},
|
||||||
|
*scale,
|
||||||
|
*off_x,
|
||||||
|
*off_y,
|
||||||
);
|
);
|
||||||
buf.present().unwrap();
|
window_buffer.present().unwrap();
|
||||||
frames_since_last_second += 1;
|
frames_since_last_second += 1;
|
||||||
last_frame += target_frame_duration;
|
last_frame += target_frame_duration;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,16 @@ pub struct Sprite {
|
||||||
pub pos: IVec2,
|
pub pos: IVec2,
|
||||||
/// Tile index
|
/// Tile index
|
||||||
pub tile: u16,
|
pub tile: u16,
|
||||||
|
/// Priority, this sprite will be drawn above map of idx priority
|
||||||
|
pub priority: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Sprite {
|
impl Display for Sprite {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "[pos: {}, tile: {}]", self.pos, self.tile)
|
write!(
|
||||||
|
f,
|
||||||
|
"[pos: {}, tile: {}, priority: {}]",
|
||||||
|
self.pos, self.tile, self.priority
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
147
src/state.rs
147
src/state.rs
|
@ -1,147 +0,0 @@
|
||||||
use crate::buttons::Buttons;
|
|
||||||
use crate::{Gsa, TILEMAP_MAX_SIZE};
|
|
||||||
use gilrs::{Button, EventType, Gilrs};
|
|
||||||
use glam::IVec2;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
pub struct State<TGame> {
|
|
||||||
pub(crate) gsa: Gsa,
|
|
||||||
pub(crate) game: TGame,
|
|
||||||
pub(crate) update_fn: fn(game: &mut TGame, gsa: &mut Gsa),
|
|
||||||
pub(crate) tileset: Vec<u8>,
|
|
||||||
pub(crate) first: bool,
|
|
||||||
pub(crate) gilrs: Gilrs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<TGame> State<TGame> {
|
|
||||||
fn update(&mut self) {
|
|
||||||
let mut new_buttons = self.gsa.input.down;
|
|
||||||
while let Some(event) = self.gilrs.next_event() {
|
|
||||||
match event {
|
|
||||||
gilrs::Event {
|
|
||||||
event: EventType::ButtonPressed(button, _),
|
|
||||||
..
|
|
||||||
} => match button {
|
|
||||||
Button::South => new_buttons.face_down = true,
|
|
||||||
Button::East => new_buttons.face_left = true,
|
|
||||||
Button::North => new_buttons.face_up = true,
|
|
||||||
Button::West => new_buttons.face_right = true,
|
|
||||||
Button::LeftTrigger | Button::LeftTrigger2 => new_buttons.l = true,
|
|
||||||
Button::RightTrigger | Button::RightTrigger2 => new_buttons.r = true,
|
|
||||||
Button::Select => new_buttons.select = true,
|
|
||||||
Button::Start => new_buttons.start = true,
|
|
||||||
Button::DPadUp => new_buttons.dpad_up = true,
|
|
||||||
Button::DPadDown => new_buttons.dpad_down = true,
|
|
||||||
Button::DPadLeft => new_buttons.dpad_left = true,
|
|
||||||
Button::DPadRight => new_buttons.dpad_right = true,
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
gilrs::Event {
|
|
||||||
event: EventType::ButtonReleased(button, _),
|
|
||||||
..
|
|
||||||
} => match button {
|
|
||||||
Button::South => new_buttons.face_down = false,
|
|
||||||
Button::East => new_buttons.face_left = false,
|
|
||||||
Button::North => new_buttons.face_up = false,
|
|
||||||
Button::West => new_buttons.face_right = false,
|
|
||||||
Button::LeftTrigger | Button::LeftTrigger2 => new_buttons.l = false,
|
|
||||||
Button::RightTrigger | Button::RightTrigger2 => new_buttons.r = false,
|
|
||||||
Button::Select => new_buttons.select = false,
|
|
||||||
Button::Start => new_buttons.start = false,
|
|
||||||
Button::DPadUp => new_buttons.dpad_up = false,
|
|
||||||
Button::DPadDown => new_buttons.dpad_down = false,
|
|
||||||
Button::DPadLeft => new_buttons.dpad_left = false,
|
|
||||||
Button::DPadRight => new_buttons.dpad_right = false,
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.gsa.input.pressed = Buttons::pressed(&self.gsa.input.down, &new_buttons);
|
|
||||||
self.gsa.input.down = new_buttons;
|
|
||||||
self.gsa.input.dir = IVec2 {
|
|
||||||
x: if self.gsa.input.down.dpad_left { -1 } else { 0 }
|
|
||||||
+ if self.gsa.input.down.dpad_right { 1 } else { 0 },
|
|
||||||
y: if self.gsa.input.down.dpad_up { -1 } else { 0 }
|
|
||||||
+ if self.gsa.input.down.dpad_down { 1 } else { 0 },
|
|
||||||
};
|
|
||||||
|
|
||||||
//todo: don't if not updated... how check? <_<
|
|
||||||
/*
|
|
||||||
for i in 0..=255 {
|
|
||||||
window_state.set_palette(
|
|
||||||
i,
|
|
||||||
self.gsa.palette[i as usize].r,
|
|
||||||
self.gsa.palette[i as usize].g,
|
|
||||||
self.gsa.palette[i as usize].b,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
(self.update_fn)(&mut self.game, &mut self.gsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&self) {
|
|
||||||
//target.clear();
|
|
||||||
|
|
||||||
for map in &self.gsa.maps {
|
|
||||||
let tcmult = if map.half_tile { 2 } else { 1 };
|
|
||||||
let tilesize = if map.half_tile { 8 } else { 16 };
|
|
||||||
let mut startx = map.scroll.x / tilesize;
|
|
||||||
let mut starty = map.scroll.y / tilesize;
|
|
||||||
let endx = (TILEMAP_MAX_SIZE as i32).min(startx + 20 * tcmult);
|
|
||||||
let endy = (TILEMAP_MAX_SIZE as i32).min(starty + 12 * tcmult);
|
|
||||||
startx = 0.max(startx);
|
|
||||||
starty = 0.max(starty);
|
|
||||||
for x in startx..endx {
|
|
||||||
for y in starty..endy {
|
|
||||||
let tile = map.tiles[x as usize][y as usize];
|
|
||||||
if tile > 0 {
|
|
||||||
let ty = tile / 0x100;
|
|
||||||
let tx = tile % 0x100;
|
|
||||||
/*
|
|
||||||
target.draw_image_partial(
|
|
||||||
IVec2 {
|
|
||||||
x: x * tilesize - map.scroll.x,
|
|
||||||
y: y * tilesize - map.scroll.y,
|
|
||||||
},
|
|
||||||
&self.tileset,
|
|
||||||
IRect {
|
|
||||||
pos: IVec2 {
|
|
||||||
x: tx as i32 * tilesize,
|
|
||||||
y: ty as i32 * tilesize,
|
|
||||||
},
|
|
||||||
size: IVec2 {
|
|
||||||
x: tilesize,
|
|
||||||
y: tilesize,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for sprite in &self.gsa.sprites {
|
|
||||||
if sprite.tile > 0 {
|
|
||||||
let ty = sprite.tile / 0x100;
|
|
||||||
let tx = sprite.tile % 0x100;
|
|
||||||
/*
|
|
||||||
target.draw_image_partial(
|
|
||||||
sprite.pos,
|
|
||||||
&self.tileset,
|
|
||||||
IRect {
|
|
||||||
pos: IVec2 {
|
|
||||||
x: tx as i32 * 16,
|
|
||||||
y: ty as i32 * 16,
|
|
||||||
},
|
|
||||||
size: IVec2 { x: 16, y: 16 },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue