adding multiplayer

This commit is contained in:
2025-06-30 21:27:55 +02:00
parent 5a813f212c
commit e5616474f1
35 changed files with 712 additions and 525 deletions

View File

@@ -46,6 +46,7 @@ namespace RebootKit.Engine.Services.Console {
string logFilePath = Application.persistentDataPath + "/rr_logs.txt"; string logFilePath = Application.persistentDataPath + "/rr_logs.txt";
#endif #endif
try {
m_LogFileStream = new FileStream(logFilePath, FileMode.Append, FileAccess.Write); m_LogFileStream = new FileStream(logFilePath, FileMode.Append, FileAccess.Write);
m_LogFileWriter = new StreamWriter(m_LogFileStream); m_LogFileWriter = new StreamWriter(m_LogFileStream);
@@ -57,6 +58,10 @@ namespace RebootKit.Engine.Services.Console {
m_LogFileWriter.WriteLine("============================"); m_LogFileWriter.WriteLine("============================");
m_LogFileWriter.Flush(); m_LogFileWriter.Flush();
} catch (Exception e) {
s_logger.Error($"Failed to open log file at {logFilePath}: {e.Message}");
}
s_logger.Info("Waking up"); s_logger.Info("Waking up");
Load(); Load();

View File

@@ -1,14 +1,15 @@
using System.Text; using System.Text;
using RebootKit.Engine.Foundation; using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main; using RebootKit.Engine.Main;
using RebootKit.Engine.Services.ConsoleUI;
using RebootKit.Engine.Services.Input; using RebootKit.Engine.Services.Input;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
using Logger = RebootKit.Engine.Foundation.Logger; using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Services.ConsoleUI { namespace RebootKit.Engine.ConsoleUI {
public class ConsoleUIService : ServiceMonoBehaviour { public class ConsoleUIService : ServiceMonoBehaviour {
static readonly Logger s_logger = new Logger(nameof(ConsoleUIService)); static readonly Logger s_Logger = new Logger(nameof(ConsoleUIService));
[SerializeField] ConsoleVC m_ConsoleVC; [SerializeField] ConsoleVC m_ConsoleVC;
[SerializeField] ScriptableInputAction m_ToggleAction; [SerializeField] ScriptableInputAction m_ToggleAction;
@@ -22,7 +23,7 @@ namespace RebootKit.Engine.Services.ConsoleUI {
} }
void OnEnable() { void OnEnable() {
s_logger.Info("OnEnable console"); s_Logger.Info("OnEnable console");
m_ToggleAction.Action.Enable(); m_ToggleAction.Action.Enable();
m_ToggleAction.Action.performed += OnToggleAction; m_ToggleAction.Action.performed += OnToggleAction;

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3d31f3fe40394defabe1b77f6ddf8861
timeCreated: 1746665551

View File

@@ -1,13 +0,0 @@
using RebootKit.Engine.Foundation;
namespace RebootKit.Engine.Services.Crosshair {
public class CrosshairService : IService {
public CrosshairService() {
}
public void Dispose() {
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f23a52c23aae46e8bd6b77222b10e5e2
timeCreated: 1746665562

View File

@@ -1,5 +1,4 @@
using System; using RebootKit.Engine.Main;
using RebootKit.Engine.Main;
using RebootKit.Engine.UI; using RebootKit.Engine.UI;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@@ -11,7 +10,6 @@ namespace RebootKit.Engine.Services.Development {
VisualElement m_RootElement; VisualElement m_RootElement;
Label m_FPSLabel; Label m_FPSLabel;
Label m_GameModeLabel;
void Update() { void Update() {
if (m_RootElement == null) { if (m_RootElement == null) {
@@ -20,9 +18,6 @@ namespace RebootKit.Engine.Services.Development {
Resolution resolution = Screen.currentResolution; Resolution resolution = Screen.currentResolution;
m_FPSLabel.text = $"fps: {Mathf.RoundToInt(1f / Time.deltaTime)} | dt: {Time.deltaTime:F4}ms | runtime: {Time.time:F4}s | resolution: {resolution.width}x{resolution.height}@{resolution.refreshRateRatio}Hz"; m_FPSLabel.text = $"fps: {Mathf.RoundToInt(1f / Time.deltaTime)} | dt: {Time.deltaTime:F4}ms | runtime: {Time.time:F4}s | resolution: {resolution.width}x{resolution.height}@{resolution.refreshRateRatio}Hz";
m_GameModeLabel.text = RR.GameModes.ActiveGameMode.CurrentValue != null
? $"Game Mode: {RR.GameModes.ActiveGameMode.CurrentValue.GetType().Name}"
: "Game Mode: none";
} }
public override VisualElement Build() { public override VisualElement Build() {
@@ -31,7 +26,6 @@ namespace RebootKit.Engine.Services.Development {
CreateLabel($"Toggle Overlay [F3] | RebootKit | game: {Application.productName}, version: {Application.version}"); CreateLabel($"Toggle Overlay [F3] | RebootKit | game: {Application.productName}, version: {Application.version}");
m_FPSLabel = CreateLabel($"FPS: {Application.targetFrameRate}"); m_FPSLabel = CreateLabel($"FPS: {Application.targetFrameRate}");
m_GameModeLabel = CreateLabel("Game Mode: none");
return m_RootElement; return m_RootElement;
} }

View File

@@ -1,16 +1,21 @@
using RebootKit.Engine.Main; using RebootKit.Engine.Main;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets;
namespace RebootKit.Engine { namespace RebootKit.Engine {
[CreateAssetMenu(menuName = RConsts.k_AddComponentMenu + RConsts.k_EngineConfigAssetName, fileName = RConsts.k_EngineConfigAssetName)] [CreateAssetMenu(menuName = RConsts.k_AddComponentMenu + RConsts.k_EngineConfigAssetName,
fileName = RConsts.k_EngineConfigAssetName)]
public class EngineConfigAsset : ScriptableObject { public class EngineConfigAsset : ScriptableObject {
[Header("Core")]
public bool initializeOnLoad = true; public bool initializeOnLoad = true;
public EngineCoreServicesAsset coreServices; public EngineCoreServicesAsset coreServices;
public GameAsset gameAsset; [Header("Game")]
public Game gamePrefab;
public AssetReference mainMenuScene;
// @NOTE: Spacewar, change as needed // @NOTE: Spacewar, change as needed
[Header("Steam")]
public uint steamAppID = 480; public uint steamAppID = 480;
} }
} }

View File

@@ -2,6 +2,7 @@
using RebootKit.Engine.Services.Console; using RebootKit.Engine.Services.Console;
using RebootKit.Engine.Services.Input; using RebootKit.Engine.Services.Input;
using RebootKit.Engine.Services.Simulation; using RebootKit.Engine.Services.Simulation;
using RebootKit.Engine.Simulation;
using UnityEngine; using UnityEngine;
namespace RebootKit.Engine { namespace RebootKit.Engine {

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 1177ea3903bdba5419ad3347250cb3b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,62 +0,0 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using R3;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Services.Simulation;
namespace RebootKit.Engine.Services.GameMode {
public class GameModesService : IService {
static readonly Logger s_logger = new Logger(nameof(GameModesService));
[Inject] DIContext m_DIContext;
GameModeAsset m_GameModeAsset;
readonly CancellationTokenSource m_DestroyCancellationTokenSource = new CancellationTokenSource();
DisposableBag m_ActiveGameModeDisposableBag;
readonly ReactiveProperty<IGameMode> m_ActiveGameMode = new ReactiveProperty<IGameMode>(null);
public ReadOnlyReactiveProperty<IGameMode> ActiveGameMode => m_ActiveGameMode;
public void Dispose() {
m_DestroyCancellationTokenSource.Cancel();
m_DestroyCancellationTokenSource.Dispose();
m_ActiveGameModeDisposableBag.Dispose();
}
public void Start(GameModeAsset asset, WorldConfig worldConfig) {
if (m_ActiveGameMode.Value != null) {
s_logger.Warning("Game is already running");
return;
}
Stop();
m_ActiveGameModeDisposableBag = new DisposableBag();
m_ActiveGameMode.Value = asset.Create();
m_ActiveGameModeDisposableBag.Add(m_ActiveGameMode);
InitializeGameModeAsync(worldConfig, CancellationToken.None).Forget();
}
async UniTask InitializeGameModeAsync(WorldConfig worldConfig, CancellationToken cancellationToken) {
await m_ActiveGameMode.Value.OnInit(m_DestroyCancellationTokenSource.Token);
await RR.World.LoadAsync(worldConfig, cancellationToken);
m_ActiveGameMode.Value.OnStart();
Observable.EveryUpdate().Subscribe(_ => { m_ActiveGameMode.Value?.OnTick(); }).AddTo(ref m_ActiveGameModeDisposableBag);
}
public void Stop() {
if (m_ActiveGameMode.Value == null) {
return;
}
m_ActiveGameMode.Value.OnStop();
m_ActiveGameMode.Value = null;
m_ActiveGameModeDisposableBag.Dispose();
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 82082a4d082be274bbec52347d25b836

View File

@@ -1,18 +0,0 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Foundation;
namespace RebootKit.Engine.Services.GameMode {
public interface IGameMode : IDisposable {
UniTask OnInit(CancellationToken cancellationToken);
void OnStart();
void OnStop();
void OnTick();
}
public abstract class GameModeAsset : FactoryAsset<IGameMode> {
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 562c4ff92afe4949b468003a0e997522
timeCreated: 1743456239

View File

@@ -35,8 +35,12 @@ namespace RebootKit.Engine.Main {
static async UniTask RunAsync(CancellationToken cancellationToken) { static async UniTask RunAsync(CancellationToken cancellationToken) {
ConfigVarsContainer.Init(); ConfigVarsContainer.Init();
// We are loading the boot scene only in the editor.
// In the player, we assume that the boot scene is already loaded.
#if UNITY_EDITOR
s_logger.Info("Loading boot scene"); s_logger.Info("Loading boot scene");
SceneManager.LoadScene(RConsts.k_BootSceneBuildIndex, LoadSceneMode.Single); SceneManager.LoadScene(RConsts.k_BootSceneBuildIndex, LoadSceneMode.Single);
#endif
s_logger.Info("Loading engine config"); s_logger.Info("Loading engine config");
EngineConfigAsset configAsset = Resources.Load<EngineConfigAsset>(RConsts.k_EngineConfigResourcesPath); EngineConfigAsset configAsset = Resources.Load<EngineConfigAsset>(RConsts.k_EngineConfigResourcesPath);
@@ -53,10 +57,11 @@ namespace RebootKit.Engine.Main {
await RR.InitAsync(configAsset, cancellationToken); await RR.InitAsync(configAsset, cancellationToken);
s_logger.Info("Loading main scene"); s_logger.Info("Loading main scene");
await SceneManager.LoadSceneAsync(RConsts.k_MainSceneBuildIndex, LoadSceneMode.Single).ToUniTask(cancellationToken: cancellationToken); await SceneManager.LoadSceneAsync(RConsts.k_MainSceneBuildIndex, LoadSceneMode.Single)
.ToUniTask(cancellationToken: cancellationToken);
s_logger.Info("Starting RR"); s_logger.Info("Starting RR");
RR.Run(); await RR.RunAsync(cancellationToken);
await UniTask.WaitUntilCanceled(Application.exitCancellationToken); await UniTask.WaitUntilCanceled(Application.exitCancellationToken);

View File

@@ -0,0 +1,78 @@
using System;
using Cysharp.Threading.Tasks;
using RebootKit.Engine.Services.Simulation;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Main {
public abstract class Game : NetworkBehaviour {
static readonly Logger s_GameLogger = new Logger(nameof(Game));
protected NetworkVariable<FixedString128Bytes> m_CurrentWorldID =
new NetworkVariable<FixedString128Bytes>(new FixedString128Bytes(""));
// Event callbacks
public virtual void OnWorldLoaded() {
}
public virtual void OnWorldUnload() {
}
public virtual void OnChatMessage(string message) {
s_GameLogger.Info($"Chat: {message}");
}
// Network
public override void OnNetworkSpawn() {
base.OnNetworkSpawn();
RR.GameInstance = this;
m_CurrentWorldID.OnValueChanged += OnCurrentWorldIDChanged;
LoadWorld(m_CurrentWorldID.Value.Value);
}
public override void OnNetworkDespawn() {
base.OnNetworkDespawn();
m_CurrentWorldID.OnValueChanged -= OnCurrentWorldIDChanged;
RR.GameInstance = null;
}
[ServerRpc]
public void SetCurrentWorldServerRpc(string worldID) {
m_CurrentWorldID.Value = new FixedString128Bytes(worldID);
}
// Chat
[Rpc(SendTo.Server)]
public void SendChatMessageRpc(string message) {
PrintChatMessageClientRpc(message);
}
[ClientRpc(Delivery = RpcDelivery.Reliable)]
void PrintChatMessageClientRpc(string message) {
OnChatMessage(message);
}
void OnCurrentWorldIDChanged(FixedString128Bytes previousValue, FixedString128Bytes newValue) {
string worldID = newValue.Value;
LoadWorld(worldID);
}
void LoadWorld(string worldID) {
if (string.IsNullOrEmpty(worldID)) {
RR.World.Unload();
} else {
WorldConfigAsset worldConfigAsset = RR.GetWorldConfigAsset(worldID);
if (worldConfigAsset is not null) {
RR.CloseMainMenu();
RR.World.LoadAsync(worldConfigAsset.Config, Application.exitCancellationToken).Forget();
} else {
s_GameLogger.Error($"World config asset for '{worldID}' not found.");
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3c1089fa01934ad69bf229123b190c07
timeCreated: 1750977602

View File

@@ -0,0 +1,6 @@
using Unity.Netcode;
namespace RebootKit.Engine.Main {
public abstract class NetworkPlayerController : NetworkBehaviour {
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2010b0aabd68415bb4aea2846d2c59b1
timeCreated: 1751208839

View File

@@ -4,52 +4,54 @@ using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using R3; using R3;
using RebootKit.Engine.Foundation; using RebootKit.Engine.Foundation;
using RebootKit.Engine.Multiplayer;
using RebootKit.Engine.Services.Console; using RebootKit.Engine.Services.Console;
using RebootKit.Engine.Services.GameMode;
using RebootKit.Engine.Services.Input; using RebootKit.Engine.Services.Input;
using RebootKit.Engine.Services.Simulation; using RebootKit.Engine.Services.Simulation;
using RebootKit.Engine.Steam; using RebootKit.Engine.Simulation;
using Unity.Collections; using Unity.Netcode;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement;
using Assert = UnityEngine.Assertions.Assert; using Assert = UnityEngine.Assertions.Assert;
using Logger = RebootKit.Engine.Foundation.Logger; using Logger = RebootKit.Engine.Foundation.Logger;
using Object = UnityEngine.Object;
namespace RebootKit.Engine.Main { namespace RebootKit.Engine.Main {
public interface IGame : IDisposable {
UniTask InitAsync(CancellationToken cancellationToken);
void Run();
}
public abstract class GameAsset : ScriptableObject {
public abstract IGame CreateGame();
}
public static class RR { public static class RR {
static readonly Logger s_Logger = new Logger("RR"); static readonly Logger s_Logger = new Logger("RR");
[ConfigVar("con.write_log", 1, "Enables writing game log to console output")] [ConfigVar("con.write_log", 1, "Enables writing game log to console output")]
static ConfigVar s_writeLogToConsole; static ConfigVar s_writeLogToConsole;
[ConfigVar("sv.tick_rate", 60, "Server tick rate in Hz")]
public static ConfigVar TickRate;
internal static EngineConfigAsset EngineConfig; internal static EngineConfigAsset EngineConfig;
static DisposableBag s_disposableBag; static DisposableBag s_disposableBag;
static DisposableBag s_servicesBag; static DisposableBag s_servicesBag;
public static ConsoleService Console { get; private set; } static AsyncOperationHandle<SceneInstance> s_mainMenuSceneHandle;
internal static ConsoleService Console { get; private set; }
public static InputService Input { get; private set; } public static InputService Input { get; private set; }
public static WorldService World { get; private set; } public static WorldService World { get; private set; }
public static GameModesService GameModes { get; private set; }
public static Camera MainCamera { get; internal set; } public static Camera MainCamera { get; internal set; }
static IGame s_game; internal static Game GameInstance;
// Core public static ulong TickCount { get; private set; }
public static event Action<ulong> ServerTick = delegate { };
public static event Action ClientTick = delegate { };
// Lifecycle API
// @NOTE: This method is called at the very start of the game, when boot scene loaded.
internal static async UniTask InitAsync(EngineConfigAsset configAsset, CancellationToken cancellationToken) { internal static async UniTask InitAsync(EngineConfigAsset configAsset, CancellationToken cancellationToken) {
Assert.IsNotNull(configAsset, "Config asset is required"); Assert.IsNotNull(configAsset, "Config asset is required");
Assert.IsNotNull(configAsset.gameAsset, "Game asset is required");
EngineConfig = configAsset; EngineConfig = configAsset;
@@ -57,36 +59,31 @@ namespace RebootKit.Engine.Main {
s_servicesBag = new DisposableBag(); s_servicesBag = new DisposableBag();
s_disposableBag = new DisposableBag(); s_disposableBag = new DisposableBag();
s_Logger.Debug("Registering core services"); s_Logger.Info("Registering core services");
Console = CreateService(EngineConfig.coreServices.consoleService); Console = CreateService(EngineConfig.coreServices.consoleService);
Input = CreateService(EngineConfig.coreServices.inputService); Input = CreateService(EngineConfig.coreServices.inputService);
World = CreateService(EngineConfig.coreServices.worldService); World = CreateService(EngineConfig.coreServices.worldService);
GameModes = CreateService<GameModesService>();
await InitializeAssetsAsync(cancellationToken); await InitializeAssetsAsync(cancellationToken);
await SteamManager.InitializeAsync(cancellationToken); // await SteamManager.InitializeAsync(cancellationToken);
if (SteamManager.IsInitialized) { // if (SteamManager.IsInitialized) {
s_networkTransport = SteamManager.NetworkTransport; // s_networkTransport = SteamManager.NetworkTransport;
// }
} }
s_Logger.Debug("Creating game"); // @NOTE: This method is called after the main scene is loaded.
s_game = EngineConfig.gameAsset.CreateGame(); internal static async UniTask RunAsync(CancellationToken cancellationToken) {
NetworkManager.Singleton.OnConnectionEvent += OnConnectionEvent;
NetworkManager.Singleton.OnServerStarted += OnServerStarted;
NetworkManager.Singleton.OnServerStopped += OnServerStopped;
await s_game.InitAsync(cancellationToken); Observable.EveryUpdate()
} .Subscribe(_ => Tick())
.AddTo(ref s_disposableBag);
internal static void Shutdown() { await OpenMainMenuAsync(cancellationToken);
SteamManager.Shutdown();
s_Logger.Info("Shutting down");
s_servicesBag.Dispose();
s_disposableBag.Dispose();
}
internal static void Run() {
s_game.Run();
#if UNITY_EDITOR #if UNITY_EDITOR
string scriptContent = UnityEditor.EditorPrefs.GetString("RebootKitEditor.OnGameRunScriptContent", ""); string scriptContent = UnityEditor.EditorPrefs.GetString("RebootKitEditor.OnGameRunScriptContent", "");
@@ -101,36 +98,40 @@ namespace RebootKit.Engine.Main {
#endif #endif
} }
internal static void Shutdown() {
s_Logger.Info("Shutting down");
if (GameInstance is not null) {
GameInstance.NetworkObject.Despawn();
Object.Destroy(GameInstance);
GameInstance = null;
}
if (NetworkManager.Singleton is not null) {
NetworkManager.Singleton.OnConnectionEvent -= OnConnectionEvent;
NetworkManager.Singleton.OnServerStarted -= OnServerStarted;
NetworkManager.Singleton.OnServerStopped -= OnServerStopped;
}
// SteamManager.Shutdown();
s_servicesBag.Dispose();
s_disposableBag.Dispose();
}
// Assets API // Assets API
static readonly List<GameModeAsset> s_GameModesAssets = new List<GameModeAsset>();
static readonly List<WorldConfigAsset> s_WorldConfigsAssets = new List<WorldConfigAsset>(); static readonly List<WorldConfigAsset> s_WorldConfigsAssets = new List<WorldConfigAsset>();
public static IReadOnlyList<GameModeAsset> GameModesAssets => s_GameModesAssets;
public static IReadOnlyList<WorldConfigAsset> WorldConfigsAssets => s_WorldConfigsAssets; public static IReadOnlyList<WorldConfigAsset> WorldConfigsAssets => s_WorldConfigsAssets;
public static async UniTask InitializeAssetsAsync(CancellationToken cancellationToken) { static async UniTask InitializeAssetsAsync(CancellationToken cancellationToken) {
s_GameModesAssets.Clear();
s_WorldConfigsAssets.Clear(); s_WorldConfigsAssets.Clear();
s_Logger.Info("Loading game assets"); s_Logger.Info("Loading game assets");
await Addressables.LoadAssetsAsync<GameModeAsset>("game_mode", asset => { s_GameModesAssets.Add(asset); }).ToUniTask(cancellationToken: cancellationToken); await Addressables.LoadAssetsAsync<WorldConfigAsset>("world", asset => { s_WorldConfigsAssets.Add(asset); })
s_Logger.Info($"Loaded {s_GameModesAssets.Count} game modes"); .ToUniTask(cancellationToken: cancellationToken);
await Addressables.LoadAssetsAsync<WorldConfigAsset>("world", asset => { s_WorldConfigsAssets.Add(asset); }).ToUniTask(cancellationToken: cancellationToken);
}
public static GameModeAsset GetGameMode(string name) {
if (string.IsNullOrEmpty(name)) {
throw new ArgumentException("Game mode name cannot be null or empty", nameof(name));
}
GameModeAsset gameMode = s_GameModesAssets.Find(asset => asset.name.Equals(name, StringComparison.Ordinal));
if (!gameMode) {
throw new KeyNotFoundException($"Game mode '{name}' not found");
}
return gameMode;
} }
public static WorldConfigAsset GetWorldConfigAsset(string name) { public static WorldConfigAsset GetWorldConfigAsset(string name) {
@@ -138,7 +139,8 @@ namespace RebootKit.Engine.Main {
throw new ArgumentException("World config name cannot be null or empty", nameof(name)); throw new ArgumentException("World config name cannot be null or empty", nameof(name));
} }
WorldConfigAsset worldConfig = s_WorldConfigsAssets.Find(asset => asset.Config.name.Equals(name, StringComparison.Ordinal)); WorldConfigAsset worldConfig =
s_WorldConfigsAssets.Find(asset => asset.Config.name.Equals(name, StringComparison.Ordinal));
if (!worldConfig) { if (!worldConfig) {
throw new KeyNotFoundException($"World config '{name}' not found"); throw new KeyNotFoundException($"World config '{name}' not found");
} }
@@ -147,22 +149,48 @@ namespace RebootKit.Engine.Main {
} }
// Game API // Game API
public static void StartGameMode(GameModeAsset gameMode, WorldConfig world) { public static async UniTask OpenMainMenuAsync(CancellationToken cancellationToken) {
if (!IsClient() || !IsHost()) { s_Logger.Info("Opening main menu");
s_Logger.Error("Cannot start game mode: you must be connected to a server and be the host");
World.Unload();
if (!EngineConfig.mainMenuScene.RuntimeKeyIsValid()) {
s_Logger.Error("Main menu scene is not set in EngineConfig");
return; return;
} }
s_Logger.Info($"Starting game mode: {gameMode.name} in world: {world.name}"); s_mainMenuSceneHandle = Addressables.LoadSceneAsync(EngineConfig.mainMenuScene, LoadSceneMode.Additive);
GameModes.Start(gameMode, world); await s_mainMenuSceneHandle;
} }
public static TGame Game<TGame>() where TGame : IGame { internal static void CloseMainMenu() {
if (s_game is TGame game) { if (!s_mainMenuSceneHandle.IsValid()) {
return game; return;
} }
throw new InvalidOperationException($"Game is not of type {typeof(TGame)}"); Addressables.UnloadSceneAsync(s_mainMenuSceneHandle);
}
public static void SetServerWorld(string worldID) {
if (!IsServer()) {
s_Logger.Error("Cannot set server world. Not a server instance.");
return;
}
if (GameInstance is null) {
s_Logger.Error("Game is not initialized. Cannot set server world.");
return;
}
s_Logger.Info($"Setting server world: {worldID}");
WorldConfigAsset worldConfigAsset = GetWorldConfigAsset(worldID);
if (worldConfigAsset is null) {
s_Logger.Error($"World '{worldID}' not found");
return;
}
GameInstance.SetCurrentWorldServerRpc(worldID);
} }
// Service API // Service API
@@ -182,10 +210,9 @@ namespace RebootKit.Engine.Main {
return service; return service;
} }
// General API // Logging API
public static void Log(string message) { public static void Log(string message) {
Debug.Log(message); Debug.Log(message);
Console?.WriteToOutput(message); Console?.WriteToOutput(message);
} }
@@ -199,6 +226,10 @@ namespace RebootKit.Engine.Main {
Console?.WriteToOutput(message); Console?.WriteToOutput(message);
} }
public static void WriteToConsole(string message) {
Console?.WriteToOutput(message);
}
// CVar API // CVar API
public static ConfigVar CVarIndex(string name, int defaultValue = -1) { public static ConfigVar CVarIndex(string name, int defaultValue = -1) {
ConfigVar cvar = ConfigVarsContainer.Get(name); ConfigVar cvar = ConfigVarsContainer.Get(name);
@@ -234,46 +265,110 @@ namespace RebootKit.Engine.Main {
} }
// Network API // Network API
static GameLobby s_gameLobby; public static bool IsServer() {
static INetworkTransport s_networkTransport; return NetworkManager.Singleton.IsServer;
public static bool IsHost() {
return s_networkTransport.IsServer();
} }
public static bool IsClient() { public static bool IsClient() {
return s_networkTransport.IsClient(); return NetworkManager.Singleton.IsClient;
} }
public static int GetPing() { public static void StartHost() {
return -1; if (NetworkManager.Singleton.IsHost) {
s_Logger.Error("Already hosting a server");
return;
} }
public static void HostServer(bool offline = false) { s_Logger.Info("Starting host");
s_networkTransport.StartServer(); NetworkManager.Singleton.StartHost();
} }
public static void ConnectToLobby() { public static void StopServer() {
s_networkTransport.Connect(Steamworks.SteamNetworkingSockets.Identity.SteamId); }
public static void Connect() {
if (NetworkManager.Singleton.IsClient) {
s_Logger.Error("Already connected to a server");
return;
}
s_Logger.Info($"Connecting to server.");
NetworkManager.Singleton.StartClient();
} }
public static void Disconnect() { public static void Disconnect() {
s_networkTransport.Disconnect();
} }
internal static void OnConnected(GameLobby lobby) { public static void SendChatMessage(string message) {
if (!IsClient()) {
s_Logger.Error("Cannot send chat message. Not connected to a server.");
return;
} }
internal static void OnDisconnected() { if (string.IsNullOrEmpty(message)) {
return;
} }
internal static void OnServerDataReceived(byte[] data) { GameInstance.SendChatMessageRpc(message);
s_Logger.Debug($"[SERVER] Data received: {data.Length} bytes");
} }
internal static void OnClientDataReceived(byte[] data) { static float s_tickTimer;
s_Logger.Debug($"[CLIENT] Data received: {data.Length} bytes");
static void Tick() {
float deltaTime = Time.deltaTime;
float minTickTime = 1.0f / TickRate.IndexValue;
s_tickTimer += deltaTime;
while (s_tickTimer >= minTickTime) {
s_tickTimer -= minTickTime;
if (IsServer()) {
ServerTick?.Invoke(TickCount);
}
if (IsClient()) {
ClientTick?.Invoke();
}
TickCount++;
}
World.Tick(deltaTime);
}
static void OnConnectionEvent(NetworkManager network, ConnectionEventData data) {
s_Logger.Info("Connection event: " + data.EventType);
}
static void OnServerStarted() {
s_Logger.Info("Server started");
GameInstance = Object.Instantiate(EngineConfig.gamePrefab);
GameInstance.NetworkObject.Spawn();
}
static void OnServerStopped(bool obj) {
s_Logger.Info("Server stopped");
if (GameInstance is not null) {
GameInstance.NetworkObject.Despawn();
Object.Destroy(GameInstance.gameObject);
}
GameInstance = null;
}
// Console Commands
[RCCMD("say", "Sends chat message")]
static void Say(string[] args) {
if (args.Length < 2) {
Console.WriteToOutput("Usage: say <message>");
return;
}
string message = string.Join(" ", args, 1, args.Length - 1);
SendChatMessage(message);
} }
} }
} }

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 83d5dc53310c40caab8b7732b9cecbdc
timeCreated: 1750615656

View File

@@ -1,7 +0,0 @@
namespace RebootKit.Engine.Multiplayer {
public class GameLobby {
public string GameModeID { get; private set; }
public string WorldID { get; private set; }
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: cf39e9785e4e4ad0a7ebb8b19c882b2a
timeCreated: 1750628631

View File

@@ -1,24 +0,0 @@
using System;
namespace RebootKit.Engine.Multiplayer {
public enum SendMode {
Reliable,
Unreliable
}
public interface INetworkTransport {
void Initialize();
void Shutdown();
bool IsServer();
bool IsClient();
bool StartServer();
void StopServer();
bool Connect(ulong serverID);
void Disconnect();
void Send(ulong clientID, ArraySegment<byte> data, SendMode mode);
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 702059b29fda4edc80caf8b22cd8c0d7
timeCreated: 1750759626

View File

@@ -1,4 +0,0 @@
namespace RebootKit.Engine.Multiplayer {
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 34d66ac5d1c443e8992a84edd5eb796e
timeCreated: 1750628495

View File

@@ -1,11 +1,8 @@
using RebootKit.Engine.Foundation; using Unity.Collections;
using UnityEngine; using UnityEngine;
namespace RebootKit.Engine.Services.Simulation { namespace RebootKit.Engine.Simulation {
public abstract class Actor : MonoBehaviour { public abstract class Actor : MonoBehaviour {
[field: SerializeField]
public SerializableGuid ActorGuid { get; private set; } = SerializableGuid.New();
bool m_IsPlaying = false; bool m_IsPlaying = false;
public bool IsPlaying { public bool IsPlaying {
get { get {
@@ -39,7 +36,13 @@ namespace RebootKit.Engine.Services.Simulation {
public virtual void OnEndPlay() { public virtual void OnEndPlay() {
} }
public virtual void OnTick(float deltaTime) { public virtual void Tick(float deltaTime) {
}
public virtual void SerializeNetworkState(ref DataStreamWriter writer) {
}
public virtual void DeserializeNetworkState(ref DataStreamReader reader) {
} }
} }
} }

View File

@@ -1,9 +1,9 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using R3;
using RebootKit.Engine.Foundation; using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Services.Simulation;
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
using UnityEngine.Assertions; using UnityEngine.Assertions;
@@ -12,7 +12,7 @@ using UnityEngine.ResourceManagement.ResourceProviders;
using UnityEngine.SceneManagement; using UnityEngine.SceneManagement;
using Logger = RebootKit.Engine.Foundation.Logger; using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Services.Simulation { namespace RebootKit.Engine.Simulation {
public interface IWorldContext { } public interface IWorldContext { }
public class WorldService : IService { public class WorldService : IService {
@@ -30,26 +30,28 @@ namespace RebootKit.Engine.Services.Simulation {
struct ActorData { struct ActorData {
public Actor Actor; public Actor Actor;
public readonly bool ManagedByAddressabled; public readonly bool ManagedByAddressables;
public ActorData(Actor actor, bool managedByAddressabled) { public ActorData(Actor actor, bool managedByAddressables) {
Actor = actor; Actor = actor;
ManagedByAddressabled = managedByAddressabled; ManagedByAddressables = managedByAddressables;
} }
} }
readonly List<ActorData> m_Actors = new List<ActorData>(); readonly List<ActorData> m_Actors = new List<ActorData>();
readonly IDisposable m_UpdateSubscription;
public IWorldContext Context { get; private set; } public IWorldContext Context { get; private set; }
public string WorldID {
get {
return string.IsNullOrEmpty(m_Config.name) ? string.Empty : m_Config.name;
}
}
public WorldService() { public WorldService() {
m_UpdateSubscription = Observable.EveryUpdate()
.Subscribe(_ => { Tick(Time.deltaTime); });
} }
public void Dispose() { public void Dispose() {
m_UpdateSubscription.Dispose();
Unload(); Unload();
} }
@@ -80,9 +82,21 @@ namespace RebootKit.Engine.Services.Simulation {
} }
m_WorldState = WorldState.Loaded; m_WorldState = WorldState.Loaded;
if (RR.GameInstance is not null) {
RR.GameInstance.OnWorldLoaded();
}
} }
public void Unload() { public void Unload() {
if (m_WorldState == WorldState.Unloaded) {
return;
}
if (RR.GameInstance is not null) {
RR.GameInstance.OnWorldUnload();
}
KillAllActors(); KillAllActors();
if (m_SceneInstance.IsValid()) { if (m_SceneInstance.IsValid()) {
@@ -134,7 +148,7 @@ namespace RebootKit.Engine.Services.Simulation {
actor.IsPlaying = false; actor.IsPlaying = false;
actor.OnDespawned(); actor.OnDespawned();
if (actorData.ManagedByAddressabled) { if (actorData.ManagedByAddressables) {
Addressables.ReleaseInstance(actor.gameObject); Addressables.ReleaseInstance(actor.gameObject);
} }
} }
@@ -144,7 +158,7 @@ namespace RebootKit.Engine.Services.Simulation {
actorData.Actor.IsPlaying = false; actorData.Actor.IsPlaying = false;
actorData.Actor.OnDespawned(); actorData.Actor.OnDespawned();
if (actorData.ManagedByAddressabled) { if (actorData.ManagedByAddressables) {
Addressables.ReleaseInstance(actorData.Actor.gameObject); Addressables.ReleaseInstance(actorData.Actor.gameObject);
} else { } else {
UnityEngine.Object.Destroy(actorData.Actor.gameObject); UnityEngine.Object.Destroy(actorData.Actor.gameObject);
@@ -154,14 +168,16 @@ namespace RebootKit.Engine.Services.Simulation {
m_Actors.Clear(); m_Actors.Clear();
} }
void Tick(float deltaTime) { public void Tick(float deltaTime) {
if (m_WorldState != WorldState.Loaded) { if (m_WorldState != WorldState.Loaded) {
return; return;
} }
foreach (ActorData actorData in m_Actors) { foreach (ActorData actorData in m_Actors) {
if (actorData.Actor.IsPlaying) { Actor actor = actorData.Actor;
actorData.Actor.OnTick(deltaTime);
if (actor.IsPlaying) {
actor.Tick(deltaTime);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using RebootKit.Engine.Foundation; using RebootKit.Engine.Foundation;
using RebootKit.Engine.Simulation;
using UnityEngine; using UnityEngine;
namespace RebootKit.Engine.Services.Simulation { namespace RebootKit.Engine.Services.Simulation {

View File

@@ -3,7 +3,6 @@ using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using R3; using R3;
using RebootKit.Engine.Main; using RebootKit.Engine.Main;
using RebootKit.Engine.Multiplayer;
using Steamworks; using Steamworks;
using Logger = RebootKit.Engine.Foundation.Logger; using Logger = RebootKit.Engine.Foundation.Logger;
@@ -13,8 +12,6 @@ namespace RebootKit.Engine.Steam {
public static bool IsInitialized { get; private set; } = false; public static bool IsInitialized { get; private set; } = false;
public static INetworkTransport NetworkTransport { get; private set; } = new SteamNetworkTransport();
internal static async UniTask InitializeAsync(CancellationToken cancellationToken = default) { internal static async UniTask InitializeAsync(CancellationToken cancellationToken = default) {
s_Logger.Info("Initializing Steam Manager..."); s_Logger.Info("Initializing Steam Manager...");
IsInitialized = false; IsInitialized = false;
@@ -26,8 +23,6 @@ namespace RebootKit.Engine.Steam {
return; return;
} }
NetworkTransport.Initialize();
IsInitialized = true; IsInitialized = true;
await UniTask.Yield(cancellationToken); await UniTask.Yield(cancellationToken);
@@ -41,7 +36,6 @@ namespace RebootKit.Engine.Steam {
s_Logger.Info("Shutting down Steam Manager..."); s_Logger.Info("Shutting down Steam Manager...");
NetworkTransport.Shutdown();
SteamClient.Shutdown(); SteamClient.Shutdown();
IsInitialized = false; IsInitialized = false;

View File

@@ -0,0 +1,237 @@
#define RR_LOCALHOST_ONLY
using System;
using R3;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using Steamworks;
using Steamworks.Data;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Steam {
// class SteamNetworkManager : INetworkManager {
// static readonly Logger s_Logger = new Logger(nameof(SteamNetworkManager));
//
// const int k_DefaultPort = 420;
//
// SocketManager m_SocketManager;
// ConnectionManager m_ConnectionManager;
// IDisposable m_TickDisposable;
//
// SteamId m_HostSteamID;
//
// public void Initialize(INetworkManagerEventHandler eventHandler) {
// m_TickDisposable = Observable.EveryUpdate()
// .Subscribe(_ => Tick());
//
// SteamNetworkingUtils.DebugLevel = NetDebugOutput.Debug;
//
// SteamNetworkingSockets.OnConnectionStatusChanged += OnConnectionStatusChanged;
// SteamNetworkingUtils.OnDebugOutput += OnSteamNetworkDebugOutput;
//
// #if !RR_LOCALHOST_ONLY
// SteamNetworkingUtils.InitRelayNetworkAccess();
// #endif
// }
//
// public void Shutdown() {
// Disconnect();
//
// SteamNetworkingUtils.OnDebugOutput -= OnSteamNetworkDebugOutput;
// SteamNetworkingSockets.OnConnectionStatusChanged -= OnConnectionStatusChanged;
//
// m_TickDisposable.Dispose();
// }
//
// public bool IsServer() {
// return m_SocketManager != null;
// }
//
// public bool IsClient() {
// return m_ConnectionManager != null;
// }
//
// public bool StartServer() {
// Disconnect();
//
// s_Logger.Info("Creating server...");
//
// try {
// m_HostSteamID = SteamNetworkingSockets.Identity.SteamId;
//
// #if RR_LOCALHOST_ONLY
// m_SocketManager = SteamNetworkingSockets.CreateNormalSocket(NetAddress.LocalHost(2137), new ServerCallbacks());
// m_ConnectionManager = SteamNetworkingSockets.ConnectNormal(NetAddress.LocalHost(2137), new ClientCallbacks());
// #else
// m_SocketManager = SteamNetworkingSockets.CreateRelaySocket<SocketManager>(k_DefaultPort);
// m_SocketManager.Interface = new ServerCallbacks();
// m_ConnectionManager =
// SteamNetworkingSockets.ConnectRelay<ConnectionManager>(m_HostSteamID, k_DefaultPort);
// m_ConnectionManager.Interface = new ClientCallbacks();
// #endif
// } catch (Exception e) {
// s_Logger.Error($"Failed to create server: {e.Message}");
// m_SocketManager = null;
// m_ConnectionManager = null;
//
// return false;
// }
//
// return true;
// }
//
// public void StopServer() {
// Disconnect();
// }
//
// public bool Connect(ulong serverID) {
// if (IsServer()) {
// s_Logger.Error("Cannot connect to a server while running as a server.");
// return false;
// }
//
// Disconnect();
//
// s_Logger.Info("Connecting to server with steam ID: " + serverID);
// try {
// #if RR_LOCALHOST_ONLY
// m_ConnectionManager =
// SteamNetworkingSockets.ConnectNormal(NetAddress.LocalHost(2137), new ClientCallbacks());
// #else
// m_ConnectionManager = SteamNetworkingSockets.ConnectRelay<ConnectionManager>(serverID, k_DefaultPort);
// m_ConnectionManager.Interface = new ClientCallbacks();
// #endif
//
// m_HostSteamID = serverID;
// } catch (Exception e) {
// s_Logger.Error($"Failed to connect to server with ID {serverID}: {e.Message}");
// m_ConnectionManager = null;
//
// return false;
// }
//
// return true;
// }
//
// public void Disconnect() {
// if (m_ConnectionManager != null) {
// s_Logger.Info("Disconnecting from the server...");
// m_ConnectionManager.Close();
// m_ConnectionManager = null;
// }
//
// if (m_SocketManager != null) {
// s_Logger.Info("Shutting down the server...");
// m_SocketManager.Close();
// m_SocketManager = null;
// }
// }
// public void Send(ulong clientID, NetworkMessage message, SendMode mode) {
// if (!IsServer()) {
// return;
// }
//
// foreach (Connection connection in m_SocketManager.Connected) {
// connection.SendMessage(new byte[] {
// 0xDE,
// 0xAD,
// 0xBE,
// 0xEF
// }, SendType.Reliable);
// }
// }
//
// public void Broadcast(NetworkMessage message, SendMode mode) {
// if (!IsServer()) {
// return;
// }
//
// foreach (Connection connection in m_SocketManager.Connected) {
// connection.SendMessage(new byte[] {
// 0xDE,
// 0xAD,
// 0xBE,
// 0xEF
// }, SendType.Reliable);
// }
// }
//
// void OnSteamNetworkDebugOutput(NetDebugOutput level, string message) {
// LogLevel logLevel = level switch {
// NetDebugOutput.Debug => LogLevel.Debug,
// NetDebugOutput.Msg => LogLevel.Info,
// NetDebugOutput.Warning => LogLevel.Warning,
// NetDebugOutput.Error => LogLevel.Error,
// _ => LogLevel.Info
// };
//
// s_Logger.Log(logLevel, message);
// }
//
// void Tick() {
// m_SocketManager?.Receive();
// m_ConnectionManager?.Receive();
// }
//
// void OnConnectionStatusChanged(Connection connection, ConnectionInfo info) {
// s_Logger.Info($"OnConnectionStatusChanged: {connection.Id} - {info.Identity} - Status: {info.State}");
// }
//
// class ServerCallbacks : ISocketManager {
// public void OnConnecting(Connection connection, ConnectionInfo data) {
// s_Logger.Info($"OnConnecting: {connection.Id} - {data.Identity}");
//
// connection.Accept();
// }
//
// public void OnConnected(Connection connection, ConnectionInfo data) {
// s_Logger.Info($"OnConnected: {connection.Id} - {data.Identity}");
//
// connection.SendMessage(new byte[] {
// 0xBE,
// 0xFE,
// 0x00,
// 0x00
// }, 0, 4, 0);
// }
//
// public void OnDisconnected(Connection connection, ConnectionInfo data) {
// s_Logger.Info($"OnDisconnected: {connection.Id} - {data.Identity}");
// }
//
// public void OnMessage(Connection connection,
// NetIdentity identity,
// IntPtr data,
// int size,
// long messageNum,
// long recvTime,
// int channel) {
// byte[] buffer = new byte[size];
// System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size);
//
// s_Logger.Info($"OnMessage: {connection.Id} - {identity} - Size: {size} - Channel: {channel}");
// }
// }
//
// class ClientCallbacks : IConnectionManager {
// public void OnConnected(ConnectionInfo info) {
// s_Logger.Info("ConnectionOnConnected");
// }
//
// public void OnConnecting(ConnectionInfo info) {
// s_Logger.Info("ConnectionOnConnecting");
// }
//
// public void OnDisconnected(ConnectionInfo info) {
// s_Logger.Info("ConnectionOnDisconnected");
// }
//
// public void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) {
// byte[] buffer = new byte[size];
// System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size);
//
// s_Logger.Info($"OnMessage: Size: {size} - Channel: {channel}");
// }
// }
// }
}

View File

@@ -1,209 +0,0 @@
using System;
using R3;
using RebootKit.Engine.Foundation;
using RebootKit.Engine.Main;
using RebootKit.Engine.Multiplayer;
using Steamworks;
using Steamworks.Data;
using Logger = RebootKit.Engine.Foundation.Logger;
namespace RebootKit.Engine.Steam {
class SteamNetworkTransport : INetworkTransport {
static readonly Logger s_Logger = new Logger(nameof(SteamNetworkTransport));
const int k_DefaultPort = 420;
ServerCallbacks m_SocketManager;
ClientCallbacks m_ConnectionManager;
IDisposable m_TickDisposable;
SteamId m_HostSteamID;
public void Initialize() {
m_TickDisposable = Observable.EveryUpdate()
.Subscribe(_ => Tick());
SteamNetworkingUtils.DebugLevel = NetDebugOutput.Debug;
SteamNetworkingSockets.OnConnectionStatusChanged += OnConnectionStatusChanged;
SteamNetworkingUtils.OnDebugOutput += OnSteamNetworkDebugOutput;
SteamNetworkingUtils.InitRelayNetworkAccess();
}
public void Shutdown() {
Disconnect();
SteamNetworkingUtils.OnDebugOutput -= OnSteamNetworkDebugOutput;
SteamNetworkingSockets.OnConnectionStatusChanged -= OnConnectionStatusChanged;
m_TickDisposable.Dispose();
}
public bool IsServer() {
return m_SocketManager != null;
}
public bool IsClient() {
return m_ConnectionManager != null;
}
public bool StartServer() {
Disconnect();
s_Logger.Info("Creating server...");
try {
m_HostSteamID = SteamNetworkingSockets.Identity.SteamId;
m_SocketManager = SteamNetworkingSockets.CreateRelaySocket<ServerCallbacks>(k_DefaultPort);
m_ConnectionManager = SteamNetworkingSockets.ConnectRelay<ClientCallbacks>(m_HostSteamID, k_DefaultPort);
} catch (Exception e) {
s_Logger.Error($"Failed to create server: {e.Message}");
m_SocketManager = null;
m_ConnectionManager = null;
return false;
}
return true;
}
public void StopServer() {
Disconnect();
}
public bool Connect(ulong serverID) {
if (IsServer()) {
s_Logger.Error("Cannot connect to a server while running as a server.");
return false;
}
Disconnect();
s_Logger.Info("Connecting to server with steam ID: " + serverID);
try {
m_ConnectionManager = SteamNetworkingSockets.ConnectRelay<ClientCallbacks>(serverID, k_DefaultPort);
m_HostSteamID = serverID;
} catch (Exception e) {
s_Logger.Error($"Failed to connect to server with ID {serverID}: {e.Message}");
m_ConnectionManager = null;
return false;
}
return true;
}
public void Disconnect() {
if (m_ConnectionManager != null) {
s_Logger.Info("Disconnecting from the server...");
m_ConnectionManager.Close();
m_ConnectionManager = null;
}
if (m_SocketManager != null) {
s_Logger.Info("Shutting down the server...");
m_SocketManager.Close();
m_SocketManager = null;
}
}
public void Send(ulong clientID, ArraySegment<byte> data, SendMode mode) {
if (clientID == 0) {
clientID = m_HostSteamID;
}
}
void OnSteamNetworkDebugOutput(NetDebugOutput level, string message) {
LogLevel logLevel = level switch {
NetDebugOutput.Debug => LogLevel.Debug,
NetDebugOutput.Msg => LogLevel.Info,
NetDebugOutput.Warning => LogLevel.Warning,
NetDebugOutput.Error => LogLevel.Error,
_ => LogLevel.Info
};
s_Logger.Log(logLevel, message);
}
void Tick() {
m_SocketManager?.Receive();
m_ConnectionManager?.Receive();
}
void OnConnectionStatusChanged(Connection connection, ConnectionInfo info) {
s_Logger.Info($"OnConnectionStatusChanged: {connection.Id} - {info.Identity} - Status: {info.State}");
}
class ServerCallbacks : SocketManager {
public override void OnConnecting(Connection connection, ConnectionInfo data) {
base.OnConnecting(connection, data);
connection.Accept();
s_Logger.Info($"OnConnecting: {connection.Id} - {data.Identity}");
}
public override void OnConnected(Connection connection, ConnectionInfo data) {
base.OnConnected(connection, data);
s_Logger.Info($"OnConnected: {connection.Id} - {data.Identity}");
connection.SendMessage(new byte[] {
0xBE,
0xFE,
0x00,
0x00
}, 0, 4, 0);
}
public override void OnDisconnected(Connection connection, ConnectionInfo data) {
base.OnDisconnected(connection, data);
s_Logger.Info($"OnDisconnected: {connection.Id} - {data.Identity}");
}
public override void OnMessage(Connection connection,
NetIdentity identity,
IntPtr data,
int size,
long messageNum,
long recvTime,
int channel) {
base.OnMessage(connection, identity, data, size, messageNum, recvTime, channel);
byte[] buffer = new byte[size];
System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size);
RR.OnServerDataReceived(buffer);
s_Logger.Info($"OnMessage: {connection.Id} - {identity} - Size: {size} - Channel: {channel}");
}
}
class ClientCallbacks : ConnectionManager {
public override void OnConnected(ConnectionInfo info) {
base.OnConnected(info);
s_Logger.Info("ConnectionOnConnected");
}
public override void OnConnecting(ConnectionInfo info) {
base.OnConnecting(info);
s_Logger.Info("ConnectionOnConnecting");
}
public override void OnDisconnected(ConnectionInfo info) {
base.OnDisconnected(info);
s_Logger.Info("ConnectionOnDisconnected");
}
public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) {
byte[] buffer = new byte[size];
System.Runtime.InteropServices.Marshal.Copy(data, buffer, 0, size);
RR.OnClientDataReceived(buffer);
s_Logger.Info($"OnMessage: Size: {size} - Channel: {channel}");
}
}
}
}

View File

@@ -13,7 +13,10 @@
"GUID:f51ebe6a0ceec4240a699833d6309b23", "GUID:f51ebe6a0ceec4240a699833d6309b23",
"GUID:593a5b492d29ac6448b1ebf7f035ef33", "GUID:593a5b492d29ac6448b1ebf7f035ef33",
"GUID:84651a3751eca9349aac36a66bba901b", "GUID:84651a3751eca9349aac36a66bba901b",
"GUID:d8b63aba1907145bea998dd612889d6b" "GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:f2d49d9fa7e7eb3418e39723a7d3b92f",
"GUID:324caed91501a9c47a04ebfd87b68794",
"GUID:1491147abca9d7d4bb7105af628b223e"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],

View File

@@ -358,6 +358,110 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0} m_Pivot: {x: 0, y: 0}
--- !u!1 &1456272197
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1456272200}
- component: {fileID: 1456272199}
- component: {fileID: 1456272198}
m_Layer: 0
m_Name: network
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1456272198
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1456272197}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3}
m_Name:
m_EditorClassIdentifier:
m_ProtocolType: 0
m_UseWebSockets: 0
m_UseEncryption: 0
m_MaxPacketQueueSize: 128
m_MaxPayloadSize: 6144
m_HeartbeatTimeoutMS: 500
m_ConnectTimeoutMS: 1000
m_MaxConnectAttempts: 60
m_DisconnectTimeoutMS: 30000
ConnectionData:
Address: 127.0.0.1
Port: 7777
ServerListenAddress: 127.0.0.1
DebugSimulator:
PacketDelayMS: 0
PacketJitterMS: 0
PacketDropRate: 0
--- !u!114 &1456272199
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1456272197}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
m_Name:
m_EditorClassIdentifier:
NetworkManagerExpanded: 0
NetworkConfig:
ProtocolVersion: 0
NetworkTransport: {fileID: 1456272198}
PlayerPrefab: {fileID: 0}
Prefabs:
NetworkPrefabsLists:
- {fileID: 11400000, guid: d30094ba881a39a4197e324b492d5db7, type: 2}
TickRate: 30
ClientConnectionBufferTimeout: 10
ConnectionApproval: 0
ConnectionData:
EnableTimeResync: 0
TimeResyncInterval: 30
EnsureNetworkVariableLengthSafety: 0
EnableSceneManagement: 0
ForceSamePrefabs: 1
RecycleNetworkIds: 1
NetworkIdRecycleDelay: 120
RpcHashSize: 0
LoadSceneTimeOut: 120
SpawnTimeout: 10
EnableNetworkLogs: 1
NetworkTopology: 0
UseCMBService: 0
AutoSpawnPlayerPrefabClientSide: 1
NetworkProfilingMetrics: 1
OldPrefabList: []
RunInBackground: 1
LogLevel: 1
--- !u!4 &1456272200
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1456272197}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1509941943 --- !u!1 &1509941943
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -519,3 +623,4 @@ SceneRoots:
- {fileID: 242831139} - {fileID: 242831139}
- {fileID: 319523215} - {fileID: 319523215}
- {fileID: 1530691767} - {fileID: 1530691767}
- {fileID: 1456272200}