From 42651aa4bef01194e48c7b62de277003f74f0483 Mon Sep 17 00:00:00 2001 From: Mitchell M Date: Sat, 24 Jan 2026 21:17:43 -0600 Subject: [PATCH] avian start --- Cargo.lock | 455 +++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + assets/player.png | Bin 0 -> 2114 bytes src/avian.rs | 357 ++++++++++++++++++++++++++++++++++++ src/main.rs | 80 +++++--- 5 files changed, 859 insertions(+), 34 deletions(-) create mode 100644 assets/player.png create mode 100644 src/avian.rs diff --git a/Cargo.lock b/Cargo.lock index cf081fa..1f3fcb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "alsa" version = "0.9.1" @@ -308,6 +314,45 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "avian2d" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "060ae40aced85ed01296f556ed1909fcafe81a5e5059889bed126339ccd55b43" +dependencies = [ + "approx", + "arrayvec", + "avian_derive", + "bevy", + "bevy_heavy", + "bevy_math", + "bevy_transform_interpolation", + "bitflags 2.10.0", + "derive_more", + "disqualified", + "glam_matrix_extras", + "itertools 0.13.0", + "nalgebra", + "parry2d", + "parry2d-f64", + "slab", + "smallvec", + "thiserror 2.0.17", + "thread_local", +] + +[[package]] +name = "avian_derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b257f601a1535e0d4a7a7796f535e3a13de62fd422b16dff7c14d27f0d4048" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "base64" version = "0.22.1" @@ -794,6 +839,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "bevy_heavy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc496d1d43b890896cf561d8ce3dcf7b7b8e4c03c4e837a49a83a530abccbc5e" +dependencies = [ + "bevy_math", + "bevy_reflect", + "glam_matrix_extras", +] + [[package]] name = "bevy_image" version = "0.18.0" @@ -972,7 +1028,7 @@ dependencies = [ "arrayvec", "bevy_reflect", "derive_more", - "glam", + "glam 0.30.10", "itertools 0.14.0", "libm", "rand", @@ -1146,7 +1202,7 @@ dependencies = [ "downcast-rs 2.0.2", "erased-serde", "foldhash 0.2.0", - "glam", + "glam 0.30.10", "indexmap", "inventory", "petgraph", @@ -1206,7 +1262,7 @@ dependencies = [ "downcast-rs 2.0.2", "encase", "fixedbitset", - "glam", + "glam 0.30.10", "image", "indexmap", "js-sys", @@ -1436,6 +1492,15 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "bevy_transform_interpolation" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88545c8b0fa8f3502b9a439c71fa6b596ee9e808bfb16f27a51c8c6f7405a657" +dependencies = [ + "bevy", +] + [[package]] name = "bevy_ui" version = "0.18.0" @@ -2041,6 +2106,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -2180,6 +2264,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + [[package]] name = "encase" version = "0.12.0" @@ -2514,12 +2607,103 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glam" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" + +[[package]] +name = "glam" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" + +[[package]] +name = "glam" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" + +[[package]] +name = "glam" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" + +[[package]] +name = "glam" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" + +[[package]] +name = "glam" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" + +[[package]] +name = "glam" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" + +[[package]] +name = "glam" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" + +[[package]] +name = "glam" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" + +[[package]] +name = "glam" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" + +[[package]] +name = "glam" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "glam" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" + [[package]] name = "glam" version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" dependencies = [ + "approx", "bytemuck", "encase", "libm", @@ -2527,6 +2711,16 @@ dependencies = [ "serde_core", ] +[[package]] +name = "glam_matrix_extras" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4664468a60479272b880a8bfc00ad2915229b93d2b2d585556fb33f9ba80e72" +dependencies = [ + "bevy_reflect", + "glam 0.30.10", +] + [[package]] name = "glob" version = "0.3.3" @@ -2697,6 +2891,8 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.1.5", ] @@ -2736,7 +2932,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29a164ceff4500f2a72b1d21beaa8aa8ad83aec2b641844c659b190cb3ea2e0b" dependencies = [ "constgebra", - "glam", + "glam 0.30.10", "tinyvec", ] @@ -3024,6 +3220,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.6" @@ -3124,6 +3330,49 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "nalgebra" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" +dependencies = [ + "approx", + "glam 0.14.0", + "glam 0.15.2", + "glam 0.16.0", + "glam 0.17.3", + "glam 0.18.0", + "glam 0.19.0", + "glam 0.20.5", + "glam 0.21.3", + "glam 0.22.0", + "glam 0.23.0", + "glam 0.24.2", + "glam 0.25.0", + "glam 0.27.0", + "glam 0.28.0", + "glam 0.29.3", + "glam 0.30.10", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ndk" version = "0.8.0" @@ -3223,6 +3472,25 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -3234,6 +3502,26 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3612,6 +3900,60 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "parry2d" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef681740349cec3ab9b5996b03b459b383b6998e1ffcb2804e8b57eb1e8491d9" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.10.0", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rayon", + "simba", + "slab", + "smallvec", + "spade", + "thiserror 2.0.17", +] + +[[package]] +name = "parry2d-f64" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835d4ca2b68026b04a75ca6196da961d8c0895420eacc9cc2201f7ad54775780" +dependencies = [ + "approx", + "arrayvec", + "bitflags 2.10.0", + "downcast-rs 2.0.2", + "either", + "ena", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "log", + "nalgebra", + "num-derive", + "num-traits", + "ordered-float", + "rayon", + "simba", + "slab", + "smallvec", + "spade", + "thiserror 2.0.17", +] + [[package]] name = "paste" version = "1.0.15" @@ -3761,6 +4103,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.105" @@ -3872,6 +4236,32 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "read-fonts" version = "0.35.0" @@ -3961,6 +4351,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "robust" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839" + [[package]] name = "rodio" version = "0.20.1" @@ -4053,6 +4449,15 @@ dependencies = [ "twox-hash", ] +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4163,6 +4568,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.8" @@ -4244,6 +4662,18 @@ dependencies = [ "serde", ] +[[package]] +name = "spade" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb313e1c8afee5b5647e00ee0fe6855e3d529eb863a0fdae1d60006c4d1e9990" +dependencies = [ + "hashbrown 0.15.5", + "num-traits", + "robust", + "smallvec", +] + [[package]] name = "spin" version = "0.10.0" @@ -4414,6 +4844,7 @@ dependencies = [ name = "time_travel" version = "0.1.0" dependencies = [ + "avian2d", "bevy", ] @@ -4592,6 +5023,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "typewit" version = "1.14.2" @@ -5037,6 +5474,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index ed356dc..2983a31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +avian2d = "0.5.0" bevy = "0.18.0" diff --git a/assets/player.png b/assets/player.png new file mode 100644 index 0000000000000000000000000000000000000000..97557cd2f580fcf0aa5f4be7bea511fffa3241df GIT binary patch literal 2114 zcmV-I2)*}-P)Ic7g#CSymUNOe|!k{tY9TgQ1E)Nh{bSzH$A(r=e=FS zKlx|&^{bhEUEN((U0oxx25V3xx+^yd3H_gNt@0`NcMs*u%JJpJ{{u?eI;DL_vHK5B z`L12!E9KA16P0%R6 zjMz+QGu8?TWT2<=JmvXfKkyOdOJbd9a&3(ME@D%dQNBXiPx-f)vYOnpn8vtF?7%f* zBYT2!h_X&hnRh5(6R@Ur4YGz|s#335mJLDUxqiMF(N&mtwB3Ryaq@ZC11lI!W8GNGyyc5a}O~O=7h2 zqL5Hd5Eip9s3;Js^834wLB`cT(gw^+D z$TCtFDAyGmy7Tg>1cD%N@O3m=Fn z`G@kiNfA0iCWu`fb^||7P_;Di9h;`Ej-4IxR%D8>F&I8()e4D#O5I>y zY^&T$dAssvF*nGDW>>LoGP8s15~KaFSm#PnD&B<|!;)Gg9T1x;CyMPIdS=Lx+$2X> ztt898XzN~}HMmeLjYaNoafTc=fz0vI88XpPv;vsYr;8nEObEzg2S~N;E7e#zK(_z^GvW9}Nj6z%CIFut!KR z7K&5==XXScABO}zaDr5|)88T$fIDMKF9`{HAOI@zcddq^{t=NMiHf|L<0Lo)saAwq zm;%@pauzr(B$xse7fP>1Ls1GKT~4}dUP#cx4q`dO&qIQ-P?Q32V>3fSDIkA9ifmFy zFcykX0NY>!nHeEL5A6EL8_@Z@aYMTVSsJE)YqLQ|i0%A_h6H1xh#A1CEx}A{a{Pd} zf#k@zkYG#{p#TgS2Oq6TbCX7+ROT6pl<3HdA{0=k+(azdkq7Mj?iR}sjS0v`M-c%B z`8xIi9C0_gD>DMu_O&C)ky0Lz^jN)XYt1=h6vWEIxno96l`qQn+ZNlMW(>)kJ_!}z@>HLCWA0V+nA`u^c4 z9c>UhJOvm$j>AWKa?}@a%)Xm+P*Bv&DDUI7Q%c8(Cv$u(0jkWBqs`d>HGtPk2L?~=$r8WJo#IDkz;LM|Vkn?gE9iTG1H}xUe;P#a zL#D_dX-est1w5~hR0%N@z;QK2lk-#B%k2oC6{{lY>$T$w3D2um2OL8Ij2QX+m5DY& z;0>jGbcX_127c8yOMqoFu*Dq(A%+47W2bm+gWYwqq=Tw3UVAx8EDM|j86n;gsLH!Q ztyKV)oobKn?lV~*EO#=6#WQ&`Y?fm=5dASz4)WTGC`pVCYH>#a$h#Rrjx#<28dBD! zcYwOMYqGkVJppU@O38k`g?E1_j%4S^4v}YaLX6TlQMy5zU&5MO<{1?uZ$98i<FcPL_a_n8nl)Oql}So}{2%zd0A5F!&-lB s@Fd+F`EAS!6uHW^m9A8&N68=j13}ZP0tShng#Z8m07*qoM6N<$f&%`=TL1t6 literal 0 HcmV?d00001 diff --git a/src/avian.rs b/src/avian.rs new file mode 100644 index 0000000..aea431e --- /dev/null +++ b/src/avian.rs @@ -0,0 +1,357 @@ +use avian2d::{math::*, prelude::*}; +use bevy::prelude::*; + +pub struct CharacterControllerPlugin; + +impl Plugin for CharacterControllerPlugin { + fn build(&self, app: &mut App) { + app.add_message::() + .add_systems( + Update, + ( + (keyboard_input, gamepad_input), + movement, + apply_movement_damping, + ) + .chain(), + ) + .add_systems( + // Run collision handling after collision detection. + // + // NOTE: The collision implementation here is very basic and a bit buggy. + // A collide-and-slide algorithm would likely work better. + PhysicsSchedule, + kinematic_controller_collisions.in_set(NarrowPhaseSystems::Last), + ); + } +} + +/// A [`Message`] written for a movement input action. +#[derive(Message)] +pub struct MovementAction(Vec2); + +/// A marker component indicating that an entity is using a character controller. +#[derive(Component)] +pub struct CharacterController; + +/// A marker component indicating that an entity is on the ground. +#[derive(Component)] +#[component(storage = "SparseSet")] +pub struct Grounded; + +/// The acceleration used for character movement. +#[derive(Component)] +pub struct MovementAcceleration(Scalar); + +/// The damping factor used for slowing down movement. +#[derive(Component)] +pub struct MovementDampingFactor(Scalar); + +/// The maximum angle a slope can have for a character controller +/// to be able to climb and jump. If the slope is steeper than this angle, +/// the character will slide down. +#[derive(Component)] +pub struct MaxSlopeAngle(Scalar); + +/// A bundle that contains the components needed for a basic +/// kinematic character controller. +#[derive(Bundle)] +pub struct CharacterControllerBundle { + character_controller: CharacterController, + body: RigidBody, + collider: Collider, + ground_caster: ShapeCaster, + movement: MovementBundle, +} + +/// A bundle that contains components for character movement. +#[derive(Bundle)] +pub struct MovementBundle { + acceleration: MovementAcceleration, + damping: MovementDampingFactor, + max_slope_angle: MaxSlopeAngle, +} + +impl MovementBundle { + pub const fn new(acceleration: Scalar, damping: Scalar, max_slope_angle: Scalar) -> Self { + Self { + acceleration: MovementAcceleration(acceleration), + damping: MovementDampingFactor(damping), + max_slope_angle: MaxSlopeAngle(max_slope_angle), + } + } +} + +impl Default for MovementBundle { + fn default() -> Self { + Self::new(30.0, 0.99, PI * 0.45) + } +} + +impl CharacterControllerBundle { + pub fn new(collider: Collider) -> Self { + // Create shape caster as a slightly smaller version of collider + let mut caster_shape = collider.clone(); + caster_shape.set_scale(Vector::ONE * 0.99, 10); + + Self { + character_controller: CharacterController, + body: RigidBody::Kinematic, + collider, + ground_caster: ShapeCaster::new(caster_shape, Vector::ZERO, 0.0, Dir2::NEG_Y) + .with_max_distance(10.0), + movement: MovementBundle::default(), + } + } + + pub fn with_movement( + mut self, + acceleration: Scalar, + damping: Scalar, + max_slope_angle: Scalar, + ) -> Self { + self.movement = MovementBundle::new(acceleration, damping, max_slope_angle); + self + } +} + +/// Sends [`MovementAction`] events based on keyboard input. +fn keyboard_input( + mut movement_writer: MessageWriter, + keyboard_input: Res>, +) { + let left = keyboard_input.any_pressed([KeyCode::KeyA, KeyCode::ArrowLeft]); + let right = keyboard_input.any_pressed([KeyCode::KeyD, KeyCode::ArrowRight]); + let up = keyboard_input.any_pressed([KeyCode::KeyW, KeyCode::ArrowUp]); + let down = keyboard_input.any_pressed([KeyCode::KeyS, KeyCode::ArrowDown]); + + let x = right as i8 - left as i8; + let y = up as i8 - down as i8; + let dir = Vec2::new(x as f32, y as f32); + + if let Some(dir) = dir.try_normalize() { + movement_writer.write(MovementAction(dir)); + } +} + +/// Sends [`MovementAction`] events based on gamepad input. +fn gamepad_input(mut movement_writer: MessageWriter, gamepads: Query<&Gamepad>) { + for gamepad in gamepads.iter() { + if let (Some(x), Some(y)) = ( + gamepad.get(GamepadAxis::LeftStickX), + gamepad.get(GamepadAxis::LeftStickY), + ) { + let mut dir = Vec2::new(x, y); + let len = dir.length(); + if len == 0. { + continue; + } + if len > 1. { + dir = dir.normalize(); + } + + movement_writer.write(MovementAction(dir)); + } + } +} + +/// Responds to [`MovementAction`] events and moves character controllers accordingly. +fn movement( + time: Res