A component that renders the playfield pattern as a single procedural quad in the world. It creates a SceneCustomObject, configures a material/shader and per-instance render attributes, computes a 2-triangle quad covering a rect, and encodes an 8x8 custom pattern into shader attributes.
using System;
using System.Collections.Generic;
using Sandbox;
namespace BlockParty;
/// <summary>Draws the arena's repeating two-colour pattern as one procedural world quad.</summary>
public sealed class PlayfieldPatternRenderer : Component
{
private SceneCustomObject _sceneObject;
private Material _material;
private readonly RenderAttributes _attributes = new();
private readonly List<Vertex> _vertices = new( 6 );
protected override void OnEnabled()
{
_material = Material.FromShader( "shaders/playfield_pattern.shader" );
_sceneObject = new SceneCustomObject( Scene.SceneWorld )
{
Transform = global::Transform.Zero,
RenderOverride = OnRender,
};
_sceneObject.Flags.IsOpaque = true;
_sceneObject.Flags.IsTranslucent = false;
_sceneObject.Flags.CastShadows = false;
}
protected override void OnDisabled()
{
_sceneObject?.Delete();
_sceneObject = null;
}
public void Setup( PlayfieldPattern pattern, int cellScale, IReadOnlyList<string> customRows, Color light, Color dark,
RectF? bounds = null, int depthOrder = 0 )
{
if ( _sceneObject is null ) return;
_attributes.Set( "Pattern", (int)pattern );
_attributes.Set( "CellScale", (float)Math.Clamp( cellScale, 1, 8 ) );
_attributes.Set( "LightColor", light );
_attributes.Set( "DarkColor", dark );
int width = customRows is { Count: > 0 } ? Math.Clamp( customRows[0]?.Length ?? 0, 1, 8 ) : 2;
int height = customRows is { Count: > 0 } ? Math.Clamp( customRows.Count, 1, 8 ) : 2;
_attributes.Set( "PatternWidth", width );
_attributes.Set( "PatternHeight", height );
for ( int row = 0; row < 8; row++ )
{
int bits = 0;
string source = customRows is not null && row < customRows.Count ? customRows[row] : null;
for ( int column = 0; column < width && column < (source?.Length ?? 0); column++ )
if ( source[column] == '1' ) bits |= 1 << column;
_attributes.Set( $"PatternRow{row}", bits );
}
RectF rect = bounds ?? new RectF( 0f, 0f, Arena.WIDTH, Arena.HEIGHT );
float z = Globals.DepthToZ( Globals.DEPTH_PLAYFIELD, depthOrder );
var color = Color.White;
Vertex V( float x, float y ) => new( new Vector3( x, y, z ), color );
_vertices.Clear();
_vertices.Add( V( rect.Left, rect.Bottom ) );
_vertices.Add( V( rect.Right, rect.Bottom ) );
_vertices.Add( V( rect.Right, rect.Top ) );
_vertices.Add( V( rect.Left, rect.Bottom ) );
_vertices.Add( V( rect.Right, rect.Top ) );
_vertices.Add( V( rect.Left, rect.Top ) );
}
private void OnRender( SceneObject sceneObject )
{
if ( _vertices.Count == 0 || _material is null ) return;
Graphics.Draw( _vertices, _vertices.Count, _material, _attributes, Graphics.PrimitiveType.Triangles );
}
}