Files
megakoop/UI/Scripts/MultiplayerLobbyController.cs
2025-10-05 18:18:12 +02:00

940 lines
30 KiB
C#

using UnityEngine;
using UnityEngine.UIElements;
using System.Collections.Generic;
using System.Linq;
using MegaKoop.Steam;
namespace MegaKoop.UI
{
/// <summary>
/// Professional Multiplayer Lobby Controller with Steam API Integration
/// Senior-level implementation with proper state management
/// </summary>
public class MultiplayerLobbyController : MonoBehaviour
{
[Header("UI Document")]
[SerializeField] private UIDocument uiDocument;
[Header("Lobby Settings")]
[SerializeField] private int maxPlayers = 4;
[SerializeField] private string[] maxPlayersOptions = { "2", "3", "4", "8" };
// Root element
private VisualElement root;
// Panel
private VisualElement multiplayerPanel;
// Header Elements
private Label lobbyStatusLabel;
private VisualElement statusIndicator;
// Lobby Code Section
private VisualElement lobbyCodeSection;
private Label lobbyCodeDisplay;
private Button copyCodeButton;
// Connection Section
private VisualElement lobbyConnectionSection;
private Button hostLobbyButton;
private Button joinLobbyButton;
private VisualElement joinLobbyContent;
private VisualElement hostLobbyContent;
private TextField lobbyCodeInput;
private Button connectButton;
private Button createLobbyButton;
private DropdownField maxPlayersDropdown;
private Toggle publicLobbyToggle;
// Players Section
private VisualElement playersSection;
private Label playerCountLabel;
private ScrollView playersListScrollView;
private VisualElement playersList;
private VisualElement emptyPlayersState;
// Host Controls
private VisualElement hostControlsSection;
private Button inviteFriendsButton;
private Button kickPlayerButton;
// Player Ready Section
private VisualElement playerReadySection;
private Button toggleReadyButton;
private Label readyIcon;
private Label readyText;
// Footer
private Button startGameButton;
private Button leaveLobbyButton;
private Button backFromMultiplayerButton;
// Lobby State
private bool isHost = false;
private bool isInLobby = false;
private bool isPlayerReady = false;
private List<PlayerInfo> connectedPlayers = new List<PlayerInfo>();
private string currentLobbyCode = "";
private string selectedPlayerSteamId = "";
// Steam Integration
private SteamLobbyService steam;
private void Start()
{
if (uiDocument == null)
uiDocument = GetComponent<UIDocument>();
if (uiDocument != null)
{
root = uiDocument.rootVisualElement;
InitializeElements();
RegisterEvents();
EnsureSteamServices();
RegisterSteamEvents();
SetupInitialState();
}
else
{
Debug.LogError("UIDocument not found on MultiplayerLobbyController!");
}
}
private void OnDisable()
{
UnregisterEvents();
UnregisterSteamEvents();
}
#region Initialization
private void InitializeElements()
{
// Panel
multiplayerPanel = root.Q<VisualElement>("MultiplayerPanel");
// Header
lobbyStatusLabel = root.Q<Label>("LobbyStatusLabel");
statusIndicator = root.Q<VisualElement>("StatusIndicator");
// Lobby Code Section
lobbyCodeSection = root.Q<VisualElement>("LobbyCodeSection");
lobbyCodeDisplay = root.Q<Label>("LobbyCodeDisplay");
copyCodeButton = root.Q<Button>("CopyCodeButton");
// Connection Section
lobbyConnectionSection = root.Q<VisualElement>("LobbyConnectionSection");
hostLobbyButton = root.Q<Button>("HostLobbyButton");
joinLobbyButton = root.Q<Button>("JoinLobbyButton");
joinLobbyContent = root.Q<VisualElement>("JoinLobbyContent");
hostLobbyContent = root.Q<VisualElement>("HostLobbyContent");
lobbyCodeInput = root.Q<TextField>("LobbyCodeInput");
connectButton = root.Q<Button>("ConnectButton");
createLobbyButton = root.Q<Button>("CreateLobbyButton");
maxPlayersDropdown = root.Q<DropdownField>("MaxPlayersDropdown");
publicLobbyToggle = root.Q<Toggle>("PublicLobbyToggle");
// Players Section
playersSection = root.Q<VisualElement>("PlayersSection");
playerCountLabel = root.Q<Label>("PlayerCountLabel");
playersListScrollView = root.Q<ScrollView>("PlayersListScrollView");
playersList = root.Q<VisualElement>("PlayersList");
emptyPlayersState = root.Q<VisualElement>("EmptyPlayersState");
// Host Controls
hostControlsSection = root.Q<VisualElement>("HostControlsSection");
inviteFriendsButton = root.Q<Button>("InviteFriendsButton");
kickPlayerButton = root.Q<Button>("KickPlayerButton");
// Player Ready Section
playerReadySection = root.Q<VisualElement>("PlayerReadySection");
toggleReadyButton = root.Q<Button>("ToggleReadyButton");
readyIcon = root.Q<Label>("ReadyIcon");
readyText = root.Q<Label>("ReadyText");
// Footer
startGameButton = root.Q<Button>("StartGameButton");
leaveLobbyButton = root.Q<Button>("LeaveLobbyButton");
backFromMultiplayerButton = root.Q<Button>("BackFromMultiplayerButton");
// Setup dropdowns
if (maxPlayersDropdown != null)
{
maxPlayersDropdown.choices = maxPlayersOptions.ToList();
maxPlayersDropdown.value = maxPlayers.ToString();
}
}
private void SetupInitialState()
{
// Show host content by default
ShowHostContent();
// Hide sections that require lobby
HideLobbyDependentSections();
UpdateUI();
}
#endregion
#region Steam Integration
private void EnsureSteamServices()
{
if (steam == null)
{
steam = GetComponent<SteamLobbyService>();
if (steam == null)
{
var go = GameObject.Find("SteamServices") ?? new GameObject("SteamServices");
if (go.GetComponent<SteamManager>() == null) go.AddComponent<SteamManager>();
steam = go.GetComponent<SteamLobbyService>() ?? go.AddComponent<SteamLobbyService>();
DontDestroyOnLoad(go);
}
}
}
private void RegisterSteamEvents()
{
if (steam == null) return;
steam.OnLobbyCreated += OnSteamLobbyCreated;
steam.OnLobbyEntered += OnSteamLobbyEntered;
steam.OnLobbyLeft += OnSteamLobbyLeft;
steam.OnMembersChanged += OnSteamMembersChanged;
steam.OnLobbyDataUpdated += OnSteamLobbyDataUpdated;
steam.OnJoinFailed += OnSteamJoinFailed;
}
private void UnregisterSteamEvents()
{
if (steam == null) return;
steam.OnLobbyCreated -= OnSteamLobbyCreated;
steam.OnLobbyEntered -= OnSteamLobbyEntered;
steam.OnLobbyLeft -= OnSteamLobbyLeft;
steam.OnMembersChanged -= OnSteamMembersChanged;
steam.OnLobbyDataUpdated -= OnSteamLobbyDataUpdated;
steam.OnJoinFailed -= OnSteamJoinFailed;
}
private void OnSteamLobbyCreated()
{
if (steam == null) return;
isHost = steam.IsHost;
isInLobby = true;
currentLobbyCode = steam.LobbyCode;
RebuildPlayersFromSteam();
UpdateUI();
}
private void OnSteamLobbyEntered()
{
if (steam == null) return;
isHost = steam.IsHost;
isInLobby = true;
currentLobbyCode = steam.LobbyCode;
RebuildPlayersFromSteam();
UpdateUI();
}
private void OnSteamLobbyLeft()
{
isHost = false;
isInLobby = false;
isPlayerReady = false;
currentLobbyCode = string.Empty;
selectedPlayerSteamId = string.Empty;
connectedPlayers.Clear();
SetupInitialState();
UpdateUI();
}
private void OnSteamMembersChanged()
{
RebuildPlayersFromSteam();
UpdateUI();
}
private void OnSteamLobbyDataUpdated()
{
if (steam == null) return;
currentLobbyCode = steam.LobbyCode;
RebuildPlayersFromSteam();
UpdateUI();
}
private void OnSteamJoinFailed(string reason)
{
Debug.LogWarning($"Join failed: {reason}");
}
private void RebuildPlayersFromSteam()
{
if (steam == null || !steam.IsInLobby) return;
connectedPlayers.Clear();
var members = steam.GetMembers();
foreach (var m in members)
{
connectedPlayers.Add(new PlayerInfo
{
steamId = m.steamId,
playerName = string.IsNullOrEmpty(m.name) ? "Player" : m.name,
isHost = m.isHost,
isReady = m.isReady
});
}
}
#endregion
#region Event Registration
private void RegisterEvents()
{
if (hostLobbyButton != null)
hostLobbyButton.clicked += OnHostTabClicked;
if (joinLobbyButton != null)
joinLobbyButton.clicked += OnJoinTabClicked;
if (createLobbyButton != null)
createLobbyButton.clicked += OnCreateLobbyClicked;
if (connectButton != null)
connectButton.clicked += OnConnectClicked;
if (copyCodeButton != null)
copyCodeButton.clicked += OnCopyCodeClicked;
if (inviteFriendsButton != null)
inviteFriendsButton.clicked += OnInviteFriendsClicked;
if (kickPlayerButton != null)
kickPlayerButton.clicked += OnKickPlayerClicked;
if (toggleReadyButton != null)
toggleReadyButton.clicked += OnToggleReadyClicked;
if (startGameButton != null)
startGameButton.clicked += OnStartGameClicked;
if (leaveLobbyButton != null)
leaveLobbyButton.clicked += OnLeaveLobbyClicked;
if (backFromMultiplayerButton != null)
backFromMultiplayerButton.clicked += OnBackClicked;
}
private void UnregisterEvents()
{
if (hostLobbyButton != null)
hostLobbyButton.clicked -= OnHostTabClicked;
if (joinLobbyButton != null)
joinLobbyButton.clicked -= OnJoinTabClicked;
if (createLobbyButton != null)
createLobbyButton.clicked -= OnCreateLobbyClicked;
if (connectButton != null)
connectButton.clicked -= OnConnectClicked;
if (copyCodeButton != null)
copyCodeButton.clicked -= OnCopyCodeClicked;
if (inviteFriendsButton != null)
inviteFriendsButton.clicked -= OnInviteFriendsClicked;
if (kickPlayerButton != null)
kickPlayerButton.clicked -= OnKickPlayerClicked;
if (toggleReadyButton != null)
toggleReadyButton.clicked -= OnToggleReadyClicked;
if (startGameButton != null)
startGameButton.clicked -= OnStartGameClicked;
if (leaveLobbyButton != null)
leaveLobbyButton.clicked -= OnLeaveLobbyClicked;
if (backFromMultiplayerButton != null)
backFromMultiplayerButton.clicked -= OnBackClicked;
}
#endregion
#region Button Callbacks
private void OnHostTabClicked()
{
ShowHostContent();
}
private void OnJoinTabClicked()
{
ShowJoinContent();
}
private void OnCreateLobbyClicked()
{
CreateLobby();
}
private void OnConnectClicked()
{
string code = lobbyCodeInput?.value ?? "";
if (!string.IsNullOrEmpty(code) && code.Length == 6)
{
JoinLobby(code);
}
else
{
Debug.LogWarning("Invalid lobby code! Must be 6 characters.");
}
}
private void OnCopyCodeClicked()
{
if (!string.IsNullOrEmpty(currentLobbyCode))
{
GUIUtility.systemCopyBuffer = currentLobbyCode;
Debug.Log($"Lobby code copied: {currentLobbyCode}");
// Visual feedback
if (copyCodeButton != null)
{
string originalText = copyCodeButton.text;
copyCodeButton.text = "COPIED!";
copyCodeButton.schedule.Execute(() => copyCodeButton.text = originalText).StartingIn(1500);
}
}
}
private void OnInviteFriendsClicked()
{
if (steam != null && isInLobby)
{
steam.InviteFriends();
}
else
{
Debug.Log("Invite: not in lobby or steam service missing");
}
}
private void OnKickPlayerClicked()
{
if (!string.IsNullOrEmpty(selectedPlayerSteamId) && isHost)
{
KickPlayer(selectedPlayerSteamId);
}
else
{
Debug.LogWarning("No player selected to kick!");
}
}
private void OnToggleReadyClicked()
{
TogglePlayerReady();
}
private void OnStartGameClicked()
{
if (isHost && isInLobby)
{
StartGame();
}
}
private void OnLeaveLobbyClicked()
{
LeaveLobby();
}
private void OnBackClicked()
{
HideMultiplayerPanel();
}
#endregion
#region Lobby Management
public void ShowMultiplayerPanel()
{
multiplayerPanel?.RemoveFromClassList("hidden");
}
public void HideMultiplayerPanel()
{
multiplayerPanel?.AddToClassList("hidden");
}
private void CreateLobby()
{
// Parse max players
if (maxPlayersDropdown != null && int.TryParse(maxPlayersDropdown.value, out int maxP))
maxPlayers = maxP;
if (steam != null)
{
bool isPublic = publicLobbyToggle == null || publicLobbyToggle.value;
steam.CreateLobby(maxPlayers, isPublic);
}
else
{
// Fallback mock
isHost = true; isInLobby = true; currentLobbyCode = GenerateLobbyCode();
connectedPlayers.Clear();
connectedPlayers.Add(new PlayerInfo { steamId = "HOST_ID", playerName = "Host Player", isHost = true, isReady = true });
UpdateUI();
}
}
private void JoinLobby(string lobbyCode)
{
if (steam != null)
{
steam.JoinLobbyByCode(lobbyCode);
}
else
{
// Fallback mock
isHost = false; isInLobby = true; isPlayerReady = false; currentLobbyCode = lobbyCode;
connectedPlayers.Clear();
connectedPlayers.Add(new PlayerInfo { steamId = "HOST_ID", playerName = "Host Player", isHost = true, isReady = true });
connectedPlayers.Add(new PlayerInfo { steamId = "MY_ID", playerName = "My Player", isHost = false, isReady = false });
UpdateUI();
}
}
private void LeaveLobby()
{
if (steam != null && steam.IsInLobby)
{
steam.LeaveLobby();
}
else
{
isHost = false; isInLobby = false; isPlayerReady = false; currentLobbyCode = ""; selectedPlayerSteamId = ""; connectedPlayers.Clear();
SetupInitialState(); UpdateUI();
}
}
private void StartGame()
{
if (!isHost)
{
Debug.LogWarning("Only host can start the game!");
return;
}
// Check if all players are ready
bool allReady = connectedPlayers.All(p => p.isReady);
if (!allReady)
{
Debug.LogWarning("Not all players are ready!");
return;
}
// Signal via Steam lobby data (host)
if (steam != null && steam.IsInLobby)
{
steam.StartGameSignal();
}
Debug.Log("Starting game...");
// Load game scene
// UnityEngine.SceneManagement.SceneManager.LoadScene("GameScene");
}
private void TogglePlayerReady()
{
if (isHost) return; // Host is always ready
isPlayerReady = !isPlayerReady;
// Steam ready flag
if (steam != null && steam.IsInLobby)
{
steam.SetReady(isPlayerReady);
}
// Update local player ready state
var myPlayer = connectedPlayers.Find(p => p.steamId == "MY_ID");
if (myPlayer != null)
{
myPlayer.isReady = isPlayerReady;
}
Debug.Log($"Player ready status: {isPlayerReady}");
UpdateUI();
}
private void KickPlayer(string steamId)
{
if (!isHost || steamId == "HOST_ID") return;
if (steam != null && steam.IsInLobby)
{
steam.Kick(steamId);
}
connectedPlayers.RemoveAll(p => p.steamId == steamId);
selectedPlayerSteamId = "";
Debug.Log($"Kicked player: {steamId}");
UpdateUI();
}
#endregion
#region UI Updates
private void UpdateUI()
{
UpdateStatusBadge();
UpdateLobbyCodeSection();
UpdateConnectionSection();
UpdatePlayersList();
UpdateHostControls();
UpdatePlayerReadySection();
UpdateFooterButtons();
}
private void UpdateStatusBadge()
{
if (lobbyStatusLabel != null)
{
if (isInLobby)
{
lobbyStatusLabel.text = isHost ? "HOSTING" : "CONNECTED";
lobbyStatusLabel.AddToClassList("connected");
statusIndicator?.AddToClassList("connected");
}
else
{
lobbyStatusLabel.text = "OFFLINE";
lobbyStatusLabel.RemoveFromClassList("connected");
statusIndicator?.RemoveFromClassList("connected");
}
}
}
private void UpdateLobbyCodeSection()
{
if (lobbyCodeSection != null)
{
if (isInLobby && !string.IsNullOrEmpty(currentLobbyCode))
{
lobbyCodeSection.RemoveFromClassList("hidden");
if (lobbyCodeDisplay != null)
lobbyCodeDisplay.text = currentLobbyCode;
}
else
{
lobbyCodeSection.AddToClassList("hidden");
}
}
}
private void UpdateConnectionSection()
{
if (lobbyConnectionSection != null)
{
if (isInLobby)
{
lobbyConnectionSection.AddToClassList("hidden");
}
else
{
lobbyConnectionSection.RemoveFromClassList("hidden");
}
}
}
private void UpdatePlayersList()
{
if (playersList == null) return;
// Update count
if (playerCountLabel != null)
{
playerCountLabel.text = $"{connectedPlayers.Count}/{maxPlayers}";
}
// Clear list
playersList.Clear();
// Show/hide empty state
if (emptyPlayersState != null)
{
if (connectedPlayers.Count == 0)
{
emptyPlayersState.RemoveFromClassList("hidden");
}
else
{
emptyPlayersState.AddToClassList("hidden");
}
}
// Add players
foreach (var player in connectedPlayers)
{
var playerItem = CreatePlayerItem(player);
playersList.Add(playerItem);
}
}
private VisualElement CreatePlayerItem(PlayerInfo player)
{
var item = new VisualElement();
item.AddToClassList("player-item");
if (player.isHost)
item.AddToClassList("host");
if (player.steamId == selectedPlayerSteamId)
item.AddToClassList("selected");
// Player info container
var playerInfo = new VisualElement();
playerInfo.AddToClassList("player-info");
// Avatar
var avatar = new VisualElement();
avatar.AddToClassList("player-avatar");
playerInfo.Add(avatar);
// Name and badge
var nameContainer = new VisualElement();
nameContainer.style.flexDirection = FlexDirection.Row;
nameContainer.style.alignItems = Align.Center;
var nameLabel = new Label(player.playerName);
nameLabel.AddToClassList("player-name");
nameContainer.Add(nameLabel);
if (player.isHost)
{
var hostBadge = new Label("HOST");
hostBadge.AddToClassList("player-host-badge");
nameContainer.Add(hostBadge);
}
playerInfo.Add(nameContainer);
item.Add(playerInfo);
// Status container
var statusContainer = new VisualElement();
statusContainer.AddToClassList("player-status");
var readyIndicator = new VisualElement();
readyIndicator.AddToClassList("player-ready-indicator");
if (player.isReady)
readyIndicator.AddToClassList("ready");
statusContainer.Add(readyIndicator);
var readyLabel = new Label(player.isReady ? "READY" : "NOT READY");
readyLabel.AddToClassList("player-ready-text");
if (player.isReady)
readyLabel.AddToClassList("ready");
statusContainer.Add(readyLabel);
item.Add(statusContainer);
// Click event for selection (host only)
if (isHost && !player.isHost)
{
item.RegisterCallback<ClickEvent>(evt =>
{
selectedPlayerSteamId = player.steamId;
UpdatePlayersList();
});
}
return item;
}
private void UpdateHostControls()
{
if (hostControlsSection != null)
{
if (isHost && isInLobby)
{
hostControlsSection.RemoveFromClassList("hidden");
}
else
{
hostControlsSection.AddToClassList("hidden");
}
}
// Enable/disable kick button
if (kickPlayerButton != null)
{
kickPlayerButton.SetEnabled(!string.IsNullOrEmpty(selectedPlayerSteamId));
}
}
private void UpdatePlayerReadySection()
{
if (playerReadySection != null)
{
if (!isHost && isInLobby)
{
playerReadySection.RemoveFromClassList("hidden");
// Update ready button appearance
if (toggleReadyButton != null)
{
if (isPlayerReady)
{
toggleReadyButton.AddToClassList("ready");
if (readyText != null) readyText.text = "READY";
}
else
{
toggleReadyButton.RemoveFromClassList("ready");
if (readyText != null) readyText.text = "NOT READY";
}
}
}
else
{
playerReadySection.AddToClassList("hidden");
}
}
}
private void UpdateFooterButtons()
{
// Start game button (host only, in lobby)
if (startGameButton != null)
{
if (isHost && isInLobby)
{
startGameButton.RemoveFromClassList("hidden");
// Enable only if all players ready
bool allReady = connectedPlayers.All(p => p.isReady);
startGameButton.SetEnabled(allReady && connectedPlayers.Count >= 2);
}
else
{
startGameButton.AddToClassList("hidden");
}
}
// Leave lobby button
if (leaveLobbyButton != null)
{
if (isInLobby)
{
leaveLobbyButton.RemoveFromClassList("hidden");
}
else
{
leaveLobbyButton.AddToClassList("hidden");
}
}
}
#endregion
#region UI State Management
private void ShowHostContent()
{
hostLobbyButton?.AddToClassList("primary-button");
joinLobbyButton?.RemoveFromClassList("primary-button");
hostLobbyContent?.RemoveFromClassList("hidden");
joinLobbyContent?.AddToClassList("hidden");
}
private void ShowJoinContent()
{
joinLobbyButton?.AddToClassList("primary-button");
hostLobbyButton?.RemoveFromClassList("primary-button");
joinLobbyContent?.RemoveFromClassList("hidden");
hostLobbyContent?.AddToClassList("hidden");
}
private void HideLobbyDependentSections()
{
lobbyCodeSection?.AddToClassList("hidden");
hostControlsSection?.AddToClassList("hidden");
playerReadySection?.AddToClassList("hidden");
startGameButton?.AddToClassList("hidden");
leaveLobbyButton?.AddToClassList("hidden");
}
#endregion
#region Helper Methods
private string GenerateLobbyCode()
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var code = "";
for (int i = 0; i < 6; i++)
{
code += chars[Random.Range(0, chars.Length)];
}
return code;
}
// Public methods for Steam API callbacks
public void OnPlayerJoinedLobby(string steamId, string playerName)
{
var player = new PlayerInfo
{
steamId = steamId,
playerName = playerName,
isHost = false,
isReady = false
};
connectedPlayers.Add(player);
UpdateUI();
Debug.Log($"Player joined: {playerName}");
}
public void OnPlayerLeftLobby(string steamId)
{
connectedPlayers.RemoveAll(p => p.steamId == steamId);
if (selectedPlayerSteamId == steamId)
selectedPlayerSteamId = "";
UpdateUI();
Debug.Log($"Player left: {steamId}");
}
public void OnPlayerReadyChanged(string steamId, bool isReady)
{
var player = connectedPlayers.Find(p => p.steamId == steamId);
if (player != null)
{
player.isReady = isReady;
UpdateUI();
}
}
#endregion
}
/// <summary>
/// Player information in lobby
/// </summary>
[System.Serializable]
public class PlayerInfo
{
public string steamId;
public string playerName;
public bool isHost;
public bool isReady;
}
}