From d35eded128cdcdf99f9f91c07e3576727ee1cb58 Mon Sep 17 00:00:00 2001 From: Charles Bournhonesque Date: Wed, 13 Mar 2024 11:00:21 -0400 Subject: [PATCH] 0.12.0 release --- book/src/tutorial/basic_systems.md | 55 +++++++++++-------- lightyear/Cargo.toml | 87 +++++++++++++++--------------- macros/Cargo.toml | 2 +- 3 files changed, 76 insertions(+), 68 deletions(-) diff --git a/book/src/tutorial/basic_systems.md b/book/src/tutorial/basic_systems.md index de6234ead..a956150a6 100644 --- a/book/src/tutorial/basic_systems.md +++ b/book/src/tutorial/basic_systems.md @@ -1,9 +1,9 @@ # Adding basic functionality - ## Initialization On the client, we can run this system on `Startup`: + ```rust,noplayground pub(crate) fn init( mut commands: Commands, @@ -20,14 +20,13 @@ pub(crate) fn init( )); client.connect(); } -# fn main() { - app.add_systems(Startup, init); -# } +app.add_systems(Startup, init); ``` This spawns a camera, but also call `client.connect()` which will start the connection process with the server. On the server we can just spawn a camera: + ```rust,noplayground pub(crate) fn init(mut commands: Commands) { commands.spawn(Camera2dBundle::default()); @@ -40,25 +39,27 @@ pub(crate) fn init(mut commands: Commands) { }, )); } -# fn main() { - app.add_systems(Startup, init); -# } + +app.add_systems(Startup, init); ``` ## Defining replicated entities We want to have the following flow: + - client connects to server - server spawns a player entity for the client - server then keeps replicating the state of that entity to the client - client can send inputs to the server to control the player entity (any updates will be replicated back to the client) We will start by defining what a player entity is. -We create a bundle that contains all the components that we want to replicate. These components must be part of the `ComponentProtocol` enum that +We create a bundle that contains all the components that we want to replicate. These components must be part of +the `ComponentProtocol` enum that we defined earlier in order to be replicated. (In general it is useful to create separate bundles for components that we want to replicate, and components -that are only used on the client or server. For example, on the client a lot of components that are only used for rendering (particles, etc.) +that are only used on the client or server. For example, on the client a lot of components that are only used for +rendering (particles, etc.) don't need to be included in the replicated bundle.) ```rust,noplayground @@ -87,23 +88,27 @@ impl PlayerBundle { We added an extra special component called `Replicate`. Only entities that have this component will be replicated. This component also lets us specify some extra parameters for the replication: -- `actions_channel`: which channel to use to replicate entity actions. I define `EntityActions` as entity events that need - exclusive `World` access (entity spawn/despawn, component insertion/removal). By default, an OrderedReliable channel is used. -- `updates_channel`: which channel to use to replicate entity updates. I define `EntityUpdates` as entity events that don't need + +- `actions_channel`: which channel to use to replicate entity actions. I define `EntityActions` as entity events that + need + exclusive `World` access (entity spawn/despawn, component insertion/removal). By default, an OrderedReliable channel + is used. +- `updates_channel`: which channel to use to replicate entity updates. I define `EntityUpdates` as entity events that + don't need exclusive `World` access (component updates). By default, an SequencedUnreliable channel is used. -- `replication_target`: who will the entity be replicated to? By default, the entity will be replicated to all clients, but you can use this +- `replication_target`: who will the entity be replicated to? By default, the entity will be replicated to all clients, + but you can use this to have a more fine-grained control - `prediction_target`/`interpolation_target`: we will mention this later. -If you remove the `Replicate` component from an entity, any updates to that entity won't be replicated anymore. (However the client entity won't get despawned) +If you remove the `Replicate` component from an entity, any updates to that entity won't be replicated anymore. (However +the client entity won't get despawned) Currently there is no way to specify for a given entity whether some components should be replicated or not. This might be added in the future. - ## Spawning entities on server - Most networking events are available on both the client and server as `bevy` `Events`, and can be read every frame using the `EventReader` `SystemParam`. This is what we will use to spawn a player on the server when a new client gets connected. @@ -151,13 +156,14 @@ The `context()` method returns the `ClientId` of the client that connected/disco We also create a map to keep track of which client is associated with which entity. - ## Add inputs to client Then we want to be able to handle inputs from the user. We add a system that reads keypresses/mouse movements and converts them into `Inputs` that we can give to the `Client`. -Inputs need to be handled with `client.add_input()`, which does some extra bookkeeping to make sure that an input on tick `n` -for the client will be handled on the server on the same tick `n`. Inputs are also stored in a buffer for client-prediction. +Inputs need to be handled with `client.add_input()`, which does some extra bookkeeping to make sure that an input on +tick `n` +for the client will be handled on the server on the same tick `n`. Inputs are also stored in a buffer for +client-prediction. ```rust,noplayground pub(crate) fn buffer_input(mut client: ResMut>, keypress: Res>) { @@ -199,15 +205,16 @@ app.add_systems( ``` `add_input` needs to be called in the `InputSystemSet::BufferInputs` `SystemSet`. -Some systems need to be run in specific `SystemSet`s because of the ordering of some operations is important for the crate +Some systems need to be run in specific `SystemSet`s because of the ordering of some operations is important for the +crate to work properly. See [SystemSet Ordering](../concepts/system_sets/title.md) for more information. - ## Handle inputs on server Once we correctly `add_inputs` on the client, we can start reading them on the server to control the player entity. We define a function that specifies how a given input updates a given player entity: + ```rust,noplayground pub(crate) fn shared_movement_behaviour(mut position: Mut, input: &Inputs) { const MOVE_SPEED: f32 = 10.0; @@ -232,7 +239,8 @@ pub(crate) fn shared_movement_behaviour(mut position: Mut, input ``` Then we can create a system that reads the inputs and applies them to the player entity. -Similarly, we have an event `InputEvent` that will give us on every tick the input that was sent by the client. +Similarly, we have an event `InputEvent` that will give us on every tick the input that was sent by the +client. We can use the `context()` method to get the `ClientId` of the client that sent the input, and `input()` to get the actual input. @@ -262,6 +270,7 @@ Any fixed-update simulation system (physics, etc.) must run in the `FixedMain` ` ## Displaying entities Finally we can add a system on both client and server to draw a box to show the player entity. + ```rust,noplayground pub(crate) fn draw_boxes( mut gizmos: Gizmos, @@ -279,9 +288,9 @@ pub(crate) fn draw_boxes( ``` Now, running the server and client in parallel should give you: + - server spawns a cube when client connects - client can send inputs to the server to control the cube - the movements of the cube in the server world are replicated to the client ! - In the next section, we will see a couple more systems. \ No newline at end of file diff --git a/lightyear/Cargo.toml b/lightyear/Cargo.toml index eaf31fb7a..2c9433a72 100644 --- a/lightyear/Cargo.toml +++ b/lightyear/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightyear" -version = "0.11.0" +version = "0.12.0" authors = ["Charles Bournhonesque "] edition = "2021" rust-version = "1.65" @@ -14,30 +14,30 @@ exclude = ["/tests"] [features] metrics = [ - "dep:metrics", - "metrics-util", - "metrics-tracing-context", - "metrics-exporter-prometheus", - "dep:tokio", + "dep:metrics", + "metrics-util", + "metrics-tracing-context", + "metrics-exporter-prometheus", + "dep:tokio", ] mock_time = ["dep:mock_instant"] render = ["bevy/bevy_render"] webtransport = [ - "dep:wtransport", - "dep:xwt-core", - "dep:xwt-web-sys", - "dep:web-sys", - "dep:tokio", - "dep:ring", + "dep:wtransport", + "dep:xwt-core", + "dep:xwt-web-sys", + "dep:web-sys", + "dep:tokio", + "dep:ring", ] leafwing = ["dep:leafwing-input-manager", "lightyear_macros/leafwing"] xpbd_2d = ["dep:bevy_xpbd_2d"] websocket = [ - "dep:tokio", - "dep:tokio-tungstenite", - "dep:futures-util", - "dep:web-sys", - "dep:wasm-bindgen", + "dep:tokio", + "dep:tokio-tungstenite", + "dep:futures-util", + "dep:web-sys", + "dep:wasm-bindgen", ] [dependencies] @@ -69,7 +69,7 @@ bevy_xpbd_2d = { version = "0.4", optional = true } # serialization bitcode = { version = "0.5.1", package = "bitcode_lightyear_patch", path = "../vendor/bitcode", features = [ - "serde", + "serde", ] } bytes = { version = "1.5", features = ["serde"] } self_cell = "1.0" @@ -80,14 +80,14 @@ chacha20poly1305 = { version = "0.10", features = ["std"] } byteorder = "1.5.0" # derive -lightyear_macros = { version = "0.11.0", path = "../macros" } +lightyear_macros = { version = "0.12.0", path = "../macros" } # tracing tracing = "0.1.40" tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.17", features = [ - "registry", - "env-filter", + "registry", + "env-filter", ] } # server @@ -98,12 +98,12 @@ metrics = { version = "0.22", optional = true } metrics-util = { version = "0.15", optional = true } metrics-tracing-context = { version = "0.15", optional = true } metrics-exporter-prometheus = { version = "0.13.0", optional = true, default-features = false, features = [ - "http-listener", + "http-listener", ] } # bevy bevy = { version = "0.13", default-features = false, features = [ - "multi-threaded", + "multi-threaded", ] } # WebSocket @@ -112,45 +112,44 @@ futures-util = { version = "0.3.30", optional = true } # transport # we don't need any tokio features, we use only use the tokio channels tokio = { version = "1.36", features = [ - "sync", + "sync", ], default-features = false, optional = true } async-compat = "0.2.3" [target."cfg(not(target_family = \"wasm\"))".dependencies] # webtransport wtransport = { version = "0.1.10", optional = true, features = [ - "self-signed", - "dangerous-configuration", + "self-signed", + "dangerous-configuration", ] } # websocket tokio-tungstenite = { version = "0.21.0", optional = true, features = [ - "connect", - "handshake", + "connect", + "handshake", ] } [target."cfg(target_family = \"wasm\")".dependencies] console_error_panic_hook = { version = "0.1.7" } ring = { version = "0.17.7", optional = true } web-sys = { version = "0.3", optional = true, features = [ - "WebTransport", - "WebTransportHash", - "WebTransportOptions", - "WebTransportBidirectionalStream", - "WebTransportSendStream", - "WebTransportReceiveStream", - "ReadableStreamDefaultReader", - "WritableStreamDefaultWriter", - "WebTransportDatagramDuplexStream", - - "WebSocket", - "CloseEvent", - "ErrorEvent", - "MessageEvent", - "BinaryType", + "WebTransport", + "WebTransportHash", + "WebTransportOptions", + "WebTransportBidirectionalStream", + "WebTransportSendStream", + "WebTransportReceiveStream", + "ReadableStreamDefaultReader", + "WritableStreamDefaultWriter", + "WebTransportDatagramDuplexStream", + "WebSocket", + "CloseEvent", + "ErrorEvent", + "MessageEvent", + "BinaryType", ] } futures-lite = { version = "2.1.0", optional = true } getrandom = { version = "0.2.11", features = [ - "js", + "js", ] } # feature 'js' is required for wasm xwt-core = { version = "0.2", optional = true } xwt-web-sys = { version = "0.6", optional = true } diff --git a/macros/Cargo.toml b/macros/Cargo.toml index f03ff8df5..69e868b8c 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lightyear_macros" -version = "0.11.0" +version = "0.12.0" authors = ["Charles Bournhonesque "] edition = "2021" rust-version = "1.65"