@@ -2099,6 +2099,7 @@ name = "unnamed" | |||
version = "0.1.0" | |||
dependencies = [ | |||
"ggez", | |||
"mint", | |||
"serde", | |||
"thiserror", | |||
"toml", | |||
@@ -11,3 +11,4 @@ ggez = "0.5.1" | |||
thiserror = "1.0.20" | |||
serde = { version = "1.0.114", features = ["derive"] } | |||
toml = "0.5.6" | |||
mint = "0.5.5" |
@@ -1,3 +0,0 @@ | |||
[nothing] | |||
path = "img/tiles/nothing.png" | |||
blocking = true |
@@ -0,0 +1,3 @@ | |||
[nothing] | |||
path = "/img/tiles/nothing.png" | |||
blocking = true |
@@ -1,30 +1,44 @@ | |||
#![allow(unused_imports)] | |||
// mods | |||
mod util; | |||
mod world; | |||
// namespacing | |||
use ggez::{ | |||
event::{self, EventHandler}, | |||
graphics, Context, ContextBuilder, GameResult, | |||
}; | |||
use world::World; | |||
/// lazy result type | |||
pub type Result<T> = std::result::Result<T, Error>; | |||
// error types | |||
#[derive(Debug, thiserror::Error)] | |||
pub enum Error { | |||
// external errors | |||
#[error(transparent)] | |||
Stdio(#[from] std::io::Error), | |||
#[error(transparent)] | |||
Toml(#[from] toml::de::Error), | |||
#[error(transparent)] | |||
Ggez(#[from] ggez::error::GameError), | |||
// internal errors | |||
#[error("could not find {expected} MasterTile on TileBuilder")] | |||
NoMasterTile { expected: String }, | |||
} | |||
// primary game state | |||
struct MainState { | |||
world: World, | |||
} | |||
impl MainState { | |||
pub fn new(_ctx: &mut Context) -> MainState { | |||
// create a new gamestate | |||
pub fn new(ctx: &mut Context) -> MainState { | |||
MainState { | |||
world: World::new(20, 20, 3), | |||
world: World::new(ctx, "/data/tiles.toml".into(), 5, 5, 1).expect("failed init world"), | |||
} | |||
} | |||
} | |||
@@ -35,15 +49,19 @@ impl EventHandler for MainState { | |||
} | |||
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> { | |||
graphics::clear(ctx, graphics::WHITE); | |||
// clear the screen | |||
graphics::clear(ctx, graphics::BLACK); | |||
self.world.render(ctx).expect("failed to render"); | |||
// render the context | |||
graphics::present(ctx) | |||
} | |||
} | |||
fn main() { | |||
let (mut ctx, mut event_loop) = ContextBuilder::new("unnamed", "Isabelle L.") | |||
.build() | |||
.expect("failed to start context"); | |||
let cb = ContextBuilder::new("unnamed", "Isabelle L.").add_resource_path("./"); | |||
let (mut ctx, mut event_loop) = cb.build().expect("failed to start context"); | |||
let mut state = MainState::new(&mut ctx); | |||
@@ -1,69 +1,62 @@ | |||
use std::convert::From; | |||
pub struct IsometricVector2(mint::Point2<f32>); | |||
pub struct CartesianVector2(mint::Point2<f32>); | |||
pub struct CartesianVector(usize, usize); | |||
pub struct IsometricVector(usize, usize); | |||
pub struct Vector3d(usize, usize, usize); | |||
impl Vector3d { | |||
pub fn x(&self) -> usize { | |||
self.0 | |||
} | |||
pub fn y(&self) -> usize { | |||
self.1 | |||
} | |||
pub fn z(&self) -> usize { | |||
self.2 | |||
} | |||
} | |||
impl From<(usize, usize, usize)> for Vector3d { | |||
fn from(tuple: (usize, usize, usize)) -> Vector3d { | |||
Vector3d(tuple.0, tuple.1, tuple.2) | |||
} | |||
} | |||
impl CartesianVector { | |||
pub fn x(&self) -> usize { | |||
self.0 | |||
impl IsometricVector2 { | |||
pub fn x(&self) -> f32 { | |||
self.0.x | |||
} | |||
pub fn y(&self) -> usize { | |||
self.1 | |||
pub fn y(&self) -> f32 { | |||
self.0.y | |||
} | |||
} | |||
impl IsometricVector { | |||
pub fn x(&self) -> usize { | |||
self.0 | |||
impl CartesianVector2 { | |||
pub fn x(&self) -> f32 { | |||
self.0.x | |||
} | |||
pub fn y(&self) -> usize { | |||
self.1 | |||
pub fn y(&self) -> f32 { | |||
self.0.y | |||
} | |||
} | |||
impl From<(usize, usize)> for CartesianVector { | |||
fn from(tuple: (usize, usize)) -> CartesianVector { | |||
CartesianVector(tuple.0, tuple.1) | |||
impl<T> From<(T, T)> for CartesianVector2 | |||
where | |||
T: std::convert::Into<f32>, | |||
{ | |||
fn from(tuple: (T, T)) -> CartesianVector2 { | |||
CartesianVector2(mint::Point2 { | |||
x: tuple.0.into(), | |||
y: tuple.1.into(), | |||
}) | |||
} | |||
} | |||
impl From<IsometricVector> for CartesianVector { | |||
fn from(iso: IsometricVector) -> CartesianVector { | |||
CartesianVector((2 * iso.y() + iso.x()) / 2, (2 * iso.y() - iso.x()) / 2) | |||
impl From<CartesianVector2> for IsometricVector2 { | |||
fn from(cart: CartesianVector2) -> IsometricVector2 { | |||
let x = cart.x() - cart.y(); | |||
let y = (cart.x() + cart.y()) / 2.0; | |||
IsometricVector2(mint::Point2 { x, y }) | |||
} | |||
} | |||
impl From<CartesianVector> for IsometricVector { | |||
fn from(cart: CartesianVector) -> IsometricVector { | |||
IsometricVector(cart.x() - cart.y(), (cart.x() + cart.y()) / 2) | |||
impl From<IsometricVector2> for CartesianVector2 { | |||
fn from(iso: IsometricVector2) -> CartesianVector2 { | |||
let x = (2.0 * iso.y() + iso.x()) / 2.0; | |||
let y = (2.0 * iso.y() - iso.x()) / 2.0; | |||
CartesianVector2(mint::Point2 { x, y }) | |||
} | |||
} | |||
impl From<(usize, usize)> for IsometricVector { | |||
fn from(tuple: (usize, usize)) -> IsometricVector { | |||
IsometricVector(tuple.0, tuple.1) | |||
impl<T> From<(T, T)> for IsometricVector2 | |||
where | |||
T: std::convert::Into<f32>, | |||
{ | |||
fn from(tuple: (T, T)) -> IsometricVector2 { | |||
IsometricVector2(mint::Point2 { | |||
x: tuple.0.into(), | |||
y: tuple.1.into(), | |||
}) | |||
} | |||
} |
@@ -1,23 +1,76 @@ | |||
// modules | |||
mod tile; | |||
use crate::util::Vector3d; | |||
use std::collections::HashMap; | |||
// namespacing | |||
use crate::{util::*, Result}; | |||
use ggez::{ | |||
graphics::{self, DrawParam}, | |||
Context, | |||
}; | |||
use mint::Vector3; | |||
use std::{collections::HashMap, path::PathBuf}; | |||
use tile::*; | |||
/// represent a whole rendered world | |||
pub struct World { | |||
width: usize, | |||
height: usize, | |||
depth: usize, | |||
data: HashMap<Vector3d, Tile>, | |||
width: isize, | |||
depth: isize, | |||
height: isize, | |||
data: HashMap<Vector3<isize>, Tile>, | |||
_builder: TileBuilder, | |||
offset: CartesianVector2, | |||
} | |||
impl World { | |||
pub fn new(width: usize, height: usize, depth: usize) -> World { | |||
World { | |||
/// create a new world instance | |||
pub fn new( | |||
ctx: &mut Context, | |||
tile_config: PathBuf, | |||
width: isize, | |||
depth: isize, | |||
height: isize, | |||
) -> Result<World> { | |||
let builder = TileBuilder::new(ctx, tile_config)?; | |||
let mut data: HashMap<Vector3<isize>, Tile> = HashMap::new(); | |||
let offset: CartesianVector2 = (350.0f32, 100.0f32).into(); | |||
for x in 0..width { | |||
for y in 0..depth { | |||
for z in 0..height { | |||
data.insert([x, y, z].into(), builder.build("nothing".to_owned())?); | |||
} | |||
} | |||
} | |||
Ok(World { | |||
width, | |||
height, | |||
depth, | |||
data: HashMap::new(), | |||
data, | |||
_builder: builder, | |||
offset, | |||
}) | |||
} | |||
/// renders the world to the window | |||
pub fn render(&self, ctx: &mut Context) -> Result<()> { | |||
for x in 0..self.width { | |||
for y in 0..self.depth { | |||
for z in 0..self.height { | |||
let tile = self.data.get(&[x, y, z].into()).unwrap(); | |||
let iso_coord: IsometricVector2 = | |||
CartesianVector2::from((x as f32, y as f32)).into(); | |||
let dest = [ | |||
(iso_coord.x() * 16.0 + self.offset.x()) as f32, | |||
(iso_coord.y() * 16.0 + self.offset.y()) as f32, | |||
]; | |||
let param = DrawParam::default().dest(mint::Point2::from_slice(&dest)); | |||
graphics::draw(ctx, tile.texture(), param)?; | |||
} | |||
} | |||
} | |||
Ok(()) | |||
} | |||
} |
@@ -1,24 +1,27 @@ | |||
use crate::Result; | |||
use ggez::graphics::Image; | |||
use ggez::Context; | |||
use crate::{Error, Result}; | |||
use ggez::{graphics::Image, Context}; | |||
use serde::Deserialize; | |||
use std::collections::HashMap; | |||
use std::path::PathBuf; | |||
use std::rc::Rc; | |||
use std::io::Read; | |||
use std::{collections::HashMap, path::PathBuf, rc::Rc}; | |||
/// used to contain all of the textures and tile data | |||
pub struct TileBuilder { | |||
textures: HashMap<String, MasterTile>, | |||
} | |||
impl TileBuilder { | |||
/// create a new tile builder | |||
pub fn new(ctx: &mut Context, tiles_config: PathBuf) -> Result<TileBuilder> { | |||
let raw_data = std::fs::read_to_string(tiles_config)?; | |||
let mut file = ggez::filesystem::open(ctx, tiles_config)?; | |||
let mut raw_data = String::new(); | |||
file.read_to_string(&mut raw_data)?; | |||
let raw_map: HashMap<String, RawTile> = toml::from_str(&raw_data)?; | |||
let mut textures: HashMap<String, MasterTile> = HashMap::new(); | |||
raw_map.iter().for_each(|(kind, raw_tile)| { | |||
let tile = MasterTile { | |||
texture: Image::new(ctx, &raw_tile.path).expect("failed to load image"), | |||
texture: Rc::new(Image::new(ctx, &raw_tile.path).expect("failed to load image")), | |||
kind: kind.to_owned(), | |||
blocking: raw_tile.blocking, | |||
}; | |||
@@ -27,20 +30,43 @@ impl TileBuilder { | |||
Ok(TileBuilder { textures: textures }) | |||
} | |||
/// build a new tile | |||
pub fn build(&self, kind: String) -> Result<Tile> { | |||
if let Some(master_tile) = self.textures.get(&kind) { | |||
Ok(Tile { | |||
texture: master_tile.texture.clone(), | |||
kind: master_tile.kind.clone(), | |||
blocking: master_tile.blocking, | |||
}) | |||
} else { | |||
Err(Error::NoMasterTile { expected: kind }) | |||
} | |||
} | |||
} | |||
/// tile for external use in rendering and etc | |||
#[allow(dead_code)] | |||
pub struct Tile { | |||
texture: Rc<Image>, | |||
kind: String, | |||
blocking: bool, | |||
} | |||
impl Tile { | |||
pub fn texture(&self) -> &Image { | |||
&*self.texture | |||
} | |||
} | |||
// internal tile type that contains the image texture data | |||
struct MasterTile { | |||
texture: Image, | |||
texture: Rc<Image>, | |||
kind: String, | |||
blocking: bool, | |||
} | |||
// for deserializing from the config file | |||
#[derive(Deserialize)] | |||
struct RawTile { | |||
path: PathBuf, | |||