UI/MainMenu.cs
using Sandbox.UI;
/// <summary>
/// 主菜单(M4):建服 / 加入 / 人机对战 / 退出。纯 C# Panel(编辑器热重载友好,同 GameHud 模式)。
/// 菜单阶段世界"壳"已建好(网格 + 相机照常渲染),菜单半透明压暗盖在上面。
/// M5:比赛参数设置移进了**游戏房间**(LobbyPanel 二级界面)——HOST/VS BOTS 进房调参,点 START 开局。
/// 点击 + 键盘快捷键双通道(编辑器内嵌视口可能收不到鼠标点击,键盘保底):1=建服 2=加入 3=人机 4=退出。
/// 样式在同目录 MainMenu.cs.scss(按"类名.cs.scss"自动加载)。
/// </summary>
public sealed class MainMenu : PanelComponent
{
Label _status;
bool _built;
protected override void OnStart()
{
base.OnStart();
BuildUi();
}
void BuildUi()
{
var root = Panel;
if ( root is null ) return;
// 已建好且引用有效 → 不重复建(热重载半残时整体清空重建)
if ( _built && _status.IsValid() && root.ChildrenCount > 0 ) return;
root.DeleteChildren( true );
var title = new Label() { Classes = "title" };
title.Text = "CIRCLEROYALE";
root.AddChild( title );
var sub = new Label() { Classes = "subtitle" };
sub.Text = "NEON ARENA — EAT OR BE EATEN";
root.AddChild( sub );
AddButton( root, "HOST GAME — [1]", "host" );
AddButton( root, "JOIN GAME — [2]", "join" );
AddButton( root, "VS BOTS — [3]", "bots" );
AddButton( root, "QUIT — [4]", "quit" );
_status = new Label() { Classes = "status" };
root.AddChild( _status );
var ver = new Label() { Classes = "version" };
ver.Text = GameConfig.Version;
root.AddChild( ver );
var hint = new Label() { Classes = "hint" };
hint.Text = "WASD MOVE — SPACE SPLIT — R FEED";
root.AddChild( hint );
_built = true;
}
void AddButton( Panel root, string text, string id )
{
var b = new Label() { Classes = $"btn {id}" };
b.Text = text;
// lambda 必须捕获 this(调实例方法)——无捕获的静态 lambda 在热重载后无法重映射,
// 点击会抛 NoMatchLambda 异常(实测:按钮全变"点了没反应")
b.AddEventListener( "onclick", () => OnMenuButton( id ) );
root.AddChild( b );
}
/// <summary> 菜单按钮点击(实例方法:热重载可重映射) </summary>
void OnMenuButton( string id ) =>
CircleroyaleGame.Current?.MenuAction( id switch { "host" => 0, "join" => 1, "bots" => 2, _ => 3 } );
protected override void OnUpdate()
{
base.OnUpdate();
// 强制光标可见可交互(面板默认 PointerEvents=None,Auto 模式下没人"想要鼠标"光标就没了);
// Hide() 时恢复 Auto。放在 OnUpdate 每帧断言,任何样式级联意外都兜得住
Mouse.Visibility = MouseVisibility.Visible;
BuildUi();
// 键盘保底(编辑器内嵌视口鼠标事件不可靠,键盘一定能进来)
if ( Input.Pressed( "Slot1" ) ) CircleroyaleGame.Current?.MenuAction( 0 );
if ( Input.Pressed( "Slot2" ) ) CircleroyaleGame.Current?.MenuAction( 1 );
if ( Input.Pressed( "Slot3" ) ) CircleroyaleGame.Current?.MenuAction( 2 );
if ( Input.Pressed( "Slot4" ) ) CircleroyaleGame.Current?.MenuAction( 3 );
}
/// <summary> 状态行(连接中 / 失败重试提示) </summary>
public void SetStatus( string text )
{
if ( _status.IsValid() ) _status.Text = text;
}
/// <summary> 开局后隐藏菜单(Panel 无 Enabled,直接禁掉整个 GameObject),并把光标还给游戏 </summary>
public void Hide()
{
if ( !GameObject.Enabled ) return; // 已隐藏:别动光标(ReconcileUi 每帧调,房间页要光标可点)
Mouse.Visibility = MouseVisibility.Auto;
GameObject.Enabled = false;
}
/// <summary> 从房间/结算回菜单(M5):重新显示(OnUpdate 恢复光标断言) </summary>
public void Show()
{
GameObject.Enabled = true;
BuildUi();
if ( _status.IsValid() ) _status.Text = "";
}
}