added pallete system

This commit is contained in:
bQUARKz 2026-01-14 08:46:23 +00:00
parent a9345244e3
commit 48c6c6d7b0
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 118 additions and 127 deletions

View File

@ -49,132 +49,99 @@ impl Machine {
/// Lógica do frame (demo hardcoded por enquanto).
pub fn tick(&mut self) {
// Limpa a tela com um azul escuro "estilo console"
// Limpa a tela com Preto
self.gfx.clear(Color::BLACK);
// Simula o carregamento de um banco se estiver vazio (apenas para teste)
// SETUP BANCO 0 (Tiles 8x8)
if self.gfx.banks[0].is_none() {
let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128);
// 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 mut bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128);
// Define Cor na Paleta 0, Índice 1 = VERDE
bank.palettes[0][1] = Color::GREEN.raw();
let tile_size = 8;
let start_x = 8; // ID 1 está na segunda posição da primeira linha
let start_y = 0;
let start_x = 8; // Tile ID 1
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::GREEN;
let idx = y * 128 + (start_x + x);
bank.pixel_indices[idx] = 1; // Atribui o índice da paleta
}
}
self.gfx.banks[0] = Some(test_bank);
self.gfx.banks[0] = Some(bank);
}
// Simula o carregamento de um banco se estiver vazio (apenas para teste)
// SETUP BANCO 1 (Tiles 16x16)
if self.gfx.banks[1].is_none() {
let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128);
// Pinta o primeiro tile (ID 1) de Branco para teste
// Ele começa no pixel (16, 0) pois o ID 0 é o primeiro (0,0)
let tile_size = 16;
let start_x = 16; // ID 1 está na segunda posição da primeira linha
let start_y = 0;
let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128);
// Define Cor na Paleta 0, Índice 1 = VERMELHO
bank.palettes[0][1] = Color::RED.raw();
let tile_size = 16;
let start_x = 16; // Tile ID 1
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::RED;
let idx = y * 128 + (start_x + x);
bank.pixel_indices[idx] = 1;
}
}
self.gfx.banks[1] = Some(test_bank);
self.gfx.banks[1] = Some(bank);
}
// Simula o carregamento de um banco se estiver vazio (apenas para teste)
// SETUP BANCO 2 (Tiles 16x16 para Sprites)
if self.gfx.banks[2].is_none() {
let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128);
// Pinta o primeiro tile (ID 1) de Branco para teste
// Ele começa no pixel (16, 0) pois o ID 0 é o primeiro (0,0)
let tile_size = 16;
let start_x = 16; // ID 1 está na segunda posição da primeira linha
let start_y = 0;
let mut bank = crate::model::TileBank::new(crate::model::TileSize::Size16, 128, 128);
// Define Cor na Paleta 0, Índice 1 = INDIGO (Roxo)
bank.palettes[0][1] = Color::rgb(0x4B, 0x00, 0x82).raw();
// Define Cor na Paleta 1, Índice 1 = AMARELO (para Palette Swap)
bank.palettes[1][1] = Color::YELLOW.raw();
let tile_size = 16;
let start_x = 16;
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::INDIGO;
let idx = y * 128 + (start_x + x);
bank.pixel_indices[idx] = 1;
}
}
self.gfx.banks[2] = Some(test_bank);
self.gfx.banks[2] = Some(bank);
}
// Coloca o Tile ID 1 no mapa do HUD (canto superior esquerdo)
self.gfx.hud.map.set_tile(18, 6, crate::model::Tile { id: 1, ..Default::default() });
// --- LÓGICA DE ESTADO ---
self.gfx.layers[0].bank_id = 1;
self.gfx.layers[0].map.set_tile(8, 1, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[0].map.set_tile(8, 2, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[0].map.set_tile(8, 3, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[0].map.set_tile(8, 4, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[0].map.set_tile(8, 5, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[1].bank_id = 1;
self.gfx.layers[1].map.set_tile(9, 1, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[1].map.set_tile(9, 2, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[1].map.set_tile(9, 3, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[1].map.set_tile(9, 4, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[1].map.set_tile(9, 5, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[2].bank_id = 1;
self.gfx.layers[2].map.set_tile(10, 1, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[2].map.set_tile(10, 2, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[2].map.set_tile(10, 3, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[2].map.set_tile(10, 4, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[2].map.set_tile(10, 5, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[3].bank_id = 1;
self.gfx.layers[3].map.set_tile(11, 1, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[3].map.set_tile(11, 2, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[3].map.set_tile(11, 3, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[3].map.set_tile(11, 4, crate::model::Tile { id: 1, ..Default::default() });
self.gfx.layers[3].map.set_tile(11, 5, crate::model::Tile { id: 1, ..Default::default() });
// HUD
self.gfx.hud.map.set_tile(18, 6, crate::model::Tile { id: 1, palette_id: 0, ..Default::default() });
// Faz a cena pulsar entre claro e escuro a cada 2 segundos
// Usamos uma conta matemática simples para gerar um valor de 0 a 31
let pulse = (self.frame_index / 4) % 64;
let level = if pulse > 31 { 63 - pulse } else { pulse };
self.gfx.scene_fade_level = level as u8;
self.gfx.scene_fade_color = Color::BLACK;
// O HUD continuará 100% visível enquanto o resto escurece!
self.gfx.hud_fade_level = 31;
// Camadas de Jogo (usando Banco 1 - Vermelho)
for i in 0..4 {
self.gfx.layers[i].bank_id = 1;
self.gfx.layers[i].map.set_tile(8 + i, 1 + i, crate::model::Tile { id: 1, palette_id: 0, ..Default::default() });
}
// 512 Sprites (usando Banco 2 - Indigo e Amarelo)
for i in 0..512 {
let s = &mut self.gfx.sprites[i];
s.active = true;
// Distribui os sprites entre os 3 bancos de teste que você criou
s.bank_id = (i % 2) as u8 + 1;
s.bank_id = 2;
s.tile.id = 1;
// Cria um movimento caótico baseado no index do sprite e no frame_index
// Palette Swap: pares Indigo (0), ímpares Amarelo (1)
s.tile.palette_id = (i % 2) as u8;
let speed = (1 + (i % 3)) as u64;
s.x = (((self.frame_index * speed) + (i as u64 * 10)) % 400) as i32 - 40;
s.y = (((self.frame_index * speed) + (i as u64 * 4)) % 180) as i32;
if i % 2 != 0 {
s.y = 179 - s.y;
}
let y_base = (((self.frame_index * speed) + (i as u64 * 4)) % 180) as i32;
s.y = if i % 2 == 0 { y_base } else { 179 - y_base };
// Distribui as prioridades de 0 a 4
// Isso vai fazer alguns sprites passarem atrás do cenário e outros na frente
s.priority = (i % 5) as u8;
// Efeito de flip alternado para testar a lógica de espelhamento
s.flip_x = i % 2 == 0;
s.flip_y = i % 3 == 0;
}
// Post-FX Fade Pulsante
let pulse = (self.frame_index / 4) % 64;
let level = if pulse > 31 { 63 - pulse } else { pulse };
self.gfx.scene_fade_level = level as u8;
self.gfx.scene_fade_color = Color::BLACK;
self.gfx.hud_fade_level = 31;
}
/// Final do frame: troca buffers.

View File

@ -11,7 +11,12 @@ pub struct TileBank {
pub tile_size: TileSize,
pub width: usize, // em pixels
pub height: usize, // em pixels
pub pixels: Vec<Color>,
// pub pixels: Vec<Color>,
/// Agora guardamos índices de 0 a 15 (4 bits por pixel simulados em 8 bits)
pub pixel_indices: Vec<u8>,
/// 256 paletas, cada uma com 16 cores (RGB565 como u16)
pub palettes: [[u16; 16]; 256],
}
impl TileBank {
@ -20,26 +25,38 @@ impl TileBank {
tile_size,
width,
height,
pixels: vec![Color::BLACK; width * height],
pixel_indices: vec![0; width * height], // Índice 0 = Transparente
palettes: [[0; 16]; 256],
}
}
/// 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;
pub fn get_pixel_index(&self, tile_id: u16, local_x: usize, local_y: usize) -> u8 {
let size = self.tile_size as usize;
let tiles_per_row = self.width / size;
let tile_x = (tile_id as usize % tiles_per_row) * size;
let tile_y = (tile_id as usize / tiles_per_row) * 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]
self.pixel_indices[pixel_y * self.width + pixel_x]
} else {
Color::BLACK
0 // Fora do banco = Transparente
}
}
/// Resolve um índice de pixel para uma Color real usando uma paleta.
pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color {
// Regra: Índice 0 é sempre transparente (usamos MAGENTA/COLOR_KEY como vácuo)
if pixel_index == 0 {
return Color::COLOR_KEY;
}
let raw_color = self.palettes[palette_id as usize][pixel_index as usize];
Color::from_raw(raw_color)
}
}

View File

@ -248,10 +248,16 @@ impl Gfx {
let fetch_x = if tile.flip_x { size - 1 - local_x } else { local_x };
let fetch_y = if tile.flip_y { size - 1 - local_y } else { local_y };
let color = bank.get_pixel(tile.id, fetch_x, fetch_y);
if color == Color::COLOR_KEY { continue; }
// 1. Pega o índice do pixel no banco
let px_index = bank.get_pixel_index(tile.id, fetch_x, fetch_y);
back[world_y as usize * screen_w + world_x as usize] = color.0;
// 2. Regra: Índice 0 é transparente
if px_index == 0 { continue; }
// 3. Resolve a cor usando a paleta do tile
let color = bank.resolve_color(tile.palette_id, px_index);
back[world_y as usize * screen_w + world_x as usize] = color.raw();
}
}
}
@ -275,40 +281,31 @@ impl Gfx {
sprite: &Sprite,
bank: &TileBank
) {
// ... (mesmo cálculo de bounds/clipping que já tínhamos) ...
let size = bank.tile_size as usize;
let sprite_w = size as i32;
let sprite_h = size as i32;
// 1. Clipping Rápido: Se o sprite estiver totalmente fora, nem processamos
if sprite.x <= -sprite_w || sprite.x >= screen_w as i32 ||
sprite.y <= -sprite_h || sprite.y >= screen_h as i32 {
return;
}
// 2. Calcular a área visível do sprite (Interseção com a tela)
let start_x = sprite.x.max(0);
let start_y = sprite.y.max(0);
let end_x = (sprite.x + sprite_w).min(screen_w as i32);
let end_y = (sprite.y + sprite_h).min(screen_h as i32);
let end_x = (sprite.x + size as i32).min(screen_w as i32);
let end_y = (sprite.y + size as i32).min(screen_h as i32);
// 3. Loop apenas sobre os pixels visíveis na tela
for world_y in start_y..end_y {
for world_x in start_x..end_x {
// Calcular a posição local dentro do tile considerando o scroll/posição do sprite
let local_x = (world_x - sprite.x) as usize;
let local_y = (world_y - sprite.y) as usize;
// Lógica de Flip (Espelhamento)
let fetch_x = if sprite.flip_x { size - 1 - local_x } else { local_x };
let fetch_y = if sprite.flip_y { size - 1 - local_y } else { local_y };
let color = bank.get_pixel(sprite.tile.id, fetch_x, fetch_y);
// 1. Pega o índice
let px_index = bank.get_pixel_index(sprite.tile.id, fetch_x, fetch_y);
if color == Color::COLOR_KEY { continue; }
// 2. Transparência
if px_index == 0 { continue; }
let idx = (world_y as usize * screen_w) + world_x as usize;
back[idx] = color.0;
// 3. Resolve cor via paleta (do tile dentro do sprite)
let color = bank.resolve_color(sprite.tile.palette_id, px_index);
back[world_y as usize * screen_w + world_x as usize] = color.raw();
}
}
}
@ -334,34 +331,44 @@ impl Gfx {
}
}
/// Retorna o uso estimado de memória das estruturas gráficas em bytes.
/// Isso ajuda a monitorar o "custo" do hardware simulado.
/// Retorna o uso real de memória das estruturas gráficas em bytes.
/// Reflete os buffers de índices, paletas e OAM conforme Capítulo 10.8.
pub fn memory_usage_bytes(&self) -> usize {
let mut total = 0;
// Buffers de tela (Front + Back)
// 1. Framebuffers (Front + Back)
// Cada um é Vec<u16>, ocupando 2 bytes por pixel.
total += self.front.len() * 2;
total += self.back.len() * 2;
// Layers (Tiles + Metadados)
// 2. Tile Layers (4 Game Layers)
for layer in &self.layers {
total += layer.map.tiles.len() * std::mem::size_of::<crate::model::Tile>();
// Tamanho da struct + os dados do mapa (Vec<Tile>)
total += std::mem::size_of::<ScrollableTileLayer>();
total += layer.map.tiles.len() * std::mem::size_of::<crate::model::Tile>();
}
// HUD
total += self.hud.map.tiles.len() * std::mem::size_of::<crate::model::Tile>();
// 3. HUD Layer
total += std::mem::size_of::<HudTileLayer>();
total += self.hud.map.tiles.len() * std::mem::size_of::<crate::model::Tile>();
// Tile Banks (Apenas bancos carregados)
// 4. Tile Banks (Assets e Paletas)
for bank_opt in &self.banks {
if let Some(bank) = bank_opt {
total += bank.pixels.len() * 2; // RGB565 = 2 bytes
total += std::mem::size_of::<TileBank>();
// Buffer de Índices (pixel_indices: Vec<u8>)
// Cada pixel ocupa exatamente 1 byte.
total += bank.pixel_indices.len();
// Tabela de Paletas (palettes: [[u16; 16]; 256])
// 256 paletas * 16 cores * 2 bytes cada.
total += 256 * 16 * 2;
}
}
// Sprites (OAM)
// 5. Sprites (OAM)
// Array fixo de 512 Sprites.
total += self.sprites.len() * std::mem::size_of::<crate::model::Sprite>();
total