Bind online session browser to canvas, drop IMGUI
- OnlineSessionManager: remove OnGUI/IMGUI state; now a logic-only layer exposing Host/Refresh/Join/Disconnect plus Status/IsBusy/IsConnected/ InSession/Sessions and Changed/SessionsChanged events - OnlineSessionBrowserUI (new): drives the Servers canvas — 4 buttons, session list from Entry Template clones (name + players), row selection, state-driven interactability, status footer - Main scene: attach binder, add status footer line, wire 8 refs; dev bootstrap panel disabled - Online Session Manager.prefab (new) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
286473930a
commit
d27ee16004
6 files changed
+3135
-85
No files matched your search
@@ -20,8 +20,9 @@ using UnityEngine;
|
||||
/// NetworkManager callbacks fire exactly as they do for loopback hosting, so MatchManager, spawning and
|
||||
/// authority are unaffected.
|
||||
///
|
||||
/// This is a quick IMGUI server browser for clone testing; replace with proper uGUI later. The loopback
|
||||
/// dev path (see <see cref="NetworkBootstrap"/>, H/C/J/K) stays for fast local testing - online is additive.
|
||||
/// This is the logic layer only: state + async host/refresh/join/disconnect calls, surfaced through the
|
||||
/// <see cref="Changed"/>/<see cref="SessionsChanged"/> events. The uGUI server browser that drives it lives
|
||||
/// on its own canvas (<see cref="OnlineSessionBrowserUI"/>); this component carries no UI of its own.
|
||||
/// Requires the project to be linked to a UGS cloud project (Project Settings > Services) with Relay,
|
||||
/// Lobby and Authentication (Anonymous) enabled; otherwise sign-in fails with a clear status message.
|
||||
/// Attach to the same GameObject as the NetworkManager.
|
||||
@@ -33,17 +34,26 @@ public class OnlineSessionManager : MonoBehaviour
|
||||
[Tooltip("Maximum players per hosted session (host included).")]
|
||||
public int maxPlayers = 8;
|
||||
|
||||
[Tooltip("Draw the on-screen online server-browser panel (below the dev panel).")]
|
||||
public bool showGui = true;
|
||||
|
||||
private NetworkManager _net;
|
||||
private ISession _session; // active session (host or client); null when not in one
|
||||
private IList<ISessionInfo> _browse = new List<ISessionInfo>();
|
||||
private List<ISessionInfo> _sessions = new List<ISessionInfo>();
|
||||
private string _hostName = "";
|
||||
private string _status = "Idle";
|
||||
private bool _busy;
|
||||
private bool _leaving; // suppress the defensive leave while an explicit leave is in flight
|
||||
private Vector2 _scroll;
|
||||
|
||||
/// <summary>Raised when status, busy state, or the connection state changes, so a bound UI can re-render.
|
||||
/// Async continuations resume on the main thread, so handlers run there.</summary>
|
||||
public event Action Changed;
|
||||
|
||||
/// <summary>Raised after the browsable session list is replaced (a refresh completed).</summary>
|
||||
public event Action SessionsChanged;
|
||||
|
||||
public string Status => _status;
|
||||
public bool IsBusy => _busy;
|
||||
public bool IsConnected => _net && _net.IsListening;
|
||||
public bool InSession => _session != null;
|
||||
public IReadOnlyList<ISessionInfo> Sessions => _sessions;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -54,6 +64,8 @@ public class OnlineSessionManager : MonoBehaviour
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!_net) return;
|
||||
_net.OnServerStarted += OnNetworkStarted;
|
||||
_net.OnClientStarted += OnNetworkStarted;
|
||||
_net.OnServerStopped += OnNetworkStopped;
|
||||
_net.OnClientStopped += OnNetworkStopped;
|
||||
}
|
||||
@@ -61,10 +73,14 @@ public class OnlineSessionManager : MonoBehaviour
|
||||
private void OnDisable()
|
||||
{
|
||||
if (!_net) return;
|
||||
_net.OnServerStarted -= OnNetworkStarted;
|
||||
_net.OnClientStarted -= OnNetworkStarted;
|
||||
_net.OnServerStopped -= OnNetworkStopped;
|
||||
_net.OnClientStopped -= OnNetworkStopped;
|
||||
}
|
||||
|
||||
private void OnNetworkStarted() => Changed?.Invoke();
|
||||
|
||||
// The match-end / disconnect / Shutdown paths stop the NetworkManager without telling UGS. Leave the
|
||||
// session here too so the Relay allocation and the public lobby entry are released.
|
||||
private void OnNetworkStopped(bool _)
|
||||
@@ -72,6 +88,7 @@ public class OnlineSessionManager : MonoBehaviour
|
||||
var stale = _session;
|
||||
_session = null;
|
||||
if (stale != null && !_leaving) LeaveSilently(stale);
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
private async void LeaveSilently(ISession session)
|
||||
@@ -80,6 +97,27 @@ public class OnlineSessionManager : MonoBehaviour
|
||||
catch (Exception e) { Debug.LogWarning($"[OnlineSession] leave-on-stop failed: {e.Message}"); }
|
||||
}
|
||||
|
||||
// ----- Public API (driven by OnlineSessionBrowserUI) -------------------------------------------------
|
||||
|
||||
public void Host() => HostAsync();
|
||||
public void Refresh() => RefreshAsync();
|
||||
public void Join(string sessionId) => JoinAsync(sessionId);
|
||||
public void Disconnect() => DisconnectAsync();
|
||||
|
||||
// ----- Implementation --------------------------------------------------------------------------------
|
||||
|
||||
private void SetStatus(string status)
|
||||
{
|
||||
_status = status;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
private void SetBusy(bool busy)
|
||||
{
|
||||
_busy = busy;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task EnsureSignedInAsync()
|
||||
{
|
||||
if (UnityServices.State != ServicesInitializationState.Initialized)
|
||||
@@ -113,131 +151,100 @@ public class OnlineSessionManager : MonoBehaviour
|
||||
|
||||
private async void HostAsync()
|
||||
{
|
||||
if (_busy) return;
|
||||
_busy = true;
|
||||
if (_busy || IsConnected) return;
|
||||
SetBusy(true);
|
||||
try
|
||||
{
|
||||
_status = "Signing in…";
|
||||
SetStatus("Signing in…");
|
||||
await EnsureSignedInAsync();
|
||||
_status = "Creating session…";
|
||||
SetStatus("Creating session…");
|
||||
var options = new SessionOptions
|
||||
{
|
||||
Name = string.IsNullOrWhiteSpace(_hostName) ? "Chameleon game" : _hostName.Trim(),
|
||||
MaxPlayers = Mathf.Max(2, maxPlayers)
|
||||
}.WithRelayNetwork();
|
||||
_session = await MultiplayerService.Instance.CreateSessionAsync(options);
|
||||
_status = $"Hosting \"{_session.Name}\" (code {_session.Code})";
|
||||
SetStatus($"Hosting \"{_session.Name}\" (code {_session.Code})");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_status = $"Host failed: {e.Message}";
|
||||
SetStatus($"Host failed: {e.Message}");
|
||||
Debug.LogException(e);
|
||||
}
|
||||
finally { _busy = false; }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
private async void RefreshAsync()
|
||||
{
|
||||
if (_busy) return;
|
||||
_busy = true;
|
||||
SetBusy(true);
|
||||
try
|
||||
{
|
||||
_status = "Signing in…";
|
||||
SetStatus("Signing in…");
|
||||
await EnsureSignedInAsync();
|
||||
_status = "Querying sessions…";
|
||||
SetStatus("Querying sessions…");
|
||||
var results = await MultiplayerService.Instance.QuerySessionsAsync(new QuerySessionsOptions());
|
||||
_browse = results.Sessions;
|
||||
_status = $"{_browse.Count} session(s) found";
|
||||
_sessions = new List<ISessionInfo>(results.Sessions);
|
||||
SessionsChanged?.Invoke();
|
||||
SetStatus($"{_sessions.Count} session(s) found");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_status = $"Refresh failed: {e.Message}";
|
||||
SetStatus($"Refresh failed: {e.Message}");
|
||||
Debug.LogException(e);
|
||||
}
|
||||
finally { _busy = false; }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
private async void JoinAsync(string sessionId)
|
||||
{
|
||||
if (_busy) return;
|
||||
_busy = true;
|
||||
if (_busy || IsConnected || string.IsNullOrEmpty(sessionId)) return;
|
||||
SetBusy(true);
|
||||
try
|
||||
{
|
||||
_status = "Signing in…";
|
||||
SetStatus("Signing in…");
|
||||
await EnsureSignedInAsync();
|
||||
_status = "Joining session…";
|
||||
SetStatus("Joining session…");
|
||||
_session = await MultiplayerService.Instance.JoinSessionByIdAsync(sessionId);
|
||||
_status = $"Joined \"{_session.Name}\"";
|
||||
SetStatus($"Joined \"{_session.Name}\"");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_status = $"Join failed: {e.Message}";
|
||||
SetStatus($"Join failed: {e.Message}");
|
||||
Debug.LogException(e);
|
||||
}
|
||||
finally { _busy = false; }
|
||||
finally { SetBusy(false); }
|
||||
}
|
||||
|
||||
private async void LeaveAsync()
|
||||
private async void DisconnectAsync()
|
||||
{
|
||||
if (_busy || _session == null) return;
|
||||
_busy = true;
|
||||
_leaving = true;
|
||||
try
|
||||
if (_busy) return;
|
||||
|
||||
// A UGS session leaves cleanly (LeaveAsync also tears down the NGO connection). A plain loopback
|
||||
// host/client - if one is ever active without a session - just shuts the transport down.
|
||||
if (_session != null)
|
||||
{
|
||||
_status = "Leaving…";
|
||||
await _session.LeaveAsync(); // also tears down the NGO connection
|
||||
_status = "Left session";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_status = $"Leave failed: {e.Message}";
|
||||
Debug.LogException(e);
|
||||
}
|
||||
finally { _session = null; _leaving = false; _busy = false; }
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!showGui || !_net) return;
|
||||
|
||||
GUILayout.BeginArea(new Rect(10, 180, 340, 360), GUI.skin.box);
|
||||
GUILayout.Label("Online (UGS Relay + Lobby)");
|
||||
GUILayout.Label(_status);
|
||||
|
||||
GUI.enabled = !_busy;
|
||||
if (!_net.IsListening)
|
||||
{
|
||||
GUILayout.Label("Session name:");
|
||||
_hostName = GUILayout.TextField(_hostName);
|
||||
if (GUILayout.Button("Host Online")) HostAsync();
|
||||
|
||||
GUILayout.Space(6);
|
||||
if (GUILayout.Button("Refresh List")) RefreshAsync();
|
||||
|
||||
_scroll = GUILayout.BeginScrollView(_scroll, GUILayout.Height(180));
|
||||
if (_browse.Count == 0)
|
||||
GUILayout.Label("No sessions. Click Refresh.");
|
||||
foreach (var s in _browse)
|
||||
SetBusy(true);
|
||||
_leaving = true;
|
||||
var session = _session;
|
||||
_session = null;
|
||||
try
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
int taken = Mathf.Max(0, s.MaxPlayers - s.AvailableSlots);
|
||||
GUILayout.Label($"{s.Name} ({taken}/{s.MaxPlayers})");
|
||||
if (GUILayout.Button("Join", GUILayout.Width(60))) JoinAsync(s.Id);
|
||||
GUILayout.EndHorizontal();
|
||||
SetStatus("Leaving…");
|
||||
await session.LeaveAsync();
|
||||
SetStatus("Left session");
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
catch (Exception e)
|
||||
{
|
||||
SetStatus($"Leave failed: {e.Message}");
|
||||
Debug.LogException(e);
|
||||
}
|
||||
finally { _leaving = false; SetBusy(false); }
|
||||
}
|
||||
else if (_session != null)
|
||||
else if (IsConnected)
|
||||
{
|
||||
GUILayout.Label($"In session: {_session.Name}");
|
||||
if (GUILayout.Button("Leave Session")) LeaveAsync();
|
||||
_net.Shutdown();
|
||||
SetStatus("Disconnected");
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Local (loopback) session active.");
|
||||
}
|
||||
GUI.enabled = true;
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user