Animace + Menu
This commit is contained in:
@@ -11,10 +11,14 @@ namespace MegaKoop.UI
|
||||
{
|
||||
[Header("UI Document")]
|
||||
[SerializeField] private UIDocument uiDocument;
|
||||
[SerializeField] private StyleSheet mainMenuStyles;
|
||||
|
||||
[Header("Scene Names")]
|
||||
[SerializeField] private string gameSceneName = "GameScene";
|
||||
|
||||
[Header("Controllers")]
|
||||
[SerializeField] private MultiplayerLobbyController multiplayerLobbyController;
|
||||
|
||||
[Header("Audio")]
|
||||
[SerializeField] private AudioSource audioSource;
|
||||
[SerializeField] private AudioClip buttonClickSound;
|
||||
@@ -28,7 +32,6 @@ namespace MegaKoop.UI
|
||||
private Button continueButton;
|
||||
private Button multiplayerButton;
|
||||
private Button settingsButton;
|
||||
private Button creditsButton;
|
||||
private Button quitButton;
|
||||
|
||||
// Settings Panel Elements
|
||||
@@ -47,8 +50,31 @@ namespace MegaKoop.UI
|
||||
// Získání UI Document
|
||||
if (uiDocument == null)
|
||||
uiDocument = GetComponent<UIDocument>();
|
||||
// If there's no UIDocument, this scene likely uses UGUI. Disable this Toolkit controller.
|
||||
if (uiDocument == null)
|
||||
{
|
||||
Debug.LogWarning("[MainMenuController] UIDocument not found. This script is for UI Toolkit. If you use UGUI, remove this component.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Získání MultiplayerLobbyController
|
||||
if (multiplayerLobbyController == null)
|
||||
multiplayerLobbyController = GetComponent<MultiplayerLobbyController>();
|
||||
|
||||
root = uiDocument.rootVisualElement;
|
||||
if (root == null)
|
||||
{
|
||||
Debug.LogWarning("[MainMenuController] rootVisualElement is null. Disabling Toolkit controller.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Přidání USS stylů (pokud nejsou už v UXML)
|
||||
if (mainMenuStyles != null && !root.styleSheets.Contains(mainMenuStyles))
|
||||
{
|
||||
root.styleSheets.Add(mainMenuStyles);
|
||||
}
|
||||
|
||||
// Inicializace UI elementů
|
||||
InitializeMenuButtons();
|
||||
@@ -75,7 +101,6 @@ namespace MegaKoop.UI
|
||||
continueButton = root.Q<Button>("ContinueButton");
|
||||
multiplayerButton = root.Q<Button>("MultiplayerButton");
|
||||
settingsButton = root.Q<Button>("SettingsButton");
|
||||
creditsButton = root.Q<Button>("CreditsButton");
|
||||
quitButton = root.Q<Button>("QuitButton");
|
||||
|
||||
// Kontrola, zda existuje uložená hra
|
||||
@@ -159,12 +184,6 @@ namespace MegaKoop.UI
|
||||
AddHoverEffects(settingsButton);
|
||||
}
|
||||
|
||||
if (creditsButton != null)
|
||||
{
|
||||
creditsButton.clicked += OnCreditsClicked;
|
||||
AddHoverEffects(creditsButton);
|
||||
}
|
||||
|
||||
if (quitButton != null)
|
||||
{
|
||||
quitButton.clicked += OnQuitClicked;
|
||||
@@ -191,7 +210,6 @@ namespace MegaKoop.UI
|
||||
if (continueButton != null) continueButton.clicked -= OnContinueClicked;
|
||||
if (multiplayerButton != null) multiplayerButton.clicked -= OnMultiplayerClicked;
|
||||
if (settingsButton != null) settingsButton.clicked -= OnSettingsClicked;
|
||||
if (creditsButton != null) creditsButton.clicked -= OnCreditsClicked;
|
||||
if (quitButton != null) quitButton.clicked -= OnQuitClicked;
|
||||
if (applyButton != null) applyButton.clicked -= OnApplySettingsClicked;
|
||||
if (backButton != null) backButton.clicked -= OnBackFromSettingsClicked;
|
||||
@@ -235,7 +253,15 @@ namespace MegaKoop.UI
|
||||
PlayButtonClickSound();
|
||||
Debug.Log("Opening Multiplayer Menu...");
|
||||
|
||||
// TODO: Implementovat multiplayer menu
|
||||
// Zobrazit multiplayer panel
|
||||
if (multiplayerLobbyController != null)
|
||||
{
|
||||
multiplayerLobbyController.ShowMultiplayerPanel();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("MultiplayerLobbyController reference is missing!");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSettingsClicked()
|
||||
@@ -245,14 +271,6 @@ namespace MegaKoop.UI
|
||||
ShowSettingsPanel();
|
||||
}
|
||||
|
||||
private void OnCreditsClicked()
|
||||
{
|
||||
PlayButtonClickSound();
|
||||
Debug.Log("Showing Credits...");
|
||||
|
||||
// TODO: Implementovat credits screen
|
||||
}
|
||||
|
||||
private void OnQuitClicked()
|
||||
{
|
||||
PlayButtonClickSound();
|
||||
|
||||
939
UI/Scripts/MultiplayerLobbyController.cs
Normal file
939
UI/Scripts/MultiplayerLobbyController.cs
Normal file
@@ -0,0 +1,939 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
2
UI/Scripts/MultiplayerLobbyController.cs.meta
Normal file
2
UI/Scripts/MultiplayerLobbyController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c87ad4288a40c146ac0b73e2d019347
|
||||
39
UI/Scripts/PanelSettingsSetup.cs
Normal file
39
UI/Scripts/PanelSettingsSetup.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace MegaKoop.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures Panel Settings are configured for proper scaling across all resolutions
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(UIDocument))]
|
||||
public class PanelSettingsSetup : MonoBehaviour
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
var uiDocument = GetComponent<UIDocument>();
|
||||
if (uiDocument != null && uiDocument.panelSettings != null)
|
||||
{
|
||||
var settings = uiDocument.panelSettings;
|
||||
|
||||
// Set Scale Mode to Scale With Screen Size for responsive design
|
||||
settings.scaleMode = PanelScaleMode.ScaleWithScreenSize;
|
||||
|
||||
// Reference resolution (base design resolution)
|
||||
settings.referenceResolution = new Vector2Int(1920, 1080);
|
||||
|
||||
// Match width or height (0.5 = balanced)
|
||||
settings.match = 0.5f;
|
||||
|
||||
// Sort order
|
||||
settings.sortingOrder = 0;
|
||||
|
||||
Debug.Log($"Panel Settings configured for responsive design: {settings.scaleMode}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("UIDocument or PanelSettings not found! UI may not scale properly.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
UI/Scripts/PanelSettingsSetup.cs.meta
Normal file
2
UI/Scripts/PanelSettingsSetup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35698d9033b8e6b4c85cf39ccb109f81
|
||||
229
UI/Scripts/UGUIMainMenuController.cs
Normal file
229
UI/Scripts/UGUIMainMenuController.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using TMPro;
|
||||
#else
|
||||
using TMPro;
|
||||
#endif
|
||||
|
||||
namespace MegaKoop.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UGUI Main Menu controller (no UI Toolkit). Handles switching panels and basic settings.
|
||||
/// </summary>
|
||||
public class UGUIMainMenuController : MonoBehaviour
|
||||
{
|
||||
// Panels
|
||||
[Header("Optional Panel Refs (assign to override auto-find)")]
|
||||
[SerializeField] private GameObject panelMainRef;
|
||||
[SerializeField] private GameObject panelSettingsRef;
|
||||
[SerializeField] private GameObject panelLobbyRef;
|
||||
|
||||
private GameObject panelMain;
|
||||
private GameObject panelSettings;
|
||||
private GameObject panelLobby;
|
||||
|
||||
// Main buttons
|
||||
private Button btnNewGame;
|
||||
private Button btnContinue;
|
||||
private Button btnMultiplayer;
|
||||
private Button btnSettings;
|
||||
private Button btnQuit;
|
||||
|
||||
// Settings controls
|
||||
private TMP_Dropdown ddQuality;
|
||||
private Toggle tgFullscreen;
|
||||
private TMP_Dropdown ddResolution;
|
||||
private Button btnApplySettings;
|
||||
private Button btnBackFromSettings;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Panels (created by UGUIMenuBuilder) – najdi i když jsou neaktivní
|
||||
RefreshPanelReferences();
|
||||
|
||||
// Buttons
|
||||
btnNewGame = FindButton("Button_NewGame");
|
||||
btnContinue = FindButton("Button_Continue");
|
||||
btnMultiplayer = FindButton("Button_Multiplayer");
|
||||
btnSettings = FindButton("Button_Settings");
|
||||
btnQuit = FindButton("Button_Quit");
|
||||
|
||||
// Settings
|
||||
ddQuality = FindDropdown("Dropdown_Quality");
|
||||
tgFullscreen = FindToggle("Toggle_Fullscreen");
|
||||
ddResolution = FindDropdown("Dropdown_Resolution");
|
||||
btnApplySettings = FindButton("Button_ApplySettings");
|
||||
btnBackFromSettings = FindButton("Button_BackFromSettings");
|
||||
|
||||
WireEvents();
|
||||
|
||||
// Ensure initial panels
|
||||
ShowMainMenu();
|
||||
}
|
||||
|
||||
// Allow UGUIMenuBuilder to inject panel refs directly
|
||||
public void SetPanels(GameObject main, GameObject settings, GameObject lobby)
|
||||
{
|
||||
panelMainRef = main;
|
||||
panelSettingsRef = settings;
|
||||
panelLobbyRef = lobby;
|
||||
RefreshPanelReferences();
|
||||
}
|
||||
|
||||
private void RefreshPanelReferences()
|
||||
{
|
||||
panelMain = panelMainRef ? panelMainRef : (FindAnyInScene("Panel_MainMenu") ?? panelMain);
|
||||
panelSettings = panelSettingsRef ? panelSettingsRef : (FindAnyInScene("Panel_Settings") ?? panelSettings);
|
||||
panelLobby = panelLobbyRef ? panelLobbyRef : (FindAnyInScene("Panel_Lobby") ?? panelLobby);
|
||||
}
|
||||
|
||||
private GameObject FindAnyInScene(string name)
|
||||
{
|
||||
// Najde i neaktivní objekty
|
||||
var all = Resources.FindObjectsOfTypeAll<Transform>();
|
||||
foreach (var t in all)
|
||||
{
|
||||
if (t.hideFlags != HideFlags.None) continue;
|
||||
var go = t.gameObject;
|
||||
if (!go.scene.IsValid() || !go.scene.isLoaded) continue;
|
||||
if (go.name == name) return go;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Button FindButton(string name)
|
||||
{
|
||||
var go = GameObject.Find(name);
|
||||
return go ? go.GetComponent<Button>() : null;
|
||||
}
|
||||
private TMP_Dropdown FindDropdown(string name)
|
||||
{
|
||||
var go = GameObject.Find(name);
|
||||
return go ? go.GetComponent<TMP_Dropdown>() : null;
|
||||
}
|
||||
private Toggle FindToggle(string name)
|
||||
{
|
||||
var go = GameObject.Find(name);
|
||||
return go ? go.GetComponent<Toggle>() : null;
|
||||
}
|
||||
|
||||
private void WireEvents()
|
||||
{
|
||||
if (btnNewGame) btnNewGame.onClick.AddListener(OnNewGame);
|
||||
if (btnContinue) btnContinue.onClick.AddListener(OnContinue);
|
||||
if (btnMultiplayer) btnMultiplayer.onClick.AddListener(OnOpenMultiplayer);
|
||||
if (btnSettings) btnSettings.onClick.AddListener(OnOpenSettings);
|
||||
if (btnQuit) btnQuit.onClick.AddListener(OnQuit);
|
||||
|
||||
if (btnApplySettings) btnApplySettings.onClick.AddListener(ApplySettings);
|
||||
if (btnBackFromSettings) btnBackFromSettings.onClick.AddListener(ShowMainMenu);
|
||||
}
|
||||
|
||||
private void ShowMainMenu()
|
||||
{
|
||||
if (panelMain) panelMain.SetActive(true);
|
||||
if (panelSettings) panelSettings.SetActive(false);
|
||||
if (panelLobby) panelLobby.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnOpenMultiplayer()
|
||||
{
|
||||
RefreshPanelReferences();
|
||||
bool showed = false;
|
||||
if (panelLobby)
|
||||
{
|
||||
if (panelMain) panelMain.SetActive(false);
|
||||
if (panelSettings) panelSettings.SetActive(false);
|
||||
panelLobby.SetActive(true);
|
||||
showed = true;
|
||||
}
|
||||
UGUIMultiplayerLobbyController lobby;
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
lobby = Object.FindFirstObjectByType<UGUIMultiplayerLobbyController>();
|
||||
#else
|
||||
lobby = Object.FindObjectOfType<UGUIMultiplayerLobbyController>();
|
||||
#endif
|
||||
if (lobby && showed) lobby.QuickHost(4, true);
|
||||
if (!showed)
|
||||
{
|
||||
Debug.LogError("[UGUIMainMenu] Panel_Lobby not found in scene. Did you run Tools > MegaKoop > Generate UGUI Main Menu?" );
|
||||
if (panelMain) panelMain.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOpenSettings()
|
||||
{
|
||||
if (panelMain) panelMain.SetActive(false);
|
||||
if (panelSettings) panelSettings.SetActive(true);
|
||||
if (panelLobby) panelLobby.SetActive(false);
|
||||
}
|
||||
|
||||
private void OnNewGame()
|
||||
{
|
||||
// Co-op only: quick host path
|
||||
UGUIMultiplayerLobbyController lobby;
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
lobby = Object.FindFirstObjectByType<UGUIMultiplayerLobbyController>();
|
||||
#else
|
||||
lobby = Object.FindObjectOfType<UGUIMultiplayerLobbyController>();
|
||||
#endif
|
||||
if (lobby)
|
||||
{
|
||||
if (panelMain) panelMain.SetActive(false);
|
||||
if (panelSettings) panelSettings.SetActive(false);
|
||||
if (panelLobby) panelLobby.SetActive(true);
|
||||
lobby.QuickHost(4, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnContinue()
|
||||
{
|
||||
// Co-op only: join path
|
||||
UGUIMultiplayerLobbyController lobby;
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
lobby = Object.FindFirstObjectByType<UGUIMultiplayerLobbyController>();
|
||||
#else
|
||||
lobby = Object.FindObjectOfType<UGUIMultiplayerLobbyController>();
|
||||
#endif
|
||||
if (lobby)
|
||||
{
|
||||
if (panelMain) panelMain.SetActive(false);
|
||||
if (panelSettings) panelSettings.SetActive(false);
|
||||
if (panelLobby) panelLobby.SetActive(true);
|
||||
lobby.ShowJoinTabPublic();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnQuit()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ApplySettings()
|
||||
{
|
||||
if (ddQuality)
|
||||
{
|
||||
QualitySettings.SetQualityLevel(ddQuality.value, true);
|
||||
}
|
||||
if (tgFullscreen)
|
||||
{
|
||||
Screen.fullScreen = tgFullscreen.isOn;
|
||||
}
|
||||
if (ddResolution)
|
||||
{
|
||||
var opt = ddResolution.options[ddResolution.value].text; // e.g., "1920x1080"
|
||||
var parts = opt.ToLower().Split('x');
|
||||
if (parts.Length == 2 && int.TryParse(parts[0], out int w) && int.TryParse(parts[1], out int h))
|
||||
{
|
||||
Screen.SetResolution(w, h, Screen.fullScreenMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
UI/Scripts/UGUIMainMenuController.cs.meta
Normal file
2
UI/Scripts/UGUIMainMenuController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40c1afbb68db70f42b5f4f11f33b69bb
|
||||
836
UI/Scripts/UGUIMultiplayerLobbyController.cs
Normal file
836
UI/Scripts/UGUIMultiplayerLobbyController.cs
Normal file
@@ -0,0 +1,836 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using MegaKoop.Steam;
|
||||
using MegaKoop.Networking;
|
||||
using TMPro;
|
||||
|
||||
namespace MegaKoop.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UGUI Multiplayer Lobby Controller integrated with SteamLobbyService.
|
||||
/// Builds players list dynamically, handles host/join/invite/kick/ready/start.
|
||||
/// </summary>
|
||||
public class UGUIMultiplayerLobbyController : MonoBehaviour
|
||||
{
|
||||
// Panels
|
||||
[Header("Optional Root Ref (assigned by builder)")]
|
||||
[SerializeField] private GameObject panelLobbyRef;
|
||||
private GameObject panelLobby;
|
||||
private GameObject groupJoin;
|
||||
private GameObject groupHost;
|
||||
|
||||
// Header & Code
|
||||
private TMP_Text textStatus;
|
||||
private TMP_Text textLobbyCodeValue;
|
||||
private Button btnCopyCode;
|
||||
|
||||
// Tabs
|
||||
private Button btnHostTab;
|
||||
private Button btnJoinTab;
|
||||
|
||||
// Join
|
||||
private TMP_InputField inputLobbyCode;
|
||||
private Button btnConnect;
|
||||
|
||||
// Host
|
||||
private TMP_Dropdown ddMaxPlayers;
|
||||
private Toggle tgPublicLobby;
|
||||
private Button btnCreateLobby;
|
||||
|
||||
// Players
|
||||
private TMP_Text textPlayerCount;
|
||||
private ScrollRect scrollPlayers;
|
||||
private Transform contentPlayers;
|
||||
private GameObject playerItemTemplate;
|
||||
private GameObject emptyPlayers;
|
||||
|
||||
// Friends picker (fallback when overlay unavailable)
|
||||
private GameObject panelFriends;
|
||||
private Transform contentFriends;
|
||||
private GameObject emptyFriends;
|
||||
private Button btnBackFromFriends;
|
||||
private Button btnCloseFriendsOverlay;
|
||||
|
||||
// Host controls
|
||||
private Button btnInviteFriends;
|
||||
private Button btnKickSelected;
|
||||
|
||||
// Ready & footer
|
||||
private Button btnToggleReady;
|
||||
private Button btnStartGame;
|
||||
private Button btnLeaveLobby;
|
||||
private Button btnBackFromLobby;
|
||||
|
||||
// Selection
|
||||
private string selectedPlayerSteamId = string.Empty;
|
||||
|
||||
// Steam service
|
||||
private SteamLobbyService steam;
|
||||
|
||||
// Local state cache
|
||||
private bool IsInLobby => steam != null && steam.IsInLobby;
|
||||
private bool IsHost => steam != null && steam.IsHost;
|
||||
private string LobbyCode => steam != null ? steam.LobbyCode : string.Empty;
|
||||
private bool clientStartedFromSignal = false;
|
||||
private bool leftDueToKick = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// Find UI (including inactive)
|
||||
panelLobby = FindAnyGO("Panel_Lobby");
|
||||
groupJoin = FindAnyGO("Group_Join");
|
||||
groupHost = FindAnyGO("Group_Host");
|
||||
|
||||
textStatus = FindText("Text_Status");
|
||||
textLobbyCodeValue = FindText("Text_LobbyCodeValue");
|
||||
btnCopyCode = FindButton("Button_CopyCode");
|
||||
|
||||
btnHostTab = FindButton("Button_HostTab");
|
||||
btnJoinTab = FindButton("Button_JoinTab");
|
||||
|
||||
inputLobbyCode = FindInput("Input_LobbyCode");
|
||||
btnConnect = FindButton("Button_Connect");
|
||||
|
||||
ddMaxPlayers = FindDropdown("Dropdown_MaxPlayers");
|
||||
tgPublicLobby = FindToggle("Toggle_PublicLobby");
|
||||
btnCreateLobby = FindButton("Button_CreateLobby");
|
||||
|
||||
textPlayerCount = FindText("Text_PlayerCount");
|
||||
scrollPlayers = FindScroll("Scroll_Players");
|
||||
contentPlayers = FindAnyGO("Content_PlayersList")?.transform;
|
||||
emptyPlayers = FindAnyGO("Empty_Players");
|
||||
playerItemTemplate = FindAnyGO("PlayerItemTemplate");
|
||||
|
||||
btnInviteFriends = FindButton("Button_InviteFriends");
|
||||
btnKickSelected = FindButton("Button_KickSelected");
|
||||
|
||||
btnToggleReady = FindButton("Button_ToggleReady");
|
||||
btnStartGame = FindButton("Button_StartGame");
|
||||
btnLeaveLobby = FindButton("Button_LeaveLobby");
|
||||
btnBackFromLobby = FindButton("Button_BackFromLobby");
|
||||
|
||||
// Friends picker
|
||||
panelFriends = FindAnyGO("Panel_Friends");
|
||||
contentFriends = FindAnyGO("Content_FriendsList")?.transform;
|
||||
emptyFriends = FindAnyGO("Empty_Friends");
|
||||
btnBackFromFriends = FindButton("Button_BackFromFriends");
|
||||
btnCloseFriendsOverlay = FindButton("Button_CloseFriendsOverlay");
|
||||
|
||||
EnsureSteamServices();
|
||||
RegisterSteamEvents();
|
||||
WireButtonEvents();
|
||||
ValidateUI();
|
||||
|
||||
// Initial view: Host tab
|
||||
ShowHostTab();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void ShowFriendsPanel()
|
||||
{
|
||||
if (panelFriends) panelFriends.SetActive(true);
|
||||
EnsureFriendsGridSetup();
|
||||
RebuildFriendsList();
|
||||
}
|
||||
|
||||
private void HideFriendsPanel()
|
||||
{
|
||||
if (panelFriends) panelFriends.SetActive(false);
|
||||
}
|
||||
|
||||
private void RebuildFriendsList()
|
||||
{
|
||||
if (contentFriends == null || steam == null) return;
|
||||
// Clear existing
|
||||
var toDestroy = new List<GameObject>();
|
||||
foreach (Transform child in contentFriends) toDestroy.Add(child.gameObject);
|
||||
foreach (var go in toDestroy) DestroyImmediate(go);
|
||||
|
||||
var friends = steam.GetFriends();
|
||||
if (friends.Count == 0)
|
||||
{
|
||||
if (emptyFriends) emptyFriends.SetActive(true);
|
||||
return;
|
||||
}
|
||||
if (emptyFriends) emptyFriends.SetActive(false);
|
||||
|
||||
foreach (var f in friends)
|
||||
{
|
||||
// Icon button cell only
|
||||
var cell = new GameObject($"Friend_{f.steamId}", typeof(RectTransform), typeof(Image), typeof(Button));
|
||||
cell.transform.SetParent(contentFriends, false);
|
||||
var img = cell.GetComponent<Image>();
|
||||
if (steam.TryGetAvatarSprite(f.steamId, out var spr, false)) { img.sprite = spr; img.color = Color.white; img.preserveAspect = true; }
|
||||
else { img.sprite = null; img.color = new Color(0.3f,0.6f,0.3f,1f); }
|
||||
var rt = (RectTransform)cell.transform; rt.sizeDelta = new Vector2(72,72);
|
||||
var btn = cell.GetComponent<Button>();
|
||||
string sid = f.steamId;
|
||||
btn.onClick.AddListener(() => { steam.InviteFriendBySteamId(sid); });
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshFriendsIcons()
|
||||
{
|
||||
foreach (Transform child in contentFriends)
|
||||
{
|
||||
if (child == null) continue;
|
||||
var img = child.GetComponent<Image>();
|
||||
if (img == null || img.sprite != null) continue;
|
||||
// Name format: Friend_<steamId>
|
||||
var name = child.name;
|
||||
const string prefix = "Friend_";
|
||||
if (!name.StartsWith(prefix)) continue;
|
||||
var sid = name.Substring(prefix.Length);
|
||||
if (steam.TryGetAvatarSprite(sid, out var spr, false))
|
||||
{
|
||||
img.sprite = spr; img.color = Color.white; img.preserveAspect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureFriendsGridSetup()
|
||||
{
|
||||
if (contentFriends == null) return;
|
||||
var grid = contentFriends.GetComponent<GridLayoutGroup>();
|
||||
if (grid == null)
|
||||
{
|
||||
var oldV = contentFriends.GetComponent<VerticalLayoutGroup>(); if (oldV) DestroyImmediate(oldV);
|
||||
grid = contentFriends.gameObject.AddComponent<GridLayoutGroup>();
|
||||
grid.cellSize = new Vector2(72,72);
|
||||
grid.spacing = new Vector2(10,10);
|
||||
grid.startAxis = GridLayoutGroup.Axis.Horizontal;
|
||||
var csf = contentFriends.GetComponent<ContentSizeFitter>(); if (csf == null) csf = contentFriends.gameObject.AddComponent<ContentSizeFitter>();
|
||||
csf.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Find an implementation of ISteamNGOAdapter in the scene (e.g., on NetworkManager)
|
||||
private ISteamNGOAdapter GetSteamAdapter()
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var monos = Object.FindObjectsByType<MonoBehaviour>(FindObjectsSortMode.None);
|
||||
#else
|
||||
var monos = Object.FindObjectsOfType<MonoBehaviour>();
|
||||
#endif
|
||||
foreach (var m in monos)
|
||||
{
|
||||
if (m is ISteamNGOAdapter adapter) return adapter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (panelFriends && panelFriends.activeSelf)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Escape)) HideFriendsPanel();
|
||||
}
|
||||
// While friends panel visible, keep trying to resolve avatars that were not ready yet
|
||||
if (panelFriends && panelFriends.activeSelf && contentFriends && steam != null)
|
||||
{
|
||||
RefreshFriendsIcons();
|
||||
}
|
||||
// Auto-start client when host signals start via lobby data
|
||||
if (!IsHost && IsInLobby && steam != null)
|
||||
{
|
||||
if (steam.IsStartSignaled() && !clientStartedFromSignal)
|
||||
{
|
||||
clientStartedFromSignal = true;
|
||||
#if UNITY_NETCODE || NETCODE_PRESENT
|
||||
// Configure transport with host SteamID before starting client
|
||||
var adapter = GetSteamAdapter();
|
||||
if (adapter != null)
|
||||
{
|
||||
if (ulong.TryParse(steam.HostSteamIdString, out var hostSid))
|
||||
adapter.ConfigureClient(hostSid);
|
||||
}
|
||||
NetworkBootstrap.StartClient("GameScene");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// If host flagged us as kicked, leave lobby
|
||||
if (steam != null && IsInLobby && !leftDueToKick && steam.IsKickedLocal())
|
||||
{
|
||||
leftDueToKick = true;
|
||||
steam.LeaveLobby();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
UnregisterSteamEvents();
|
||||
}
|
||||
|
||||
private GameObject FindAnyGO(string name)
|
||||
{
|
||||
// Prefer searching under provided root (more robust if scene has duplicates)
|
||||
if (panelLobbyRef)
|
||||
{
|
||||
var found = FindChildRecursive(panelLobbyRef.transform, name);
|
||||
if (found) return found.gameObject;
|
||||
}
|
||||
// Fallback: search globally (including inactive)
|
||||
var all = Resources.FindObjectsOfTypeAll<Transform>();
|
||||
foreach (var t in all)
|
||||
{
|
||||
if (t == null) continue;
|
||||
var go = t.gameObject;
|
||||
if (!go.scene.IsValid() || !go.scene.isLoaded) continue;
|
||||
if (go.name == name) return go;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Transform FindChildRecursive(Transform root, string name)
|
||||
{
|
||||
if (root.name == name) return root;
|
||||
for (int i = 0; i < root.childCount; i++)
|
||||
{
|
||||
var c = root.GetChild(i);
|
||||
var r = FindChildRecursive(c, name);
|
||||
if (r != null) return r;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private T FindAnyComponent<T>(string name) where T : Component
|
||||
{
|
||||
var go = FindAnyGO(name);
|
||||
return go ? go.GetComponent<T>() : null;
|
||||
}
|
||||
|
||||
// Allow builder to inject the root panel reference
|
||||
public void SetLobbyRoot(GameObject root)
|
||||
{
|
||||
panelLobbyRef = root;
|
||||
}
|
||||
|
||||
private TMP_Text FindText(string name) => FindAnyComponent<TMP_Text>(name);
|
||||
private Button FindButton(string name) => FindAnyComponent<Button>(name);
|
||||
private Toggle FindToggle(string name) => FindAnyComponent<Toggle>(name);
|
||||
private TMP_Dropdown FindDropdown(string name) => FindAnyComponent<TMP_Dropdown>(name);
|
||||
private TMP_InputField FindInput(string name) => FindAnyComponent<TMP_InputField>(name);
|
||||
private ScrollRect FindScroll(string name) => FindAnyComponent<ScrollRect>(name);
|
||||
|
||||
#region Steam
|
||||
private void EnsureSteamServices()
|
||||
{
|
||||
if (steam == null)
|
||||
{
|
||||
// Unity 2023+: use FindFirstObjectByType; older: FindObjectOfType
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
steam = Object.FindFirstObjectByType<SteamLobbyService>();
|
||||
#else
|
||||
steam = Object.FindObjectOfType<SteamLobbyService>();
|
||||
#endif
|
||||
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 += OnLobbyCreated;
|
||||
steam.OnLobbyEntered += OnLobbyEntered;
|
||||
steam.OnLobbyLeft += OnLobbyLeft;
|
||||
steam.OnMembersChanged += OnMembersChanged;
|
||||
steam.OnLobbyDataUpdated += OnLobbyDataUpdated;
|
||||
steam.OnJoinFailed += OnJoinFailed;
|
||||
steam.OnAvatarUpdated += OnAvatarUpdated;
|
||||
}
|
||||
|
||||
private void UnregisterSteamEvents()
|
||||
{
|
||||
if (steam == null) return;
|
||||
steam.OnLobbyCreated -= OnLobbyCreated;
|
||||
steam.OnLobbyEntered -= OnLobbyEntered;
|
||||
steam.OnLobbyLeft -= OnLobbyLeft;
|
||||
steam.OnMembersChanged -= OnMembersChanged;
|
||||
steam.OnLobbyDataUpdated -= OnLobbyDataUpdated;
|
||||
steam.OnJoinFailed -= OnJoinFailed;
|
||||
steam.OnAvatarUpdated -= OnAvatarUpdated;
|
||||
}
|
||||
|
||||
private void OnLobbyCreated()
|
||||
{
|
||||
selectedPlayerSteamId = string.Empty;
|
||||
UpdateUIFromSteam();
|
||||
// Auto-open invite overlay for the host
|
||||
if (steam != null && steam.IsInLobby && steam.IsHost)
|
||||
{
|
||||
steam.InviteFriends();
|
||||
if (!steam.IsOverlayEnabled()) ShowFriendsPanel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLobbyEntered()
|
||||
{
|
||||
selectedPlayerSteamId = string.Empty;
|
||||
UpdateUIFromSteam();
|
||||
// Auto-open invite overlay if we are the host entering our lobby
|
||||
if (steam != null && steam.IsInLobby && steam.IsHost)
|
||||
{
|
||||
steam.InviteFriends();
|
||||
if (!steam.IsOverlayEnabled()) ShowFriendsPanel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLobbyLeft()
|
||||
{
|
||||
selectedPlayerSteamId = string.Empty;
|
||||
UpdateUIFromSteam();
|
||||
}
|
||||
|
||||
private void OnMembersChanged() => UpdateUIFromSteam();
|
||||
private void OnLobbyDataUpdated() => UpdateUIFromSteam();
|
||||
private void OnJoinFailed(string reason) { Debug.LogWarning($"[Lobby] Join failed: {reason}"); UpdateUIFromSteam(); }
|
||||
#endregion
|
||||
|
||||
private void OnAvatarUpdated(string steamId)
|
||||
{
|
||||
UpdateAvatarUIForSteamId(steamId);
|
||||
}
|
||||
|
||||
private void UpdateAvatarUIForSteamId(string steamId)
|
||||
{
|
||||
// Update friends grid cell
|
||||
if (contentFriends)
|
||||
{
|
||||
var cell = FindChildRecursive(contentFriends, $"Friend_{steamId}");
|
||||
if (cell)
|
||||
{
|
||||
var img = cell.GetComponent<Image>();
|
||||
if (img && steam.TryGetAvatarSprite(steamId, out var spr, true))
|
||||
{
|
||||
img.sprite = spr; img.color = Color.white; img.preserveAspect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update player list item avatar
|
||||
if (contentPlayers)
|
||||
{
|
||||
var item = FindChildRecursive(contentPlayers, $"PlayerItem_{steamId}");
|
||||
if (item)
|
||||
{
|
||||
var av = FindChildRecursive(item, "Image_Avatar");
|
||||
if (av)
|
||||
{
|
||||
var img = av.GetComponent<Image>();
|
||||
if (img && steam.TryGetAvatarSprite(steamId, out var spr2, true))
|
||||
{
|
||||
img.sprite = spr2; img.color = Color.white; img.preserveAspect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WireButtonEvents()
|
||||
{
|
||||
if (btnHostTab) btnHostTab.onClick.AddListener(ShowHostTab);
|
||||
if (btnJoinTab) btnJoinTab.onClick.AddListener(ShowJoinTab);
|
||||
if (btnCreateLobby) btnCreateLobby.onClick.AddListener(CreateLobby);
|
||||
if (btnConnect) btnConnect.onClick.AddListener(JoinLobby);
|
||||
if (btnCopyCode) btnCopyCode.onClick.AddListener(CopyCode);
|
||||
if (btnInviteFriends) btnInviteFriends.onClick.AddListener(() => {
|
||||
if (!IsInLobby)
|
||||
{
|
||||
// Create a lobby first; overlay will auto-open in callbacks
|
||||
CreateLobby();
|
||||
}
|
||||
else
|
||||
{
|
||||
steam.InviteFriends();
|
||||
if (!steam.IsOverlayEnabled())
|
||||
{
|
||||
ShowFriendsPanel();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (btnKickSelected) btnKickSelected.onClick.AddListener(() => { if (IsInLobby && IsHost && !string.IsNullOrEmpty(selectedPlayerSteamId)) steam.Kick(selectedPlayerSteamId); });
|
||||
if (btnToggleReady) btnToggleReady.onClick.AddListener(ToggleReady);
|
||||
if (btnStartGame) btnStartGame.onClick.AddListener(StartGame);
|
||||
if (btnLeaveLobby) btnLeaveLobby.onClick.AddListener(() => { if (IsInLobby) steam.LeaveLobby(); });
|
||||
if (btnBackFromLobby) btnBackFromLobby.onClick.AddListener(() => {
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
var mm = Object.FindFirstObjectByType<UGUIMainMenuController>();
|
||||
#else
|
||||
var mm = Object.FindObjectOfType<UGUIMainMenuController>();
|
||||
#endif
|
||||
if (mm) { mm.SendMessage("ShowMainMenu", SendMessageOptions.DontRequireReceiver); }
|
||||
});
|
||||
if (btnBackFromFriends) btnBackFromFriends.onClick.AddListener(HideFriendsPanel);
|
||||
if (btnCloseFriendsOverlay) btnCloseFriendsOverlay.onClick.AddListener(HideFriendsPanel);
|
||||
}
|
||||
|
||||
private void ShowHostTab()
|
||||
{
|
||||
if (groupHost) groupHost.SetActive(true);
|
||||
if (groupJoin) groupJoin.SetActive(false);
|
||||
HighlightTab(btnHostTab, true);
|
||||
HighlightTab(btnJoinTab, false);
|
||||
}
|
||||
|
||||
private void ShowJoinTab()
|
||||
{
|
||||
if (groupHost) groupHost.SetActive(false);
|
||||
if (groupJoin) groupJoin.SetActive(true);
|
||||
HighlightTab(btnHostTab, false);
|
||||
HighlightTab(btnJoinTab, true);
|
||||
}
|
||||
|
||||
// Public helpers for main menu
|
||||
public void QuickHost(int maxPlayers = 4, bool isPublic = true)
|
||||
{
|
||||
if (panelLobby) panelLobby.SetActive(true);
|
||||
ShowHostTab();
|
||||
if (ddMaxPlayers)
|
||||
{
|
||||
var idx = ddMaxPlayers.options.FindIndex(o => o.text == maxPlayers.ToString());
|
||||
if (idx >= 0) ddMaxPlayers.value = idx;
|
||||
}
|
||||
if (tgPublicLobby) tgPublicLobby.isOn = isPublic;
|
||||
CreateLobby();
|
||||
}
|
||||
|
||||
public void ShowJoinTabPublic()
|
||||
{
|
||||
if (panelLobby) panelLobby.SetActive(true);
|
||||
ShowJoinTab();
|
||||
if (inputLobbyCode) inputLobbyCode.Select();
|
||||
}
|
||||
|
||||
private void HighlightTab(Button btn, bool active)
|
||||
{
|
||||
if (!btn) return;
|
||||
var img = btn.GetComponent<Image>();
|
||||
if (img) img.color = active ? new Color(0.3f,0.6f,0.3f,0.9f) : new Color(0.2f,0.2f,0.2f,0.9f);
|
||||
}
|
||||
|
||||
private void CreateLobby()
|
||||
{
|
||||
int maxPlayers = 4;
|
||||
if (ddMaxPlayers && int.TryParse(ddMaxPlayers.options[ddMaxPlayers.value].text, out var mp)) maxPlayers = mp;
|
||||
bool isPublic = tgPublicLobby ? tgPublicLobby.isOn : true;
|
||||
steam?.CreateLobby(maxPlayers, isPublic);
|
||||
}
|
||||
|
||||
private void JoinLobby()
|
||||
{
|
||||
var code = inputLobbyCode ? inputLobbyCode.text : string.Empty;
|
||||
if (!string.IsNullOrEmpty(code) && code.Length == 6)
|
||||
{
|
||||
steam?.JoinLobbyByCode(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[Lobby] Invalid code. Must be 6 characters.");
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyCode()
|
||||
{
|
||||
var code = LobbyCode;
|
||||
if (!string.IsNullOrEmpty(code))
|
||||
{
|
||||
GUIUtility.systemCopyBuffer = code;
|
||||
if (btnCopyCode)
|
||||
{
|
||||
var text = btnCopyCode.GetComponentInChildren<TMP_Text>();
|
||||
if (text)
|
||||
{
|
||||
text.text = "COPIED!";
|
||||
Invoke(nameof(ResetCopyText), 1.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void ResetCopyText()
|
||||
{
|
||||
var text = btnCopyCode ? btnCopyCode.GetComponentInChildren<TMP_Text>() : null;
|
||||
if (text) text.text = "COPY";
|
||||
}
|
||||
|
||||
private void ResetInviteText()
|
||||
{
|
||||
var text = btnInviteFriends ? btnInviteFriends.GetComponentInChildren<TMP_Text>() : null;
|
||||
if (text) text.text = "INVITE FRIENDS";
|
||||
}
|
||||
|
||||
private void ToggleReady()
|
||||
{
|
||||
if (IsHost || !IsInLobby || steam == null) return;
|
||||
var members = GetSteamMembers();
|
||||
var localId = steam.LocalSteamIdString;
|
||||
bool current = false;
|
||||
var me = members.FirstOrDefault(m => m.steamId == localId);
|
||||
// When tuple is default, steamId will be null; guard
|
||||
if (me.steamId == localId) current = me.isReady;
|
||||
steam.SetReady(!current);
|
||||
UpdateUIFromSteam();
|
||||
}
|
||||
|
||||
private void StartGame()
|
||||
{
|
||||
if (!IsHost || !IsInLobby) return;
|
||||
// Ensure all ready
|
||||
var allReady = GetSteamMembers().All(m => m.isReady);
|
||||
if (!allReady)
|
||||
{
|
||||
Debug.LogWarning("[Lobby] Not all players are ready.");
|
||||
return;
|
||||
}
|
||||
steam?.StartGameSignal();
|
||||
#if UNITY_NETCODE || NETCODE_PRESENT
|
||||
// Optionally start host and load scene
|
||||
var adapter = GetSteamAdapter();
|
||||
if (adapter != null && steam != null)
|
||||
{
|
||||
if (ulong.TryParse(steam.LocalSteamIdString, out var hostSid))
|
||||
adapter.ConfigureHost(hostSid);
|
||||
}
|
||||
NetworkBootstrap.StartHost("GameScene");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void UpdateUIFromSteam()
|
||||
{
|
||||
UpdateStatus();
|
||||
UpdateCode();
|
||||
RebuildPlayers();
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
UpdateStatus();
|
||||
UpdateCode();
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
private void ValidateUI()
|
||||
{
|
||||
// Log any missing critical references to help scene setup
|
||||
void Check(object obj, string name)
|
||||
{
|
||||
if (obj == null) Debug.LogError($"[Lobby UI] Missing UI element: {name}. Ensure Tools > MegaKoop > Generate UGUI Main Menu and names match.");
|
||||
}
|
||||
Check(panelLobby, "Panel_Lobby");
|
||||
Check(groupHost, "Group_Host");
|
||||
Check(groupJoin, "Group_Join");
|
||||
Check(panelFriends, "Panel_Friends");
|
||||
Check(contentFriends, "Content_FriendsList");
|
||||
Check(emptyFriends, "Empty_Friends");
|
||||
Check(btnHostTab, "Button_HostTab");
|
||||
Check(btnJoinTab, "Button_JoinTab");
|
||||
Check(btnCreateLobby, "Button_CreateLobby");
|
||||
Check(btnConnect, "Button_Connect");
|
||||
Check(inputLobbyCode, "Input_LobbyCode");
|
||||
Check(ddMaxPlayers, "Dropdown_MaxPlayers");
|
||||
Check(tgPublicLobby, "Toggle_PublicLobby");
|
||||
Check(btnInviteFriends, "Button_InviteFriends");
|
||||
Check(btnBackFromFriends, "Button_BackFromFriends");
|
||||
Check(btnStartGame, "Button_StartGame");
|
||||
Check(btnLeaveLobby, "Button_LeaveLobby");
|
||||
}
|
||||
|
||||
private void UpdateStatus()
|
||||
{
|
||||
if (textStatus)
|
||||
{
|
||||
textStatus.text = IsInLobby ? (IsHost ? "HOSTING" : "CONNECTED") : "OFFLINE";
|
||||
textStatus.color = IsInLobby ? new Color(0.7f,1f,0.7f) : new Color(0.8f,0.8f,0.8f);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCode()
|
||||
{
|
||||
if (textLobbyCodeValue)
|
||||
{
|
||||
textLobbyCodeValue.text = (!string.IsNullOrEmpty(LobbyCode) && IsInLobby) ? LobbyCode : "------";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisibility()
|
||||
{
|
||||
if (btnStartGame) btnStartGame.gameObject.SetActive(IsInLobby && IsHost);
|
||||
if (btnLeaveLobby) btnLeaveLobby.gameObject.SetActive(IsInLobby);
|
||||
if (btnToggleReady) btnToggleReady.gameObject.SetActive(IsInLobby && !IsHost);
|
||||
if (btnInviteFriends) btnInviteFriends.gameObject.SetActive(IsInLobby && IsHost);
|
||||
if (btnKickSelected) btnKickSelected.gameObject.SetActive(IsInLobby && IsHost);
|
||||
|
||||
if (groupHost) groupHost.SetActive(!IsInLobby); // hide setup groups once in lobby
|
||||
if (groupJoin) groupJoin.SetActive(!IsInLobby);
|
||||
|
||||
if (emptyPlayers) emptyPlayers.SetActive(IsInLobby && contentPlayers && contentPlayers.childCount == 0);
|
||||
}
|
||||
|
||||
private List<(string steamId, string name, bool isReady, bool isHost)> GetSteamMembers()
|
||||
{
|
||||
return steam != null ? steam.GetMembers().ToList() : new List<(string, string, bool, bool)>();
|
||||
}
|
||||
|
||||
private void RebuildPlayers()
|
||||
{
|
||||
if (contentPlayers == null) return;
|
||||
// Clear current (keep template)
|
||||
var toDestroy = new List<GameObject>();
|
||||
foreach (Transform child in contentPlayers)
|
||||
{
|
||||
if (playerItemTemplate != null && child.gameObject == playerItemTemplate) continue;
|
||||
toDestroy.Add(child.gameObject);
|
||||
}
|
||||
foreach (var go in toDestroy) DestroyImmediate(go);
|
||||
|
||||
var members = GetSteamMembers();
|
||||
if (textPlayerCount) textPlayerCount.text = $"{members.Count}/{(ddMaxPlayers ? int.Parse(ddMaxPlayers.options[ddMaxPlayers.value].text) : 4)}";
|
||||
|
||||
if (members.Count == 0)
|
||||
{
|
||||
if (emptyPlayers) emptyPlayers.SetActive(true);
|
||||
return;
|
||||
}
|
||||
if (emptyPlayers) emptyPlayers.SetActive(false);
|
||||
|
||||
foreach (var m in members)
|
||||
{
|
||||
var item = CreatePlayerItem(m.steamId, m.name, m.isReady, m.isHost);
|
||||
item.transform.SetParent(contentPlayers, false);
|
||||
item.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject CreatePlayerItem(string steamId, string name, bool ready, bool host)
|
||||
{
|
||||
GameObject go;
|
||||
if (playerItemTemplate)
|
||||
{
|
||||
go = Instantiate(playerItemTemplate);
|
||||
go.name = $"PlayerItem_{steamId}";
|
||||
}
|
||||
else
|
||||
{
|
||||
go = new GameObject("PlayerItem", typeof(RectTransform), typeof(HorizontalLayoutGroup), typeof(Image));
|
||||
var img = go.GetComponent<Image>(); img.color = new Color(0.2f,0.2f,0.2f,0.9f);
|
||||
}
|
||||
|
||||
// Ensure image background for highlight
|
||||
var bg = go.GetComponent<Image>(); if (!bg) bg = go.AddComponent<Image>();
|
||||
|
||||
// Ensure button for selection
|
||||
var btn = go.GetComponent<Button>(); if (!btn) btn = go.AddComponent<Button>();
|
||||
|
||||
// Ensure layout element to reserve height
|
||||
var goLE = go.GetComponent<LayoutElement>(); if (!goLE) goLE = go.AddComponent<LayoutElement>();
|
||||
goLE.minHeight = 100; goLE.flexibleWidth = 1f;
|
||||
|
||||
// Children
|
||||
// Find avatar inside this item (not globally)
|
||||
Image avatarGO = null;
|
||||
var avatarTr = FindChildRecursive(go.transform, "Image_Avatar");
|
||||
if (avatarTr)
|
||||
{
|
||||
avatarGO = avatarTr.GetComponent<Image>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create avatar placeholder if missing
|
||||
var av = new GameObject("Image_Avatar", typeof(RectTransform), typeof(Image));
|
||||
av.transform.SetParent(go.transform, false);
|
||||
avatarGO = av.GetComponent<Image>();
|
||||
var avRT = (RectTransform)av.transform; avRT.sizeDelta = new Vector2(40,40);
|
||||
}
|
||||
// Remove any name/status texts if present (UX: avatar only)
|
||||
var texts = go.GetComponentsInChildren<TMP_Text>(true);
|
||||
foreach (var t in texts)
|
||||
{
|
||||
if (t.name.Contains("Text_PlayerName") || t.name.Contains("Text_PlayerStatus"))
|
||||
{
|
||||
DestroyImmediate(t.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Load Steam avatar sprite if available
|
||||
if (avatarGO)
|
||||
{
|
||||
if (steam != null && steam.TryGetAvatarSprite(steamId, out var spr, large: true))
|
||||
{
|
||||
avatarGO.sprite = spr;
|
||||
avatarGO.color = Color.white;
|
||||
avatarGO.type = Image.Type.Simple;
|
||||
avatarGO.preserveAspect = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback color avatar
|
||||
avatarGO.sprite = null;
|
||||
avatarGO.color = new Color(0.3f,0.6f,0.3f,1f);
|
||||
}
|
||||
|
||||
// Make avatar centered with compact size for PC layout
|
||||
var avRT = (RectTransform)avatarGO.transform;
|
||||
avRT.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
avRT.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
avRT.pivot = new Vector2(0.5f, 0.5f);
|
||||
avRT.anchoredPosition = Vector2.zero;
|
||||
avRT.sizeDelta = new Vector2(96, 96);
|
||||
var avLE = avatarGO.GetComponent<LayoutElement>() ?? avatarGO.gameObject.AddComponent<LayoutElement>();
|
||||
avLE.minWidth = 96; avLE.minHeight = 96;
|
||||
}
|
||||
|
||||
// Selection handler
|
||||
btn.onClick.RemoveAllListeners();
|
||||
btn.onClick.AddListener(() => { selectedPlayerSteamId = steamId; HighlightSelection(); });
|
||||
|
||||
// Highlight if selected
|
||||
if (!string.IsNullOrEmpty(selectedPlayerSteamId) && selectedPlayerSteamId == steamId)
|
||||
{
|
||||
bg.color = new Color(0.3f,0.5f,0.3f,0.9f);
|
||||
}
|
||||
else
|
||||
{
|
||||
bg.color = new Color(0.2f,0.2f,0.2f,0.9f);
|
||||
}
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
private TMP_Text CreateChildText(Transform parent, string name, int fontSize)
|
||||
{
|
||||
var tgo = new GameObject(name, typeof(RectTransform), typeof(TextMeshProUGUI));
|
||||
tgo.transform.SetParent(parent, false);
|
||||
var t = tgo.GetComponent<TextMeshProUGUI>();
|
||||
// Use LegacyRuntime.ttf on newer Unity; fallback to Arial.ttf for older versions
|
||||
Font builtinFont = null;
|
||||
try { builtinFont = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf"); } catch {}
|
||||
if (builtinFont == null) { try { builtinFont = Resources.GetBuiltinResource<Font>("Arial.ttf"); } catch {} }
|
||||
if (TMP_Settings.defaultFontAsset != null) t.font = TMP_Settings.defaultFontAsset;
|
||||
t.fontSize = fontSize; t.color = Color.white; t.alignment = TextAlignmentOptions.MidlineLeft;
|
||||
var rt = (RectTransform)tgo.transform; rt.sizeDelta = new Vector2(0, fontSize + 10);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void HighlightSelection()
|
||||
{
|
||||
if (contentPlayers == null) return;
|
||||
foreach (Transform child in contentPlayers)
|
||||
{
|
||||
var img = child.GetComponent<Image>();
|
||||
if (!img) continue;
|
||||
if (child.name.EndsWith(selectedPlayerSteamId)) img.color = new Color(0.3f,0.5f,0.3f,0.9f);
|
||||
else img.color = new Color(0.2f,0.2f,0.2f,0.9f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
UI/Scripts/UGUIMultiplayerLobbyController.cs.meta
Normal file
2
UI/Scripts/UGUIMultiplayerLobbyController.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e45ca33f326ec9c4f8c456c63981a436
|
||||
Reference in New Issue
Block a user