Initial work on rollback and refactor
This commit is contained in:
Generated
+268
-5
@@ -192,12 +192,56 @@ version = "0.1.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.104"
|
version = "1.0.104"
|
||||||
@@ -1007,6 +1051,19 @@ dependencies = [
|
|||||||
"smol_str",
|
"smol_str",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_ggrs"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "git+https://github.com/gschup/bevy_ggrs#f1853823bfb888fcd9d468a136bb81fd6b9ed481"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
"disqualified",
|
||||||
|
"ggrs",
|
||||||
|
"log",
|
||||||
|
"seahash",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_gilrs"
|
name = "bevy_gilrs"
|
||||||
version = "0.19.0"
|
version = "0.19.0"
|
||||||
@@ -2072,6 +2129,15 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit-set"
|
name = "bit-set"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@@ -2087,6 +2153,42 @@ version = "0.9.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitfield-rle"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "215d5574bfd0d6d9243a9c741690f0b7ee8a0f39d3195283779793ed46d984e1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"varinteger",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitfields"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca5a55e8cbc3b75d1f002a3b62261ab67954755f7e7866f079b8aa63a409b549"
|
||||||
|
dependencies = [
|
||||||
|
"bitfields-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitfields-impl"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "797cb6b6e8d5e8c2ecbb5ef52b7c273596d58ce1ab79ef2a6c38fa8dca0817c5"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"getset",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"thiserror 2.0.19",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -2357,21 +2459,36 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.5"
|
version = "4.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "301b56658598e48f3648647ac6fc887be7e7108eddfa4e9b63fcf3ec58c0cadf"
|
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.6.5"
|
version = "4.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94a65403d1a1bd28f7dc68eb8506e8874808ee5eecb59298de588e2e1407a078"
|
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2420,6 +2537,12 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -3281,7 +3404,11 @@ dependencies = [
|
|||||||
"bevy",
|
"bevy",
|
||||||
"bevy-inspector-egui",
|
"bevy-inspector-egui",
|
||||||
"bevy_egui",
|
"bevy_egui",
|
||||||
|
"bevy_ggrs",
|
||||||
|
"bitfields",
|
||||||
|
"clap",
|
||||||
"jackdaw_runtime",
|
"jackdaw_runtime",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3310,6 +3437,17 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -3335,6 +3473,32 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getset"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6cf442baaabe4213ce7d1239afc26c039180b6456da2cededa316ae2c8a77a77"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ggrs"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "git+https://github.com/gschup/ggrs#0cda022c7871806b89f81c493d303ee1bb26e70e"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"bitfield-rle",
|
||||||
|
"instant",
|
||||||
|
"js-sys",
|
||||||
|
"parking_lot",
|
||||||
|
"rand 0.8.7",
|
||||||
|
"serde",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gilrs"
|
name = "gilrs"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@@ -3620,6 +3784,12 @@ dependencies = [
|
|||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -3908,6 +4078,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inventory"
|
name = "inventory"
|
||||||
version = "0.3.24"
|
version = "0.3.24"
|
||||||
@@ -3928,6 +4107,12 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@@ -5061,6 +5246,12 @@ version = "1.21.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oorandom"
|
name = "oorandom"
|
||||||
version = "11.1.5"
|
version = "11.1.5"
|
||||||
@@ -5553,13 +5744,24 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22"
|
checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha 0.3.1",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha 0.9.0",
|
||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5573,6 +5775,16 @@ dependencies = [
|
|||||||
"rand_core 0.10.1",
|
"rand_core 0.10.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -5583,6 +5795,15 @@ dependencies = [
|
|||||||
"rand_core 0.9.5",
|
"rand_core 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
@@ -5909,6 +6130,12 @@ dependencies = [
|
|||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "seahash"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "self_cell"
|
name = "self_cell"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -6244,6 +6471,30 @@ dependencies = [
|
|||||||
"precomputed-hash",
|
"precomputed-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svg_fmt"
|
name = "svg_fmt"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@@ -6687,6 +6938,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.24.0"
|
version = "1.24.0"
|
||||||
@@ -6716,6 +6973,12 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "varinteger"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ea29db9f94ff08bb619656b8120878f280526f71dc88b5262c958a510181812"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vec_map"
|
name = "vec_map"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ avian3d = "0.7"
|
|||||||
arrayvec = "0.7.8"
|
arrayvec = "0.7.8"
|
||||||
bevy-inspector-egui = "0.37.0"
|
bevy-inspector-egui = "0.37.0"
|
||||||
bevy_egui = "0.40.0"
|
bevy_egui = "0.40.0"
|
||||||
|
bevy_ggrs = { git = "https://github.com/gschup/bevy_ggrs" }
|
||||||
|
serde = "1.0.229"
|
||||||
|
bitfields = "3.0.0"
|
||||||
|
clap = { version = "4.6.6", features = ["derive"] }
|
||||||
|
|
||||||
# Enable a small amount of optimization in the dev profile.
|
# Enable a small amount of optimization in the dev profile.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug, Clone, Resource)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Args {
|
||||||
|
#[clap(long)]
|
||||||
|
pub synctest: bool,
|
||||||
|
#[clap(long, default_value_t = 2)]
|
||||||
|
pub input_delay: usize,
|
||||||
|
#[clap(long, default_value_t = 2)]
|
||||||
|
pub check_distance: usize,
|
||||||
|
#[clap(long, default_value_t = 1)]
|
||||||
|
pub players: usize
|
||||||
|
}
|
||||||
-100
@@ -1,100 +0,0 @@
|
|||||||
use bevy::{
|
|
||||||
color::palettes::css::{HOT_PINK, YELLOW},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod plugin;
|
|
||||||
pub mod data;
|
|
||||||
pub mod state;
|
|
||||||
pub mod states;
|
|
||||||
pub use plugin::*;
|
|
||||||
pub use data::*;
|
|
||||||
|
|
||||||
use crate::stage::StagePlane;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct Grounded;
|
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Default, Debug)]
|
|
||||||
pub struct FighterVelocity(pub Vec2);
|
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Default)]
|
|
||||||
pub struct FighterPreviousTranslation(pub Vec2);
|
|
||||||
|
|
||||||
#[derive(Component, Deref, DerefMut, Default)]
|
|
||||||
#[require(FighterPreviousTranslation)]
|
|
||||||
pub struct FighterTranslation(pub Vec2);
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
#[require(Transform)]
|
|
||||||
pub struct FighterECB {
|
|
||||||
pub vertical_half: f32,
|
|
||||||
pub horizontal_half: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FighterECB {
|
|
||||||
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
|
||||||
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
|
||||||
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
|
|
||||||
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
|
|
||||||
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
|
||||||
[rel_up, rel_right, rel_down, rel_left]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation)>, mut gizmos: Gizmos) {
|
|
||||||
for (ecb, trf) in ecbs {
|
|
||||||
let transformed = ecb.to_3d_lineloop(Transform::from_xyz(trf.x, trf.y, 0.0));
|
|
||||||
gizmos.lineloop(transformed, YELLOW);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn integrate_gravity(
|
|
||||||
_fighters: Query<&mut FighterVelocity>,
|
|
||||||
_time: Res<Time<Fixed>>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_motion(
|
|
||||||
fighters: Query<(
|
|
||||||
&FighterVelocity,
|
|
||||||
&mut FighterTranslation,
|
|
||||||
&mut FighterPreviousTranslation,
|
|
||||||
)>,
|
|
||||||
_time: Res<Time<Fixed>>,
|
|
||||||
) {
|
|
||||||
for (velocity, mut translation, mut prev_translation) in fighters {
|
|
||||||
prev_translation.0 = translation.0;
|
|
||||||
translation.0 += velocity.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collide_fighter_with_scene(
|
|
||||||
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
|
||||||
stage_planes: Query<&StagePlane>,
|
|
||||||
mut gizmos: Gizmos,
|
|
||||||
) {
|
|
||||||
for (ecb, mut velocity, mut translation) in fighters {
|
|
||||||
for plane in stage_planes {
|
|
||||||
let dist_sq = ecb.vertical_half * ecb.vertical_half;
|
|
||||||
let ray = Ray2d::new(translation.0, Dir2::NEG_Y);
|
|
||||||
|
|
||||||
let intersection_result = plane
|
|
||||||
.intersect_ray(ray)
|
|
||||||
.filter(|result| result.position.distance_squared(translation.0) <= dist_sq);
|
|
||||||
|
|
||||||
if let Some(result) = intersection_result {
|
|
||||||
gizmos.sphere(
|
|
||||||
Isometry3d::from(Vec3::new(result.position.x, result.position.y, 0.0)),
|
|
||||||
0.1f32,
|
|
||||||
HOT_PINK,
|
|
||||||
);
|
|
||||||
velocity.0 = plane.project(velocity.0);
|
|
||||||
translation.0 = result.position + Vec2::new(0.0, ecb.vertical_half);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fighter_movement() {}
|
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::input::FighterInputFrame;
|
||||||
|
|
||||||
|
/// Per-fighter tuning values. These are character data, not global rules —
|
||||||
|
/// anything that applies to every fighter belongs in
|
||||||
|
/// [`crate::game_settings::FighterSettingsCommon`] instead.
|
||||||
|
#[derive(Component, Clone, Copy)]
|
||||||
|
pub struct FighterAttributes {
|
||||||
|
pub base_walk_accel: f32,
|
||||||
|
pub stick_walk_accel: f32,
|
||||||
|
pub max_walk_vel: f32,
|
||||||
|
|
||||||
|
pub dash_duration: u32,
|
||||||
|
pub dash_initial_velocity: f32,
|
||||||
|
pub base_dash_accel: f32,
|
||||||
|
pub stick_dash_accel: f32,
|
||||||
|
pub max_dash_vel: f32,
|
||||||
|
|
||||||
|
pub ground_friction: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterAttributes {
|
||||||
|
pub fn get_accel_and_target_dashrun(&self, input: &FighterInputFrame) -> (f32, f32) {
|
||||||
|
let accel = input.movement.x * self.stick_dash_accel;
|
||||||
|
let accel = accel + input.movement.x.signum() * self.base_dash_accel;
|
||||||
|
let target_vel = input.movement.x * self.max_dash_vel;
|
||||||
|
|
||||||
|
(accel, target_vel)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_accel_and_target_walk(&self, input: &FighterInputFrame) -> (f32, f32) {
|
||||||
|
let accel = input.movement.x * self.stick_walk_accel;
|
||||||
|
let accel = accel + input.movement.x.signum() * self.base_walk_accel;
|
||||||
|
let target_vel = input.movement.x * self.max_walk_vel;
|
||||||
|
|
||||||
|
(accel, target_vel)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//! Resolving an integrated fighter position against the stage.
|
||||||
|
|
||||||
|
use bevy::{color::palettes::css::HOT_PINK, prelude::*};
|
||||||
|
|
||||||
|
use crate::fighter::{FighterECB, FighterTranslation, FighterVelocity};
|
||||||
|
use crate::stage::StageLine;
|
||||||
|
|
||||||
|
pub fn collide_fighter_with_scene(
|
||||||
|
fighters: Query<(&FighterECB, &mut FighterVelocity, &mut FighterTranslation)>,
|
||||||
|
stage_planes: Query<&StageLine>,
|
||||||
|
mut gizmos: Gizmos,
|
||||||
|
) {
|
||||||
|
for (ecb, mut velocity, mut translation) in fighters {
|
||||||
|
for plane in stage_planes {
|
||||||
|
let ray_from = translation.0;
|
||||||
|
let ray_to = translation.0 + Vec2::new(0.0, -ecb.vertical_half);
|
||||||
|
|
||||||
|
let intersection_result = plane.intersect_ray(Segment2d::new(ray_from, ray_to));
|
||||||
|
|
||||||
|
if let Some(result) = intersection_result {
|
||||||
|
gizmos.sphere(
|
||||||
|
Isometry3d::from(Vec3::new(result.position.x, result.position.y, 0.0)),
|
||||||
|
0.1f32,
|
||||||
|
HOT_PINK,
|
||||||
|
);
|
||||||
|
velocity.0 = plane.project(velocity.0);
|
||||||
|
translation.0 = result.position + Vec2::new(0.0, ecb.vertical_half);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct FighterAttributes {
|
|
||||||
pub base_walk_accel: f32,
|
|
||||||
pub stick_walk_accel: f32,
|
|
||||||
pub max_walk_vel: f32,
|
|
||||||
|
|
||||||
pub dash_duration: u32,
|
|
||||||
pub dash_initial_velocity: f32,
|
|
||||||
pub base_dash_accel: f32,
|
|
||||||
pub stick_dash_accel: f32,
|
|
||||||
pub max_dash_vel: f32,
|
|
||||||
|
|
||||||
pub ground_friction: f32,
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! Fighter debug views: gizmos and the egui inspector window.
|
||||||
|
|
||||||
|
use bevy::{color::palettes::css::YELLOW, prelude::*};
|
||||||
|
use bevy_egui::{EguiContexts, egui};
|
||||||
|
|
||||||
|
use crate::fighter::state::StateNameDebug;
|
||||||
|
use crate::fighter::{FighterECB, FighterTranslation, FighterVelocity};
|
||||||
|
use crate::input::FighterInput;
|
||||||
|
|
||||||
|
pub fn debug_draw_ecb(ecbs: Query<(&FighterECB, &FighterTranslation)>, mut gizmos: Gizmos) {
|
||||||
|
for (ecb, trf) in ecbs {
|
||||||
|
let transformed = ecb.to_3d_lineloop(Transform::from_xyz(trf.x, trf.y, 0.0));
|
||||||
|
gizmos.lineloop(transformed, YELLOW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fighter_debug(
|
||||||
|
mut contexts: EguiContexts,
|
||||||
|
query: Query<(&FighterVelocity, &FighterInput, &StateNameDebug)>,
|
||||||
|
) -> Result {
|
||||||
|
for (vel, input, name) in query {
|
||||||
|
egui::Window::new("Hello").show(contexts.ctx_mut()?, |ui| {
|
||||||
|
ui.label(format!("State: {}", name.0));
|
||||||
|
ui.label(format!("{:?}", vel));
|
||||||
|
ui.label(format!("{:?}", input.get_last_frame()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
/// Environment collision box: the diamond used to resolve a fighter against
|
||||||
|
/// the stage. Purely geometry — the collision response lives in
|
||||||
|
/// [`super::collision`].
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
#[require(Transform)]
|
||||||
|
pub struct FighterECB {
|
||||||
|
pub vertical_half: f32,
|
||||||
|
pub horizontal_half: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterECB {
|
||||||
|
pub fn to_3d_lineloop(&self, trf: Transform) -> [Vec3; 4] {
|
||||||
|
let rel_up = trf.transform_point(Vec3::new(0.0, self.vertical_half, 0.0));
|
||||||
|
let rel_down = trf.transform_point(Vec3::new(0.0, -self.vertical_half, 0.0));
|
||||||
|
let rel_left = trf.transform_point(Vec3::new(-self.horizontal_half, 0.0, 0.0));
|
||||||
|
let rel_right = trf.transform_point(Vec3::new(self.horizontal_half, 0.0, 0.0));
|
||||||
|
[rel_up, rel_right, rel_down, rel_left]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
//! Everything fighter-shaped: the components that describe a fighter, the
|
||||||
|
//! state machine that drives it, and the plugin that schedules it all.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_egui::EguiPrimaryContextPass;
|
||||||
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
pub mod attributes;
|
||||||
|
pub mod collision;
|
||||||
|
pub mod debug;
|
||||||
|
pub mod ecb;
|
||||||
|
pub mod motion;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
// The fighter components are re-exported so the rest of the crate can say
|
||||||
|
// `fighter::FighterVelocity` without caring which file it lives in. Named
|
||||||
|
// explicitly rather than globbed, so moving a type between submodules can't
|
||||||
|
// silently change what this module exports.
|
||||||
|
pub use attributes::FighterAttributes;
|
||||||
|
pub use ecb::FighterECB;
|
||||||
|
pub use motion::{FighterPreviousTranslation, FighterTranslation, FighterVelocity, Grounded};
|
||||||
|
|
||||||
|
use crate::schedule::GameplaySet;
|
||||||
|
use state::state_interrupt_system;
|
||||||
|
|
||||||
|
/// Owns fighter simulation, the rollback registrations for fighter
|
||||||
|
/// components, and the fighter debug overlays.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FighterPlugin;
|
||||||
|
|
||||||
|
impl Plugin for FighterPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app
|
||||||
|
// Every state gets a chance to interrupt itself, in priority order.
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
(
|
||||||
|
state_interrupt_system::<state::wait::WaitState>,
|
||||||
|
state_interrupt_system::<state::walk::WalkState>,
|
||||||
|
state_interrupt_system::<state::dash::DashState>,
|
||||||
|
state_interrupt_system::<state::run::RunState>,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(GameplaySet::Interrupt),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
(
|
||||||
|
state::wait::wait_update,
|
||||||
|
state::walk::walk_update,
|
||||||
|
state::dash::dash_update,
|
||||||
|
state::run::run_update,
|
||||||
|
)
|
||||||
|
.chain()
|
||||||
|
.in_set(GameplaySet::StateUpdate),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
(motion::integrate_gravity, motion::apply_motion)
|
||||||
|
.chain()
|
||||||
|
.in_set(GameplaySet::Physics),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
collision::collide_fighter_with_scene.in_set(GameplaySet::Collision),
|
||||||
|
)
|
||||||
|
.add_observer(state::dash::on_insert_dash)
|
||||||
|
// Rollback registration lives next to the systems that write these
|
||||||
|
// components; a component simulated here but missing from this list
|
||||||
|
// is a desync waiting to happen.
|
||||||
|
.rollback_component_with_copy::<FighterECB>()
|
||||||
|
.rollback_component_with_copy::<FighterVelocity>()
|
||||||
|
.rollback_component_with_copy::<FighterTranslation>()
|
||||||
|
.rollback_component_with_copy::<FighterPreviousTranslation>()
|
||||||
|
.rollback_component_with_copy::<FighterAttributes>()
|
||||||
|
.rollback_component_with_copy::<Grounded>()
|
||||||
|
.rollback_component_with_copy::<state::wait::WaitState>()
|
||||||
|
.rollback_component_with_copy::<state::walk::WalkState>()
|
||||||
|
.rollback_component_with_copy::<state::dash::DashState>()
|
||||||
|
.rollback_component_with_copy::<state::run::RunState>()
|
||||||
|
.checksum_component::<state::dash::DashState>(state::dash::hash_dash_state)
|
||||||
|
// Debug views.
|
||||||
|
.add_systems(FixedPostUpdate, debug::debug_draw_ecb)
|
||||||
|
.add_systems(EguiPrimaryContextPass, debug::fighter_debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
//! Where a fighter is and how it moves. States write [`FighterVelocity`];
|
||||||
|
//! this module is what turns that into a position.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy)]
|
||||||
|
pub struct Grounded;
|
||||||
|
|
||||||
|
#[derive(Component, Deref, DerefMut, Default, Debug, Clone, Copy)]
|
||||||
|
pub struct FighterVelocity(pub Vec2);
|
||||||
|
|
||||||
|
#[derive(Component, Deref, DerefMut, Default, Clone, Copy)]
|
||||||
|
pub struct FighterPreviousTranslation(pub Vec2);
|
||||||
|
|
||||||
|
#[derive(Component, Deref, DerefMut, Default, Clone, Copy)]
|
||||||
|
#[require(FighterPreviousTranslation)]
|
||||||
|
pub struct FighterTranslation(pub Vec2);
|
||||||
|
|
||||||
|
pub fn integrate_gravity(_fighters: Query<&mut FighterVelocity>, _time: Res<Time<Fixed>>) {}
|
||||||
|
|
||||||
|
pub fn apply_motion(
|
||||||
|
fighters: Query<(
|
||||||
|
&FighterVelocity,
|
||||||
|
&mut FighterTranslation,
|
||||||
|
&mut FighterPreviousTranslation,
|
||||||
|
)>,
|
||||||
|
_time: Res<Time<Fixed>>,
|
||||||
|
) {
|
||||||
|
for (velocity, mut translation, mut prev_translation) in fighters {
|
||||||
|
prev_translation.0 = translation.0;
|
||||||
|
translation.0 += velocity.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fighter_movement() {}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use bevy_egui::{EguiContexts, EguiPrimaryContextPass, egui};
|
|
||||||
use crate::fighter::state::StateNameDebug;
|
|
||||||
use crate::fighter::{FighterVelocity, states};
|
|
||||||
use crate::fighter::{
|
|
||||||
apply_motion, collide_fighter_with_scene, integrate_gravity, state::state_interrupt_system,
|
|
||||||
};
|
|
||||||
use crate::input::player::FighterInput;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct FighterPlugin;
|
|
||||||
|
|
||||||
impl Plugin for FighterPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.add_systems(FixedPostUpdate, super::debug_draw_ecb)
|
|
||||||
.add_systems(
|
|
||||||
FixedUpdate,
|
|
||||||
(
|
|
||||||
state_interrupt_system::<states::wait::WaitState>,
|
|
||||||
state_interrupt_system::<states::walk::WalkState>,
|
|
||||||
state_interrupt_system::<states::dash::DashState>,
|
|
||||||
state_interrupt_system::<states::run::RunState>,
|
|
||||||
states::wait::wait_update,
|
|
||||||
states::walk::walk_update,
|
|
||||||
states::dash::dash_update,
|
|
||||||
states::run::run_update,
|
|
||||||
integrate_gravity,
|
|
||||||
apply_motion,
|
|
||||||
collide_fighter_with_scene,
|
|
||||||
)
|
|
||||||
.chain(),
|
|
||||||
)
|
|
||||||
.add_observer(states::dash::on_insert_dash)
|
|
||||||
.add_systems(EguiPrimaryContextPass, fighter_debug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fighter_debug(mut contexts: EguiContexts, query: Query<(&FighterVelocity, &FighterInput, &StateNameDebug)>) -> Result {
|
|
||||||
for (vel, input, name) in query {
|
|
||||||
egui::Window::new("Hello").show(contexts.ctx_mut()?, |ui| {
|
|
||||||
ui.label(format!("State: {}", name.0));
|
|
||||||
ui.label(format!("{:?}", vel));
|
|
||||||
ui.label(format!("{:?}", input.get_last_frame()));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, run, wait, walk};
|
||||||
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::input::{
|
||||||
|
FighterCommands::{SmashMoveLeft, SmashMoveRight},
|
||||||
|
FighterInput,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct DashStateCreator {}
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct DashState {
|
||||||
|
frames_in_dash: u32,
|
||||||
|
direction: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_dash_state(dash_state: &DashState) -> u64 {
|
||||||
|
let mut hasher = bevy_ggrs::checksum_hasher();
|
||||||
|
dash_state.direction.to_bits().hash(&mut hasher);
|
||||||
|
dash_state.frames_in_dash.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DashState {
|
||||||
|
pub fn new(direction: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
frames_in_dash: 0,
|
||||||
|
direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterState for DashState {
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<FighterStateTransition> {
|
||||||
|
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
|
||||||
|
// Dash finish shenanigans
|
||||||
|
run::check_input(state_context)
|
||||||
|
.or_else(|| walk::check_input(state_context))
|
||||||
|
.or_else(|| wait::check_input(state_context))
|
||||||
|
} else {
|
||||||
|
check_smash_input_with_dir(state_context)
|
||||||
|
.filter(|dir| *dir != self.direction)
|
||||||
|
.and_then(|dir| Some(FighterStateTransition::Dash(dir)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(state_context: &mut FighterStateContext) {
|
||||||
|
state_context.input.clear_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
||||||
|
if state_context.input.has_command(SmashMoveLeft) {
|
||||||
|
return Some(-1.0);
|
||||||
|
} else if state_context.input.has_command(SmashMoveRight) {
|
||||||
|
return Some(1.0);
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||||
|
check_smash_input_with_dir(state_context).map(|dir| FighterStateTransition::Dash(dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dash_update(
|
||||||
|
dash_query: Query<(
|
||||||
|
&mut DashState,
|
||||||
|
&FighterAttributes,
|
||||||
|
&FighterInput,
|
||||||
|
&mut FighterVelocity,
|
||||||
|
)>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
) {
|
||||||
|
for (mut dash, attributes, input, mut velocity) in dash_query {
|
||||||
|
dash.frames_in_dash += 1;
|
||||||
|
|
||||||
|
let last_frame = input.get_last_frame();
|
||||||
|
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(&last_frame);
|
||||||
|
let accel = ground::compute_ground_accel(
|
||||||
|
accel,
|
||||||
|
target_vel,
|
||||||
|
velocity.x,
|
||||||
|
attributes,
|
||||||
|
game_settings.as_ref(),
|
||||||
|
);
|
||||||
|
velocity.x += accel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_insert_dash(
|
||||||
|
add: On<Insert, DashState>,
|
||||||
|
query: Query<
|
||||||
|
(
|
||||||
|
Entity,
|
||||||
|
&mut FighterInput,
|
||||||
|
&FighterAttributes,
|
||||||
|
&mut FighterVelocity,
|
||||||
|
),
|
||||||
|
With<DashState>,
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
for (entity, mut input, attribs, mut velocity) in query {
|
||||||
|
if entity == add.entity {
|
||||||
|
velocity.x = attribs.dash_initial_velocity * input.get_last_frame().movement.x.signum();
|
||||||
|
input.clear_buffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
use bevy::log::info;
|
//! Ground movement math shared by every grounded state. Deliberately free of
|
||||||
|
//! ECS types so it stays cheap to reason about and to unit-test — it has to be
|
||||||
|
//! bit-for-bit deterministic for rollback.
|
||||||
|
|
||||||
use crate::{fighter::FighterAttributes, game_settings::GameSettings, input::player::InputFrame};
|
use crate::fighter::FighterAttributes;
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
|
||||||
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
||||||
if friction.abs() > ground_vel.abs() {
|
if friction.abs() > ground_vel.abs() {
|
||||||
@@ -10,11 +13,15 @@ pub fn apply_grounded_friction(friction: f32, ground_vel: f32) -> f32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_accelerate() {
|
pub fn move_accelerate() {}
|
||||||
|
|
||||||
}
|
pub fn compute_ground_accel(
|
||||||
|
accel: f32,
|
||||||
pub fn compute_ground_accel(accel: f32, target_vel: f32, gr_vel: f32, attribs: &FighterAttributes, game_settings: &GameSettings) -> f32 {
|
target_vel: f32,
|
||||||
|
gr_vel: f32,
|
||||||
|
attribs: &FighterAttributes,
|
||||||
|
game_settings: &GameSettings,
|
||||||
|
) -> f32 {
|
||||||
if target_vel == 0.0 {
|
if target_vel == 0.0 {
|
||||||
apply_grounded_friction(attribs.ground_friction, gr_vel)
|
apply_grounded_friction(attribs.ground_friction, gr_vel)
|
||||||
} else {
|
} else {
|
||||||
@@ -46,24 +53,3 @@ pub fn compute_ground_accel(accel: f32, target_vel: f32, gr_vel: f32, attribs: &
|
|||||||
accel
|
accel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl FighterAttributes {
|
|
||||||
pub fn get_accel_and_target_dashrun(&self, input: &InputFrame) -> (f32, f32) {
|
|
||||||
let accel = input.movement.x * self.stick_dash_accel;
|
|
||||||
let accel = accel + input.movement.x.signum() * self.base_dash_accel;
|
|
||||||
let target_vel = input.movement.x * self.max_dash_vel;
|
|
||||||
|
|
||||||
info!("accel {:?} target_vel {:?}", accel, target_vel);
|
|
||||||
|
|
||||||
(accel, target_vel)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_accel_and_target_walk(&self, input: &InputFrame) -> (f32, f32) {
|
|
||||||
let accel = input.movement.x * self.stick_walk_accel;
|
|
||||||
let accel = accel + input.movement.x.signum() * self.base_walk_accel;
|
|
||||||
let target_vel = input.movement.x * self.max_walk_vel;
|
|
||||||
|
|
||||||
(accel, target_vel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,28 @@
|
|||||||
use bevy::{prelude::*};
|
//! The fighter state machine: the [`FighterState`] trait every state
|
||||||
|
//! implements, the context they inspect, and the generic system that runs the
|
||||||
|
//! interrupt check. One state per submodule.
|
||||||
|
|
||||||
use crate::{fighter::{FighterAttributes, states}, game_settings::GameSettings, input::player::FighterInput};
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::fighter::FighterAttributes;
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::input::FighterInput;
|
||||||
|
|
||||||
|
pub mod dash;
|
||||||
|
pub mod ground;
|
||||||
|
pub mod run;
|
||||||
|
pub mod wait;
|
||||||
|
pub mod walk;
|
||||||
|
|
||||||
pub enum FighterStateTransition {
|
pub enum FighterStateTransition {
|
||||||
Wait,
|
Wait,
|
||||||
Walk,
|
Walk,
|
||||||
Dash,
|
Dash(f32),
|
||||||
Run,
|
Run,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FighterState : Component + Sized {
|
pub trait FighterState: Component + Sized {
|
||||||
fn remove(&self,
|
fn remove(&self, state_context: &FighterStateContext, commands: &mut Commands) {
|
||||||
state_context: &FighterStateContext,
|
|
||||||
commands: &mut Commands) {
|
|
||||||
commands.entity(state_context.entity).remove::<Self>();
|
commands.entity(state_context.entity).remove::<Self>();
|
||||||
}
|
}
|
||||||
fn check_interrupt(
|
fn check_interrupt(
|
||||||
@@ -24,17 +34,21 @@ pub trait FighterState : Component + Sized {
|
|||||||
pub struct FighterStateContext<'a> {
|
pub struct FighterStateContext<'a> {
|
||||||
pub input: &'a mut FighterInput,
|
pub input: &'a mut FighterInput,
|
||||||
pub fighter_attribs: &'a FighterAttributes,
|
pub fighter_attribs: &'a FighterAttributes,
|
||||||
pub game_settings: &'a Res<'a, GameSettings>,
|
pub game_settings: &'a GameSettings,
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_state(transition: FighterStateTransition, state_context: FighterStateContext, commands: &mut Commands) {
|
fn create_state(
|
||||||
|
transition: FighterStateTransition,
|
||||||
|
state_context: FighterStateContext,
|
||||||
|
commands: &mut Commands,
|
||||||
|
) {
|
||||||
let mut ent_cmd = commands.entity(state_context.entity);
|
let mut ent_cmd = commands.entity(state_context.entity);
|
||||||
match transition {
|
match transition {
|
||||||
FighterStateTransition::Wait => ent_cmd.insert(states::wait::WaitState),
|
FighterStateTransition::Wait => ent_cmd.insert(wait::WaitState),
|
||||||
FighterStateTransition::Walk => ent_cmd.insert(states::walk::WalkState),
|
FighterStateTransition::Walk => ent_cmd.insert(walk::WalkState),
|
||||||
FighterStateTransition::Dash => ent_cmd.insert(states::dash::DashState::default()),
|
FighterStateTransition::Dash(dir) => ent_cmd.insert(dash::DashState::new(dir)),
|
||||||
FighterStateTransition::Run => ent_cmd.insert(states::run::RunState),
|
FighterStateTransition::Run => ent_cmd.insert(run::RunState),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +56,7 @@ fn get_state_debug_name(transition: &FighterStateTransition) -> &'static str {
|
|||||||
match transition {
|
match transition {
|
||||||
FighterStateTransition::Wait => "wait",
|
FighterStateTransition::Wait => "wait",
|
||||||
FighterStateTransition::Walk => "walk",
|
FighterStateTransition::Walk => "walk",
|
||||||
FighterStateTransition::Dash => "dash",
|
FighterStateTransition::Dash(_) => "dash",
|
||||||
FighterStateTransition::Run => "run",
|
FighterStateTransition::Run => "run",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +76,6 @@ pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
for (ent, state, mut input, attribs) in query {
|
for (ent, state, mut input, attribs) in query {
|
||||||
info!("{:?}", state);
|
|
||||||
let state_context = FighterStateContext {
|
let state_context = FighterStateContext {
|
||||||
input: &mut input,
|
input: &mut input,
|
||||||
fighter_attribs: &attribs,
|
fighter_attribs: &attribs,
|
||||||
@@ -71,7 +84,10 @@ pub fn state_interrupt_system<T: FighterState + Component + std::fmt::Debug>(
|
|||||||
};
|
};
|
||||||
if let Some(new_state) = state.check_interrupt(&state_context) {
|
if let Some(new_state) = state.check_interrupt(&state_context) {
|
||||||
commands.entity(ent).remove::<T>();
|
commands.entity(ent).remove::<T>();
|
||||||
commands.entity(ent).insert(StateNameDebug(get_state_debug_name(&new_state)));
|
info!("State {:?} -> {}", state, get_state_debug_name(&new_state));
|
||||||
|
commands
|
||||||
|
.entity(ent)
|
||||||
|
.insert(StateNameDebug(get_state_debug_name(&new_state)));
|
||||||
create_state(new_state, state_context, &mut commands);
|
create_state(new_state, state_context, &mut commands);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, wait, walk};
|
||||||
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::input::FighterInput;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct RunState;
|
||||||
|
|
||||||
|
impl FighterState for RunState {
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<FighterStateTransition> {
|
||||||
|
if let Some(_) = check_input(state_context) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
walk::check_input(state_context).or_else(|| wait::check_input(state_context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||||
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold
|
||||||
|
{
|
||||||
|
return Some(FighterStateTransition::Run);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_update(
|
||||||
|
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<RunState>>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
) {
|
||||||
|
for (mut velocity, attributes, input) in fighters {
|
||||||
|
let last_frame = input.get_last_frame();
|
||||||
|
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(&last_frame);
|
||||||
|
let accel = ground::compute_ground_accel(
|
||||||
|
accel,
|
||||||
|
target_vel,
|
||||||
|
velocity.x,
|
||||||
|
attributes,
|
||||||
|
game_settings.as_ref(),
|
||||||
|
);
|
||||||
|
velocity.x += accel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{FighterState, FighterStateContext, FighterStateTransition, ground, walk};
|
||||||
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct WaitState;
|
||||||
|
|
||||||
|
impl FighterState for WaitState {
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<FighterStateTransition> {
|
||||||
|
walk::check_input(state_context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||||
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
|
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
||||||
|
return Some(FighterStateTransition::Wait);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_update(
|
||||||
|
query: Query<(&FighterAttributes, &mut FighterVelocity), With<WaitState>>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
) {
|
||||||
|
for (attribs, mut velocity) in query {
|
||||||
|
let friction = if velocity.x.abs() > attribs.max_walk_vel {
|
||||||
|
attribs.ground_friction
|
||||||
|
* game_settings
|
||||||
|
.figher_common
|
||||||
|
.ground_friction_over_walk_speed_multiplier
|
||||||
|
} else {
|
||||||
|
attribs.ground_friction
|
||||||
|
};
|
||||||
|
velocity.x += ground::apply_grounded_friction(friction, velocity.x);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{FighterState, FighterStateContext, FighterStateTransition, dash, ground, wait};
|
||||||
|
use crate::fighter::{FighterAttributes, FighterVelocity};
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::input::FighterInput;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, Clone, Copy)]
|
||||||
|
pub struct WalkState;
|
||||||
|
|
||||||
|
impl FighterState for WalkState {
|
||||||
|
fn check_interrupt(
|
||||||
|
&self,
|
||||||
|
state_context: &FighterStateContext,
|
||||||
|
) -> Option<FighterStateTransition> {
|
||||||
|
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
||||||
|
let input_frame = state_context.input.get_last_frame();
|
||||||
|
|
||||||
|
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
|
||||||
|
return Some(FighterStateTransition::Walk);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn walk_update(
|
||||||
|
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<WalkState>>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
) {
|
||||||
|
for (mut velocity, attributes, input) in fighters {
|
||||||
|
let last_frame = input.get_last_frame();
|
||||||
|
let (accel, target_vel) = attributes.get_accel_and_target_walk(&last_frame);
|
||||||
|
|
||||||
|
let accel =
|
||||||
|
ground::compute_ground_accel(accel, target_vel, velocity.x, attributes, &game_settings);
|
||||||
|
|
||||||
|
velocity.x += accel;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
pub mod wait;
|
|
||||||
pub mod walk;
|
|
||||||
pub mod dash;
|
|
||||||
pub mod run;
|
|
||||||
pub mod grounded;
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
fighter::{
|
|
||||||
FighterAttributes, FighterVelocity,
|
|
||||||
state::{FighterState, FighterStateContext, FighterStateTransition},
|
|
||||||
states::grounded,
|
|
||||||
},
|
|
||||||
game_settings::GameSettings,
|
|
||||||
input::player::FighterInput,
|
|
||||||
};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
pub struct DashStateCreator {}
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct DashState {
|
|
||||||
frames_in_dash: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DashState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { frames_in_dash: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FighterState for DashState {
|
|
||||||
fn check_interrupt(
|
|
||||||
&self,
|
|
||||||
state_context: &FighterStateContext,
|
|
||||||
) -> Option<FighterStateTransition> {
|
|
||||||
if self.frames_in_dash > state_context.fighter_attribs.dash_duration {
|
|
||||||
// Dash finish shenanigans
|
|
||||||
super::run::check_input(state_context)
|
|
||||||
.or_else(|| super::walk::check_input(state_context))
|
|
||||||
.or_else(|| super::wait::check_input(state_context))
|
|
||||||
} else if let Some(last_frame) = state_context.input.get_last_frame() {
|
|
||||||
check_smash_input_with_dir(state_context)
|
|
||||||
.filter(|dir| *dir != last_frame.movement.x.signum())
|
|
||||||
.and_then(|_| super::dash::check_input(state_context))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(state_context: &mut FighterStateContext) {
|
|
||||||
state_context.input.clear_buffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_smash_input_with_dir(state_context: &FighterStateContext) -> Option<f32> {
|
|
||||||
state_context.input.has_smash_x_movement(
|
|
||||||
state_context
|
|
||||||
.game_settings
|
|
||||||
.input_common
|
|
||||||
.smash_input_reset_axis_threshold,
|
|
||||||
state_context
|
|
||||||
.game_settings
|
|
||||||
.input_common
|
|
||||||
.smash_input_axis_threshold,
|
|
||||||
state_context
|
|
||||||
.game_settings
|
|
||||||
.input_common
|
|
||||||
.smash_input_frame_threshold,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dash_update(
|
|
||||||
dash_query: Query<(
|
|
||||||
&mut DashState,
|
|
||||||
&FighterAttributes,
|
|
||||||
&FighterInput,
|
|
||||||
&mut FighterVelocity,
|
|
||||||
)>,
|
|
||||||
game_settings: Res<GameSettings>,
|
|
||||||
) {
|
|
||||||
for (mut dash, attributes, input, mut velocity) in dash_query {
|
|
||||||
dash.frames_in_dash += 1;
|
|
||||||
|
|
||||||
if let Some(last_frame) = input.get_last_frame() {
|
|
||||||
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(last_frame);
|
|
||||||
let accel = grounded::compute_ground_accel(
|
|
||||||
accel,
|
|
||||||
target_vel,
|
|
||||||
velocity.x,
|
|
||||||
attributes,
|
|
||||||
game_settings.as_ref(),
|
|
||||||
);
|
|
||||||
velocity.x += accel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
|
||||||
check_smash_input_with_dir(state_context).map(|_| FighterStateTransition::Dash)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_insert_dash(
|
|
||||||
add: On<Insert, DashState>,
|
|
||||||
query: Query<
|
|
||||||
(
|
|
||||||
Entity,
|
|
||||||
&mut FighterInput,
|
|
||||||
&FighterAttributes,
|
|
||||||
&mut FighterVelocity,
|
|
||||||
),
|
|
||||||
With<DashState>,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
for (entity, mut input, attribs, mut velocity) in query {
|
|
||||||
if entity == add.entity {
|
|
||||||
if let Some(last_frame) = input.get_last_frame() {
|
|
||||||
velocity.x = attribs.dash_initial_velocity * last_frame.movement.x.signum();
|
|
||||||
}
|
|
||||||
input.clear_buffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
fighter::{FighterAttributes, FighterVelocity, state::{FighterStateContext, FighterStateTransition}, states::grounded}, game_settings::GameSettings, input::player::FighterInput, state::FighterState,
|
|
||||||
};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct RunState;
|
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
|
||||||
if let Some(input_frame) = state_context.input.get_last_frame() {
|
|
||||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.run_stick_threshold {
|
|
||||||
return Some(FighterStateTransition::Run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FighterState for RunState {
|
|
||||||
fn check_interrupt(
|
|
||||||
&self,
|
|
||||||
state_context: &crate::fighter::state::FighterStateContext,
|
|
||||||
) -> Option<crate::fighter::state::FighterStateTransition> {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
if let Some(_) = check_input(state_context) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
walk::check_input(state_context).or_else(|| wait::check_input(state_context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_update(
|
|
||||||
fighters: Query<(&mut FighterVelocity, &FighterAttributes, &FighterInput), With<RunState>>,
|
|
||||||
game_settings: Res<GameSettings>,
|
|
||||||
) {
|
|
||||||
for ( mut velocity, attributes, input) in fighters {
|
|
||||||
if let Some(last_frame) = input.get_last_frame() {
|
|
||||||
let (accel, target_vel) = attributes.get_accel_and_target_dashrun(last_frame);
|
|
||||||
let accel = grounded::compute_ground_accel(accel, target_vel, velocity.x, attributes, game_settings.as_ref());
|
|
||||||
velocity.x += accel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
use crate::{fighter::{state::{FighterState, FighterStateContext, FighterStateTransition}, states::{grounded, walk}}, game_settings::GameSettings};
|
|
||||||
use crate::fighter::*;
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct WaitState;
|
|
||||||
|
|
||||||
impl FighterState for WaitState {
|
|
||||||
fn check_interrupt(
|
|
||||||
&self,
|
|
||||||
state_context: &FighterStateContext,
|
|
||||||
) -> Option<FighterStateTransition> {
|
|
||||||
walk::check_input(state_context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
|
||||||
if let Some(input_frame) = state_context.input.get_last_frame() {
|
|
||||||
if input_frame.movement.x.abs() < state_context.game_settings.input_common.stick_deadzone {
|
|
||||||
return Some(FighterStateTransition::Wait);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wait_update(query: Query<(&FighterAttributes, &mut FighterVelocity), With<WaitState>>, game_settings: Res<GameSettings>) {
|
|
||||||
for (attribs, mut velocity) in query {
|
|
||||||
let friction = if velocity.x.abs() > attribs.max_walk_vel {
|
|
||||||
attribs.ground_friction * game_settings.figher_common.ground_friction_over_walk_speed_multiplier
|
|
||||||
} else {
|
|
||||||
attribs.ground_friction
|
|
||||||
};
|
|
||||||
velocity.x += grounded::apply_grounded_friction(friction, velocity.x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
fighter::{
|
|
||||||
FighterAttributes, FighterVelocity, state::{FighterState, FighterStateContext, FighterStateTransition}, states::{dash, grounded, wait},
|
|
||||||
}, game_settings::GameSettings, input::player::FighterInput,
|
|
||||||
};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct WalkState;
|
|
||||||
|
|
||||||
impl FighterState for WalkState {
|
|
||||||
fn check_interrupt(
|
|
||||||
&self,
|
|
||||||
state_context: &FighterStateContext,
|
|
||||||
) -> Option<FighterStateTransition> {
|
|
||||||
wait::check_input(state_context).or_else(|| dash::check_input(state_context))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn walk_update(
|
|
||||||
fighters: Query<(
|
|
||||||
&mut FighterVelocity,
|
|
||||||
&FighterAttributes,
|
|
||||||
&FighterInput,
|
|
||||||
), With<WalkState>>,
|
|
||||||
game_settings: Res<GameSettings>,
|
|
||||||
) {
|
|
||||||
for (mut velocity, attributes, input) in fighters {
|
|
||||||
if let Some(last_frame) = input.get_last_frame() {
|
|
||||||
let (accel, target_vel) = attributes.get_accel_and_target_walk(last_frame);
|
|
||||||
|
|
||||||
let accel = grounded::compute_ground_accel(accel, target_vel, velocity.x, attributes, &game_settings);
|
|
||||||
|
|
||||||
velocity.x += accel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_input(state_context: &FighterStateContext) -> Option<FighterStateTransition> {
|
|
||||||
if let Some(input_frame) = state_context.input.get_last_frame() {
|
|
||||||
if input_frame.movement.x.abs() >= state_context.game_settings.input_common.stick_deadzone {
|
|
||||||
return Some(FighterStateTransition::Walk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,8 @@ pub struct InputSettingsCommon {
|
|||||||
pub smash_input_reset_axis_threshold: f32,
|
pub smash_input_reset_axis_threshold: f32,
|
||||||
pub smash_input_axis_threshold: f32,
|
pub smash_input_axis_threshold: f32,
|
||||||
pub run_stick_threshold: f32,
|
pub run_stick_threshold: f32,
|
||||||
pub smash_input_frame_threshold: u32,
|
pub smash_input_frame_threshold: u8,
|
||||||
|
pub input_buffer_size: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for InputSettingsCommon {
|
impl Default for InputSettingsCommon {
|
||||||
@@ -22,7 +23,8 @@ impl Default for InputSettingsCommon {
|
|||||||
smash_input_reset_axis_threshold: 0.25,
|
smash_input_reset_axis_threshold: 0.25,
|
||||||
smash_input_axis_threshold: 0.8,
|
smash_input_axis_threshold: 0.8,
|
||||||
run_stick_threshold: 0.625,
|
run_stick_threshold: 0.625,
|
||||||
smash_input_frame_threshold: 2
|
smash_input_frame_threshold: 2,
|
||||||
|
input_buffer_size: 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod player;
|
|
||||||
pub mod logic;
|
|
||||||
pub mod plugin;
|
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
//! Per-fighter input state: the current and previous frame, plus the buffered
|
||||||
|
//! commands (smash inputs and friends) that states query when deciding whether
|
||||||
|
//! to interrupt.
|
||||||
|
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::{PlayerInputs, Rollback};
|
||||||
|
|
||||||
|
use crate::game_settings::GameSettings;
|
||||||
|
use crate::input::FighterInputFrame;
|
||||||
|
use crate::netcode::GGRSCfg;
|
||||||
|
use crate::player::Player;
|
||||||
|
|
||||||
|
struct FighterInputBufferedActions {}
|
||||||
|
|
||||||
|
pub enum FighterCommands {
|
||||||
|
SmashMoveLeft,
|
||||||
|
SmashMoveRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frames of life remaining for each buffered command. Indexed by
|
||||||
|
/// [`FighterCommands`] so adding a command is a two-line change here.
|
||||||
|
#[derive(Default, Clone, Copy)]
|
||||||
|
pub struct FighterCommandLifetimes {
|
||||||
|
smash_move_left: u8,
|
||||||
|
smash_move_right: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<FighterCommands> for FighterCommandLifetimes {
|
||||||
|
type Output = u8;
|
||||||
|
|
||||||
|
fn index(&self, command: FighterCommands) -> &Self::Output {
|
||||||
|
match command {
|
||||||
|
FighterCommands::SmashMoveLeft => &self.smash_move_left,
|
||||||
|
FighterCommands::SmashMoveRight => &self.smash_move_right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<FighterCommands> for FighterCommandLifetimes {
|
||||||
|
fn index_mut(&mut self, command: FighterCommands) -> &mut Self::Output {
|
||||||
|
match command {
|
||||||
|
FighterCommands::SmashMoveLeft => &mut self.smash_move_left,
|
||||||
|
FighterCommands::SmashMoveRight => &mut self.smash_move_right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Default, Clone, Copy)]
|
||||||
|
#[require(Rollback)]
|
||||||
|
pub struct FighterInput {
|
||||||
|
command_lifetimes: FighterCommandLifetimes,
|
||||||
|
prev_frame: FighterInputFrame,
|
||||||
|
current_frame: FighterInputFrame,
|
||||||
|
frames_in_smash_move_deadzone: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FighterInput {
|
||||||
|
pub fn get_last_frame(&self) -> FighterInputFrame {
|
||||||
|
self.current_frame
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_input_frame(&mut self, frame: FighterInputFrame) {
|
||||||
|
self.prev_frame = self.current_frame;
|
||||||
|
self.current_frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_buffer(&mut self) {
|
||||||
|
self.command_lifetimes = FighterCommandLifetimes::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_command(&mut self, command: FighterCommands) {
|
||||||
|
self.command_lifetimes[command] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lifetime(&mut self, command: FighterCommands, lifetime: u8) {
|
||||||
|
self.command_lifetimes[command] = lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_command(&self, command: FighterCommands) -> bool {
|
||||||
|
self.command_lifetimes[command] > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns this frame's rolled-back inputs into per-fighter [`FighterInput`]
|
||||||
|
/// state. Runs in `GameplaySet::Input`, before any state looks at it.
|
||||||
|
pub fn postprocess_input(
|
||||||
|
query: Query<(&mut FighterInput, &Player)>,
|
||||||
|
inputs: Res<PlayerInputs<GGRSCfg>>,
|
||||||
|
game_settings: Res<GameSettings>,
|
||||||
|
) {
|
||||||
|
for (mut input, player) in query {
|
||||||
|
input.push_input_frame(inputs[player.handle].0);
|
||||||
|
|
||||||
|
// Directional smash detection
|
||||||
|
let x_abs = input.current_frame.movement.x.abs();
|
||||||
|
|
||||||
|
let reset_deadzone = game_settings.input_common.smash_input_reset_axis_threshold;
|
||||||
|
let axis_threshold = game_settings.input_common.smash_input_axis_threshold;
|
||||||
|
let frame_threshold = game_settings.input_common.smash_input_frame_threshold;
|
||||||
|
|
||||||
|
if x_abs < reset_deadzone {
|
||||||
|
input.frames_in_smash_move_deadzone = 0xFE;
|
||||||
|
} else {
|
||||||
|
let prev_x_abs = input.prev_frame.movement.x.abs();
|
||||||
|
if prev_x_abs < reset_deadzone
|
||||||
|
|| input.prev_frame.movement.x.signum() != input.current_frame.movement.x.signum()
|
||||||
|
{
|
||||||
|
input.frames_in_smash_move_deadzone = 0;
|
||||||
|
} else {
|
||||||
|
input.frames_in_smash_move_deadzone += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if x_abs >= axis_threshold && input.frames_in_smash_move_deadzone <= frame_threshold {
|
||||||
|
input.clear_command(FighterCommands::SmashMoveLeft);
|
||||||
|
input.clear_command(FighterCommands::SmashMoveRight);
|
||||||
|
|
||||||
|
let command_to_set = if input.current_frame.movement.x > 0.0 {
|
||||||
|
FighterCommands::SmashMoveRight
|
||||||
|
} else {
|
||||||
|
FighterCommands::SmashMoveLeft
|
||||||
|
};
|
||||||
|
input.set_lifetime(command_to_set, game_settings.input_common.input_buffer_size);
|
||||||
|
|
||||||
|
if input.current_frame.movement.x > 0.0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//! Which entity is driving which. Kept separate from the input pipeline
|
||||||
|
//! itself: this is a relationship between entities, not a source of input.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
#[relationship(relationship_target = ControlledBy)]
|
||||||
|
pub struct Controls(pub Entity);
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
#[relationship_target(relationship = Controls)]
|
||||||
|
pub struct ControlledBy(Entity);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// One frame of a player's raw input. This is the type GGRS serializes and
|
||||||
|
/// sends over the wire, so keep it small and keep it `PartialEq`.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
pub struct FighterInputFrame {
|
||||||
|
pub movement: Vec2,
|
||||||
|
pub directional_attack: Vec2,
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//! Keyboard backend: samples the keys into the local input frames GGRS sends
|
||||||
|
//! over the wire.
|
||||||
|
|
||||||
|
use bevy::platform::collections::HashMap;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::{LocalInputs, LocalPlayers};
|
||||||
|
|
||||||
|
use crate::input::{BaseInputMap, FighterInputFrame, InputActionState, InputMapAction};
|
||||||
|
use crate::netcode::GGRSCfg;
|
||||||
|
|
||||||
|
pub fn preprocess_keyboard_input(
|
||||||
|
mut commands: Commands,
|
||||||
|
key: Res<ButtonInput<KeyCode>>,
|
||||||
|
input_map: Res<BaseInputMap>,
|
||||||
|
local_players: Res<LocalPlayers>,
|
||||||
|
) {
|
||||||
|
// Keyboard input is only for player 0
|
||||||
|
|
||||||
|
let mut local_inputs = HashMap::new();
|
||||||
|
|
||||||
|
for handle in &local_players.0 {
|
||||||
|
let mut input_frame = FighterInputFrame::default();
|
||||||
|
for keyboard_elment in &input_map.keyboard {
|
||||||
|
let action_state = if key.just_pressed(keyboard_elment.key) {
|
||||||
|
InputActionState::JustPressed
|
||||||
|
} else if key.just_released(keyboard_elment.key) {
|
||||||
|
InputActionState::JustReleased
|
||||||
|
} else if key.pressed(keyboard_elment.key) {
|
||||||
|
InputActionState::Pressed
|
||||||
|
} else {
|
||||||
|
InputActionState::Released
|
||||||
|
};
|
||||||
|
|
||||||
|
let action_strength = match action_state {
|
||||||
|
InputActionState::JustPressed | InputActionState::Pressed => 1.0f32,
|
||||||
|
InputActionState::JustReleased | InputActionState::Released => 0.0f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
match keyboard_elment.action {
|
||||||
|
InputMapAction::MovementXDir(sign) => {
|
||||||
|
input_frame.movement.x += (sign as f32) * action_strength
|
||||||
|
}
|
||||||
|
InputMapAction::MovementYDir(sign) => {
|
||||||
|
input_frame.movement.y += (sign as f32) * action_strength
|
||||||
|
}
|
||||||
|
InputMapAction::Jump => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input_frame.movement = input_frame.movement.normalize_or_zero();
|
||||||
|
input_frame.directional_attack = input_frame.directional_attack.normalize_or_zero();
|
||||||
|
local_inputs.insert(*handle, input_frame);
|
||||||
|
}
|
||||||
|
commands.insert_resource(LocalInputs::<GGRSCfg>(local_inputs));
|
||||||
|
}
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
use crate::input::player::InputFrame;
|
|
||||||
|
|
||||||
use super::player::{FighterInput, BaseInputMap, InputActionState};
|
|
||||||
|
|
||||||
pub fn postprocess_input(input_state: Query<&mut FighterInput>) {
|
|
||||||
for _player_input in input_state {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn preprocess_keyboard_input(key: Res<ButtonInput<KeyCode>>, input_map: Res<BaseInputMap>, mut input_state: Query<&mut FighterInput>) {
|
|
||||||
// Keyboard input is only for player 0
|
|
||||||
for mut keeb_input in &mut input_state.iter_mut().take(1) {
|
|
||||||
let mut input_frame = InputFrame::default();
|
|
||||||
for keyboard_elment in &input_map.keyboard {
|
|
||||||
let action_state = if key.just_pressed(keyboard_elment.key) {
|
|
||||||
InputActionState::JustPressed
|
|
||||||
} else if key.just_released(keyboard_elment.key) {
|
|
||||||
InputActionState::JustReleased
|
|
||||||
} else if key.pressed(keyboard_elment.key) {
|
|
||||||
InputActionState::Pressed
|
|
||||||
} else {
|
|
||||||
InputActionState::Released
|
|
||||||
};
|
|
||||||
|
|
||||||
let action_strength = match action_state {
|
|
||||||
InputActionState::JustPressed | InputActionState::Pressed => 1.0f32,
|
|
||||||
InputActionState::JustReleased | InputActionState::Released => 0.0f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
match keyboard_elment.action {
|
|
||||||
super::player::InputMapAction::MovementXDir(sign) => input_frame.movement.x += (sign as f32) * action_strength,
|
|
||||||
super::player::InputMapAction::MovementYDir(sign) => input_frame.movement.y += (sign as f32) * action_strength,
|
|
||||||
super::player::InputMapAction::Jump => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input_frame.movement = input_frame.movement.normalize_or_zero();
|
|
||||||
input_frame.directional_attack = input_frame.directional_attack.normalize_or_zero();
|
|
||||||
keeb_input.push_input_frame(input_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//! What a physical button means. Device-agnostic: a binding names an
|
||||||
|
//! [`InputMapAction`], and each device backend (see [`super::keyboard`])
|
||||||
|
//! decides how to read it.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputActionState {
|
||||||
|
JustPressed,
|
||||||
|
Pressed,
|
||||||
|
JustReleased,
|
||||||
|
Released,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputMapAction {
|
||||||
|
MovementXDir(i32),
|
||||||
|
MovementYDir(i32),
|
||||||
|
Jump,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct KeyboardInputMapElement {
|
||||||
|
pub key: KeyCode,
|
||||||
|
pub action: InputMapAction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct BaseInputMap {
|
||||||
|
pub keyboard: Vec<KeyboardInputMapElement>,
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//! Input, from physical device to the buffered per-fighter state that the
|
||||||
|
//! state machine reads.
|
||||||
|
//!
|
||||||
|
//! The pipeline is: [`keyboard`] samples devices into [`FighterInputFrame`]s
|
||||||
|
//! that GGRS ships over the wire, then [`buffer::postprocess_input`] turns the
|
||||||
|
//! rolled-back frames into [`FighterInput`].
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
pub mod buffer;
|
||||||
|
pub mod control;
|
||||||
|
pub mod frame;
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod map;
|
||||||
|
|
||||||
|
pub use buffer::{FighterCommands, FighterInput};
|
||||||
|
pub use frame::FighterInputFrame;
|
||||||
|
pub use map::{BaseInputMap, InputActionState, InputMapAction, KeyboardInputMapElement};
|
||||||
|
|
||||||
|
use crate::schedule::GameplaySet;
|
||||||
|
|
||||||
|
/// Owns the input map, the local-input collection that feeds GGRS, and the
|
||||||
|
/// per-fighter input state derived from the rolled-back inputs.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FighterInputPlugin;
|
||||||
|
|
||||||
|
impl Plugin for FighterInputPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(BaseInputMap {
|
||||||
|
keyboard: vec![
|
||||||
|
// right
|
||||||
|
KeyboardInputMapElement {
|
||||||
|
key: KeyCode::KeyD,
|
||||||
|
action: InputMapAction::MovementXDir(1),
|
||||||
|
},
|
||||||
|
// left
|
||||||
|
KeyboardInputMapElement {
|
||||||
|
key: KeyCode::KeyA,
|
||||||
|
action: InputMapAction::MovementXDir(-1),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.add_systems(ReadInputs, keyboard::preprocess_keyboard_input)
|
||||||
|
.add_systems(
|
||||||
|
GgrsSchedule,
|
||||||
|
buffer::postprocess_input.in_set(GameplaySet::Input),
|
||||||
|
)
|
||||||
|
.rollback_component_with_clone::<FighterInput>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct InputFrame {
|
|
||||||
pub movement: Vec2,
|
|
||||||
pub directional_attack: Vec2
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct FighterInput {
|
|
||||||
input_buffer: VecDeque<InputFrame>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FighterInput {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { input_buffer: vec!(InputFrame::default()).into() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FighterInput {
|
|
||||||
const INPUT_BUFFER_SIZE: usize = 5;
|
|
||||||
pub fn get_last_frame(&self) -> Option<&InputFrame> {
|
|
||||||
Some(self.input_buffer.back().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_input_frame(&mut self, frame: InputFrame) {
|
|
||||||
while self.input_buffer.len() >= Self::INPUT_BUFFER_SIZE {
|
|
||||||
self.input_buffer.pop_front();
|
|
||||||
}
|
|
||||||
self.input_buffer.push_back(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_buffer(&mut self) {
|
|
||||||
info!("CLEAR");
|
|
||||||
while self.input_buffer.len() > 1 {
|
|
||||||
self.input_buffer.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_smash_x_movement(&self, reset_deadzone: f32, threshold: f32, frame_threshold: u32) -> Option<f32> {
|
|
||||||
let iter = self.input_buffer.iter().zip(self.input_buffer.iter().skip(1));
|
|
||||||
|
|
||||||
let mut frames_in_deadzone = 0xFE;
|
|
||||||
for (prev, current) in iter {
|
|
||||||
let x_abs = current.movement.x.abs();
|
|
||||||
if x_abs < reset_deadzone {
|
|
||||||
frames_in_deadzone = 0xFE;
|
|
||||||
} else {
|
|
||||||
let prev_x_abs = prev.movement.x.abs();
|
|
||||||
if prev_x_abs < reset_deadzone || prev.movement.x.signum() != current.movement.x.signum() {
|
|
||||||
frames_in_deadzone = 0;
|
|
||||||
} else {
|
|
||||||
frames_in_deadzone += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if x_abs >= threshold && frames_in_deadzone <= frame_threshold {
|
|
||||||
return Some(x_abs.signum());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InputActionState {
|
|
||||||
JustPressed,
|
|
||||||
Pressed,
|
|
||||||
JustReleased,
|
|
||||||
Released
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum InputMapAction {
|
|
||||||
MovementXDir(i32),
|
|
||||||
MovementYDir(i32),
|
|
||||||
Jump
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct KeyboardInputMapElement {
|
|
||||||
pub key: KeyCode,
|
|
||||||
pub action: InputMapAction
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct BaseInputMap {
|
|
||||||
pub keyboard: Vec<KeyboardInputMapElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
#[relationship(relationship_target = ControlledBy)]
|
|
||||||
pub struct Controls(pub Entity);
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
#[relationship_target(relationship = Controls)]
|
|
||||||
pub struct ControlledBy(Entity);
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use crate::input::{
|
|
||||||
logic::{postprocess_input, preprocess_keyboard_input}, player::{BaseInputMap, InputMapAction, KeyboardInputMapElement},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct FighterInputPlugin;
|
|
||||||
|
|
||||||
impl Plugin for FighterInputPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.insert_resource(BaseInputMap {
|
|
||||||
keyboard: vec![
|
|
||||||
// right
|
|
||||||
KeyboardInputMapElement {
|
|
||||||
key: KeyCode::KeyD,
|
|
||||||
action: InputMapAction::MovementXDir(1),
|
|
||||||
},
|
|
||||||
// left
|
|
||||||
KeyboardInputMapElement {
|
|
||||||
key: KeyCode::KeyA,
|
|
||||||
action: InputMapAction::MovementXDir(-1),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
.add_systems(FixedPreUpdate, (preprocess_keyboard_input, postprocess_input).chain());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+32
-6
@@ -22,14 +22,29 @@
|
|||||||
//! appears in `Add Component`. No registration code is needed.
|
//! appears in `Add Component`. No registration code is needed.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use clap::Parser;
|
||||||
use jackdaw_runtime::prelude::*;
|
use jackdaw_runtime::prelude::*;
|
||||||
|
|
||||||
use crate::{fighter::{states::wait::WaitState, *}, game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon}, stage::debug_draw_scene};
|
use crate::{
|
||||||
|
fighter::{
|
||||||
|
FighterAttributes, FighterECB, FighterTranslation, FighterVelocity,
|
||||||
|
state::wait::WaitState,
|
||||||
|
},
|
||||||
|
game_settings::{FighterSettingsCommon, GameSettings, InputSettingsCommon},
|
||||||
|
input::FighterInput,
|
||||||
|
player::Player,
|
||||||
|
schedule::GameplaySchedulePlugin,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod fighter;
|
pub mod fighter;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod stage;
|
pub mod stage;
|
||||||
pub mod game_settings;
|
pub mod game_settings;
|
||||||
|
pub mod netcode;
|
||||||
|
pub mod player;
|
||||||
|
pub mod schedule;
|
||||||
|
mod math;
|
||||||
|
mod args;
|
||||||
|
|
||||||
/// Your game's Bevy plugin. The editor finds it by this name (override
|
/// Your game's Bevy plugin. The editor finds it by this name (override
|
||||||
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
|
/// with `plugin = "..."` in jackdaw.toml) and runs it on Play; the
|
||||||
@@ -41,6 +56,9 @@ pub fn setup(mut commands: Commands) {
|
|||||||
let _fighter = commands
|
let _fighter = commands
|
||||||
.spawn((
|
.spawn((
|
||||||
Name("Fighter".into()),
|
Name("Fighter".into()),
|
||||||
|
Player {
|
||||||
|
handle: 0,
|
||||||
|
},
|
||||||
FighterECB {
|
FighterECB {
|
||||||
vertical_half: 0.5,
|
vertical_half: 0.5,
|
||||||
horizontal_half: 0.25,
|
horizontal_half: 0.25,
|
||||||
@@ -59,19 +77,27 @@ pub fn setup(mut commands: Commands) {
|
|||||||
dash_initial_velocity: 0.19,
|
dash_initial_velocity: 0.19,
|
||||||
},
|
},
|
||||||
WaitState {},
|
WaitState {},
|
||||||
input::player::FighterInput::default()
|
FighterInput::default()
|
||||||
))
|
))
|
||||||
.id();
|
.id();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Plugin for GamePlugin {
|
impl Plugin for GamePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, stage::test_scene.spawn())
|
// Each module owns its own systems, rollback registrations and
|
||||||
|
// resources; this plugin only wires them together and supplies the
|
||||||
|
// game-level content and configuration.
|
||||||
|
app.add_plugins((
|
||||||
|
// Brings up GGRS (and with it `GgrsSchedule`), so it goes first.
|
||||||
|
netcode::FighterNetcodePlugin::default(),
|
||||||
|
GameplaySchedulePlugin,
|
||||||
|
input::FighterInputPlugin,
|
||||||
|
fighter::FighterPlugin,
|
||||||
|
stage::StagePlugin,
|
||||||
|
))
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, spin_cubes)
|
.add_systems(Update, spin_cubes)
|
||||||
.add_systems(Update, debug_draw_scene)
|
.insert_resource(args::Args::parse())
|
||||||
.add_plugins(input::plugin::FighterInputPlugin)
|
|
||||||
.add_plugins(fighter::FighterPlugin)
|
|
||||||
.insert_resource(GameSettings {
|
.insert_resource(GameSettings {
|
||||||
figher_common: FighterSettingsCommon {
|
figher_common: FighterSettingsCommon {
|
||||||
walk_speed_ease: 0.5,
|
walk_speed_ease: 0.5,
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
use bevy::math::{Vec2, primitives::Segment2d};
|
||||||
|
|
||||||
|
pub trait SegmentIntersection {
|
||||||
|
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentIntersection for Segment2d {
|
||||||
|
fn segment_intersection(&self, rhs: &Segment2d) -> Option<Vec2> {
|
||||||
|
let p0 = self.point1();
|
||||||
|
let p1 = self.point2();
|
||||||
|
let p2 = rhs.point1();
|
||||||
|
let p3 = rhs.point2();
|
||||||
|
|
||||||
|
let s1_x = p1.x - p0.x;
|
||||||
|
let s1_y = p1.y - p0.y;
|
||||||
|
let s2_x = p3.x - p2.x;
|
||||||
|
let s2_y = p3.y - p2.y;
|
||||||
|
|
||||||
|
let denom = -s2_x * s1_y + s1_x * s2_y;
|
||||||
|
if denom.abs() < f32::EPSILON {
|
||||||
|
return None; // Parallel or collinear
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / denom;
|
||||||
|
let t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / denom;
|
||||||
|
|
||||||
|
if s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0 {
|
||||||
|
Some(Vec2::new(p0.x + (t * s1_x), p0.y + (t * s1_y)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//! Network stats overlay.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_egui::prelude::*;
|
||||||
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
use crate::netcode::GGRSCfg;
|
||||||
|
|
||||||
|
pub fn network_debug(
|
||||||
|
mut contexts: EguiContexts,
|
||||||
|
session: Option<Res<Session<GGRSCfg>>>,
|
||||||
|
) -> Result {
|
||||||
|
if let Some(sess) = session {
|
||||||
|
match sess.as_ref() {
|
||||||
|
Session::P2P(s) => {
|
||||||
|
let num_players = s.num_players();
|
||||||
|
for i in 0..num_players {
|
||||||
|
egui::Window::new(format!("Player {}", i)).show(contexts.ctx_mut()?, |ui| {
|
||||||
|
if let Ok(stats) = s.network_stats(i) {
|
||||||
|
ui.label(format!("{:?}", stats));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//! Rollback networking: session setup and the engine-level rollback
|
||||||
|
//! registrations. Gameplay components are registered by the plugin that owns
|
||||||
|
//! them, not here.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_egui::EguiPrimaryContextPass;
|
||||||
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
pub mod debug;
|
||||||
|
pub mod session;
|
||||||
|
|
||||||
|
/// The GGRS configuration for this game — notably, what a frame of input is.
|
||||||
|
pub type GGRSCfg = GgrsConfig<crate::input::FighterInputFrame>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FighterNetcodePlugin;
|
||||||
|
|
||||||
|
impl Plugin for FighterNetcodePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let plugin = GgrsPlugin::<GGRSCfg>::default();
|
||||||
|
app.add_plugins(plugin)
|
||||||
|
.add_systems(Startup, session::netcode_setup)
|
||||||
|
// Engine-level components that every rolled-back entity may carry.
|
||||||
|
// Gameplay components are registered by the plugin that owns them.
|
||||||
|
.rollback_component_with_clone::<Transform>()
|
||||||
|
.rollback_component_with_clone::<Name>()
|
||||||
|
.add_systems(EguiPrimaryContextPass, debug::network_debug)
|
||||||
|
.add_systems(Update, session::print_events_system)
|
||||||
|
.insert_resource(RollbackFrameRate(60));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//! Bringing up the GGRS session and reporting what it does.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::{ggrs::UdpNonBlockingSocket, prelude::*};
|
||||||
|
|
||||||
|
use crate::args::Args;
|
||||||
|
use crate::netcode::GGRSCfg;
|
||||||
|
|
||||||
|
pub fn netcode_setup(mut commands: Commands, args: Res<Args>) -> Result {
|
||||||
|
let mut sess_build = SessionBuilder::<GGRSCfg>::new()
|
||||||
|
.with_num_players(args.players)?
|
||||||
|
.with_check_distance(args.check_distance)
|
||||||
|
.with_input_delay(args.input_delay); // (optional) set input delay for the local player
|
||||||
|
|
||||||
|
for i in 0..args.players {
|
||||||
|
sess_build = sess_build.add_player(PlayerType::Local, i)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.synctest {
|
||||||
|
println!("Start synctest!");
|
||||||
|
let sess = sess_build.start_synctest_session()?;
|
||||||
|
commands.insert_resource(Session::SyncTest(sess));
|
||||||
|
} else {
|
||||||
|
println!("Start p2p!");
|
||||||
|
let socket = UdpNonBlockingSocket::bind_to_port(6969)?;
|
||||||
|
let sess = sess_build.start_p2p_session(socket)?;
|
||||||
|
commands.insert_resource(Session::P2P(sess));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_events_system(mut session: ResMut<Session<GGRSCfg>>) {
|
||||||
|
match session.as_mut() {
|
||||||
|
Session::P2P(s) => {
|
||||||
|
for event in s.events() {
|
||||||
|
match event {
|
||||||
|
GgrsEvent::Disconnected { .. } | GgrsEvent::NetworkInterrupted { .. } => {
|
||||||
|
warn!("GGRS event: {event:?}")
|
||||||
|
}
|
||||||
|
GgrsEvent::DesyncDetected { .. } => error!("GGRS event: {event:?}"),
|
||||||
|
_ => info!("GGRS event: {event:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
// #[require(Rollback)] is the idiomatic way to ensure an entity is always included in
|
||||||
|
// the rollback system. Whenever a Player is spawned, Rollback (and its RollbackId) will
|
||||||
|
// be added automatically — no need to add it manually in the spawn bundle.
|
||||||
|
/// Ties an entity to one of the session's input streams. Lives at the crate
|
||||||
|
/// root rather than in `netcode` because "which player is this" is a game
|
||||||
|
/// concept; `netcode` only decides where the inputs come from.
|
||||||
|
#[derive(Default, Component)]
|
||||||
|
#[require(Rollback)]
|
||||||
|
pub struct Player {
|
||||||
|
pub handle: usize,
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//! The gameplay frame pipeline.
|
||||||
|
//!
|
||||||
|
//! Every rollback-simulated system belongs to exactly one of these sets, and
|
||||||
|
//! the sets run in the order declared here. Plugins add their systems with
|
||||||
|
//! `.in_set(GameplaySet::X)` instead of being threaded into one central
|
||||||
|
//! `.chain()`, so a module can schedule its own work without any other module
|
||||||
|
//! knowing about it.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ggrs::prelude::*;
|
||||||
|
|
||||||
|
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum GameplaySet {
|
||||||
|
/// Turn this frame's netcode inputs into per-fighter input state.
|
||||||
|
Input,
|
||||||
|
/// Decide whether the current fighter state hands off to another one.
|
||||||
|
Interrupt,
|
||||||
|
/// Run the fighter state that survived the interrupt pass.
|
||||||
|
StateUpdate,
|
||||||
|
/// Integrate velocities into positions.
|
||||||
|
Physics,
|
||||||
|
/// Resolve the integrated positions against the stage.
|
||||||
|
Collision,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declares the order of [`GameplaySet`] inside [`GgrsSchedule`]. Added by
|
||||||
|
/// `GamePlugin` before any plugin that populates the sets.
|
||||||
|
pub struct GameplaySchedulePlugin;
|
||||||
|
|
||||||
|
impl Plugin for GameplaySchedulePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.configure_sets(
|
||||||
|
GgrsSchedule,
|
||||||
|
(
|
||||||
|
GameplaySet::Input,
|
||||||
|
GameplaySet::Interrupt,
|
||||||
|
GameplaySet::StateUpdate,
|
||||||
|
GameplaySet::Physics,
|
||||||
|
GameplaySet::Collision,
|
||||||
|
)
|
||||||
|
.chain(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use bevy::{color::palettes::css::RED, prelude::*};
|
||||||
|
|
||||||
|
use crate::stage::StageLine;
|
||||||
|
|
||||||
|
pub fn debug_draw_scene(mut gizmos: Gizmos, planes: Query<&StageLine>) {
|
||||||
|
for plane in planes {
|
||||||
|
let from = plane.segment.point1();
|
||||||
|
let to = plane.segment.point2();
|
||||||
|
let from_3d = Vec3::new(from.x, from.y, 0.0);
|
||||||
|
let end_3d = Vec3::new(to.x, to.y, 0.0);
|
||||||
|
let normal_3d = Vec3::new(plane.normal.x, plane.normal.y, 0.0);
|
||||||
|
gizmos.line(from_3d, end_3d, RED);
|
||||||
|
let midpoint = (from_3d + end_3d) * 0.5;
|
||||||
|
gizmos.arrow(midpoint, midpoint + normal_3d, RED);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//! Stage collision geometry.
|
||||||
|
|
||||||
|
use bevy::{math::InvalidDirectionError, prelude::*};
|
||||||
|
|
||||||
|
use crate::math::SegmentIntersection;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Clone, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct StageLine {
|
||||||
|
pub segment: Segment2d,
|
||||||
|
pub normal: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StagePlaneIntersectResult {
|
||||||
|
pub position: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StageLine {
|
||||||
|
pub fn new(start: Vec2, end: Vec2) -> Result<Self, InvalidDirectionError> {
|
||||||
|
let segment = Segment2d::new(start, end);
|
||||||
|
let normal = segment.left_normal().as_vec2();
|
||||||
|
Ok(Self { segment, normal })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersect_ray(&self, segment: Segment2d) -> Option<StagePlaneIntersectResult> {
|
||||||
|
self.segment
|
||||||
|
.segment_intersection(&segment)
|
||||||
|
.map(|position| StagePlaneIntersectResult { position })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn project(&self, vec: Vec2) -> Vec2 {
|
||||||
|
vec - vec.project_onto_normalized(self.normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-80
@@ -1,87 +1,21 @@
|
|||||||
use bevy::{color::palettes::css::{GREEN, RED}, prelude::*};
|
//! The stage: its collision geometry, the scene that gets spawned, and how
|
||||||
|
//! both are drawn.
|
||||||
|
|
||||||
#[derive(Component, Reflect, Clone, Default)]
|
use bevy::prelude::*;
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct StagePlane {
|
|
||||||
pub plane: Plane2d,
|
|
||||||
pub position: Vec2,
|
|
||||||
half_size: f32,
|
|
||||||
basis_x: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StagePlaneIntersectResult {
|
pub mod debug;
|
||||||
pub position: Vec2
|
pub mod line;
|
||||||
}
|
pub mod scene;
|
||||||
|
|
||||||
impl StagePlane {
|
pub use line::{StageLine, StagePlaneIntersectResult};
|
||||||
pub fn new(normal: Dir2, half_size: f32, position: Vec2) -> Self {
|
|
||||||
let orthogonal = normal.perp();
|
|
||||||
let basis_x = orthogonal * half_size;
|
|
||||||
|
|
||||||
Self {
|
/// Owns the stage geometry: which scene gets spawned and how it is drawn.
|
||||||
plane: Plane2d::new(*normal),
|
#[derive(Default)]
|
||||||
position,
|
pub struct StagePlugin;
|
||||||
half_size,
|
|
||||||
basis_x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn intersect_ray(&self, ray: Ray2d) -> Option<StagePlaneIntersectResult> {
|
|
||||||
if let Some(result) = ray.plane_intersection_point(self.position, self.plane) {
|
|
||||||
return Some(StagePlaneIntersectResult {
|
|
||||||
position: result
|
|
||||||
});
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn project(&self, vec: Vec2) -> Vec2 {
|
impl Plugin for StagePlugin {
|
||||||
vec - vec.project_onto_normalized(self.plane.normal.as_vec2())
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_systems(Startup, scene::test_scene.spawn())
|
||||||
|
.add_systems(Update, debug::debug_draw_scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn debug_draw_scene(mut gizmos: Gizmos, planes: Query<&StagePlane>) {
|
|
||||||
for plane in planes {
|
|
||||||
let plane_pos_3d = Vec3::new(plane.position.x, plane.position.y, 0.0);
|
|
||||||
let plane_normal_3d = Vec3::new(plane.plane.normal.x, plane.plane.normal.y, 0.0);
|
|
||||||
let plane_basis_x_3d = Vec3::new(plane.basis_x.x, plane.basis_x.y, 0.0);
|
|
||||||
gizmos.rect(
|
|
||||||
Transform::from_translation(plane_pos_3d).looking_to(plane_normal_3d, Dir3::Z).to_isometry(),
|
|
||||||
Vec2::new(plane.half_size * 2.0, 1.0),
|
|
||||||
GREEN,
|
|
||||||
);
|
|
||||||
gizmos.line(plane_pos_3d, plane_pos_3d + plane_basis_x_3d, RED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// set up a simple 3D scene
|
|
||||||
pub fn test_scene() -> impl SceneList {
|
|
||||||
let up = Dir2::Y;
|
|
||||||
bsn_list! [
|
|
||||||
(
|
|
||||||
#Cube
|
|
||||||
Mesh3d(asset_value(Cuboid::new(5.0, 1.0, 1.0)))
|
|
||||||
MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
|
|
||||||
Transform::from_xyz(0.0, 0.5, 0.0)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
#StagePlane
|
|
||||||
template_value(StagePlane::new(up, 2.5, Vec2::new(0.0, 1.0)))
|
|
||||||
),
|
|
||||||
(
|
|
||||||
PointLight {
|
|
||||||
shadow_maps_enabled: true,
|
|
||||||
}
|
|
||||||
Transform::from_xyz(4.0, 8.0, 4.0)
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Camera3d
|
|
||||||
template_value(Transform::from_xyz(0.0, 1.5, 9.0))
|
|
||||||
/*FreeCamera {
|
|
||||||
sensitivity: 0.2,
|
|
||||||
friction: 25.0,
|
|
||||||
walk_speed: 3.0,
|
|
||||||
run_speed: 9.0,
|
|
||||||
}*/
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//! The scene content that gets spawned at startup.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::stage::StageLine;
|
||||||
|
|
||||||
|
/// set up a simple 3D scene
|
||||||
|
pub fn test_scene() -> impl SceneList {
|
||||||
|
bsn_list! [
|
||||||
|
(
|
||||||
|
#Cube
|
||||||
|
Mesh3d(asset_value(Cuboid::new(5.0, 1.0, 1.0)))
|
||||||
|
MeshMaterial3d::<StandardMaterial>(asset_value(Color::srgb_u8(124, 144, 255)))
|
||||||
|
Transform::from_xyz(0.0, 0.5, 0.0)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
#StagePlane
|
||||||
|
template_value(StageLine::new(Vec2::new(-2.5, 1.0), Vec2::new(2.5, 1.0)).unwrap())
|
||||||
|
),
|
||||||
|
(
|
||||||
|
PointLight {
|
||||||
|
shadow_maps_enabled: true,
|
||||||
|
}
|
||||||
|
Transform::from_xyz(4.0, 8.0, 4.0)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Camera3d
|
||||||
|
template_value(Transform::from_xyz(0.0, 1.5, 9.0))
|
||||||
|
/*FreeCamera {
|
||||||
|
sensitivity: 0.2,
|
||||||
|
friction: 25.0,
|
||||||
|
walk_speed: 3.0,
|
||||||
|
run_speed: 9.0,
|
||||||
|
}*/
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user