ui/PanelComponents/MainMenu.razor

A Razor UI component for the game's main menu. It renders title, play/quit buttons, lobby and stats panels, handles input (gamepad & click), starts the game by loading a scene, hides/restores sprites during transition, and manages simple state and animations.

File AccessHttp Calls
@using Sandbox;
@using Sandbox.UI;
@namespace SS1
@inherits PanelComponent

<root>
	<div class="title" style="color:@(Color.Lerp(new Color(0f, 0.5f, 0f), new Color(0.7f, 1f, 0.7f), 0.5f + Utils.FastSin(Time.Now * 2f) * 0.5f).Rgba);">Sausage Survivors</div>
	<div class="content">
		<div class="button-container">
			@if (State == MenuState.Main)
			{
				<div class="button" onclick="@(() => ButtonStart(true))">
					@if(Input.UsingController) { <InputHint class="@(_playButton.IsHeld ? "pressed" : "")" Button=@_playButton.Action /> }
					Play
				</div>
				<div class="button red" onclick="@(() => Quit())">
					@if(Input.UsingController) { <InputHint class="@(_quitButton.IsHeld ? "pressed" : "")" Button=@_quitButton.Action /> }
					Quit
				</div>

				@*<div class="button" onclick="@(() => ButtonStart(true))">Start Singleplayer</div>
			<div class="button" onclick="@(() => ButtonStart())">Start Multiplayer</div>
			<div class="button blue" onclick="@(() => ButtonLobbies())">Lobbies</div>
			<div class="button red" onclick="@(() => Quit())">Quit</div>*@
			}
			else if (State == MenuState.Lobbies)
			{
				<div class="button red" onclick="@(() => ButtonBack())">Back</div>
			}
		</div>
		@if (State == MenuState.Lobbies)
		{
			<div class="content-panel">
				<LobbyList />
			</div>
		}
		else if (State == MenuState.Stats)
		{
			<div class="content-panel">
			</div>
		}
	</div>

	<DifficultyPanel></DifficultyPanel>
	<LeaderboardPanel [email protected] IsSurvival=@(false) LabelText=@("Fastest Victory") style="right: 165px;"></LeaderboardPanel>
	@*<LeaderboardPanel [email protected] IsSurvival=@(true) LabelText=@("Longest Survival") style="right: 400px;"></LeaderboardPanel>*@

	@if (_startingGame || MenuManager.Instance.ElapsedTime < 0.75f)
	{
		<div class="fade_in" style="background-color:@(Color.Black.WithAlpha(_startingGame ? 1f : Utils.Map(MenuManager.Instance.ElapsedTime, 0f, 0.75f, 1f, 0f)).Rgba);"></div>
	}
</root>

@code
{
	public static MainMenu Instance { get; private set; }
	public static bool IsSingleplayerGame { get; private set; } = true;

	public MenuState State = MenuState.Main;
	Angles cameraAngles = Angles.Zero;
	bool _startingGame;
	int _startFrames;
	readonly System.Collections.Generic.List<Sprite2D> _hiddenSprites = new();
	readonly GamepadButton _playButton = new("reload");
	readonly GamepadButton _quitButton = new("use");

	public bool CanUseButtons => State == MenuState.Main && !_startingGame;

	public enum MenuState
	{
		Main,
		Lobbies,
		Stats
	}

	protected override void OnAwake()
	{
		Instance = this;
	}

	protected override void OnStart()
	{
		cameraAngles = Scene.Camera.WorldRotation;
	}

	void ButtonStart(bool isSingleplayer = false)
	{
		if (_startingGame) return;
		IsSingleplayerGame = isSingleplayer;
		_startingGame = true;
		_startFrames = 0;
		foreach (var sprite in Scene.GetAllComponents<Sprite2D>())
		{
			if (!sprite.Enabled) continue;
			_hiddenSprites.Add(sprite);
			sprite.Enabled = false;
		}
		if (Scene.Camera.IsValid())
			Scene.Camera.BackgroundColor = Color.Black;
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		Gamepad.UpdateCursor(true);

		if (_playButton.Update(CanUseButtons)) ButtonStart(true);
		else if (_quitButton.Update(CanUseButtons)) Quit();

		// Complete a frame with no menu sprites before the gameplay camera takes over.
		// Sprite batches are uploaded at FinishUpdate, so loading in the click handler
		// can render the old menu batch through the new camera for one frame.
		if (!_startingGame || ++_startFrames < 2) return;
		if (Game.ActiveScene.LoadFromFile("scenes/game.scene")) return;

		_startingGame = false;
		foreach (var sprite in _hiddenSprites)
			if (sprite.IsValid()) sprite.Enabled = true;
		_hiddenSprites.Clear();
		if (Scene.Camera.IsValid())
			Scene.Camera.BackgroundColor = MenuCamera.BackgroundColor;
		Log.Warning("Could not load scenes/game.scene.");
	}

	void ButtonLobbies()
	{
		State = MenuState.Lobbies;
		Sound.Play("ui.button.press");
	}

	void ButtonBack()
	{
		State = MenuState.Main;
		Sound.Play("ui.navigate.back");
	}

	void Quit()
	{
		if (Game.IsEditor)
		{
			Log.Info("Quit");
		}
		else
		{
			Game.Close();
		}
	}

	protected override int BuildHash()
	{
		float timeHash = MenuManager.Instance.ElapsedTime < 0.75f ? MenuManager.Instance.ElapsedTime : 0f;
		return HashCode.Combine(
			State,
			_startingGame,
			timeHash,
			Time.Now // todo
		);
	}
}