Level/LevelResource.cs

An asset type representing a saved Breakout level (.level). It stores display name, grid layout, cell sizing and renders a thumbnail bitmap by drawing a rounded-rect colored cell for each character using a BlockPalette.

File Access
using System;
using Sandbox;
using Sandbox.UI;

namespace Breakout;

/// <summary>
/// A saved level, stored as a .level asset. Layout is the grid of characters, where each character
/// maps to a brick through the spawner's palette. RenderThumbnail draws a small preview of that
/// layout, so the asset shows the actual board in the editor's asset browser.
/// </summary>
[AssetType( Name = "Breakout Level", Extension = "level", Category = "Breakout" )]
public sealed class LevelResource : GameResource
{
	[Property] public string DisplayName { get; set; }

	[Hide] public List<string> Layout { get; set; } = new();

	[Property] public float TopOffset { get; set; } = 80f;

	[Property, Group( "Grid" )] public float CellWidth { get; set; } = 0f;

	[Property, Group( "Grid" )] public float CellHeight { get; set; } = 0f;

	/// <summary>
	/// Draws the small preview image the editor shows for this .level asset, rendering the brick layout so the board is recognisable in the asset browser.
	/// </summary>
	public override Bitmap RenderThumbnail( ThumbnailOptions options )
	{
		if ( Layout is null || Layout.Count == 0 )
			return null;

		int rows = Layout.Count;
		int cols = 0;
		foreach ( var row in Layout )
			cols = Math.Max( cols, row?.Length ?? 0 );

		if ( cols == 0 )
			return null;

		int width = options.Width > 0 ? options.Width : 256;
		int height = options.Height > 0 ? options.Height : 256;

		var bitmap = new Bitmap( width, height );
		bitmap.SetAntialias( true );
		bitmap.Clear( BlockPalette.Background );

		float pad = width * 0.06f;
		float cell = MathF.Min( (width - pad * 2f) / cols, (height - pad * 2f) / rows );
		float startX = (width - cell * cols) * 0.5f;
		float startY = (height - cell * rows) * 0.5f;
		float inset = MathF.Max( 1f, cell * 0.06f );
		float radius = MathF.Max( 1.5f, cell * 0.16f );

		for ( int r = 0; r < rows; r++ )
		{
			var row = Layout[r];

			for ( int c = 0; c < cols; c++ )
			{
				char symbol = row is not null && c < row.Length ? row[c] : '.';

				float x = startX + c * cell;
				float y = startY + r * cell;

				bitmap.SetFill( BlockPalette.ColorFor( symbol ) );
				bitmap.DrawRoundRect( new Rect( x + inset, y + inset, cell - inset * 2f, cell - inset * 2f ), new Margin( radius ) );
			}
		}

		return bitmap;
	}
}