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