online game working
This commit is contained in:
252
Game/Scripts/Networking/LobbyGameSceneCoordinator.cs
Normal file
252
Game/Scripts/Networking/LobbyGameSceneCoordinator.cs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.SceneManagement;
|
||||||
|
using MegaKoop.Game;
|
||||||
|
|
||||||
|
#if STEAMWORKSNET
|
||||||
|
using Steamworks;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace MegaKoop.Game.Networking
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles transitioning from the lobby into the character scene and spawning player avatars.
|
||||||
|
/// Lives alongside the Steam services object so it survives scene loads.
|
||||||
|
/// </summary>
|
||||||
|
[DefaultExecutionOrder(-500)]
|
||||||
|
public class LobbyGameSceneCoordinator : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private string characterSceneName = "CharacterScene";
|
||||||
|
[SerializeField] private float spawnRadius = 3f;
|
||||||
|
[SerializeField] private float minimumSpawnSpacing = 2.5f;
|
||||||
|
|
||||||
|
private readonly List<LobbyPlayerInfo> pendingPlayers = new();
|
||||||
|
private string localSteamId = string.Empty;
|
||||||
|
private bool loadPending;
|
||||||
|
private bool hasSpawned;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
DontDestroyOnLoad(gameObject);
|
||||||
|
SceneManager.sceneLoaded += HandleSceneLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
SceneManager.sceneLoaded -= HandleSceneLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Captures the current lobby membership and moves everyone into the character scene.
|
||||||
|
/// </summary>
|
||||||
|
public void BeginGame(MegaKoop.Steam.SteamLobbyService steam)
|
||||||
|
{
|
||||||
|
if (steam == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[LobbyGameSceneCoordinator] SteamLobbyService missing; cannot begin game.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CapturePlayers(steam);
|
||||||
|
|
||||||
|
if (!string.Equals(SceneManager.GetActiveScene().name, characterSceneName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
loadPending = true;
|
||||||
|
hasSpawned = false;
|
||||||
|
SceneManager.LoadScene(characterSceneName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Already in the target scene (e.g., during hot reload).
|
||||||
|
loadPending = false;
|
||||||
|
hasSpawned = false;
|
||||||
|
SpawnPlayersInScene(SceneManager.GetActiveScene());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CapturePlayers(MegaKoop.Steam.SteamLobbyService steam)
|
||||||
|
{
|
||||||
|
pendingPlayers.Clear();
|
||||||
|
localSteamId = steam.LocalSteamIdString ?? string.Empty;
|
||||||
|
|
||||||
|
var members = steam.GetMembers();
|
||||||
|
if (members != null)
|
||||||
|
{
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
pendingPlayers.Add(new LobbyPlayerInfo
|
||||||
|
{
|
||||||
|
SteamId = member.steamId,
|
||||||
|
DisplayName = string.IsNullOrWhiteSpace(member.name) ? "Player" : member.name,
|
||||||
|
IsLocal = member.steamId == localSteamId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the local player is represented even if lobby data is empty (e.g., offline stub).
|
||||||
|
if (!pendingPlayers.Exists(p => p.IsLocal))
|
||||||
|
{
|
||||||
|
pendingPlayers.Add(new LobbyPlayerInfo
|
||||||
|
{
|
||||||
|
SteamId = localSteamId,
|
||||||
|
DisplayName = GetFallbackLocalName(),
|
||||||
|
IsLocal = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||||
|
{
|
||||||
|
if (!string.Equals(scene.name, characterSceneName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loadPending && hasSpawned)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPending = false;
|
||||||
|
hasSpawned = false;
|
||||||
|
SpawnPlayersInScene(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnPlayersInScene(Scene scene)
|
||||||
|
{
|
||||||
|
if (hasSpawned)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = FindWizardTemplate(scene);
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[LobbyGameSceneCoordinator] Wizard template not found in CharacterScene; cannot spawn players.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSpawned = true;
|
||||||
|
|
||||||
|
Vector3 basePosition = template.transform.position;
|
||||||
|
Quaternion baseRotation = template.transform.rotation;
|
||||||
|
Transform parent = template.transform.parent;
|
||||||
|
|
||||||
|
// Hide template so only spawned copies are visible.
|
||||||
|
template.SetActive(false);
|
||||||
|
|
||||||
|
int total = Mathf.Max(1, pendingPlayers.Count);
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
LobbyPlayerInfo info = i < pendingPlayers.Count
|
||||||
|
? pendingPlayers[i]
|
||||||
|
: new LobbyPlayerInfo { DisplayName = $"Player {i + 1}", IsLocal = i == 0 };
|
||||||
|
|
||||||
|
Vector3 spawnPosition = CalculateSpawnPosition(basePosition, i, total);
|
||||||
|
var clone = Instantiate(template, spawnPosition, baseRotation, parent);
|
||||||
|
clone.name = BuildPlayerName(info, i);
|
||||||
|
|
||||||
|
ConfigureCloneForPlayer(clone, info);
|
||||||
|
|
||||||
|
clone.SetActive(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject FindWizardTemplate(Scene scene)
|
||||||
|
{
|
||||||
|
foreach (var root in scene.GetRootGameObjects())
|
||||||
|
{
|
||||||
|
if (root.name.Equals("Wizard", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
var controller = root.GetComponentInChildren<ThirdPersonCharacterController>(true);
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
return controller.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 CalculateSpawnPosition(Vector3 center, int index, int total)
|
||||||
|
{
|
||||||
|
if (total <= 1)
|
||||||
|
{
|
||||||
|
return center;
|
||||||
|
}
|
||||||
|
|
||||||
|
float radiusForRing = Mathf.Max(spawnRadius, minimumSpawnSpacing) * Mathf.Ceil((index + 1) / 6f);
|
||||||
|
float angle = (Mathf.PI * 2f / Mathf.Max(1, Mathf.Min(total, 6))) * (index % 6);
|
||||||
|
Vector3 offset = new Vector3(Mathf.Cos(angle), 0f, Mathf.Sin(angle)) * radiusForRing;
|
||||||
|
return center + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureCloneForPlayer(GameObject clone, LobbyPlayerInfo info)
|
||||||
|
{
|
||||||
|
var controller = clone.GetComponent<ThirdPersonCharacterController>();
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
controller.enabled = info.IsLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local player keeps the camera and audio; remote avatars are visual only.
|
||||||
|
var cameras = clone.GetComponentsInChildren<Camera>(true);
|
||||||
|
foreach (var camera in cameras)
|
||||||
|
{
|
||||||
|
camera.enabled = info.IsLocal;
|
||||||
|
if (!info.IsLocal)
|
||||||
|
{
|
||||||
|
camera.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var listeners = clone.GetComponentsInChildren<AudioListener>(true);
|
||||||
|
foreach (var listener in listeners)
|
||||||
|
{
|
||||||
|
listener.enabled = info.IsLocal;
|
||||||
|
if (!info.IsLocal)
|
||||||
|
{
|
||||||
|
listener.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var thirdPersonCamera = clone.GetComponentInChildren<ThirdPersonCamera>(true);
|
||||||
|
if (thirdPersonCamera != null)
|
||||||
|
{
|
||||||
|
thirdPersonCamera.enabled = info.IsLocal;
|
||||||
|
if (info.IsLocal)
|
||||||
|
{
|
||||||
|
thirdPersonCamera.SetTarget(clone.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildPlayerName(LobbyPlayerInfo info, int index)
|
||||||
|
{
|
||||||
|
string baseName = !string.IsNullOrWhiteSpace(info.DisplayName) ? info.DisplayName : $"Player {index + 1}";
|
||||||
|
return $"Wizard_{baseName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFallbackLocalName()
|
||||||
|
{
|
||||||
|
#if STEAMWORKSNET
|
||||||
|
if (SteamBootstrap.IsInitialized)
|
||||||
|
{
|
||||||
|
return SteamFriends.GetPersonaName();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return "You";
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
private struct LobbyPlayerInfo
|
||||||
|
{
|
||||||
|
public string SteamId;
|
||||||
|
public string DisplayName;
|
||||||
|
public bool IsLocal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 46f2c531c179d29e39aa831ff5290c20
|
||||||
8
Settings/Build Profiles.meta
Normal file
8
Settings/Build Profiles.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eda40d14141ed13b99eb74bd3cbb962b
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
44
Settings/Build Profiles/Linux.asset
Normal file
44
Settings/Build Profiles/Linux.asset
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_Name: Linux
|
||||||
|
m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.Build.Profile.BuildProfile
|
||||||
|
m_AssetVersion: 1
|
||||||
|
m_BuildTarget: 24
|
||||||
|
m_Subtarget: 2
|
||||||
|
m_PlatformId: cb423bfea44b4d658edb8bc5d91a3024
|
||||||
|
m_PlatformBuildProfile:
|
||||||
|
rid: 8527041553903386862
|
||||||
|
m_OverrideGlobalSceneList: 0
|
||||||
|
m_Scenes: []
|
||||||
|
m_ScriptingDefines: []
|
||||||
|
m_PlayerSettingsYaml:
|
||||||
|
m_Settings: []
|
||||||
|
references:
|
||||||
|
version: 2
|
||||||
|
RefIds:
|
||||||
|
- rid: 8527041553903386862
|
||||||
|
type: {class: LinuxPlatformSettings, ns: UnityEditor.LinuxStandalone, asm: UnityEditor.LinuxStandalone.Extensions}
|
||||||
|
data:
|
||||||
|
m_Development: 0
|
||||||
|
m_ConnectProfiler: 0
|
||||||
|
m_BuildWithDeepProfilingSupport: 0
|
||||||
|
m_AllowDebugging: 0
|
||||||
|
m_WaitForManagedDebugger: 0
|
||||||
|
m_ManagedDebuggerFixedPort: 0
|
||||||
|
m_ExplicitNullChecks: 0
|
||||||
|
m_ExplicitDivideByZeroChecks: 0
|
||||||
|
m_ExplicitArrayBoundsChecks: 0
|
||||||
|
m_CompressionType: -1
|
||||||
|
m_InstallInBuildFolder: 0
|
||||||
|
m_InsightsSettingsContainer:
|
||||||
|
m_BuildProfileEngineDiagnosticsState: 2
|
||||||
8
Settings/Build Profiles/Linux.asset.meta
Normal file
8
Settings/Build Profiles/Linux.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 05f210c1ba43a51c6a548f8030a4cfec
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -5,6 +5,7 @@ using UnityEngine.UI;
|
|||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
using MegaKoop.Steam;
|
using MegaKoop.Steam;
|
||||||
using MegaKoop.Networking;
|
using MegaKoop.Networking;
|
||||||
|
using MegaKoop.Game.Networking;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
|
|
||||||
namespace MegaKoop.UI
|
namespace MegaKoop.UI
|
||||||
@@ -77,6 +78,7 @@ namespace MegaKoop.UI
|
|||||||
|
|
||||||
// Steam service
|
// Steam service
|
||||||
private SteamLobbyService steam;
|
private SteamLobbyService steam;
|
||||||
|
private LobbyGameSceneCoordinator lobbyGameCoordinator;
|
||||||
|
|
||||||
// Local state cache
|
// Local state cache
|
||||||
private bool IsInLobby => steam != null && steam.IsInLobby;
|
private bool IsInLobby => steam != null && steam.IsInLobby;
|
||||||
@@ -247,6 +249,7 @@ namespace MegaKoop.UI
|
|||||||
if (steam.IsStartSignaled() && !clientStartedFromSignal)
|
if (steam.IsStartSignaled() && !clientStartedFromSignal)
|
||||||
{
|
{
|
||||||
clientStartedFromSignal = true;
|
clientStartedFromSignal = true;
|
||||||
|
lobbyGameCoordinator?.BeginGame(steam);
|
||||||
#if UNITY_NETCODE || NETCODE_PRESENT
|
#if UNITY_NETCODE || NETCODE_PRESENT
|
||||||
// Configure transport with host SteamID before starting client
|
// Configure transport with host SteamID before starting client
|
||||||
var adapter = GetSteamAdapter();
|
var adapter = GetSteamAdapter();
|
||||||
@@ -327,6 +330,8 @@ namespace MegaKoop.UI
|
|||||||
#region Steam
|
#region Steam
|
||||||
private void EnsureSteamServices()
|
private void EnsureSteamServices()
|
||||||
{
|
{
|
||||||
|
GameObject servicesRoot = null;
|
||||||
|
|
||||||
if (steam == null)
|
if (steam == null)
|
||||||
{
|
{
|
||||||
// Unity 2023+: use FindFirstObjectByType; older: FindObjectOfType
|
// Unity 2023+: use FindFirstObjectByType; older: FindObjectOfType
|
||||||
@@ -335,14 +340,34 @@ namespace MegaKoop.UI
|
|||||||
#else
|
#else
|
||||||
steam = Object.FindObjectOfType<SteamLobbyService>();
|
steam = Object.FindObjectOfType<SteamLobbyService>();
|
||||||
#endif
|
#endif
|
||||||
|
if (steam != null)
|
||||||
|
{
|
||||||
|
servicesRoot = steam.gameObject;
|
||||||
|
}
|
||||||
|
|
||||||
if (steam == null)
|
if (steam == null)
|
||||||
{
|
{
|
||||||
var go = GameObject.Find("SteamServices") ?? new GameObject("SteamServices");
|
servicesRoot = GameObject.Find("SteamServices") ?? new GameObject("SteamServices");
|
||||||
if (go.GetComponent<SteamManager>() == null) go.AddComponent<SteamManager>();
|
if (servicesRoot.GetComponent<SteamManager>() == null)
|
||||||
steam = go.GetComponent<SteamLobbyService>() ?? go.AddComponent<SteamLobbyService>();
|
{
|
||||||
DontDestroyOnLoad(go);
|
servicesRoot.AddComponent<SteamManager>();
|
||||||
|
}
|
||||||
|
steam = servicesRoot.GetComponent<SteamLobbyService>() ?? servicesRoot.AddComponent<SteamLobbyService>();
|
||||||
|
DontDestroyOnLoad(servicesRoot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (steam != null)
|
||||||
|
{
|
||||||
|
servicesRoot ??= steam.gameObject;
|
||||||
|
|
||||||
|
if (servicesRoot.GetComponent<SteamManager>() == null)
|
||||||
|
{
|
||||||
|
servicesRoot.AddComponent<SteamManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
lobbyGameCoordinator = servicesRoot.GetComponent<LobbyGameSceneCoordinator>() ?? servicesRoot.AddComponent<LobbyGameSceneCoordinator>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterSteamEvents()
|
private void RegisterSteamEvents()
|
||||||
@@ -625,6 +650,7 @@ namespace MegaKoop.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
steam?.StartGameSignal();
|
steam?.StartGameSignal();
|
||||||
|
lobbyGameCoordinator?.BeginGame(steam);
|
||||||
#if UNITY_NETCODE || NETCODE_PRESENT
|
#if UNITY_NETCODE || NETCODE_PRESENT
|
||||||
// Optionally start host and load scene
|
// Optionally start host and load scene
|
||||||
var adapter = GetSteamAdapter();
|
var adapter = GetSteamAdapter();
|
||||||
|
|||||||
Reference in New Issue
Block a user