adding multiplayer

This commit is contained in:
2025-06-30 21:28:11 +02:00
parent f4cd2e47a4
commit a51a7d3c07
44 changed files with 841 additions and 475 deletions

View File

@@ -1,150 +1,168 @@
using System;
using System.Linq;
using System.Threading;
using Cysharp.Threading.Tasks;
using R3;
using System.Collections.Generic;
using RebootKit.Engine.Main;
using RebootKit.Engine.Services.Console;
using RebootKit.Engine.Services.Crosshair;
using RebootKit.Engine.Services.GameMode;
using RebootKit.Engine.Services.Simulation;
using RebootReality.jelycho.Player;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Assertions;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootReality.jelycho.Main {
public static class JelychoConsoleCommands {
[RCCMD("start", "Starts game mode with given name. Usage: start <game_mode_name> <world_name>")]
public static void StartGameMode(string[] args) {
if (args.Length != 3) {
RR.Console.WriteToOutput($"Usage: {args[0]} <game_mode_name> <world_name>");
class PlayerState : INetworkSerializable {
public ulong clientID;
public PlayerController Controller;
public PlayerActor Actor;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { }
}
public class JelychoGame : Game {
static readonly Logger s_Logger = new Logger(nameof(JelychoGame));
[SerializeField] PlayerController m_PlayerControllerPrefab;
[SerializeField] PlayerActor m_PlayerActorPrefab;
[SerializeField] string m_MainWorldID = "dev";
List<PlayerState> m_PlayerStates = new List<PlayerState>();
void Awake() { }
public override void OnDestroy() {
base.OnDestroy();
}
public override void OnNetworkSpawn() {
base.OnNetworkSpawn();
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientStopped;
}
public override void OnNetworkDespawn() {
base.OnNetworkDespawn();
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientStopped;
}
void OnClientConnected(ulong clientID) {
s_Logger.Info($"Client {clientID} connected");
if (IsServer) {
s_Logger.Info($"Creating player state for client {clientID}");
PlayerController controller = Instantiate(m_PlayerControllerPrefab);
controller.name = $"PlayerController_{clientID}";
controller.NetworkObject.SpawnAsPlayerObject(clientID);
m_PlayerStates.Add(new PlayerState {
clientID = clientID,
Controller = controller,
});
}
}
void OnClientStopped(ulong clientID) {
s_Logger.Info($"Client {clientID} disconnected");
if (IsServer) {
for (int i = m_PlayerStates.Count - 1; i >= 0; i--) {
if (m_PlayerStates[i].clientID == clientID) {
s_Logger.Info($"Removing player state for client {clientID}");
m_PlayerStates.RemoveAtSwapBack(i);
break;
}
}
}
}
public override void OnWorldLoaded() {
base.OnWorldLoaded();
OnPlayerReadyRpc(NetworkManager.Singleton.LocalClientId);
}
[Rpc(SendTo.Server)]
void OnPlayerReadyRpc(ulong clientID) {
if (!IsServer) {
return;
}
string worldName = args[2];
WorldConfigAsset worldConfig = RR.WorldConfigsAssets.FirstOrDefault(t => t.Config.name.Equals(worldName));
if (worldConfig is null) {
RR.Console.WriteToOutput($"World '{worldName}' not found");
return;
}
PlayerActor actor = Instantiate(m_PlayerActorPrefab);
actor.name = $"PlayerActor_{clientID}";
actor.NetworkObject.SpawnAsPlayerObject(clientID);
foreach (GameModeAsset gameModeAsset in RR.GameModesAssets) {
if (gameModeAsset.name == args[1]) {
RR.Console.WriteToOutput($"Starting game mode '{gameModeAsset.name}'");
RR.StartGameMode(gameModeAsset, worldConfig.Config);
return;
PlayerState playerState = GetPlayerState(clientID);
Assert.IsNotNull(playerState);
playerState.Actor = actor;
playerState.Controller.SetActorClientRpc(actor.NetworkObjectId);
}
PlayerState GetPlayerState(ulong clientID) {
foreach (PlayerState state in m_PlayerStates) {
if (state.clientID == clientID) {
return state;
}
}
RR.Console.WriteToOutput($"Game mode '{args[1]}' not found");
s_Logger.Error($"Player state for client {clientID} not found");
return null;
}
[RCCMD("gamemodes", "Lists all game modes")]
public static void GameModes(string[] args) {
if (args.Length != 1) {
RR.Console.WriteToOutput($"Usage: {args[0]}");
return;
}
public override void OnWorldUnload() {
base.OnWorldUnload();
RR.Console.WriteToOutput("Game modes:");
foreach (GameModeAsset gameModeAsset in RR.GameModesAssets) {
RR.Console.WriteToOutput($" {gameModeAsset.name}");
}
if (IsServer) { }
}
}
public static class JelychoConsoleCommands {
[RCCMD("worlds", "Lists all worlds")]
public static void Worlds(string[] args) {
if (args.Length != 1) {
RR.Console.WriteToOutput($"Usage: {args[0]}");
RR.WriteToConsole($"Usage: {args[0]}");
return;
}
RR.Console.WriteToOutput("Worlds:");
RR.WriteToConsole("Worlds:");
foreach (WorldConfigAsset worldConfigAsset in RR.WorldConfigsAssets) {
RR.Console.WriteToOutput($" {worldConfigAsset.Config.name}");
RR.WriteToConsole($" {worldConfigAsset.Config.name}");
}
}
[RCCMD("connect", "Connects to a server with given Steam ID. Usage: connect <steam_id>")]
public static void ConnectToServer(string[] args) {
RR.Console.WriteToOutput("Connecting to server...");
if (args.Length != 1) {
RR.Console.WriteToOutput($"Usage: {args[0]} <steam_id>");
if (args.Length < 1 || args.Length > 2) {
RR.WriteToConsole($"Usage: {args[0]} <steam_id>");
return;
}
RR.ConnectToLobby();
RR.WriteToConsole("Connecting to server...");
// if (args.Length != 2) {
// RR.Console.WriteToOutput($"Usage: {args[0]} <steam_id>");
// return;
// }
RR.Connect();
}
[RCCMD("disconnect", "Disconnects from the current server")]
public static void DisconnectFromServer(string[] args) {
if (args.Length != 1) {
RR.Console.WriteToOutput($"Usage: {args[0]}");
RR.WriteToConsole($"Usage: {args[0]}");
return;
}
RR.Disconnect();
}
}
public class JelychoGame : IGame {
static readonly Logger s_Logger = new Logger(nameof(JelychoGame));
[Serializable]
public class Config {
public string mainMenuWorld = "main_menu";
public string mainGameplayWorld = "dev";
public string standardGameMode = "gm_standard";
}
readonly Config m_Config;
DisposableBag m_DisposableBag;
public JelychoGame(Config config) {
m_Config = config;
m_DisposableBag = new DisposableBag();
}
public async UniTask InitAsync(CancellationToken cancellationToken) {
Screen.SetResolution(1600, 900, FullScreenMode.Windowed);
RR.CreateService<CrosshairService>();
await UniTask.Yield();
}
public void Run() {
RR.GameModes.ActiveGameMode.Subscribe(gameMode => {
if (gameMode == null) {
RR.Console.WriteToOutput("Game mode stopped, loading main menu world");
WorldConfigAsset mainMenuWorldConfigAsset = RR.GetWorldConfigAsset(m_Config.mainMenuWorld);
RR.World.LoadAsync(mainMenuWorldConfigAsset.Config, CancellationToken.None).Forget();
}
})
.AddTo(ref m_DisposableBag);
}
public void Dispose() {
m_DisposableBag.Dispose();
}
public void NewGame() {
NewGameAsync().Forget();
}
async UniTask NewGameAsync() {
RR.HostServer();
await UniTask.WaitForSeconds(0.5f);
s_Logger.Info("Starting new game");
s_Logger.Info($"Is connected: {RR.IsClient()}");
s_Logger.Info($"Is host: {RR.IsHost()}");
GameModeAsset gameModeAsset = RR.GetGameMode(m_Config.standardGameMode);
WorldConfigAsset worldConfigAsset = RR.GetWorldConfigAsset(m_Config.mainGameplayWorld);
RR.StartGameMode(gameModeAsset, worldConfigAsset.Config);
[RCCMD("version", "Displays the current game version")]
public static void GameVersion(string[] args) {
RR.WriteToConsole($"Game version: {Application.version}");
}
}
}

View File

@@ -1,14 +0,0 @@
using RebootKit.Engine.Main;
using TriInspector;
using UnityEngine;
namespace RebootReality.jelycho.Main {
[CreateAssetMenu(menuName = GameConsts.k_AddComponentMenu + "Jelycho Game")]
public class JelychoGameAsset : GameAsset {
[SerializeField, InlineProperty, HideLabel] JelychoGame.Config m_Config;
public override IGame CreateGame() {
return new JelychoGame(m_Config);
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 6db6aab28280422f92077cf32c409b0f
timeCreated: 1746666083