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 0000000..7582cab Binary files /dev/null and b/examples/imgui_integration/assets/blue_slime.pcx differ diff --git a/examples/imgui_integration/assets/db16.pal b/examples/imgui_integration/assets/db16.pal new file mode 100644 index 0000000..869edaa Binary files /dev/null and b/examples/imgui_integration/assets/db16.pal differ diff --git a/examples/imgui_integration/assets/dp.fnt b/examples/imgui_integration/assets/dp.fnt new file mode 100644 index 0000000..038ecc6 Binary files /dev/null and b/examples/imgui_integration/assets/dp.fnt differ diff --git a/examples/imgui_integration/assets/green_slime.pcx b/examples/imgui_integration/assets/green_slime.pcx new file mode 100644 index 0000000..a663d3b Binary files /dev/null and b/examples/imgui_integration/assets/green_slime.pcx differ diff --git a/examples/imgui_integration/assets/orange_slime.pcx b/examples/imgui_integration/assets/orange_slime.pcx new file mode 100644 index 0000000..d7583a3 Binary files /dev/null and b/examples/imgui_integration/assets/orange_slime.pcx differ diff --git a/examples/imgui_integration/assets/small.fnt b/examples/imgui_integration/assets/small.fnt new file mode 100644 index 0000000..4b9af84 Binary files /dev/null and b/examples/imgui_integration/assets/small.fnt differ diff --git a/examples/imgui_integration/assets/tiles.pcx b/examples/imgui_integration/assets/tiles.pcx new file mode 100644 index 0000000..1801a11 Binary files /dev/null and b/examples/imgui_integration/assets/tiles.pcx differ diff --git a/examples/imgui_integration/src/context.rs b/examples/imgui_integration/src/context.rs new file mode 100644 index 0000000..3e209a4 --- /dev/null +++ b/examples/imgui_integration/src/context.rs @@ -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, + 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); + } + } + } +}