Expand description
§Lightyear
Lightyear is a networking library for Bevy. It provides a set of plugins that can be used to create multiplayer games.
It has been tested mostly in the server-client topology, but the API should be flexible enough to support other topologies such as peer-to-peer.
You can find more information in the book or check out the examples!
§Getting started
§Adding the Plugins
Similarly to Bevy, lightyear is composed of several sub crates that each provide a set of features.
A wrapper crate lightyear
is provided for convenience, which contain two main PluginGroups: ClientPlugins
and ServerPlugins
that you can add
to your app.
use bevy_app::App;
use core::time::Duration;
use lightyear::prelude::*;
pub const FIXED_TIMESTEP_HZ: f64 = 60.0;
fn main() {
let mut app = App::new();
app.add_plugins(client::ClientPlugins {
tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ),
});
app.add_plugins(server::ServerPlugins {
tick_duration: Duration::from_secs_f64(1.0 / FIXED_TIMESTEP_HZ),
});
}
It is also possible to use the subcrates directly:
IO (How to send bytes over the network)
lightyear_link
: provides a transport-agnosticLink
component which is responsible for sending and receiving bytes over the network.lightyear_crossbeam
: IO layer that uses crossbeam channels. Useful for testing or for local networking (by having a server process and a client process on the same machine).lightyear_udp
/lightyear_webtransport
: IO layers for the UDP protocol and WebTransport protocol respectively.
Connection
lightyear_connection
: this layer wraps the IO layer by providing a long-runningPeerId
identifier component that is used to identify a peer in the network. Also provides theClient
andServer
components for client-server topologies.lightyear_netcode
: a connection layer that uses the netcode.io standard for creating secure connections over an unreliable IO such as UDP.lightyear_steam
: a connection layer that uses the Steam networking API to both send the bytes and to provide a long-running identifier. This layer operates at both the IO and the connection level.
Currently it is not possible to use an IO layer without a connection layer.
Messages
lightyear_transport
: provides aTransport
component that is provides several channels with different reliability/ordering guarantees when sending raw bytes. This crate also organizes the raw bytes into messages that are assembled into packets.lightyear_messages
: provides aMessageManager
component responsible for handling the serialization ofMessages
(serializable structs) into raw bytes that can be sent over the network.
Replication
lightyear_replication
: provides utilities to replicate the state of the Bevy World between two peers.lightyear_sync
: helps synchronize the timelines between two peers.lightyear_prediction
: provides client-side prediction and rollback to help hide latencylightyear_interpolation
: provides interpolation for replicated entities to smooth out the network updates received from the remote peer.lightyear_frame_interpolation
: most of the game logic should run in the FixedMain schedule, but the rendering is done in the PostUpdate schedule. To avoid visual artifacts, we need some interpolation to interpolate the rendering between the FixedMain states.
Inputs
lightyear_inputs
: backend-agnostic general input queue plugin to network client inputslightyear_inputs_native
: provides support to use any user-defined struct as an input typelightyear_inputs_leafwing
: provides support to network leafwing_input_manager inputslightyear_inputs_bei
: provides support to network bevy_enhanced_input inputs
Extra
lightyear_avian2d
/lightyear_avian3d
: provides a plugin to help handle networking Avian components. This sets the correct system ordering, etc.
§Implement the Protocol
The Protocol
is a shared configuration between the local and remote peers that defines which types will be sent over the network.
You will have to define your protocol in a shared module that is accessible to both the client and the server.
There are several steps:
NOTE: the protocol must currently be added AFTER the Client/Server Plugins, but BEFORE any Client
or Server
entity is spawned.
§Spawn your Link entity
In lightyear, your Client or Server will simply be an entity with the right sets of components.
The Link
component can be added on an entity to make it able to send and receive data over the network.
Usually you will add more components on that entity to customize its behavior:
- define its role using the
Client
orServer
components. Most of the lightyear plugins currently expect theLink
entity to have one of these components. - make it able to receive/send replication data using the
ReplicationSender
orReplicationReceiver
components. - add a
MessageManager
component to handle the serialization and deserialization of messages. - etc.
The Server
entity works a bit differently. It starts a server that listens for incoming connections. When a new client connects, a new entity is spawned with the LinkOf
component.
You can add a trigger to listen to this event and add the extra components to customize the behaviour of this connection.
fn handle_new_client(trigger: Trigger<OnAdd, LinkOf>, mut commands: Commands) {
commands.entity(trigger.target()).insert((
ReplicationSender::new(Duration::from_millis(100), SendUpdatesMode::SinceLastAck, false),
Name::from("Client"),
));
}
§Using lightyear
§Linking
There is a set that reflects if the Link
is established. The link represents an IO connection to send bytes to a remote peer.
You can trigger LinkStart
to start the link, and Unlink
to stop it.
The Unlinked
, Linking
, Linked
components represent the current state of the link.
§Connections
A connection is a wrapper around a Link
that provides a long-running identifier for the peer.
You can use the PeerId
component to identify the remote peer that the link is connected to, and LocalId
to identify the local peer.
The lifecycle of a connection is controlled by several sets of components.
You can trigger Connect
to start the connection, and Disconnect
to stop it.
The Disconnected
, Connecting
, Connected
components represent the current state of the connection.
On the server, Start
and Stop
components are used to control the server’s listening state.
The Stopped
, Starting
, Started
components represent the current state of the connection.
While a client is disconnected, you can update its configuration (ReplicationSender
, MessageManager
, etc.), it will be applied on the next connection attempt.
§Sending messages
The MessageSender
component is used to send messages that you have defined in your protocol.
use lightyear::prelude::*;
use lightyear::prelude::server::*;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct MyMessage;
struct MyChannel;
fn send_message(mut sender: Single<&mut MessageSender<MyMessage>>) {
let _ = sender.send::<MyChannel>(MyMessage);
}
§Receiving messages
The MessageReceiver
component is used to receive messages that you have defined in your protocol.
fn send_message(mut receivers: Query<&mut MessageReceiver<MyMessage>>) {
for mut receiver in receivers.iter_mut() {
let _ = receiver.receive().for_each(|message| {});
}
}
§Starting replication
To replicate an entity from the local world to the remote world, you can just add the Replicate
component to the entity.
The marker component Replicating
indicates that the entity is getting replicated to a remote peer.
You can remove the Replicating
component to pause the replication. This will not despawn the entity on the remote world; it will simply stop sending replication updates.
§Reacting to replication events
On the receiver side, entities that are replicated from a remote peer will have the Replicated
marker component.
You can use to react to components being inserted via replication.
fn component_inserted(query: Query<Entity, (With<Replicated>, Added<MyComponent>)>) {
for entity in query.iter() {
println!("MyComponent was inserted via replication on {entity:?}");
}
}
§Feature Flags
-
client
(enabled by default) — enable client plugins -
server
(enabled by default) — enable server plugins and server-only features -
replication
(enabled by default) — Enables replicating entities between two peers -
prediction
(enabled by default) — Enables client-side prediction handling to mask latency for local player actions -
frame_interpolation
— Enables re-exports of lightyear_frame_interpolation -
interpolation
(enabled by default) — Enables interpolation handling, to smooth entity updates received from the remote peer -
metrics
— enable metrics collection (viametrics
crate) -
netcode
— Enables netcode to provide persistent IDs to clientsNote: currently, this must always be enabled when not using Steam
-
webtransport
— Enable WebTransport support as an IO layer (defers to aeronet_webtransport) -
webtransport_dangerous_configuration
— Enable unsafe configurations for WebTransport (e.g. allowing unencrypted connections) as a convenience for testing -
input_native
— Add support for handling inputs where you can define your own input structs -
leafwing
— Add support for handling inputs using the leafwing-input-manager crate -
avian2d
— Adds support for AvianNOTE: because lightyear doesn’t enable any features of avian by default, your crate must enable essential features (like f32 or f64). Use avian2d
-
avian3d
— Use avian3d -
udp
— Enables UDP as an IO layer -
crossbeam
— Enables crossbeam channels as an IO layerThis is useful when running a client and server in two processes of the same machine so that a client can act as the host
-
steam
— Enables Steam as a connection layer
Modules§
- connection
- core
- crossbeam
crossbeam
- frame_
interpolation frame_interpolation
- input
input_native
orleafwing
orinput_bei
- interpolation
interpolation
- link
- netcode
netcode
- prediction
prediction
- prelude
- steam
steam
- utils
- webtransport
webtransport