UI/TutorialTips.razor

A UI Razor component that displays a sequence of tutorial tips. It shows an image, text, a progress bar, and advances tips when the player presses the use key or when a timer expires, then marks that the local player has seen tips and deletes the panel.

Networking
@using System
@using System.Collections.Generic
@using Sandbox
@using Sandbox.UI
@namespace BrickJam.UI
@inherits Panel

<root>
	<div class="container">
		<div class="image" style="background-image: url(@(Tip.Image));" />
		<span class="text">@Tip.Text</span>
		<div class="footer">
			<span>Press [E] for the next tip.</span>
			<span class="count">@(tipIndex + 1) of @tips.Count</span>
		</div>
		<div class="bar"><div class="fill" style="width: @($"{Progress * 100f}%")"></div></div>
	</div>
</root>

@code {
	private const float TipTime = 15f;
	private const string TipAction = "use";

	private int tipIndex;
	private TimeSince tipTime;

	private readonly List<(string Image, string Text)> tips = new()
	{
		("ui/tutorial/1.png", "Loot as much stuff as possible while avoiding the level's enemy!"),
		("ui/tutorial/2.png", "Avoid tripping by staying away from obstacles on the floor."),
		("ui/tutorial/3.png", "Look for an exit to the store or next level."),
		("ui/tutorial/4.png", "Sell your stuff at the shop by dropping everything behind the counter."),
		("ui/tutorial/5.png", "Invite a few friends over and have fun!")
	};

	private (string Image, string Text) Tip => tips[Math.Clamp( tipIndex, 0, tips.Count - 1 )];
	private float Progress => ((float)tipTime / TipTime).Clamp( 0f, 1f );

	private void NextTip()
	{
		tipIndex++;
		if ( tipIndex >= tips.Count )
		{
			if ( Player.Local is { } player )
			{
				player.SeenTips = true;
				Player.DataChanged = true;
			}

			Delete();
			return;
		}

		tipTime = 0;
	}

	public override void Tick()
	{
		if ( Input.Pressed( TipAction ) || tipTime >= TipTime )
			NextTip();
	}

	protected override int BuildHash() => HashCode.Combine( tipIndex, (int)((float)tipTime * 4f) );
}

<style>
	TutorialTips {
		position: absolute;
		top: 0; right: 10px;
		height: 100%;
		align-items: center;
		pointer-events: none;

		.container {
			width: 600px;
			flex-direction: column;
			background-color: rgba(0,0,0,0.5);
			font-size: 32px;
			color: white;
			text-shadow: 4px 4px 0px black;
			padding: 12px;

			.image {
				width: 100%;
				aspect-ratio: 1.3333;
				background-size: 100%;
				background-position: center;
				background-repeat: no-repeat;
				border: 2px solid white;
				margin-bottom: 10px;
			}

			.text { padding-bottom: 10px; }

			.footer {
				flex-direction: row;
				justify-content: space-between;
				color: rgba(180,180,180,1);
				font-size: 22px;
			}

			.bar {
				margin-top: 10px;
				height: 8px;
				background-color: rgba(255,255,255,0.15);

				.fill { height: 8px; background-color: rgba(255,220,80,1); }
			}
		}
	}
</style>