From 424c63d414f847ff981c146ea0db80a9b6a66c63 Mon Sep 17 00:00:00 2001 From: gered Date: Mon, 10 Apr 2023 16:38:38 -0400 Subject: [PATCH] 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. --- examples/imgui_integration/Cargo.toml | 12 + .../imgui_integration/assets/arena.map.json | 9 + .../imgui_integration/assets/blue_slime.pcx | Bin 0 -> 2354 bytes examples/imgui_integration/assets/db16.pal | Bin 0 -> 768 bytes examples/imgui_integration/assets/dp.fnt | Bin 0 -> 2305 bytes .../imgui_integration/assets/green_slime.pcx | Bin 0 -> 2354 bytes .../imgui_integration/assets/orange_slime.pcx | Bin 0 -> 2412 bytes examples/imgui_integration/assets/small.fnt | Bin 0 -> 2305 bytes examples/imgui_integration/assets/tiles.pcx | Bin 0 -> 33941 bytes examples/imgui_integration/src/context.rs | 136 ++++ examples/imgui_integration/src/entities.rs | 767 ++++++++++++++++++ examples/imgui_integration/src/main.rs | 108 +++ examples/imgui_integration/src/support.rs | 18 + examples/imgui_integration/src/tilemap.rs | 110 +++ 14 files changed, 1160 insertions(+) create mode 100644 examples/imgui_integration/Cargo.toml create mode 100644 examples/imgui_integration/assets/arena.map.json create mode 100644 examples/imgui_integration/assets/blue_slime.pcx create mode 100644 examples/imgui_integration/assets/db16.pal create mode 100644 examples/imgui_integration/assets/dp.fnt create mode 100644 examples/imgui_integration/assets/green_slime.pcx create mode 100644 examples/imgui_integration/assets/orange_slime.pcx create mode 100644 examples/imgui_integration/assets/small.fnt create mode 100644 examples/imgui_integration/assets/tiles.pcx create mode 100644 examples/imgui_integration/src/context.rs create mode 100644 examples/imgui_integration/src/entities.rs create mode 100644 examples/imgui_integration/src/main.rs create mode 100644 examples/imgui_integration/src/support.rs create mode 100644 examples/imgui_integration/src/tilemap.rs diff --git a/examples/imgui_integration/Cargo.toml b/examples/imgui_integration/Cargo.toml new file mode 100644 index 0000000..5fb22ae --- /dev/null +++ b/examples/imgui_integration/Cargo.toml @@ -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" diff --git a/examples/imgui_integration/assets/arena.map.json b/examples/imgui_integration/assets/arena.map.json new file mode 100644 index 0000000..d1e4a6f --- /dev/null +++ b/examples/imgui_integration/assets/arena.map.json @@ -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] + ] +} \ No newline at end of file diff --git a/examples/imgui_integration/assets/blue_slime.pcx b/examples/imgui_integration/assets/blue_slime.pcx new file mode 100644 index 0000000000000000000000000000000000000000..7582cabe33cc99f8c8e26dfb2919ead9538e9e04 GIT binary patch literal 2354 zcmeH|zit#U5XNVakS!EPL2;VpM?rjcIq<{h?Iq!Hp<9PhrZ!Xuasu%p+l!r8OedEUM&FN%1+rGcO z`(QGCy7}(Tz30!KJlc74xU;qU;?-<#>u~SoEG|C2fBfmk2hoYB+`V#3ZTp2EKc)OO z(&~w1(dc7{UiaZIFd9jsz71aIL#!KSS)w@Yg1V+=wbdqz#GvKla?R^)L|k!HiF@Vx z*jvTa@r>v@mK9xGMa;K?>tBiL7-hM5A`@F$jI(pamIYShnpKyuk`aMDXO3E;>k!7x zVO=ZcMQJ6wgF4#Elw`^EK$5=3K+ZsJU!o?I(Azxb?3=>dY#|x61!Yq5n0eH?ni}e$ zK{Ha2?l7f8T=YD=2BaO~C5wO*_nQNZbokb++iDLd_`8< zz|l_0A^FTRGOofVwi)xfrvOmXFHPMMZVlVA y!{C@O*#7JVx=Z#1lh{2-D&IgyLJ7FWBzP8eU77|tz=HZp(G!Yb%m5~sWH#U*yR#erH*5WqNQj<0?GBXu7w>GtB NupdREAuw!0008_`dx!u4 literal 0 HcmV?d00001 diff --git a/examples/imgui_integration/assets/dp.fnt b/examples/imgui_integration/assets/dp.fnt new file mode 100644 index 0000000000000000000000000000000000000000..038ecc6d0b241b9da97978e6270f53c837db2654 GIT binary patch literal 2305 zcmeHGy=vqz5FWBWb|I3KhJ>r~0D@~=hy?N&sRRqY#zm@NIGuO%82bSC7y-e8Dv>fQ zWLaEgyWlFgFmk?;lWVWM;xb-)G@5U|8GZH=jaJ|PzJFNX*Y)r`Yz{+Rh7e4CN~gJr zJ~ugUnA35?5M@ebM8j~{06&l_uCEJ4!)E>Pu-*(O$;mJ8eWi+`P+6T_eZP|Owr!S+ zvj-jW1C?>%+i^N$RUWv$^D)}g=cEan=$jrpB7ge1y5wLCuN|Ar}f zPUpAn);nSru5+*9$vH>vyn#i2=5h%72$G=7O0+D8GXVOb*Hu>hzW@yy5PALGtOJ`fV^8s_2t`0z;U z9_0!#;BgKv;}44kV7@58n8!SL$q=BYb154s&xyJC<^WrCw*!=og>d}G__8krz7+Uh z6-fVLv&lS7(sVkTrO7mzr0Fb4(u8d?ohS2;HNsiSxR}9%gBh-L`nfZiC(}Ra5jlbT G=k^Y8n7@Mn literal 0 HcmV?d00001 diff --git a/examples/imgui_integration/assets/green_slime.pcx b/examples/imgui_integration/assets/green_slime.pcx new file mode 100644 index 0000000000000000000000000000000000000000..a663d3bdfd29503cae171bfa3db5244dee8d13cc GIT binary patch literal 2354 zcmeH|zit#U5XNVakP(htQBtOi#1+@2MSu_rH4PF42I2e0-n-2~iWE?wBqBoDQ^$8i-?6Od;xb~s6(qf#SE4IwA8dt2kgq4g4>;-ew5?zNd zP7do@F)vCh*&Wo;R;DCNt_PCz4F+-+bfK!)rj=5ni$gNO8Y8z(|J|k2E*~ZYo+CB8cLv6MBYNBjgU-Fyj?j zZ3~X}mK>7Ld`HHSS=N9e@Fj>R5vD<$1L8Z3yaI7vAx;MnyA_B_u~LRZh()MCXoPS= zOnZoBNaYDnK7$EtRZ;B2rp4q#7nj~d8Jq)a0VFT1r#ZVBE{b+$~g)0jjI$< zaSj>vOmXm!|Frr-p6W yVQ@?sY=8Cw-8p-LN$egZm2aRUp#)rG5DGG%@O1to2M#_swfci}@3 zqM$%YAPq=}e-Pex*7n)vLyFR&+?_o$yJPR}v+wL(zpB>+pIh>XMs94}yt6r<%@^Ab zws#-S=Fc|Y-@X6h`P0WcZx462c3-|;>}?(Hy;}I?rw>m)|M)066P0^cZmT_h?%PjE z-$F_~l_=^npZiSr5ifBS5=7ZF@p-0I*I##D6faKmNtvrFdbwrK50+CSBX+rK&!d-P zOzoE`d-~D3PaVhdS|cCL z#Jd{ZSU#*P#Ud}IM0e0!WdoOp3HU&QzQKi@4&T0b4VXe};~2QXPwZPa;Y`R0E6XPZ zGF<5SMtoWUIkKI?BQbNa58e?TAmceu<8u}OD+9oqmVD!opNbNfiGda|#nSR^d7_!v zYrFs+_sS;MF}2}h96B9uE7Oa;O*Ps}$-6sza~O%h!;M2n8-MNa@$_rL3ZE!eR~wp0ptf;OEKj0g1& z1)+^_+sZfHCVtpUbP-gm$8p%|^FsArq(X1XKK!BHQE&R)7L@BZ*oZr-8bUusRA|J! zzKDsh!X}?k5mp1-@?$#~5C-h0x+6#qwq&s(u;uZ*V8Jk4!CDeajB2(c>qw{$S13#V YSWq2KzskmW|69)F?DBU>;J+d83jj^M8vp!01rHp`xJE zfdz-ELjQz7gH_ZwcGSO6&>QXM`OS>y&5VhX4NuQcKc5B~wqM)pHW0NT7=@TJ361*8 z1N(_If4uh-{x~(~b3=5yU0+_VH=<8>U!Cj7jH6K~WX`?T%R>EWIGwInlK1s%eJ{x_ ze)Wqb*_hQdlI5b%#6}bK^ZZcPHR^P2Ga8np5W=39P$@b6dCwzYww ze%u~wpgay6^KCdv#@#H8ED=Pboo{FSP^Og|e+ zf>%;vY~=HX8ONUE=p_D7G3i1@E4U%=8NiaR#L|e4r*F^S^$~N8VH}Cp5%p20Ia|wh zy&EGB)_PuKL>uE5jw2fHd9=^{2Pq| z0$E1_e=NKY@TKvn2Q}R>$2F6X=f}ixe)zjC*5m6Q{#EG})ivI|+x2anU-A6yM9HwV z5qtWuj}3fm;Q!q~RaFH{h_WmTkUTF$QRHwT!2BS}qAap36Cx9blB3BE@Q5PbX2)Ou d9xjTk%tW5$yNN8%igXqb!ATrM&JzFB%0HerLO=ij literal 0 HcmV?d00001 diff --git a/examples/imgui_integration/assets/tiles.pcx b/examples/imgui_integration/assets/tiles.pcx new file mode 100644 index 0000000000000000000000000000000000000000..1801a1117f92626ef640f44793be67162231e879 GIT binary patch literal 33941 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, + pub palette: Palette, + pub font: BitmaskFont, + pub small_font: BitmaskFont, + pub entities: Entities, + pub event_publisher: EventPublisher, + pub tiles: Rc>, + pub green_slime: Rc>, + pub blue_slime: Rc>, + pub orange_slime: Rc>, + pub tilemap: TileMap, + pub slime_activity_states: Rc>>, + pub sprite_render_list: Vec<(EntityId, Vector2, RgbaBlitMethod)>, +} + +impl CoreState for CoreContext { + fn system(&self) -> &System { + &self.system + } + + fn system_mut(&mut self) -> &mut System { + &mut self.system + } + + fn delta(&self) -> f32 { + self.delta + } + + fn set_delta(&mut self, delta: f32) { + self.delta = delta; + } +} + +impl CoreStateWithEvents for CoreContext { + fn event_publisher(&mut self) -> &mut EventPublisher { + &mut self.event_publisher + } +} + +pub struct SupportContext { + pub component_systems: ComponentSystems, + pub event_listeners: EventListeners, + pub imgui: ggdt_imgui::ImGui, +} + +impl SupportSystems for SupportContext {} + +impl SupportSystemsWithEvents for SupportContext { + type ContextType = CoreContext; + + fn event_listeners(&mut self) -> &mut EventListeners { + &mut self.event_listeners + } +} + +pub struct GameContext { + pub core: CoreContext, + pub support: SupportContext, +} + +impl AppContext 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) -> Result { + 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 }, + }) + } +} diff --git a/examples/imgui_integration/src/entities.rs b/examples/imgui_integration/src/entities.rs new file mode 100644 index 0000000..d9fc6b2 --- /dev/null +++ b/examples/imgui_integration/src/entities.rs @@ -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>>); + +#[derive(Debug)] +pub struct AnimationDef { + pub frames: &'static [usize], + pub loops: bool, + pub delay: f32, + pub multi_direction_offset: Option, +} + +impl AnimationDef { + #[inline] + pub fn new(frames: &'static [usize], loops: bool, delay: f32, multi_direction_offset: Option) -> Self { + AnimationDef { frames, loops, delay, multi_direction_offset } + } +} + +#[derive(Debug)] +pub struct AnimationInstance { + pub def: Rc, + pub frame_index: usize, + pub frame_timer: f32, + pub complete: bool, + pub delay_override: Option, +} + +impl AnimationInstance { + #[inline] + pub fn from(def: Rc) -> Self { + AnimationInstance { + def, // + frame_index: 0, + frame_timer: 0.0, + complete: false, + delay_override: None, + } + } + + #[inline] + pub fn change_to(&mut self, def: Rc) { + 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, +} + +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>, + 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::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + entities.init_components::(); + 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_listener.clear(); + event_listener.add(event_handler); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +pub fn move_entity_forward(context: &mut CoreContext, entity: EntityId) { + let mut velocities = context.entities.components_mut::(); + let facing_directions = context.entities.components::(); + let movement_speeds = context.entities.components::(); + + 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::(); + 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::(); + 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::(); + 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::(); + 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::(); + 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::().unwrap(); + let velocities = context.entities.components::(); + let forces = context.entities.components::(); + let bounds = context.entities.components::(); + let ignores_collision = context.entities.components::(); + + 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::().unwrap(); + let ignores_friction = context.entities.components::(); + + 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::().unwrap(); + for (_, force) in forces.iter_mut() { + force.decay(); + } +} + +fn update_system_pushing(context: &mut CoreContext) { + let positions = context.entities.components::(); + let bounds = context.entities.components::(); + let mut forces = context.entities.components_mut::(); + let pushers = context.entities.components::().unwrap(); + let pushable = context.entities.components::().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::().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::().unwrap(); + let mut sprites = context.entities.components_mut::(); + let facing_directions = context.entities.components::(); + + 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::().unwrap(); + let mut sprites = context.entities.components_mut::(); + let facing_directions = context.entities.components::(); + + 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::().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::().unwrap(); + let activities = context.entities.components::(); + let mut walking_times = context.entities.components_mut::().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::().unwrap(); + let velocities = context.entities.components::(); + + 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::().unwrap(); + let positions = context.entities.components::().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::().unwrap(); + let positions = context.entities.components::().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) { + 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 +} diff --git a/examples/imgui_integration/src/main.rs b/examples/imgui_integration/src/main.rs new file mode 100644 index 0000000..0547ad0 --- /dev/null +++ b/examples/imgui_integration/src/main.rs @@ -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 for DemoState { + fn update(&mut self, state: State, context: &mut GameContext) -> Option> { + 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(()) +} diff --git a/examples/imgui_integration/src/support.rs b/examples/imgui_integration/src/support.rs new file mode 100644 index 0000000..5eb0648 --- /dev/null +++ b/examples/imgui_integration/src/support.rs @@ -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::load_from_file(path, PaletteFormat::Vga).context(format!("Loading palette: {:?}", path)) +} + +pub fn load_font(path: &std::path::Path) -> Result { + BitmaskFont::load_from_file(path).context(format!("Loading font: {:?}", path)) +} + +pub fn load_bitmap_atlas_autogrid(path: &std::path::Path) -> Result> { + 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) +} diff --git a/examples/imgui_integration/src/tilemap.rs b/examples/imgui_integration/src/tilemap.rs new file mode 100644 index 0000000..0f69245 --- /dev/null +++ b/examples/imgui_integration/src/tilemap.rs @@ -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>, +} + +impl TileMap { + pub fn load_from(path: &std::path::Path) -> Result { + 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 { + 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, + 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); + } + } + } +}