The static class BankPrototype builds a bank scene using materials and models, and the BankPrototypeDoor component handles interactive door logic for the scene.
namespace HorizonRP;
/// <summary>Maquette indépendante, murs solides et portes de Truenorth à échelle native.</summary>
public static class BankPrototype
{
public const float PlanScale = 0.72f;
public static Vector3 Point( Vector3 p ) => new( p.x * PlanScale, p.y * PlanScale, p.z );
public static GameObject Build( Scene scene, Vector3 origin )
{
var root = new GameObject( scene ) { Name = "HZ BANK · prototype indépendant" };
root.WorldPosition = origin;
var stone = Material.Load( "truenorth/materials/agency/wall/marble01.vmat" );
var navy = Material.Load( "materials/horizon/bank_navy.vmat" );
var red = Material.Load( "materials/horizon/bank_red.vmat" );
var glass = Material.Load( "materials/horizon/bank_test_glass.vmat" );
var floor = Material.Load( "truenorth/materials/concrete/concretefloor008a.vmat" );
var baseMaterial = Material.Load( "truenorth/materials/concrete/concrete_ext_01.vmat" );
var roof = Material.Load( "truenorth/materials/building_template/roof_template001a.vmat" );
var light = Material.Load( "materials/horizon/bank_light.vmat" );
var box = Model.Load( "models/dev/box.vmdl" );
void Block( string name, Vector3 p, Vector3 size, Material material )
{
var go = new GameObject( root, true, name );
go.LocalPosition = Point( p ); go.LocalScale = Point( size ) / 50;
var r = go.Components.Create<ModelRenderer>(); r.Model = box; r.MaterialOverride = material;
var c = go.Components.Create<BoxCollider>(); c.Scale = new Vector3( 50 ); c.Static = true;
}
void Text( string text, Vector3 p, float size, float yaw )
{
var go = new GameObject( root, true, text ); go.LocalPosition = Point( p );
go.LocalRotation = Rotation.FromYaw( yaw );
var t = go.Components.Create<TextRenderer>(); t.Text = text; t.FontFamily = "Inter";
t.FontSize = 64; t.Scale = size / 64; t.FontWeight = 700; t.Color = Color.White;
t.HorizontalAlignment = TextRenderer.HAlignment.Center; t.VerticalAlignment = TextRenderer.VAlignment.Center;
}
Block( "Parvis / dalle de test", new( 0, -168, -12 ), new( 1320, 1376, 24 ), floor );
foreach ( float y in new[] { -720f, 384f } )
{
bool front = y > 0;
Block( "Linteau longitudinal", new( 0, y, 162 ), new( 1104, 24, 36 ), stone );
Block( "Acrotère longitudinal", new( 0, y, 201 ), new( 1120, 32, 18 ), stone );
Block( "Frise longitudinale", new( 0, y + (front ? 14 : -14), 181 ), new( 1104, 4, 3 ), red );
if ( !front )
{
const float gap = 26 / PlanScale;
foreach ( float side in new[] { -1f, 1f } )
Block( "Mur arrière sécurisé", new( side * (540 + gap) / 2, y, 72 ), new( 540 - gap, 24, 144 ), stone );
Block( "Linteau sortie de service", new( 0, y, 125 ), new( 52 / PlanScale, 24, 38 ), stone );
continue;
}
foreach ( float x in new[] { -528f, -360f, -180f, 180f, 360f, 528f } )
{
Block( "Pile de façade", new( x, y, 72 ), new( 28, 30, 144 ), stone );
Block( "Socle de pile", new( x, y, 10 ), new( 36, 38, 20 ), baseMaterial );
}
foreach ( float x in new[] { -444f, -270f, 270f, 444f } )
{
float width = MathF.Abs( x ) > 400 ? 140 : 152;
Block( "Allège de façade", new( x, y, 16 ), new( width, 24, 32 ), baseMaterial );
Block( "Vitrage de façade", new( x, y, 88 ), new( width, 4, 112 ), glass );
Block( "Traverse fenêtre", new( x, y + 4, 96 ), new( width, 6, 3 ), navy );
Block( "Meneau fenêtre", new( x, y, 88 ), new( 4, 12, 112 ), navy );
}
// Réservation de 96 unités pour les deux portes vitrées originales (2 × 46).
foreach ( float side in new[] { -1f, 1f } )
Block( "Vitrine de vestibule", new( side * 116.33333f, y, 72 ), new( 99.33333f, 24, 144 ), glass );
Block( "Imposte entrée", new( 0, y, 125 ), new( 96 / PlanScale, 24, 38 ), navy );
}
foreach ( float x in new[] { -540f, 540f } )
{
foreach ( float y in new[] { -712f, 376f } )
Block( "Angle porteur", new( x, y, 72 ), new( 24, 16, 144 ), stone );
Block( "Linteau latéral", new( x, -168, 162 ), new( 24, 1104, 36 ), stone );
Block( "Acrotère latéral", new( x, -168, 201 ), new( 32, 1136, 18 ), stone );
Block( "Frise latérale", new( x + MathF.Sign( x ) * 14, -168, 181 ), new( 4, 1104, 3 ), red );
Block( "Extension arrière opaque", new( x, -545, 72 ), new( 24, 350, 144 ), stone );
for ( int i = 0; i < 4; i++ )
{
float y = -288 + i * 192;
if ( i < 2 ) { Block( "Paroi zone privée", new( x, y, 72 ), new( 24, 164, 144 ), stone ); continue; }
Block( "Allège latérale", new( x, y, 16 ), new( 24, 164, 32 ), baseMaterial );
Block( "Vitrage latéral", new( x, y, 88 ), new( 4, 164, 112 ), glass );
Block( "Meneau latéral", new( x, y, 88 ), new( 12, 4, 112 ), navy );
Block( "Traverse latérale", new( x, y, 96 ), new( 8, 164, 3 ), navy );
}
foreach ( float y in new[] { -192f, 0f, 192f } )
Block( "Pile latérale", new( x, y, 72 ), new( 30, 28, 144 ), stone );
}
Block( "Toiture solide", new( 0, -168, 188 ), new( 1080, 1104, 8 ), roof );
Block( "Portique central", new( 0, 406, 162 ), new( 400, 72, 44 ), navy );
Block( "Couronne portique", new( 0, 406, 189 ), new( 416, 84, 8 ), stone );
Block( "Logo rouge", new( 115, 445, 166 ), new( 58, 6, 30 ), red );
Text( "HZ", new( 115, 450, 166 ), 20, -90 );
Text( "BANK", new( -40, 445, 166 ), 30, -90 );
Block( "Auvent entrée", new( 0, 438, 114 ), new( 180, 100, 6 ), navy );
Block( "Lumière sous auvent", new( 0, 452, 110.5f ), new( 120, 4, 1 ), light );
Text( "AGENCE CENTRALE", new( 0, 444, 146 ), 9, -90 );
Text( "HZ BANK", new( 0, -736, 162 ), 22, 90 );
BankPrototypeMapDoor.Entrance( root, Point( new Vector3( 0, 384, 2 ) ) );
BankPrototypeInterior.Build( root );
return root;
}
}
/// <summary>Interaction locale de la maquette uniquement : aucune porte de la ville modifiée.</summary>
public sealed class BankPrototypeDoor : Component, Component.IPressable
{
[Property] public Vector3 Slide { get; set; }
[Property] public bool IsOpen { get; set; }
[Property] public string Group { get; set; } = "entree";
[Property] public float TurnDegrees { get; set; }
Vector3 closed;
Rotation closedRotation;
float amount;
protected override void OnStart() { closed = LocalPosition; closedRotation = LocalRotation; }
public bool Press( IPressable.Event e )
{
// Les deux vantaux sont commandés ensemble.
var target = !IsOpen;
foreach ( var door in GameObject.Parent.Components.GetAll<BankPrototypeDoor>( FindMode.EverythingInSelfAndDescendants ) )
if ( door.Group == Group ) door.IsOpen = target;
return true;
}
public IPressable.Tooltip? GetTooltip( IPressable.Event e ) =>
new IPressable.Tooltip( GameObject.Name, "door_front", IsOpen ? "Fermer" : "Ouvrir" );
protected override void OnUpdate()
{
amount = Math.Clamp( amount + (IsOpen ? 1 : -1) * Time.Delta * 1.8f, 0, 1 );
LocalPosition = closed + Slide * (amount * amount * (3 - 2 * amount));
LocalRotation = closedRotation * Rotation.FromYaw( TurnDegrees * (amount * amount * (3 - 2 * amount)) );
}
}