Animace + Menu

This commit is contained in:
Dominik G.
2025-10-05 18:18:12 +02:00
parent b52b3aa830
commit 550efdfaad
2192 changed files with 602748 additions and 2703 deletions

View File

@@ -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();

View 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;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5c87ad4288a40c146ac0b73e2d019347

View 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.");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 35698d9033b8e6b4c85cf39ccb109f81

View 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);
}
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 40c1afbb68db70f42b5f4f11f33b69bb

View 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);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e45ca33f326ec9c4f8c456c63981a436