Comitting lobby code without testing.
This commit is contained in:
parent
66f84652dc
commit
60fa58b07f
14 changed files with 1207 additions and 37 deletions
233
Assets/_Project/Scripts/UI/MainMenuController.cs
Normal file
233
Assets/_Project/Scripts/UI/MainMenuController.cs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// Assets/_Project/Scripts/UI/MainMenuController.cs
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using TD.Net;
|
||||
|
||||
namespace TD.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Drives the main menu UI. Requires a <see cref="UIDocument"/> on the same
|
||||
/// GameObject. Builds the UI programmatically — no UXML needed for v1.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Buttons.</b>
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Host</b> — calls <see cref="NetworkBootstrap.StartHost"/> on
|
||||
/// the default port, then NGO scene-loads the Lobby. Clients that
|
||||
/// join later get pulled into whatever scene the server is in.</item>
|
||||
/// <item><b>Join</b> — reveals IP + port fields, then calls
|
||||
/// <see cref="NetworkBootstrap.StartClient"/>. The server will pull
|
||||
/// the client into the current networked scene (Lobby or Match)
|
||||
/// once the connection completes.</item>
|
||||
/// <item><b>Quit</b> — Application.Quit (no-op in the editor).</item>
|
||||
/// </list></para>
|
||||
///
|
||||
/// <para><b>Future Steam integration.</b> A lobby-browser panel will be
|
||||
/// added here. The Host button will create a Steam lobby instead of a
|
||||
/// direct-IP host; Join becomes "browse public lobbies" + "accept friend
|
||||
/// invite". See Project_Roadmap.md §1.7-Future Steam Lobby Migration.</para>
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(UIDocument))]
|
||||
public class MainMenuController : MonoBehaviour
|
||||
{
|
||||
// ----- Inspector --------------------------------------------------
|
||||
|
||||
[Tooltip("Default port shown in the Join field and used by the Host button.")]
|
||||
[SerializeField] private ushort defaultPort = NetworkBootstrap.DefaultPort;
|
||||
|
||||
[Tooltip("Default address pre-filled in the Join field for solo testing.")]
|
||||
[SerializeField] private string defaultJoinAddress = NetworkBootstrap.DefaultConnectAddress;
|
||||
|
||||
// ----- Cached UI elements -----------------------------------------
|
||||
|
||||
private Button hostButton;
|
||||
private Button joinButton;
|
||||
private Button quitButton;
|
||||
private VisualElement joinPanel;
|
||||
private TextField joinAddressField;
|
||||
private TextField joinPortField;
|
||||
private Button joinConfirmButton;
|
||||
private Button joinCancelButton;
|
||||
private Label statusLabel;
|
||||
|
||||
// ----- Lifecycle --------------------------------------------------
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// UIDocument creates its panel in OnEnable, which runs after Awake.
|
||||
// Start is the safe time to access rootVisualElement.
|
||||
var doc = GetComponent<UIDocument>();
|
||||
var root = doc?.rootVisualElement;
|
||||
if (root == null)
|
||||
{
|
||||
Debug.LogError("[MainMenuController] rootVisualElement is null. " +
|
||||
"Check the UIDocument's Panel Settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
BuildUI(root);
|
||||
}
|
||||
|
||||
// ----- UI construction --------------------------------------------
|
||||
|
||||
private void BuildUI(VisualElement root)
|
||||
{
|
||||
// Centered vertical stack on a dark background.
|
||||
root.style.flexDirection = FlexDirection.Column;
|
||||
root.style.justifyContent = Justify.Center;
|
||||
root.style.alignItems = Align.Center;
|
||||
root.style.backgroundColor = new Color(0.05f, 0.05f, 0.08f, 1f);
|
||||
// The UIDocument root needs explicit width/height to fill the screen.
|
||||
root.style.width = Length.Percent(100);
|
||||
root.style.height = Length.Percent(100);
|
||||
|
||||
// Title.
|
||||
var title = new Label("Unity Tower Defense");
|
||||
title.style.fontSize = 48;
|
||||
title.style.color = Color.white;
|
||||
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
title.style.marginBottom = 60;
|
||||
root.Add(title);
|
||||
|
||||
// Primary button column.
|
||||
var buttonColumn = new VisualElement();
|
||||
buttonColumn.style.flexDirection = FlexDirection.Column;
|
||||
buttonColumn.style.alignItems = Align.Center;
|
||||
root.Add(buttonColumn);
|
||||
|
||||
hostButton = MakeMenuButton("Host Game", OnHostClicked);
|
||||
joinButton = MakeMenuButton("Join Game", OnJoinClicked);
|
||||
quitButton = MakeMenuButton("Quit", OnQuitClicked);
|
||||
buttonColumn.Add(hostButton);
|
||||
buttonColumn.Add(joinButton);
|
||||
buttonColumn.Add(quitButton);
|
||||
|
||||
// Join sub-panel — hidden until Join is clicked. Holds the IP+port
|
||||
// fields and the Connect / Cancel buttons.
|
||||
joinPanel = new VisualElement();
|
||||
joinPanel.style.flexDirection = FlexDirection.Column;
|
||||
joinPanel.style.alignItems = Align.Center;
|
||||
joinPanel.style.marginTop = 24;
|
||||
joinPanel.style.paddingTop = 16;
|
||||
joinPanel.style.paddingBottom = 16;
|
||||
joinPanel.style.paddingLeft = 24;
|
||||
joinPanel.style.paddingRight = 24;
|
||||
joinPanel.style.backgroundColor = new Color(0f, 0f, 0f, 0.4f);
|
||||
joinPanel.style.display = DisplayStyle.None;
|
||||
root.Add(joinPanel);
|
||||
|
||||
joinAddressField = new TextField("Host address");
|
||||
joinAddressField.value = defaultJoinAddress;
|
||||
joinAddressField.style.width = 280;
|
||||
joinAddressField.style.color = Color.white;
|
||||
joinPanel.Add(joinAddressField);
|
||||
|
||||
joinPortField = new TextField("Port");
|
||||
joinPortField.value = defaultPort.ToString();
|
||||
joinPortField.style.width = 280;
|
||||
joinPortField.style.marginTop = 8;
|
||||
joinPortField.style.color = Color.white;
|
||||
joinPanel.Add(joinPortField);
|
||||
|
||||
var joinButtons = new VisualElement();
|
||||
joinButtons.style.flexDirection = FlexDirection.Row;
|
||||
joinButtons.style.marginTop = 12;
|
||||
joinPanel.Add(joinButtons);
|
||||
|
||||
joinConfirmButton = new Button(OnJoinConfirmClicked) { text = "Connect" };
|
||||
joinConfirmButton.style.minWidth = 120;
|
||||
joinConfirmButton.style.height = 32;
|
||||
joinConfirmButton.style.marginRight = 8;
|
||||
joinButtons.Add(joinConfirmButton);
|
||||
|
||||
joinCancelButton = new Button(OnJoinCancelClicked) { text = "Cancel" };
|
||||
joinCancelButton.style.minWidth = 120;
|
||||
joinCancelButton.style.height = 32;
|
||||
joinButtons.Add(joinCancelButton);
|
||||
|
||||
// Status line at the bottom for connection feedback.
|
||||
statusLabel = new Label(string.Empty);
|
||||
statusLabel.style.color = new Color(1f, 0.6f, 0.3f);
|
||||
statusLabel.style.marginTop = 32;
|
||||
statusLabel.style.minHeight = 20;
|
||||
root.Add(statusLabel);
|
||||
}
|
||||
|
||||
private static Button MakeMenuButton(string text, System.Action onClick)
|
||||
{
|
||||
var btn = new Button(() => onClick?.Invoke()) { text = text };
|
||||
btn.style.minWidth = 240;
|
||||
btn.style.height = 48;
|
||||
btn.style.fontSize = 20;
|
||||
btn.style.marginBottom = 8;
|
||||
return btn;
|
||||
}
|
||||
|
||||
// ----- Button handlers --------------------------------------------
|
||||
|
||||
private void OnHostClicked()
|
||||
{
|
||||
statusLabel.text = "Starting host…";
|
||||
if (!NetworkBootstrap.StartHost(defaultPort))
|
||||
{
|
||||
statusLabel.text = "Failed to start host. Check the console.";
|
||||
return;
|
||||
}
|
||||
|
||||
// After StartHost the local peer is the server + client. Trigger
|
||||
// the networked scene load to take everyone to the Lobby. NGO
|
||||
// replicates this to any future-joining clients automatically.
|
||||
NetworkBootstrap.LoadSceneAsHost(SceneNames.Lobby);
|
||||
}
|
||||
|
||||
private void OnJoinClicked()
|
||||
{
|
||||
// Toggle the join sub-panel.
|
||||
joinPanel.style.display = joinPanel.style.display == DisplayStyle.None
|
||||
? DisplayStyle.Flex
|
||||
: DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void OnJoinConfirmClicked()
|
||||
{
|
||||
string address = string.IsNullOrWhiteSpace(joinAddressField.value)
|
||||
? defaultJoinAddress
|
||||
: joinAddressField.value.Trim();
|
||||
|
||||
ushort port = defaultPort;
|
||||
if (!string.IsNullOrWhiteSpace(joinPortField.value)
|
||||
&& ushort.TryParse(joinPortField.value.Trim(), out var parsed))
|
||||
{
|
||||
port = parsed;
|
||||
}
|
||||
|
||||
statusLabel.text = $"Connecting to {address}:{port}…";
|
||||
if (!NetworkBootstrap.StartClient(address, port))
|
||||
{
|
||||
statusLabel.text = "Failed to start client. Check the console.";
|
||||
return;
|
||||
}
|
||||
|
||||
// The server's NGO SceneManager will pull this client into whatever
|
||||
// scene the server is in (Lobby or Match) once the connection
|
||||
// completes. SessionFlow handles disconnect recovery.
|
||||
joinPanel.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void OnJoinCancelClicked()
|
||||
{
|
||||
joinPanel.style.display = DisplayStyle.None;
|
||||
statusLabel.text = string.Empty;
|
||||
}
|
||||
|
||||
private void OnQuitClicked()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue