mirror of
https://github.com/Noratrieb/game-wip-dontplay.git
synced 2026-01-16 04:25:00 +01:00
Implement texture atlas building
This commit is contained in:
parent
5ec0ad0da4
commit
45801205dc
17 changed files with 558 additions and 60 deletions
|
|
@ -59,7 +59,7 @@ impl App {
|
|||
Ok(Self {
|
||||
rw,
|
||||
should_quit: false,
|
||||
game: GameState::new(args.world_name, path),
|
||||
game: GameState::new(args.world_name, path, &res),
|
||||
res,
|
||||
sf_egui,
|
||||
input: Input::default(),
|
||||
|
|
|
|||
17
src/game.rs
17
src/game.rs
|
|
@ -50,16 +50,16 @@ impl GameState {
|
|||
}
|
||||
pub(crate) fn draw_world(&mut self, rt: &mut RenderTexture, res: &mut Res) {
|
||||
self.light_sources.clear();
|
||||
let mut s = Sprite::with_texture(&res.tile_atlas);
|
||||
let mut s = Sprite::with_texture(&res.atlas.tex);
|
||||
for_each_tile_on_screen(self.camera_offset, rt.size(), |tp, sp| {
|
||||
let tile = self.world.tile_at_mut(tp, &self.worldgen);
|
||||
s.set_position(sp.to_sf_vec());
|
||||
if tile.bg != Tile::EMPTY {
|
||||
s.set_texture_rect(self.tile_db[tile.bg].atlas_offset.to_sf_rect());
|
||||
s.set_texture_rect(self.tile_db[tile.bg].tex_rect.to_sf());
|
||||
rt.draw(&s);
|
||||
}
|
||||
if tile.mid != Tile::EMPTY {
|
||||
s.set_texture_rect(self.tile_db[tile.mid].atlas_offset.to_sf_rect());
|
||||
s.set_texture_rect(self.tile_db[tile.mid].tex_rect.to_sf());
|
||||
if let Some(light) = self.tile_db[tile.mid].light {
|
||||
let pos = ScreenVec {
|
||||
x: sp.x + light.x,
|
||||
|
|
@ -70,7 +70,7 @@ impl GameState {
|
|||
rt.draw(&s);
|
||||
}
|
||||
if tile.fg != Tile::EMPTY {
|
||||
s.set_texture_rect(self.tile_db[tile.fg].atlas_offset.to_sf_rect());
|
||||
s.set_texture_rect(self.tile_db[tile.fg].tex_rect.to_sf());
|
||||
rt.draw(&s);
|
||||
}
|
||||
});
|
||||
|
|
@ -106,8 +106,9 @@ impl GameState {
|
|||
self.ambient_light,
|
||||
255,
|
||||
));
|
||||
let mut s = Sprite::with_texture(&res.atlas.tex);
|
||||
s.set_texture_rect(res.atlas.rects["light/1"].to_sf());
|
||||
for ls in &self.light_sources {
|
||||
let mut s = Sprite::with_texture(&res.light_texture);
|
||||
let flicker = smoothwave(self.world.ticks, 40) as f32 / 64.0;
|
||||
s.set_scale((4. + flicker, 4. + flicker));
|
||||
s.set_origin((128., 128.));
|
||||
|
|
@ -116,9 +117,11 @@ impl GameState {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(world_name: String, path: PathBuf) -> GameState {
|
||||
pub(crate) fn new(world_name: String, path: PathBuf, res: &Res) -> GameState {
|
||||
let mut spawn_point = WorldPos::SURFACE_CENTER;
|
||||
spawn_point.y -= 1104;
|
||||
let mut tile_db = TileDb::load_or_default();
|
||||
tile_db.update_rects(&res.atlas.rects);
|
||||
Self {
|
||||
camera_offset: spawn_point,
|
||||
world: World::new(spawn_point, &world_name, path),
|
||||
|
|
@ -129,7 +132,7 @@ impl GameState {
|
|||
worldgen: Worldgen::from_seed(0),
|
||||
ambient_light: 0,
|
||||
light_sources: Vec::new(),
|
||||
tile_db: TileDb::load_or_default(),
|
||||
tile_db,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ mod math;
|
|||
mod player;
|
||||
mod res;
|
||||
mod stringfmt;
|
||||
mod texture_atlas;
|
||||
mod tiles;
|
||||
mod world;
|
||||
mod worldgen;
|
||||
|
|
|
|||
21
src/math.rs
21
src/math.rs
|
|
@ -1,5 +1,8 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use egui_inspect::derive::Inspect;
|
||||
use num_traits::{Num, Signed};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::world::{TPosSc, TilePos};
|
||||
|
||||
|
|
@ -81,6 +84,24 @@ pub fn smoothwave<T: Num + From<u8> + PartialOrd + Copy>(input: T, max: T) -> T
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Inspect, Default, Clone, Copy)]
|
||||
pub struct IntRect {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
}
|
||||
impl IntRect {
|
||||
pub(crate) fn to_sf(&self) -> sfml::graphics::Rect<i32> {
|
||||
sfml::graphics::Rect::<i32> {
|
||||
left: self.x,
|
||||
top: self.y,
|
||||
width: self.w,
|
||||
height: self.h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_smooth_wave() {
|
||||
assert_eq!(smoothwave(0, 100), 0);
|
||||
|
|
|
|||
10
src/res.rs
10
src/res.rs
|
|
@ -1,9 +1,10 @@
|
|||
use sfml::{audio::Music, graphics::Texture, SfBox};
|
||||
use sfml::audio::Music;
|
||||
|
||||
use crate::texture_atlas::AtlasBundle;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Res {
|
||||
pub tile_atlas: SfBox<Texture>,
|
||||
pub light_texture: SfBox<Texture>,
|
||||
pub atlas: AtlasBundle,
|
||||
pub surf_music: Music<'static>,
|
||||
pub und_music: Music<'static>,
|
||||
}
|
||||
|
|
@ -11,8 +12,7 @@ pub struct Res {
|
|||
impl Res {
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
Ok(Self {
|
||||
tile_atlas: Texture::from_file("res/graphics/tiles.png")?,
|
||||
light_texture: Texture::from_file("res/graphics/light2.png")?,
|
||||
atlas: AtlasBundle::new()?,
|
||||
surf_music: Music::from_file("res/music/music.ogg").unwrap(),
|
||||
und_music: Music::from_file("res/music/cave2.ogg").unwrap(),
|
||||
})
|
||||
|
|
|
|||
102
src/texture_atlas.rs
Normal file
102
src/texture_atlas.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use crate::math::IntRect;
|
||||
use sfml::{graphics::Texture, SfBox};
|
||||
use texture_packer::{texture::Texture as _, TexturePacker, TexturePackerConfig};
|
||||
|
||||
pub type RectMap = HashMap<String, IntRect>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AtlasBundle {
|
||||
pub tex: SfBox<Texture>,
|
||||
// Key could be `tiles/dirt` for example, derived from folder+filename without extension
|
||||
pub rects: RectMap,
|
||||
}
|
||||
|
||||
impl AtlasBundle {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let cfg = TexturePackerConfig {
|
||||
max_width: 4096,
|
||||
max_height: 4096,
|
||||
allow_rotation: false,
|
||||
border_padding: 0,
|
||||
texture_padding: 1,
|
||||
texture_extrusion: 0,
|
||||
trim: true,
|
||||
texture_outlines: false,
|
||||
};
|
||||
let mut packer = TexturePacker::new_skyline(cfg);
|
||||
walk_graphics(|path| {
|
||||
let img = image::open(path).unwrap();
|
||||
let key = path_img_key(path);
|
||||
packer.pack_own(key, img).unwrap();
|
||||
dbg!(path);
|
||||
});
|
||||
let mut rects = HashMap::new();
|
||||
let mut tex = Texture::new().unwrap();
|
||||
if !tex.create(packer.width(), packer.height()) {
|
||||
panic!("Failed to create texture");
|
||||
}
|
||||
let pixbuf = make_pix_buf(&packer);
|
||||
unsafe {
|
||||
tex.update_from_pixels(&pixbuf, packer.width(), packer.height(), 0, 0);
|
||||
}
|
||||
for (k, frame) in packer.get_frames() {
|
||||
rects.insert(
|
||||
k.clone(),
|
||||
IntRect {
|
||||
x: frame.frame.x as i32,
|
||||
y: frame.frame.y as i32,
|
||||
w: frame.frame.w as i32,
|
||||
h: frame.frame.h as i32,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(AtlasBundle { tex, rects })
|
||||
}
|
||||
}
|
||||
|
||||
fn make_pix_buf(packer: &TexturePacker<image::DynamicImage, String>) -> Vec<u8> {
|
||||
let (w, h) = (packer.width(), packer.height());
|
||||
let px_size = 4;
|
||||
let mut vec = vec![0; w as usize * h as usize * px_size as usize];
|
||||
dbg!(w, h);
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let idx = ((y * w + x) * px_size) as usize;
|
||||
if let Some(px) = packer.get(x, y) {
|
||||
vec[idx..idx + px_size as usize].copy_from_slice(&px.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
fn path_img_key(path: &Path) -> String {
|
||||
let mut rev_iter = path.components().rev();
|
||||
let fname = rev_iter.next().unwrap();
|
||||
let folder = rev_iter.next().unwrap();
|
||||
let fname: &Path = fname.as_ref();
|
||||
let folder: &Path = folder.as_ref();
|
||||
folder
|
||||
.join(fname.file_stem().unwrap())
|
||||
.display()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_img_key() {
|
||||
assert_eq!(
|
||||
&path_img_key("/home/person/res/graphics/tiles/foo.png".as_ref()),
|
||||
"tiles/foo"
|
||||
);
|
||||
}
|
||||
|
||||
fn walk_graphics(mut f: impl FnMut(&Path)) {
|
||||
for en in walkdir::WalkDir::new("res/graphics") {
|
||||
let en = en.unwrap();
|
||||
if en.file_type().is_file() {
|
||||
f(en.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/tiles.rs
75
src/tiles.rs
|
|
@ -4,11 +4,11 @@ use std::ops::Index;
|
|||
|
||||
use egui_inspect::derive::Inspect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sfml::graphics::IntRect;
|
||||
|
||||
use crate::{
|
||||
graphics::{ScreenSc, ScreenVec},
|
||||
math::TILE_SIZE,
|
||||
math::{IntRect, TILE_SIZE},
|
||||
texture_atlas::RectMap,
|
||||
world::TileId,
|
||||
};
|
||||
|
||||
|
|
@ -17,9 +17,11 @@ pub struct TileDef {
|
|||
pub bb: Option<TileBb>,
|
||||
/// Whether the tile emits light, and the light source offset
|
||||
pub light: Option<ScreenVec>,
|
||||
pub atlas_offset: AtlasOffset,
|
||||
/// Platform behavior: Horizontally passable, vertically passable upwards
|
||||
pub platform: bool,
|
||||
#[serde(default)]
|
||||
pub graphic_name: String,
|
||||
pub tex_rect: IntRect,
|
||||
}
|
||||
|
||||
const DEFAULT_TILE_BB: TileBb = TileBb {
|
||||
|
|
@ -44,9 +46,18 @@ pub struct TileDb {
|
|||
|
||||
impl Default for TileDb {
|
||||
fn default() -> Self {
|
||||
let unknown = TileDef {
|
||||
bb: None,
|
||||
light: Some(ScreenVec {
|
||||
x: TILE_SIZE as ScreenSc / 2,
|
||||
y: TILE_SIZE as ScreenSc / 2,
|
||||
}),
|
||||
platform: false,
|
||||
graphic_name: String::from("tiles/unknown"),
|
||||
tex_rect: IntRect::default(),
|
||||
};
|
||||
Self {
|
||||
// Add empty/air tile
|
||||
db: vec![EMPTY],
|
||||
db: vec![EMPTY, unknown],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,52 +67,26 @@ const EMPTY: TileDef = TileDef {
|
|||
light: None,
|
||||
// Rendering empty tile is actually special cased, and no rendering is done.
|
||||
// But just in case, put the offset to UNKNOWN
|
||||
atlas_offset: UNKNOWN_ATLAS_OFF,
|
||||
tex_rect: IntRect {
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0,
|
||||
h: 0,
|
||||
},
|
||||
platform: false,
|
||||
graphic_name: String::new(),
|
||||
};
|
||||
|
||||
impl Index<TileId> for TileDb {
|
||||
type Output = TileDef;
|
||||
|
||||
fn index(&self, index: TileId) -> &Self::Output {
|
||||
self.db.get(index as usize).unwrap_or(&UNKNOWN_TILE)
|
||||
self.db.get(index as usize).unwrap_or_else(|| {
|
||||
&self.db[1] // Unknown tile def is stored at index 1
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Inspect, Serialize, Deserialize)]
|
||||
pub struct AtlasOffset {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
}
|
||||
impl AtlasOffset {
|
||||
pub(crate) fn to_sf_rect(&self) -> IntRect {
|
||||
IntRect {
|
||||
left: self.x as i32,
|
||||
top: self.y as i32,
|
||||
width: TILE_SIZE as i32,
|
||||
height: TILE_SIZE as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AtlasOffset {
|
||||
fn default() -> Self {
|
||||
UNKNOWN_ATLAS_OFF
|
||||
}
|
||||
}
|
||||
|
||||
const UNKNOWN_ATLAS_OFF: AtlasOffset = AtlasOffset { x: 320, y: 0 };
|
||||
|
||||
static UNKNOWN_TILE: TileDef = TileDef {
|
||||
bb: None,
|
||||
light: Some(ScreenVec {
|
||||
x: TILE_SIZE as ScreenSc / 2,
|
||||
y: TILE_SIZE as ScreenSc / 2,
|
||||
}),
|
||||
atlas_offset: UNKNOWN_ATLAS_OFF,
|
||||
platform: false,
|
||||
};
|
||||
|
||||
const PATH: &str = "tiles.dat";
|
||||
|
||||
impl TileDb {
|
||||
|
|
@ -129,4 +114,12 @@ impl TileDb {
|
|||
Err(e) => log::warn!("Failed to save tile db: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_rects(&mut self, rects: &RectMap) {
|
||||
for def in &mut self.db {
|
||||
if !def.graphic_name.is_empty() {
|
||||
def.tex_rect = rects[&def.graphic_name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl World {
|
|||
chk.at_mut(local)
|
||||
}
|
||||
pub fn save(&self) {
|
||||
let result = std::fs::create_dir(&self.path);
|
||||
let result = std::fs::create_dir_all(&self.path);
|
||||
log::info!("{result:?}");
|
||||
self.save_meta();
|
||||
self.player.save(&self.path);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Worldgen {
|
|||
.add(
|
||||
Tile::new(Tl {
|
||||
bg: 9,
|
||||
mid: 2,
|
||||
mid: 3,
|
||||
fg: 6,
|
||||
})
|
||||
.when(constraint!(nm.clone(), < -0.8)),
|
||||
|
|
@ -43,7 +43,7 @@ impl Worldgen {
|
|||
.add(
|
||||
Tile::new(Tl {
|
||||
bg: 9,
|
||||
mid: 2,
|
||||
mid: 3,
|
||||
fg: 0,
|
||||
})
|
||||
.when(constraint!(nm.clone(), < -0.1)),
|
||||
|
|
@ -52,7 +52,7 @@ impl Worldgen {
|
|||
.add(
|
||||
Tile::new(Tl {
|
||||
bg: 7,
|
||||
mid: 1,
|
||||
mid: 2,
|
||||
fg: 0,
|
||||
})
|
||||
.when(constraint!(nm, < 0.45)),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue