@@ -2099,6 +2099,7 @@ name = "unnamed" | |||||
version = "0.1.0" | version = "0.1.0" | ||||
dependencies = [ | dependencies = [ | ||||
"ggez", | "ggez", | ||||
"mint", | |||||
"serde", | "serde", | ||||
"thiserror", | "thiserror", | ||||
"toml", | "toml", | ||||
@@ -11,3 +11,4 @@ ggez = "0.5.1" | |||||
thiserror = "1.0.20" | thiserror = "1.0.20" | ||||
serde = { version = "1.0.114", features = ["derive"] } | serde = { version = "1.0.114", features = ["derive"] } | ||||
toml = "0.5.6" | 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 util; | ||||
mod world; | mod world; | ||||
// namespacing | |||||
use ggez::{ | use ggez::{ | ||||
event::{self, EventHandler}, | event::{self, EventHandler}, | ||||
graphics, Context, ContextBuilder, GameResult, | graphics, Context, ContextBuilder, GameResult, | ||||
}; | }; | ||||
use world::World; | use world::World; | ||||
/// lazy result type | |||||
pub type Result<T> = std::result::Result<T, Error>; | pub type Result<T> = std::result::Result<T, Error>; | ||||
// error types | |||||
#[derive(Debug, thiserror::Error)] | #[derive(Debug, thiserror::Error)] | ||||
pub enum Error { | pub enum Error { | ||||
// external errors | |||||
#[error(transparent)] | #[error(transparent)] | ||||
Stdio(#[from] std::io::Error), | Stdio(#[from] std::io::Error), | ||||
#[error(transparent)] | #[error(transparent)] | ||||
Toml(#[from] toml::de::Error), | 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 { | struct MainState { | ||||
world: World, | world: World, | ||||
} | } | ||||
impl MainState { | impl MainState { | ||||
pub fn new(_ctx: &mut Context) -> MainState { | |||||
// create a new gamestate | |||||
pub fn new(ctx: &mut Context) -> MainState { | |||||
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<()> { | 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) | graphics::present(ctx) | ||||
} | } | ||||
} | } | ||||
fn main() { | 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); | 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; | 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::*; | use tile::*; | ||||
/// represent a whole rendered world | |||||
pub struct 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 { | 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, | width, | ||||
height, | height, | ||||
depth, | 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 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 { | pub struct TileBuilder { | ||||
textures: HashMap<String, MasterTile>, | textures: HashMap<String, MasterTile>, | ||||
} | } | ||||
impl TileBuilder { | impl TileBuilder { | ||||
/// create a new tile builder | |||||
pub fn new(ctx: &mut Context, tiles_config: PathBuf) -> Result<TileBuilder> { | 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 raw_map: HashMap<String, RawTile> = toml::from_str(&raw_data)?; | ||||
let mut textures: HashMap<String, MasterTile> = HashMap::new(); | let mut textures: HashMap<String, MasterTile> = HashMap::new(); | ||||
raw_map.iter().for_each(|(kind, raw_tile)| { | raw_map.iter().for_each(|(kind, raw_tile)| { | ||||
let tile = MasterTile { | 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(), | kind: kind.to_owned(), | ||||
blocking: raw_tile.blocking, | blocking: raw_tile.blocking, | ||||
}; | }; | ||||
@@ -27,20 +30,43 @@ impl TileBuilder { | |||||
Ok(TileBuilder { textures: textures }) | 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 { | pub struct Tile { | ||||
texture: Rc<Image>, | texture: Rc<Image>, | ||||
kind: String, | kind: String, | ||||
blocking: bool, | blocking: bool, | ||||
} | } | ||||
impl Tile { | |||||
pub fn texture(&self) -> &Image { | |||||
&*self.texture | |||||
} | |||||
} | |||||
// internal tile type that contains the image texture data | |||||
struct MasterTile { | struct MasterTile { | ||||
texture: Image, | |||||
texture: Rc<Image>, | |||||
kind: String, | kind: String, | ||||
blocking: bool, | blocking: bool, | ||||
} | } | ||||
// for deserializing from the config file | |||||
#[derive(Deserialize)] | #[derive(Deserialize)] | ||||
struct RawTile { | struct RawTile { | ||||
path: PathBuf, | path: PathBuf, | ||||