imgui_integration example initial commit, work in progress

mostly working, but the imgui ui part is only a placeholder.

also this example project is kind of overly complicated, but i really
wanted this to be something that somewhat resembles a real tool that
i'd want to build.
This commit is contained in:
Gered 2023-04-10 16:38:38 -04:00
parent d85293d9bf
commit 424c63d414
14 changed files with 1160 additions and 0 deletions

View file

@ -0,0 +1,12 @@
[package]
name = "imgui_integration"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "=1.0.55"
ggdt = { path = "../../ggdt" }
ggdt_imgui = { path = "../../ggdt_imgui" }
imgui = "0.11.0"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"

View file

@ -0,0 +1,9 @@
{
"width":40,
"height":30,
"layers":[
[96,96,96,96,96,96,96,96,96,96,96,96,16,17,16,16,16,16,16,17,17,32,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,33,16,16,96,96,96,96,96,96,96,96,96,96,96,16,17,17,17,32,17,32,16,16,32,16,16,32,16,16,16,16,16,16,32,16,33,16,16,16,16,16,16,16,96,96,96,96,96,96,96,96,96,96,181,178,178,178,178,183,32,16,17,181,178,178,178,178,178,178,178,178,178,178,178,183,16,16,16,16,16,16,16,32,96,96,96,96,96,96,96,96,96,181,195,16,32,17,17,193,178,178,178,195,16,16,16,16,16,16,32,16,16,16,16,193,178,183,16,32,16,16,16,16,96,96,96,96,96,96,96,181,178,195,16,16,16,32,17,17,17,17,32,16,16,16,33,16,16,16,16,16,16,16,16,16,16,193,183,16,16,16,33,16,96,96,96,96,96,96,181,195,32,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,193,183,16,16,16,16,16,17,16,16,96,181,195,16,16,33,16,16,16,16,16,32,16,16,16,16,16,32,16,16,16,16,48,48,48,48,16,16,16,16,16,196,16,16,16,16,32,16,17,16,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,32,193,183,16,32,16,8,8,8,181,195,16,16,16,16,17,32,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,16,7,7,7,196,16,32,16,32,17,17,17,16,16,16,16,33,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,16,16,193,183,16,32,7,7,181,195,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,32,16,16,16,48,48,48,48,48,48,48,48,48,48,48,48,32,16,196,32,16,7,7,196,16,16,32,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,48,48,16,16,16,196,16,16,7,7,196,8,8,16,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,16,16,48,48,48,48,48,48,48,48,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,48,48,48,48,48,16,16,16,16,16,196,17,16,7,7,196,7,7,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,17,16,16,16,196,16,33,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,32,16,16,16,16,32,16,16,16,32,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,196,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,16,16,16,16,16,32,16,196,16,33,32,16,196,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,16,16,196,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,196,16,16,16,16,193,183,16,16,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,32,16,32,16,33,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,16,16,32,16,16,16,16,16,16,16,16,33,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,32,16,16,196,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,32,16,196,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,181,195,16,16,16,16,16,16,16,193,183,48,48,48,48,48,48,48,48,48,48,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,196,16,16,16,16,16,16,16,16,16,193,178,178,183,48,48,48,48,48,48,48,48,16,16,16,16,16,32,16,16,16,16,32,16,16,16,16,16,16,181,195,16,16,16,32,16,16,33,16,32,16,16,16,193,178,178,178,178,178,178,178,178,183,16,32,16,16,16,16,16,33,16,16,16,16,16,16,181,178,195,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,16,16,16,33,32,17,193,178,178,178,178,178,178,178,178,178,178,178,178,178,178,195,16,16,16,16,16,48,16,16,32,16,16,16,16,16,16,16,16,16,16,32,16,16,16,16,32,16,16,32,16,16,16,32,16,16,16,33,16,16,32,16,16,16,16,16,48,48],
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,87,-1,104,108,108,108,105,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,98,105,-1,-1,-1,26,-1,26,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,80,108,108,108,97,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,46,46,46,102,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,-1,27,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,51,-1,-1,26,-1,-1,-1,-1,-1,-1,27,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,52,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,67,-1,-1,27,279,-1,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,83,-1,-1,-1,46,46,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,-1,26,-1,-1,266,68,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,65,67,-1,-1,-1,-1,-1,-1,-1,-1,68,66,-1,-1,-1,-1,86,-1,-1,-1,-1,-1,29,-1,27,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,65,67,-1,-1,-1,-1,-1,68,66,-1,-1,26,-1,-1,86,-1,-1,-1,-1,-1,29,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,14,14,-1,14,14,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,-1,282,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,104,-1,282,-1,-1,-1,259,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,281,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,80,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,266,27,-1,278,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,-1,-1,-1,-1,27,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,294,-1,-1,294,-1,27,279,-1,-1,294,-1,-1,26,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,-1,52,50,-1,-1,-1,-1,-1,-1,-1,-1,278,-1,-1,278,-1,-1,278,-1,-1,80,-1,-1,-1,-1,26,-1,294,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,-1,-1,-1,266,52,-1,-1,-1,27,279,-1,-1,294,-1,-1,294,-1,-1,294,-1,266,-1,-1,-1,26,-1,-1,-1,-1,-1,260,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,26,-1,-1,-1,50,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,27,-1,49,-1,278,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,266,-1,-1,-1,-1,-1,-1,-1,27,-1,-1,-1,-1,-1,49,51,-1,294,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,49,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,49,51,27,-1],
[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,0,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,-1,0,1,1,0,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,-1,-1,-1,-1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,0,0,-1,-1,-1,-1,-1,-1,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,0,1,1,0,1,1,1,1,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,136 @@
use crate::entities::{AnimationDef, EntityActivity, Event};
use crate::support::{load_bitmap_atlas_autogrid, load_font, load_palette};
use crate::tilemap::TileMap;
use anyhow::Result;
use ggdt::prelude::*;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
pub struct CoreContext {
pub delta: f32,
pub camera_x: i32,
pub camera_y: i32,
pub transparent_color: u32,
pub system: System<Standard>,
pub palette: Palette,
pub font: BitmaskFont,
pub small_font: BitmaskFont,
pub entities: Entities,
pub event_publisher: EventPublisher<Event>,
pub tiles: Rc<BitmapAtlas<RgbaBitmap>>,
pub green_slime: Rc<BitmapAtlas<RgbaBitmap>>,
pub blue_slime: Rc<BitmapAtlas<RgbaBitmap>>,
pub orange_slime: Rc<BitmapAtlas<RgbaBitmap>>,
pub tilemap: TileMap,
pub slime_activity_states: Rc<HashMap<EntityActivity, Rc<AnimationDef>>>,
pub sprite_render_list: Vec<(EntityId, Vector2, RgbaBlitMethod)>,
}
impl CoreState<Standard> for CoreContext {
fn system(&self) -> &System<Standard> {
&self.system
}
fn system_mut(&mut self) -> &mut System<Standard> {
&mut self.system
}
fn delta(&self) -> f32 {
self.delta
}
fn set_delta(&mut self, delta: f32) {
self.delta = delta;
}
}
impl CoreStateWithEvents<Standard, Event> for CoreContext {
fn event_publisher(&mut self) -> &mut EventPublisher<Event> {
&mut self.event_publisher
}
}
pub struct SupportContext {
pub component_systems: ComponentSystems<CoreContext, CoreContext>,
pub event_listeners: EventListeners<Event, CoreContext>,
pub imgui: ggdt_imgui::ImGui,
}
impl SupportSystems for SupportContext {}
impl SupportSystemsWithEvents<Standard, Event> for SupportContext {
type ContextType = CoreContext;
fn event_listeners(&mut self) -> &mut EventListeners<Event, Self::ContextType> {
&mut self.event_listeners
}
}
pub struct GameContext {
pub core: CoreContext,
pub support: SupportContext,
}
impl AppContext<Standard> for GameContext {
type CoreType = CoreContext;
type SupportType = SupportContext;
fn core(&mut self) -> &mut Self::CoreType {
&mut self.core
}
fn support(&mut self) -> &mut Self::SupportType {
&mut self.support
}
}
impl GameContext {
pub fn new(mut system: System<Standard>) -> Result<Self> {
let palette = load_palette(Path::new("./assets/db16.pal"))?;
let font = load_font(Path::new("./assets/dp.fnt"))?;
let small_font = load_font(Path::new("./assets/small.fnt"))?;
let tiles = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/tiles.pcx"))?);
let green_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/green_slime.pcx"))?);
let blue_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/blue_slime.pcx"))?);
let orange_slime = Rc::new(load_bitmap_atlas_autogrid(Path::new("./assets/orange_slime.pcx"))?);
let mut tilemap = TileMap::load_from(Path::new("./assets/arena.map.json"))?;
let entities = Entities::new();
let component_systems = ComponentSystems::new();
let event_publisher = EventPublisher::new();
let event_listeners = EventListeners::new();
let imgui = ggdt_imgui::ImGui::new();
let slime_activity_states = HashMap::from([
(EntityActivity::Idle, Rc::new(AnimationDef::new(&[1, 2], true, 1.0, Some(3)))),
(EntityActivity::Walking, Rc::new(AnimationDef::new(&[1, 0, 2, 0], true, 0.25, Some(3)))),
]);
Ok(GameContext {
core: CoreContext {
delta: 0.0,
camera_x: 0,
camera_y: 0,
transparent_color: palette[0],
system,
palette,
font,
small_font,
entities,
event_publisher,
tiles,
green_slime,
blue_slime,
orange_slime,
tilemap,
slime_activity_states: Rc::new(slime_activity_states),
sprite_render_list: Vec::new(),
},
support: SupportContext { component_systems, event_listeners, imgui },
})
}
}

View file

@ -0,0 +1,767 @@
use crate::context::{CoreContext, GameContext};
use crate::tilemap::TileMap;
use ggdt::prelude::*;
use std::collections::HashMap;
use std::rc::Rc;
pub const FRICTION: f32 = 0.5;
pub const DEFAULT_PUSH_STRENGTH: f32 = 0.5;
pub const DEFAULT_PUSH_DISSIPATION: f32 = 0.5;
///////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum EntityActivity {
Idle,
Walking,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Direction {
South = 0,
West = 1,
East = 2,
North = 3,
}
impl Direction {
pub fn new_random() -> Self {
use Direction::*;
match rnd_value(0, 3) {
0 => South,
1 => West,
2 => East,
3 => North,
_ => panic!("unknown random direction!"),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum SlimeColor {
Green = 0,
Blue = 1,
Orange = 2,
}
impl SlimeColor {
pub fn new_random() -> Self {
use SlimeColor::*;
match rnd_value(0, 2) {
0 => Green,
1 => Blue,
2 => Orange,
_ => panic!("unknown random slime color!"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Force {
pub force: Vector2,
pub dissipation_factor: f32,
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Activity(pub EntityActivity);
pub struct AnimateByActivity(pub Rc<HashMap<EntityActivity, Rc<AnimationDef>>>);
#[derive(Debug)]
pub struct AnimationDef {
pub frames: &'static [usize],
pub loops: bool,
pub delay: f32,
pub multi_direction_offset: Option<usize>,
}
impl AnimationDef {
#[inline]
pub fn new(frames: &'static [usize], loops: bool, delay: f32, multi_direction_offset: Option<usize>) -> Self {
AnimationDef { frames, loops, delay, multi_direction_offset }
}
}
#[derive(Debug)]
pub struct AnimationInstance {
pub def: Rc<AnimationDef>,
pub frame_index: usize,
pub frame_timer: f32,
pub complete: bool,
pub delay_override: Option<f32>,
}
impl AnimationInstance {
#[inline]
pub fn from(def: Rc<AnimationDef>) -> Self {
AnimationInstance {
def, //
frame_index: 0,
frame_timer: 0.0,
complete: false,
delay_override: None,
}
}
#[inline]
pub fn change_to(&mut self, def: Rc<AnimationDef>) {
self.def = def;
self.reset();
}
#[inline]
pub fn reset(&mut self) {
self.frame_index = 0;
self.frame_timer = 0.0;
self.complete = false;
}
}
pub struct Bounds {
pub width: u32,
pub height: u32,
pub radius: u32,
}
pub struct FacingDirection(pub Direction);
pub struct Forces {
pub forces: Vec<Force>,
}
impl Forces {
pub fn new() -> Self {
Forces { forces: Vec::with_capacity(5) }
}
pub fn current_force(&self) -> Vector2 {
let mut total_force = Vector2::ZERO;
for force in self.forces.iter() {
total_force += force.force;
}
total_force
}
pub fn add(&mut self, force: Vector2, dissipation_factor: f32) {
self.forces.push(Force { force, dissipation_factor });
}
pub fn decay(&mut self) {
for force in self.forces.iter_mut() {
force.force *= force.dissipation_factor;
}
self.forces.retain(|f| !f.force.almost_zero(0.001));
}
}
pub struct IgnoresCollision;
pub struct IgnoresFriction;
pub struct MovementSpeed(pub f32);
pub struct Position(pub Vector2);
pub struct Pushable;
pub struct Pusher {
pub strength: f32,
pub push_force_dissipation: f32,
}
impl Pusher {
pub fn new() -> Self {
Pusher {
strength: DEFAULT_PUSH_STRENGTH, //
push_force_dissipation: DEFAULT_PUSH_DISSIPATION,
}
}
}
pub struct RandomlyWalksAround {
pub min_walk_time: f32,
pub max_walk_time: f32,
pub chance_to_move: u32,
pub min_cooldown: f32,
pub max_cooldown: f32,
pub cooldown_timer: f32,
}
impl RandomlyWalksAround {
pub fn new(
min_walk_time: f32,
max_walk_time: f32,
chance_to_move: u32,
min_cooldown: f32,
max_cooldown: f32,
) -> Self {
RandomlyWalksAround {
min_walk_time,
max_walk_time,
chance_to_move,
min_cooldown,
max_cooldown,
cooldown_timer: 0.0,
}
}
pub fn should_start_walking(&self) -> bool {
rnd_value(0, 100) < self.chance_to_move
}
}
pub struct Slime;
pub struct Sprite {
pub atlas: Rc<BitmapAtlas<RgbaBitmap>>,
pub index: usize,
}
pub struct SpriteIndexByDirection {
pub base_index: usize,
}
pub struct Velocity(pub Vector2);
pub struct WalkingTime(pub f32);
pub fn init_entities(entities: &mut Entities) {
entities.init_components::<Activity>();
entities.init_components::<AnimateByActivity>();
entities.init_components::<AnimationDef>();
entities.init_components::<AnimationInstance>();
entities.init_components::<Bounds>();
entities.init_components::<FacingDirection>();
entities.init_components::<Forces>();
entities.init_components::<IgnoresCollision>();
entities.init_components::<IgnoresFriction>();
entities.init_components::<MovementSpeed>();
entities.init_components::<Position>();
entities.init_components::<Pushable>();
entities.init_components::<Pusher>();
entities.init_components::<RandomlyWalksAround>();
entities.init_components::<Slime>();
entities.init_components::<Sprite>();
entities.init_components::<SpriteIndexByDirection>();
entities.init_components::<Velocity>();
entities.init_components::<WalkingTime>();
entities.remove_all_entities();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub enum Event {
AnimationFinished(EntityId),
MoveForward(EntityId),
Remove(EntityId),
SetActivity(EntityId, EntityActivity),
TurnAndMove(EntityId, Direction),
}
fn event_handler(event: &Event, context: &mut CoreContext) -> bool {
match event {
Event::AnimationFinished(entity) => {
// no-op
}
Event::MoveForward(entity) => {
if context.entities.has_entity(*entity) {
move_entity_forward(context, *entity);
}
}
Event::Remove(entity) => {
if context.entities.has_entity(*entity) {
remove_entity(&mut context.entities, *entity);
}
}
Event::SetActivity(entity, activity) => {
if context.entities.has_entity(*entity) {
set_entity_activity(&mut context.entities, *entity, *activity);
}
}
Event::TurnAndMove(entity, direction) => {
if context.entities.has_entity(*entity) {
turn_and_move_entity(context, *entity, *direction);
}
}
};
false
}
pub fn init_events(event_listener: &mut EventListeners<Event, CoreContext>) {
event_listener.clear();
event_listener.add(event_handler);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub fn move_entity_forward(context: &mut CoreContext, entity: EntityId) {
let mut velocities = context.entities.components_mut::<Velocity>();
let facing_directions = context.entities.components::<FacingDirection>();
let movement_speeds = context.entities.components::<MovementSpeed>();
let velocity = velocities.get_mut(&entity).unwrap();
let facing_direction = facing_directions.get(&entity).unwrap();
let movement_speed = movement_speeds.get(&entity).unwrap();
let movement = match facing_direction.0 {
Direction::North => Vector2::UP * movement_speed.0,
Direction::East => Vector2::RIGHT * movement_speed.0,
Direction::West => Vector2::LEFT * movement_speed.0,
Direction::South => Vector2::DOWN * movement_speed.0,
};
velocity.0 += movement;
}
fn move_entity_with_collision(
position: &mut Position,
bounds: &Bounds,
velocity: Option<&Velocity>,
forces: Option<&Forces>,
map: &TileMap,
delta: f32,
) -> bool {
const NUM_STEPS: usize = 2;
const STEP_SCALE: f32 = 1.0 / NUM_STEPS as f32;
let mut collided = false;
// apply entity velocity + force (if any/either) and exit early with no collision if this entity
// has no movement ... no need to check collisions in such a case
let mut step_velocity = Vector2::ZERO;
if let Some(velocity) = velocity {
step_velocity += velocity.0 * delta;
}
if let Some(forces) = forces {
step_velocity += forces.current_force();
}
if step_velocity.nearly_equal(Vector2::ZERO, 0.00001) {
return collided;
}
// entity is actually moving, so check collisions and move accordingly
step_velocity *= STEP_SCALE;
for _ in 0..NUM_STEPS {
let old_position = position.0;
position.0.x += step_velocity.x;
if map.is_colliding(&Rect::new(position.0.x as i32, position.0.y as i32, bounds.width, bounds.height)) {
collided = true;
position.0.x = old_position.x;
}
position.0.y += step_velocity.y;
if map.is_colliding(&Rect::new(position.0.x as i32, position.0.y as i32, bounds.width, bounds.height)) {
collided = true;
position.0.y = old_position.y;
}
}
collided
}
pub fn remove_entity(entities: &mut Entities, entity: EntityId) {
entities.remove_entity(entity);
}
pub fn set_entity_activity(entities: &mut Entities, entity: EntityId, new_activity: EntityActivity) {
let mut activities = entities.components_mut::<Activity>();
let mut activity = activities.get_mut(&entity).unwrap();
// only change the activity, and more importantly, the animation if we are actually applying
// an actual activity change from what it was before
if activity.0 != new_activity {
activity.0 = new_activity;
let animate_by_activitys = entities.components::<AnimateByActivity>();
if let Some(animate_by_activity) = animate_by_activitys.get(&entity) {
if let Some(new_animation_def) = animate_by_activity.0.get(&new_activity) {
let mut animations = entities.components_mut::<AnimationInstance>();
let animation = animations.get_mut(&entity).unwrap();
animation.change_to(new_animation_def.clone());
}
}
}
}
pub fn turn_and_move_entity(context: &mut CoreContext, entity: EntityId, direction: Direction) {
// can this entity currently move at all?
let activities = context.entities.components::<Activity>();
if let Some(activity) = activities.get(&entity) {
if activity.0 != EntityActivity::Idle && activity.0 != EntityActivity::Walking {
return;
}
}
drop(activities);
// make the entity face in the direction specified
let mut facing_directions = context.entities.components_mut::<FacingDirection>();
let facing_direction = facing_directions.get_mut(&entity).unwrap();
facing_direction.0 = direction;
drop(facing_directions);
move_entity_forward(context, entity);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
fn update_system_movement(context: &mut CoreContext) {
let mut positions = context.entities.components_mut::<Position>().unwrap();
let velocities = context.entities.components::<Velocity>();
let forces = context.entities.components::<Forces>();
let bounds = context.entities.components::<Bounds>();
let ignores_collision = context.entities.components::<IgnoresCollision>();
for (entity, position) in positions.iter_mut() {
if ignores_collision.contains_key(entity) {
if let Some(velocity) = velocities.get(entity) {
position.0 += velocity.0 * context.delta;
}
} else {
let velocity = velocities.get(entity);
let force = forces.get(entity);
if velocity.is_some() || force.is_some() {
move_entity_with_collision(
position,
bounds.get(entity).unwrap(),
velocity,
force,
&context.tilemap,
context.delta,
);
}
}
}
}
fn update_system_friction(context: &mut CoreContext) {
let mut velocities = context.entities.components_mut::<Velocity>().unwrap();
let ignores_friction = context.entities.components::<IgnoresFriction>();
for (entity, velocity) in velocities.iter_mut() {
if !ignores_friction.contains_key(entity) {
velocity.0 *= FRICTION;
if velocity.0.almost_zero(0.001) {
velocity.0 = Vector2::ZERO;
}
}
}
}
fn update_system_force_decay(context: &mut CoreContext) {
let mut forces = context.entities.components_mut::<Forces>().unwrap();
for (_, force) in forces.iter_mut() {
force.decay();
}
}
fn update_system_pushing(context: &mut CoreContext) {
let positions = context.entities.components::<Position>();
let bounds = context.entities.components::<Bounds>();
let mut forces = context.entities.components_mut::<Forces>();
let pushers = context.entities.components::<Pusher>().unwrap();
let pushable = context.entities.components::<Pushable>().unwrap();
// TODO: this is slow
for (pusher_entity, pusher) in pushers.iter() {
let pusher_position = positions.get(pusher_entity).unwrap();
let pusher_bounds = bounds.get(pusher_entity).unwrap();
let pusher_circle = Circle::new(pusher_position.0.x as i32, pusher_position.0.y as i32, pusher_bounds.radius);
for (pushable_entity, pushable) in pushable.iter() {
// don't push ourself ...
if *pushable_entity == *pusher_entity {
continue;
}
let pushable_position = positions.get(pushable_entity).unwrap();
let pushable_bounds = bounds.get(pushable_entity).unwrap();
let pushable_circle = Circle::new(
pushable_position.0.x as i32, //
pushable_position.0.y as i32,
pushable_bounds.radius,
);
if pusher_circle.overlaps(&pushable_circle) {
let mut push_direction = (pushable_position.0 - pusher_position.0).normalize();
// this can happen if the pusher's and pushable's positions are exactly the same. we just need to
// "break the tie" so to speak ...
if !push_direction.x.is_normal() || !push_direction.y.is_normal() {
push_direction = Vector2::UP; // TODO: use one of their facing directions? which one?
}
let pushable_force = forces.get_mut(pushable_entity).unwrap();
pushable_force.add(push_direction * pusher.strength, pusher.push_force_dissipation);
}
}
}
}
fn update_system_animation(context: &mut CoreContext) {
let mut animations = context.entities.components_mut::<AnimationInstance>().unwrap();
for (entity, animation) in animations.iter_mut() {
if animation.complete {
continue;
}
animation.frame_timer += context.delta;
let delay = if let Some(delay_override) = animation.delay_override {
delay_override //
} else {
animation.def.delay
};
if animation.frame_timer >= delay {
// move to the next frame in the current sequence
animation.frame_timer = 0.0;
if animation.frame_index == (animation.def.frames.len() - 1) {
// we're at the last frame in the current sequence
if !animation.def.loops {
animation.complete = true;
context.event_publisher.queue(Event::AnimationFinished(*entity));
} else {
animation.frame_index = 0;
}
} else {
animation.frame_index += 1;
}
}
}
}
fn update_system_set_sprite_index_from_animation(context: &mut CoreContext) {
let animations = context.entities.components::<AnimationInstance>().unwrap();
let mut sprites = context.entities.components_mut::<Sprite>();
let facing_directions = context.entities.components::<FacingDirection>();
for (entity, animation) in animations.iter() {
if let Some(sprite) = sprites.get_mut(entity) {
// base animation sprite-sheet index for the current animation state
let mut index = animation.def.frames[animation.frame_index];
// add multi-direction offset if applicable
let multi_direction_offset = animation.def.multi_direction_offset;
let facing_direction = facing_directions.get(entity);
if multi_direction_offset.is_some() && facing_direction.is_some() {
index += multi_direction_offset.unwrap() * facing_direction.unwrap().0 as usize;
}
sprite.index = index;
}
}
}
fn update_system_set_sprite_index_by_direction(context: &mut CoreContext) {
let sprite_index_by_directions = context.entities.components::<SpriteIndexByDirection>().unwrap();
let mut sprites = context.entities.components_mut::<Sprite>();
let facing_directions = context.entities.components::<FacingDirection>();
for (entity, sprite_index_by_direction) in sprite_index_by_directions.iter() {
if let Some(sprite) = sprites.get_mut(entity) {
if let Some(facing_direction) = facing_directions.get(entity) {
sprite.index = sprite_index_by_direction.base_index + facing_direction.0 as usize;
}
}
}
}
fn update_system_walking_time(context: &mut CoreContext) {
let mut walking_times = context.entities.components_mut::<WalkingTime>().unwrap();
for (entity, walking_time) in walking_times.iter_mut() {
if walking_time.0 > 0.0 {
walking_time.0 -= context.delta;
context.event_publisher.queue(Event::MoveForward(*entity));
}
}
// remove walking time components whose timers have elapsed
walking_times.retain(|_, comp| comp.0 > 0.0);
}
fn update_system_randomly_walk_around(context: &mut CoreContext) {
let mut randomly_walk_arounds = context.entities.components_mut::<RandomlyWalksAround>().unwrap();
let activities = context.entities.components::<Activity>();
let mut walking_times = context.entities.components_mut::<WalkingTime>().unwrap();
for (entity, randomly_walk_around) in randomly_walk_arounds.iter_mut() {
if let Some(activity) = activities.get(entity) {
if activity.0 == EntityActivity::Idle {
if randomly_walk_around.cooldown_timer > 0.0 {
randomly_walk_around.cooldown_timer -= context.delta;
if randomly_walk_around.cooldown_timer < 0.0 {
randomly_walk_around.cooldown_timer = 0.0;
}
} else if randomly_walk_around.should_start_walking() {
randomly_walk_around.cooldown_timer = rnd_value(
randomly_walk_around.min_cooldown, //
randomly_walk_around.max_cooldown,
);
let direction = Direction::new_random();
let walk_time = rnd_value(randomly_walk_around.min_walk_time, randomly_walk_around.max_walk_time);
walking_times.insert(*entity, WalkingTime(walk_time));
context.event_publisher.queue(Event::TurnAndMove(*entity, direction));
}
}
}
}
}
fn update_system_current_entity_activity(context: &mut CoreContext) {
let activities = context.entities.components::<Activity>().unwrap();
let velocities = context.entities.components::<Velocity>();
for (entity, activity) in activities.iter() {
// try to detect current entity activity based on it's own movement speed
// (intentionally NOT checking force velocity!)
if let Some(velocity) = velocities.get(entity) {
match activity.0 {
EntityActivity::Idle => {
if velocity.0.length_squared() > 0.0 {
context.event_publisher.queue(Event::SetActivity(*entity, EntityActivity::Walking));
}
}
EntityActivity::Walking => {
if velocity.0.almost_zero(0.001) {
context.event_publisher.queue(Event::SetActivity(*entity, EntityActivity::Idle));
}
}
_ => {}
}
}
}
}
fn render_system_sprites(context: &mut CoreContext) {
context.sprite_render_list.clear();
let sprites = context.entities.components::<Sprite>().unwrap();
let positions = context.entities.components::<Position>().unwrap();
// build up list of entities to be rendered with their positions so we can sort them
// and render these entities with a proper y-based sort order
for (entity, _) in sprites.iter() {
let mut blit_method = RgbaBlitMethod::Transparent(context.transparent_color);
let position = positions.get(entity).unwrap();
context.sprite_render_list.push((*entity, position.0, blit_method));
}
context.sprite_render_list.sort_unstable_by(|a, b| (a.1.y as i32).cmp(&(b.1.y as i32)));
// now render them in the correct order ...
for (entity, position, blit_method) in context.sprite_render_list.iter() {
let sprite = sprites.get(entity).unwrap();
context.system.res.video.blit_atlas(
blit_method.clone(),
&sprite.atlas,
sprite.index,
position.x as i32 - context.camera_x,
position.y as i32 - context.camera_y,
);
}
}
fn render_system_entity_ids(context: &mut CoreContext) {
let sprites = context.entities.components::<Sprite>().unwrap();
let positions = context.entities.components::<Position>().unwrap();
for (entity, _) in sprites.iter() {
let position = positions.get(entity).unwrap();
let x = position.0.x as i32 - context.camera_x;
let y = position.0.y as i32 - context.small_font.line_height() as i32 - context.camera_y;
context.system.res.video.print_string(
&entity.to_string(),
x,
y,
FontRenderOpts::Color(context.palette[15]),
&context.small_font,
);
}
}
pub fn init_component_system(cs: &mut ComponentSystems<CoreContext, CoreContext>) {
cs.reset();
cs.add_update_system(update_system_current_entity_activity);
cs.add_update_system(update_system_walking_time);
cs.add_update_system(update_system_pushing);
cs.add_update_system(update_system_movement);
cs.add_update_system(update_system_friction);
cs.add_update_system(update_system_force_decay);
cs.add_update_system(update_system_randomly_walk_around);
cs.add_update_system(update_system_animation);
cs.add_update_system(update_system_set_sprite_index_from_animation);
cs.add_update_system(update_system_set_sprite_index_by_direction);
cs.add_render_system(render_system_sprites);
cs.add_render_system(render_system_entity_ids);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
pub fn init(context: &mut GameContext) {
init_entities(&mut context.core.entities);
init_component_system(&mut context.support.component_systems);
init_events(&mut context.support.event_listeners);
context.core.event_publisher.clear();
}
pub fn new_slime_entity(
context: &mut CoreContext,
x: i32,
y: i32,
direction: Direction,
color: SlimeColor,
) -> EntityId {
let id = context.entities.new_entity();
let (
atlas, //
chance_to_move,
movement_speed,
min_walk_time,
max_walk_time,
min_walk_cooldown,
max_walk_cooldown,
) = match color {
SlimeColor::Green => (context.green_slime.clone(), 10, 8.0, 0.5, 2.0, 0.5, 5.0),
SlimeColor::Blue => (context.blue_slime.clone(), 40, 12.0, 0.5, 2.0, 0.5, 3.0),
SlimeColor::Orange => (context.orange_slime.clone(), 90, 24.0, 0.5, 1.0, 0.5, 2.0),
};
let activity = EntityActivity::Idle;
let animate_by_activity = AnimateByActivity(context.slime_activity_states.clone());
let animation = AnimationInstance::from(animate_by_activity.0.get(&activity).unwrap().clone());
context.entities.add_component(id, Slime);
context.entities.add_component(id, Position(Vector2::new(x as f32, y as f32)));
context.entities.add_component(id, Velocity(Vector2::ZERO));
context.entities.add_component(id, Forces::new());
context.entities.add_component(id, Bounds { width: 16, height: 16, radius: 8 });
context.entities.add_component(id, FacingDirection(direction));
context.entities.add_component(id, Sprite { atlas, index: 0 });
context.entities.add_component(id, Activity(activity));
context.entities.add_component(id, animate_by_activity);
context.entities.add_component(id, animation);
context.entities.add_component(
id,
RandomlyWalksAround::new(
min_walk_time, //
max_walk_time,
chance_to_move,
min_walk_cooldown,
max_walk_cooldown,
),
);
context.entities.add_component(id, MovementSpeed(movement_speed));
context.entities.add_component(id, Pusher::new());
context.entities.add_component(id, Pushable);
id
}

View file

@ -0,0 +1,108 @@
mod context;
mod entities;
mod support;
mod tilemap;
use anyhow::{Context, Result};
use crate::context::GameContext;
use crate::tilemap::{TILE_HEIGHT, TILE_WIDTH};
use ggdt::prelude::*;
pub struct DemoState;
impl AppState<GameContext> for DemoState {
fn update(&mut self, state: State, context: &mut GameContext) -> Option<StateChange<GameContext>> {
if context.core.system.res.keyboard.is_key_pressed(Scancode::Escape) {
return Some(StateChange::Pop(1));
}
let ui = context.support.imgui.new_frame(&context.core.system.res.video);
ui.window("Entities").build(|| {
ui.text("TODO: display entity list or something");
});
let ui_focused = ui.is_window_hovered_with_flags(imgui::WindowHoveredFlags::ANY_WINDOW)
|| ui.is_window_focused_with_flags(imgui::WindowFocusedFlags::ANY_WINDOW);
if !ui_focused {
if context.core.system.res.mouse.is_button_down(1) {
context.core.camera_x -= context.core.system.res.mouse.x_delta() * 2;
context.core.camera_y -= context.core.system.res.mouse.y_delta() * 2;
}
}
context.support.do_events(&mut context.core);
context.support.component_systems.update(&mut context.core);
None
}
fn render(&mut self, state: State, context: &mut GameContext) {
context.core.system.res.video.clear(context.core.palette[0]);
context.core.tilemap.draw(
&mut context.core.system.res.video,
&context.core.tiles,
context.core.camera_x,
context.core.camera_y,
context.core.transparent_color,
);
context.support.component_systems.render(&mut context.core);
context.support.imgui.render(&mut context.core.system.res.video);
}
fn transition(&mut self, state: State, context: &mut GameContext) -> bool {
true
}
fn state_change(&mut self, new_state: State, old_state: State, context: &mut GameContext) {
if new_state == State::Pending {
entities::init(context);
for _ in 0..10 {
let (x, y) = context.core.tilemap.get_random_spawnable_coordinates();
entities::new_slime_entity(
&mut context.core,
x * TILE_WIDTH as i32,
y * TILE_HEIGHT as i32,
entities::Direction::new_random(),
entities::SlimeColor::new_random(),
);
}
}
}
}
fn main() -> Result<()> {
let config = StandardConfig::variable_screen_size(640, 480).scale_factor(2);
let mut system = SystemBuilder::new() //
.window_title("ImGui Example Integration")
.vsync(true)
.build(config)?;
system.res.cursor.enable_cursor(true);
let mut game = GameContext::new(system)?;
let mut states = States::new();
states.push(DemoState);
let mut last_ticks = game.core.system.ticks();
'mainloop: while !states.is_empty() {
game.core.system.res.update_event_state()?;
for event in game.core.system.event_pump.poll_iter() {
game.core.system.res.handle_event(&event)?;
game.support.imgui.handle_event(&event);
if event == SystemEvent::Quit {
break 'mainloop;
}
}
last_ticks = game.core.update_frame_delta(last_ticks);
states.update(&mut game);
game.core.system.update()?;
states.render(&mut game);
game.core.system.display()?;
}
Ok(())
}

View file

@ -0,0 +1,18 @@
use crate::tilemap::{TILE_HEIGHT, TILE_WIDTH};
use anyhow::{Context, Result};
use ggdt::prelude::*;
pub fn load_palette(path: &std::path::Path) -> Result<Palette> {
Palette::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path))
}
pub fn load_font(path: &std::path::Path) -> Result<BitmaskFont> {
BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path))
}
pub fn load_bitmap_atlas_autogrid(path: &std::path::Path) -> Result<BitmapAtlas<RgbaBitmap>> {
let (bmp, _) = RgbaBitmap::load_file(path).context(format!("Loading bitmap atlas: {:?}", path))?;
let mut atlas = BitmapAtlas::new(bmp);
atlas.add_grid(TILE_WIDTH, TILE_HEIGHT)?;
Ok(atlas)
}

View file

@ -0,0 +1,110 @@
use anyhow::{Context, Result};
use ggdt::prelude::*;
pub const TILE_WIDTH: u32 = 16;
pub const TILE_HEIGHT: u32 = 16;
pub const TILE_FLAG_NONE: i32 = 1;
pub const TILE_FLAG_COLLISION: i32 = 0;
pub const TILE_FLAG_SPAWNABLE: i32 = 1;
#[derive(Debug, serde::Deserialize)]
pub struct TileMap {
width: u32,
height: u32,
layers: Vec<Box<[i32]>>,
}
impl TileMap {
pub fn load_from(path: &std::path::Path) -> Result<Self> {
let f = std::fs::File::open(path)?;
let reader = std::io::BufReader::new(f);
serde_json::from_reader(reader).context(format!("Loading json tilemap: {:?}", path))
}
#[inline]
pub fn index_to(&self, x: i32, y: i32) -> Option<usize> {
if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
Some(((y * self.width as i32) + x) as usize)
} else {
None
}
}
#[inline]
pub fn collision(&self) -> &[i32] {
&self.layers[2]
}
pub fn is_colliding(&self, rect: &Rect) -> bool {
let x1 = rect.x / TILE_WIDTH as i32;
let y1 = rect.y / TILE_HEIGHT as i32;
let x2 = rect.right() / TILE_WIDTH as i32;
let y2 = rect.bottom() / TILE_HEIGHT as i32;
for y in y1..=y2 {
for x in x1..=x2 {
match self.index_to(x, y) {
Some(index) => {
if self.collision()[index] == TILE_FLAG_COLLISION {
return true;
}
}
None => return true,
}
}
}
false
}
pub fn draw(
&self,
dest: &mut RgbaBitmap,
tiles: &BitmapAtlas<RgbaBitmap>,
camera_x: i32,
camera_y: i32,
transparent_color: u32,
) {
let xt = camera_x / TILE_WIDTH as i32;
let yt = camera_y / TILE_HEIGHT as i32;
let xp = camera_x % TILE_WIDTH as i32;
let yp = camera_y % TILE_HEIGHT as i32;
let tiles_y = (dest.height() as f32 / TILE_HEIGHT as f32).ceil() as i32 + 1;
let tiles_x = (dest.width() as f32 / TILE_WIDTH as f32).ceil() as i32 + 1;
for y in 0..tiles_y {
for x in 0..tiles_x {
if let Some(index) = self.index_to(x + xt, y + yt) {
let xd = (x * TILE_WIDTH as i32) - xp;
let yd = (y * TILE_HEIGHT as i32) - yp;
let lower = self.layers[0][index];
if lower >= 0 {
dest.blit_region(RgbaBlitMethod::Solid, tiles.bitmap(), &tiles[lower as usize], xd, yd);
}
let upper = self.layers[1][index];
if upper >= 0 {
dest.blit_region(
RgbaBlitMethod::Transparent(transparent_color),
tiles.bitmap(),
&tiles[upper as usize],
xd,
yd,
);
}
}
}
}
}
pub fn get_random_spawnable_coordinates(&self) -> (i32, i32) {
loop {
let x = rnd_value(0, self.width as i32 - 1);
let y = rnd_value(0, self.height as i32 - 1);
if self.collision()[self.index_to(x, y).unwrap()] == TILE_FLAG_SPAWNABLE {
return (x, y);
}
}
}
}