tile, layers and bank implementation
This commit is contained in:
parent
072b6133d5
commit
13169698cf
@ -1,32 +0,0 @@
|
|||||||
/// Cor simples em RGB565 (0bRRRRRGGGGGGBBBBB).
|
|
||||||
/// - 5 bits Red
|
|
||||||
/// - 6 bits Green
|
|
||||||
/// - 5 bits Blue
|
|
||||||
///
|
|
||||||
/// Não há canal alpha.
|
|
||||||
/// Transparência é tratada via Color Key ou Blend Mode.
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub struct Color(pub u16);
|
|
||||||
|
|
||||||
impl Color {
|
|
||||||
/// Cria uma cor RGB565 a partir de componentes 8-bit.
|
|
||||||
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
|
|
||||||
let r5 = (r as u16 >> 3) & 0x1F;
|
|
||||||
let g6 = (g as u16 >> 2) & 0x3F;
|
|
||||||
let b5 = (b as u16 >> 3) & 0x1F;
|
|
||||||
|
|
||||||
Self((r5 << 11) | (g6 << 5) | b5)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn gray_scale(c: u8) -> Self {
|
|
||||||
Self::rgb(c, c, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_raw(raw: u16) -> Self {
|
|
||||||
Self(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn raw(self) -> u16 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,5 @@
|
|||||||
pub mod machine;
|
pub mod machine;
|
||||||
pub mod peripherals;
|
pub mod peripherals;
|
||||||
mod color;
|
mod model;
|
||||||
mod c_button;
|
|
||||||
|
|
||||||
pub use machine::Machine;
|
pub use machine::Machine;
|
||||||
pub use color::Color;
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use crate::peripherals::{BlendMode, Gfx, InputSignals, Pad, Touch};
|
use crate::model::Color;
|
||||||
use crate::Color;
|
use crate::peripherals::{Gfx, InputSignals, Pad, Touch};
|
||||||
|
|
||||||
/// PROMETEU "hardware lógico" (v0).
|
/// PROMETEU "hardware lógico" (v0).
|
||||||
/// O Host alimenta INPUT SIGNALS e chama `step_frame()` em 60Hz.
|
/// O Host alimenta INPUT SIGNALS e chama `step_frame()` em 60Hz.
|
||||||
@ -44,20 +44,50 @@ impl Machine {
|
|||||||
|
|
||||||
/// Lógica do frame (demo hardcoded por enquanto).
|
/// Lógica do frame (demo hardcoded por enquanto).
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
self.gfx.clear(Color::rgb(0x10, 0x10, 0x10));
|
// self.gfx.clear(Color::rgb(0x10, 0x10, 0x10));
|
||||||
|
//
|
||||||
|
// let (x, y) = self.demo_pos();
|
||||||
|
//
|
||||||
|
// let color = if self.pad.a.down || self.touch.f.down {
|
||||||
|
// Color::rgb(0x00, 0xFF, 0x00)
|
||||||
|
// } else {
|
||||||
|
// Color::rgb(0xFF, 0x40, 0x40)
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// // game
|
||||||
|
// self.gfx.fill_rect(x, y, 20, 20, color);
|
||||||
|
// // smoke
|
||||||
|
// self.gfx.fill_rect_blend(140, 0, 40, 180, Color::gray_scale(0x88), BlendMode::Half)
|
||||||
|
|
||||||
let (x, y) = self.demo_pos();
|
|
||||||
|
|
||||||
let color = if self.pad.a.down || self.touch.f.down {
|
// Limpa a tela com um azul escuro "estilo console"
|
||||||
Color::rgb(0x00, 0xFF, 0x00)
|
self.gfx.clear(Color::rgb(0x20, 0x20, 0x40));
|
||||||
} else {
|
|
||||||
Color::rgb(0xFF, 0x40, 0x40)
|
|
||||||
};
|
|
||||||
|
|
||||||
// game
|
// Simula o carregamento de um banco se estiver vazio (apenas para teste)
|
||||||
self.gfx.fill_rect(x, y, 20, 20, color);
|
if self.gfx.banks[0].is_none() {
|
||||||
// smoke
|
let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128);
|
||||||
self.gfx.fill_rect_blend(140, 0, 40, 180, Color::gray_scale(0x88), BlendMode::Half)
|
// Pinta o primeiro tile (ID 1) de Branco para teste
|
||||||
|
// Ele começa no pixel (8, 0) pois o ID 0 é o primeiro (0,0)
|
||||||
|
let tile_size = 8;
|
||||||
|
let start_x = 8; // ID 1 está na segunda posição da primeira linha
|
||||||
|
let start_y = 0;
|
||||||
|
|
||||||
|
for y in 0..tile_size {
|
||||||
|
for x in 0..tile_size {
|
||||||
|
let px = start_x + x;
|
||||||
|
let py = start_y + y;
|
||||||
|
let idx = py * 128 + px;
|
||||||
|
test_bank.pixels[idx] = Color::WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.gfx.banks[0] = Some(test_bank);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coloca o Tile ID 1 no mapa do HUD (canto superior esquerdo)
|
||||||
|
self.gfx.hud.map.set_tile(0, 0, crate::model::Tile { id: 1, ..Default::default() });
|
||||||
|
|
||||||
|
// Executa o pipeline gráfico
|
||||||
|
self.gfx.render_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Final do frame: troca buffers.
|
/// Final do frame: troca buffers.
|
||||||
@ -65,9 +95,9 @@ impl Machine {
|
|||||||
self.gfx.present();
|
self.gfx.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demo_pos(&self) -> (i32, i32) {
|
// fn demo_pos(&self) -> (i32, i32) {
|
||||||
let x = (self.frame_index % 300) as i32;
|
// let x = (self.frame_index % 300) as i32;
|
||||||
let y = (self.pad.a.hold_frames % 160) as i32;
|
// let y = (self.pad.a.hold_frames % 160) as i32;
|
||||||
(x, y)
|
// (x, y)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub struct CButton {
|
pub struct Button {
|
||||||
pub pressed: bool,
|
pub pressed: bool,
|
||||||
pub released: bool,
|
pub released: bool,
|
||||||
pub down: bool,
|
pub down: bool,
|
||||||
pub hold_frames: u32,
|
pub hold_frames: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CButton {
|
impl Button {
|
||||||
pub fn begin_frame(&mut self, is_down_now: bool) {
|
pub fn begin_frame(&mut self, is_down_now: bool) {
|
||||||
let was_down = self.down;
|
let was_down = self.down;
|
||||||
self.down = is_down_now;
|
self.down = is_down_now;
|
||||||
|
|
||||||
// Detecta transições
|
|
||||||
self.pressed = !was_down && self.down;
|
self.pressed = !was_down && self.down;
|
||||||
self.released = was_down && !self.down;
|
self.released = was_down && !self.down;
|
||||||
|
|
||||||
// Atualiza contador de frames
|
|
||||||
if self.down {
|
if self.down {
|
||||||
self.hold_frames = self.hold_frames.saturating_add(1);
|
self.hold_frames = self.hold_frames.saturating_add(1);
|
||||||
} else {
|
} else {
|
||||||
59
crates/core/src/model/color.rs
Normal file
59
crates/core/src/model/color.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/// Cor simples em RGB565 (0bRRRRRGGGGGGBBBBB).
|
||||||
|
/// - 5 bits Red
|
||||||
|
/// - 6 bits Green
|
||||||
|
/// - 5 bits Blue
|
||||||
|
///
|
||||||
|
/// Não há canal alpha.
|
||||||
|
/// Transparência é tratada via Color Key ou Blend Mode.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub struct Color(pub u16);
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub const BLACK: Self = Self::rgb(0, 0, 0); // 0x0000
|
||||||
|
pub const WHITE: Self = Self::rgb(255, 255, 255); // 0xFFFF
|
||||||
|
pub const RED: Self = Self::rgb(255, 0, 0); // 0xF800
|
||||||
|
pub const GREEN: Self = Self::rgb(0, 255, 0); // 0x07E0
|
||||||
|
pub const BLUE: Self = Self::rgb(0, 0, 255); // 0x001F
|
||||||
|
pub const YELLOW: Self = Self::rgb(255, 255, 0); // 0xFFE0
|
||||||
|
pub const CYAN: Self = Self::rgb(0, 255, 255); // 0x07FF
|
||||||
|
pub const MAGENTA: Self = Self::rgb(255, 0, 255); // 0xF81F
|
||||||
|
pub const COLOR_KEY: Self = Self::MAGENTA;
|
||||||
|
|
||||||
|
/// Extrai canais já na faixa nativa do RGB565:
|
||||||
|
/// R: 0..31, G: 0..63, B: 0..31
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn unpack_to_native(px: u16) -> (u8, u8, u8) {
|
||||||
|
let r = ((px >> 11) & 0x1F) as u8;
|
||||||
|
let g = ((px >> 5) & 0x3F) as u8;
|
||||||
|
let b = (px & 0x1F) as u8;
|
||||||
|
(r, g, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Incorpora canais já na faixa nativa do RGB565 em um pixel:
|
||||||
|
/// R: 0..31, G: 0..63, B: 0..31
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn pack_from_native(r: u8, g: u8, b: u8) -> u16 {
|
||||||
|
((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cria uma cor RGB565 a partir de componentes 8-bit (0..255).
|
||||||
|
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
|
||||||
|
let r5 = (r as u16 >> 3) & 0x1F;
|
||||||
|
let g6 = (g as u16 >> 2) & 0x3F;
|
||||||
|
let b5 = (b as u16 >> 3) & 0x1F;
|
||||||
|
|
||||||
|
Self((r5 << 11) | (g6 << 5) | b5)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn gray_scale(c: u8) -> Self {
|
||||||
|
Self::rgb(c, c, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from_raw(raw: u16) -> Self {
|
||||||
|
Self(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn raw(self) -> u16 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
11
crates/core/src/model/mod.rs
Normal file
11
crates/core/src/model/mod.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
mod color;
|
||||||
|
mod button;
|
||||||
|
mod tile;
|
||||||
|
mod tile_layer;
|
||||||
|
mod tile_bank;
|
||||||
|
|
||||||
|
pub use button::Button;
|
||||||
|
pub use color::Color;
|
||||||
|
pub use tile::Tile;
|
||||||
|
pub use tile_bank::{TileBank, TileSize};
|
||||||
|
pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
|
||||||
7
crates/core/src/model/tile.rs
Normal file
7
crates/core/src/model/tile.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Tile {
|
||||||
|
pub id: u16,
|
||||||
|
pub flip_x: bool,
|
||||||
|
pub flip_y: bool,
|
||||||
|
pub palette_id: u8,
|
||||||
|
}
|
||||||
45
crates/core/src/model/tile_bank.rs
Normal file
45
crates/core/src/model/tile_bank.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
use crate::model::Color;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum TileSize {
|
||||||
|
Size8 = 8,
|
||||||
|
Size16 = 16,
|
||||||
|
Size32 = 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TileBank {
|
||||||
|
pub tile_size: TileSize,
|
||||||
|
pub width: usize, // em pixels
|
||||||
|
pub height: usize, // em pixels
|
||||||
|
pub pixels: Vec<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileBank {
|
||||||
|
pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
tile_size,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
pixels: vec![Color::BLACK; width * height],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retorna a cor de um pixel específico dentro de um tile.
|
||||||
|
/// tile_id: o índice do tile no banco
|
||||||
|
/// local_x, local_y: a posição do pixel dentro do tile (0 até tile_size-1)
|
||||||
|
pub fn get_pixel(&self, tile_id: u16, local_x: usize, local_y: usize) -> Color {
|
||||||
|
let tile_size = self.tile_size as usize;
|
||||||
|
let tiles_per_row = self.width / tile_size;
|
||||||
|
let tile_x = (tile_id as usize % tiles_per_row) * tile_size;
|
||||||
|
let tile_y = (tile_id as usize / tiles_per_row) * tile_size;
|
||||||
|
|
||||||
|
let pixel_x = tile_x + local_x;
|
||||||
|
let pixel_y = tile_y + local_y;
|
||||||
|
|
||||||
|
if pixel_x < self.width && pixel_y < self.height {
|
||||||
|
self.pixels[pixel_y * self.width + pixel_x]
|
||||||
|
} else {
|
||||||
|
Color::BLACK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
crates/core/src/model/tile_layer.rs
Normal file
109
crates/core/src/model/tile_layer.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use crate::model::Tile;
|
||||||
|
use crate::model::tile_bank::TileSize;
|
||||||
|
use crate::model::TileSize::Size8;
|
||||||
|
|
||||||
|
pub struct TileMap {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub tiles: Vec<Tile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileMap {
|
||||||
|
fn create(width: usize, height: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
tiles: vec![Tile::default(); width * height],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) {
|
||||||
|
if x < self.width && y < self.height {
|
||||||
|
self.tiles[y * self.width + x] = tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct TileLayer {
|
||||||
|
pub bank_id: u8,
|
||||||
|
pub tile_size: TileSize,
|
||||||
|
pub map: TileMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TileLayer {
|
||||||
|
fn create(width: usize, height: usize, tile_size: TileSize) -> Self {
|
||||||
|
Self {
|
||||||
|
bank_id: 0,
|
||||||
|
tile_size: tile_size,
|
||||||
|
map: TileMap::create(width, height),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for TileLayer {
|
||||||
|
type Target = TileMap;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for TileLayer {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScrollableTileLayer {
|
||||||
|
pub layer: TileLayer,
|
||||||
|
pub scroll_x: i32,
|
||||||
|
pub scroll_y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollableTileLayer {
|
||||||
|
pub fn new(width: usize, height: usize, tile_size: TileSize) -> Self {
|
||||||
|
Self {
|
||||||
|
layer: TileLayer::create(width, height, tile_size),
|
||||||
|
scroll_x: 0,
|
||||||
|
scroll_y: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for ScrollableTileLayer {
|
||||||
|
type Target = TileLayer;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for ScrollableTileLayer {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HudTileLayer {
|
||||||
|
pub layer: TileLayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HudTileLayer {
|
||||||
|
pub fn new(width: usize, height: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
layer: TileLayer::create(width, height, Size8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for HudTileLayer {
|
||||||
|
type Target = TileLayer;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for HudTileLayer {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.layer
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::color::Color;
|
use crate::model::{Color, HudTileLayer, ScrollableTileLayer, TileBank, TileMap, TileSize};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum BlendMode {
|
pub enum BlendMode {
|
||||||
@ -20,16 +20,30 @@ pub struct Gfx {
|
|||||||
h: usize,
|
h: usize,
|
||||||
front: Vec<u16>,
|
front: Vec<u16>,
|
||||||
back: Vec<u16>,
|
back: Vec<u16>,
|
||||||
|
|
||||||
|
pub layers: [ScrollableTileLayer; 4],
|
||||||
|
pub hud: HudTileLayer,
|
||||||
|
pub banks: [Option<TileBank>; 16],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gfx {
|
impl Gfx {
|
||||||
pub fn new(w: usize, h: usize) -> Self {
|
pub fn new(w: usize, h: usize) -> Self {
|
||||||
|
const EMPTY_BANK: Option<TileBank> = None;
|
||||||
|
|
||||||
let len = w * h;
|
let len = w * h;
|
||||||
Self {
|
Self {
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
front: vec![0; len],
|
front: vec![0; len],
|
||||||
back: vec![0; len],
|
back: vec![0; len],
|
||||||
|
layers: [
|
||||||
|
ScrollableTileLayer::new(64, 64, TileSize::Size16),
|
||||||
|
ScrollableTileLayer::new(64, 64, TileSize::Size16),
|
||||||
|
ScrollableTileLayer::new(64, 64, TileSize::Size16),
|
||||||
|
ScrollableTileLayer::new(64, 64, TileSize::Size16),
|
||||||
|
],
|
||||||
|
hud: HudTileLayer::new(64, 32),
|
||||||
|
banks: [EMPTY_BANK; 16],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +99,82 @@ impl Gfx {
|
|||||||
pub fn present(&mut self) {
|
pub fn present(&mut self) {
|
||||||
std::mem::swap(&mut self.front, &mut self.back);
|
std::mem::swap(&mut self.front, &mut self.back);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pipeline principal de renderização do frame.
|
||||||
|
/// Segue a ordem de prioridade do manual (Capítulo 4.11).
|
||||||
|
pub fn render_all(&mut self) {
|
||||||
|
// 1. Layers de jogo (0 a 3)
|
||||||
|
for i in 0..self.layers.len() {
|
||||||
|
self.render_layer(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Sprites (Ainda vamos implementar)
|
||||||
|
|
||||||
|
// 3. HUD (Sempre por cima)
|
||||||
|
self.render_hud();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_layer(&mut self, layer_idx: usize) {
|
||||||
|
if layer_idx >= self.layers.len() { return; }
|
||||||
|
|
||||||
|
let bank_id = self.layers[layer_idx].bank_id as usize;
|
||||||
|
let scroll_x = self.layers[layer_idx].scroll_x;
|
||||||
|
let scroll_y = self.layers[layer_idx].scroll_y;
|
||||||
|
|
||||||
|
let bank = match self.banks.get(bank_id) {
|
||||||
|
Some(Some(b)) => b,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, bank, scroll_x, scroll_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renderiza o HUD (sem scroll).
|
||||||
|
pub fn render_hud(&mut self) {
|
||||||
|
let bank_id = self.hud.bank_id as usize;
|
||||||
|
let bank = match self.banks.get(bank_id) {
|
||||||
|
Some(Some(b)) => b,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::draw_tile_map(&mut self.back, self.w, self.h, &self.hud.map, bank, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_tile_map(
|
||||||
|
back: &mut [u16],
|
||||||
|
screen_w: usize,
|
||||||
|
screen_h: usize,
|
||||||
|
map: &TileMap,
|
||||||
|
bank: &TileBank,
|
||||||
|
scroll_x: i32,
|
||||||
|
scroll_y: i32
|
||||||
|
) {
|
||||||
|
let size = bank.tile_size as usize;
|
||||||
|
|
||||||
|
for screen_y in 0..screen_h {
|
||||||
|
for screen_x in 0..screen_w {
|
||||||
|
let world_x = screen_x as i32 + scroll_x;
|
||||||
|
let world_y = screen_y as i32 + scroll_y;
|
||||||
|
|
||||||
|
let tile_x = (world_x / size as i32) as usize;
|
||||||
|
let tile_y = (world_y / size as i32) as usize;
|
||||||
|
|
||||||
|
if tile_x >= map.width || tile_y >= map.height { continue; }
|
||||||
|
|
||||||
|
let tile = map.tiles[tile_y * map.width + tile_x];
|
||||||
|
if tile.id == 0 { continue; }
|
||||||
|
|
||||||
|
let local_x = (world_x % size as i32) as usize;
|
||||||
|
let local_y = (world_y % size as i32) as usize;
|
||||||
|
|
||||||
|
let color = bank.get_pixel(tile.id, local_x, local_y);
|
||||||
|
if color == Color::COLOR_KEY { continue; }
|
||||||
|
|
||||||
|
let idx = (screen_y * screen_w) + screen_x;
|
||||||
|
back[idx] = color.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Faz blend em RGB565 por canal com saturação.
|
/// Faz blend em RGB565 por canal com saturação.
|
||||||
@ -94,60 +184,45 @@ fn blend_rgb565(dst: u16, src: u16, mode: BlendMode) -> u16 {
|
|||||||
BlendMode::None => src,
|
BlendMode::None => src,
|
||||||
|
|
||||||
BlendMode::Half => {
|
BlendMode::Half => {
|
||||||
let (dr, dg, db) = unpack_rgb565(dst);
|
let (dr, dg, db) = Color::unpack_to_native(dst);
|
||||||
let (sr, sg, sb) = unpack_rgb565(src);
|
let (sr, sg, sb) = Color::unpack_to_native(src);
|
||||||
let r = ((dr as u16 + sr as u16) >> 1) as u8;
|
let r = ((dr as u16 + sr as u16) >> 1) as u8;
|
||||||
let g = ((dg as u16 + sg as u16) >> 1) as u8;
|
let g = ((dg as u16 + sg as u16) >> 1) as u8;
|
||||||
let b = ((db as u16 + sb as u16) >> 1) as u8;
|
let b = ((db as u16 + sb as u16) >> 1) as u8;
|
||||||
pack_rgb565(r, g, b)
|
Color::pack_from_native(r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
BlendMode::HalfPlus => {
|
BlendMode::HalfPlus => {
|
||||||
let (dr, dg, db) = unpack_rgb565(dst);
|
let (dr, dg, db) = Color::unpack_to_native(dst);
|
||||||
let (sr, sg, sb) = unpack_rgb565(src);
|
let (sr, sg, sb) = Color::unpack_to_native(src);
|
||||||
|
|
||||||
let r = (dr as u16 + ((sr as u16) >> 1)).min(31) as u8;
|
let r = (dr as u16 + ((sr as u16) >> 1)).min(31) as u8;
|
||||||
let g = (dg as u16 + ((sg as u16) >> 1)).min(63) as u8;
|
let g = (dg as u16 + ((sg as u16) >> 1)).min(63) as u8;
|
||||||
let b = (db as u16 + ((sb as u16) >> 1)).min(31) as u8;
|
let b = (db as u16 + ((sb as u16) >> 1)).min(31) as u8;
|
||||||
|
|
||||||
pack_rgb565(r, g, b)
|
Color::pack_from_native(r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
BlendMode::HalfMinus => {
|
BlendMode::HalfMinus => {
|
||||||
let (dr, dg, db) = unpack_rgb565(dst);
|
let (dr, dg, db) = Color::unpack_to_native(dst);
|
||||||
let (sr, sg, sb) = unpack_rgb565(src);
|
let (sr, sg, sb) = Color::unpack_to_native(src);
|
||||||
|
|
||||||
let r = (dr as i16 - ((sr as i16) >> 1)).max(0) as u8;
|
let r = (dr as i16 - ((sr as i16) >> 1)).max(0) as u8;
|
||||||
let g = (dg as i16 - ((sg as i16) >> 1)).max(0) as u8;
|
let g = (dg as i16 - ((sg as i16) >> 1)).max(0) as u8;
|
||||||
let b = (db as i16 - ((sb as i16) >> 1)).max(0) as u8;
|
let b = (db as i16 - ((sb as i16) >> 1)).max(0) as u8;
|
||||||
|
|
||||||
pack_rgb565(r, g, b)
|
Color::pack_from_native(r, g, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
BlendMode::Full => {
|
BlendMode::Full => {
|
||||||
let (dr, dg, db) = unpack_rgb565(dst);
|
let (dr, dg, db) = Color::unpack_to_native(dst);
|
||||||
let (sr, sg, sb) = unpack_rgb565(src);
|
let (sr, sg, sb) = Color::unpack_to_native(src);
|
||||||
|
|
||||||
let r = (dr as u16 + sr as u16).min(31) as u8;
|
let r = (dr as u16 + sr as u16).min(31) as u8;
|
||||||
let g = (dg as u16 + sg as u16).min(63) as u8;
|
let g = (dg as u16 + sg as u16).min(63) as u8;
|
||||||
let b = (db as u16 + sb as u16).min(31) as u8;
|
let b = (db as u16 + sb as u16).min(31) as u8;
|
||||||
|
|
||||||
pack_rgb565(r, g, b)
|
Color::pack_from_native(r, g, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extrai canais já na faixa nativa do RGB565:
|
|
||||||
/// R: 0..31, G: 0..63, B: 0..31
|
|
||||||
#[inline(always)]
|
|
||||||
fn unpack_rgb565(px: u16) -> (u8, u8, u8) {
|
|
||||||
let r = ((px >> 11) & 0x1F) as u8;
|
|
||||||
let g = ((px >> 5) & 0x3F) as u8;
|
|
||||||
let b = (px & 0x1F) as u8;
|
|
||||||
(r, g, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn pack_rgb565(r: u8, g: u8, b: u8) -> u16 {
|
|
||||||
((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,22 +1,22 @@
|
|||||||
use crate::c_button::CButton;
|
use crate::model::Button;
|
||||||
use crate::peripherals::input_signal::InputSignals;
|
use crate::peripherals::input_signal::InputSignals;
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub struct Pad {
|
pub struct Pad {
|
||||||
pub up: CButton,
|
pub up: Button,
|
||||||
pub down: CButton,
|
pub down: Button,
|
||||||
pub left: CButton,
|
pub left: Button,
|
||||||
pub right: CButton,
|
pub right: Button,
|
||||||
|
|
||||||
pub a: CButton, // ps: square
|
pub a: Button, // ps: square
|
||||||
pub d: CButton, // ps: circle
|
pub d: Button, // ps: circle
|
||||||
pub w: CButton, // ps: triangle
|
pub w: Button, // ps: triangle
|
||||||
pub s: CButton, // ps: cross
|
pub s: Button, // ps: cross
|
||||||
pub q: CButton, // ps: R
|
pub q: Button, // ps: R
|
||||||
pub e: CButton, // ps: L
|
pub e: Button, // ps: L
|
||||||
|
|
||||||
pub start: CButton,
|
pub start: Button,
|
||||||
pub select: CButton,
|
pub select: Button,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pad {
|
impl Pad {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
use crate::c_button::CButton;
|
use crate::model::Button;
|
||||||
use crate::peripherals::input_signal::InputSignals;
|
use crate::peripherals::input_signal::InputSignals;
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Default, Clone, Copy, Debug)]
|
||||||
pub struct Touch {
|
pub struct Touch {
|
||||||
pub f: CButton,
|
pub f: Button,
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,7 +72,7 @@ Isso garante:
|
|||||||
O mundo gráfico é composto por:
|
O mundo gráfico é composto por:
|
||||||
|
|
||||||
- Até **16 Tile Banks**
|
- Até **16 Tile Banks**
|
||||||
- **4 Game Layers** (scrolláveis)
|
- **4 Tile Layers** (scrolláveis)
|
||||||
- **1 HUD Layer** (fixa, sempre por cima)
|
- **1 HUD Layer** (fixa, sempre por cima)
|
||||||
- Sprites com prioridade entre layers
|
- Sprites com prioridade entre layers
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ O mundo gráfico é composto por:
|
|||||||
### 4.2 Layers
|
### 4.2 Layers
|
||||||
|
|
||||||
- Existem:
|
- Existem:
|
||||||
- 4 Game Layers
|
- 4 Tile Layers
|
||||||
- 1 HUD Layer
|
- 1 HUD Layer
|
||||||
- Cada layer aponta para **um único bank**
|
- Cada layer aponta para **um único bank**
|
||||||
- Sprites podem usar **qualquer bank**
|
- Sprites podem usar **qualquer bank**
|
||||||
@ -296,7 +296,7 @@ Por design:
|
|||||||
O PROMETEU suporta **fade gradual** como um PostFX especial, com dois controles
|
O PROMETEU suporta **fade gradual** como um PostFX especial, com dois controles
|
||||||
independentes:
|
independentes:
|
||||||
|
|
||||||
- **Scene Fade**: afeta toda a cena (Game Layers 0–3 + Sprites)
|
- **Scene Fade**: afeta toda a cena (Tile Layers 0–3 + Sprites)
|
||||||
- **HUD Fade**: afeta apenas o HUD Layer (sempre composto por último)
|
- **HUD Fade**: afeta apenas o HUD Layer (sempre composto por último)
|
||||||
|
|
||||||
O fade é implementado sem alpha contínuo por pixel e sem floats.
|
O fade é implementado sem alpha contínuo por pixel e sem floats.
|
||||||
@ -371,7 +371,7 @@ Observações:
|
|||||||
|
|
||||||
A composição do frame segue esta ordem:
|
A composição do frame segue esta ordem:
|
||||||
|
|
||||||
1. Rasterizar **Game Layers 0–3** → Back Buffer
|
1. Rasterizar **Tile Layers 0–3** → Back Buffer
|
||||||
2. Rasterizar **Sprites** conforme prioridade
|
2. Rasterizar **Sprites** conforme prioridade
|
||||||
3. (Opcional) Pipeline extra (Emission/Light/Glow etc.)
|
3. (Opcional) Pipeline extra (Emission/Light/Glow etc.)
|
||||||
4. Aplicar **Scene Fade** usando:
|
4. Aplicar **Scene Fade** usando:
|
||||||
@ -415,7 +415,7 @@ O GFX do PROMETEU é simples **por escolha**, não por limitação.
|
|||||||
- Color key para transparência
|
- Color key para transparência
|
||||||
- Blending discreto estilo SNES
|
- Blending discreto estilo SNES
|
||||||
- Até 16 tile banks
|
- Até 16 tile banks
|
||||||
- 4 game layers + 1 HUD
|
- 4 Tile Layers + 1 HUD
|
||||||
- Layer = tilemap + cache + scroll
|
- Layer = tilemap + cache + scroll
|
||||||
- Projeção rasterizada por frame
|
- Projeção rasterizada por frame
|
||||||
- Profundidade definida por ordem de desenho
|
- Profundidade definida por ordem de desenho
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user