Procedural mesh components for primitives, walls, stairs and props, with parameters you can retune in the Inspector or from code, palette-driven coloring, and optional colliders.
Summary
The 3D shape utilities are a family of 38 procedural mesh components. Each one is a MonoBehaviour that owns its mesh and regenerates it from a handful of parameters, so a box is a width, height and depth rather than a mesh asset, and a staircase is a step count, a step height and a step depth. Change a parameter from code, or in the Inspector while editing a scene, and the mesh rebuilds to match.
The family covers three kinds of geometry:
- Primitives such as the box, rounded box, UV sphere and icosphere, capsule, cylinder, cone, torus, plane, quad, dome and sky dome.
- Level pieces for blocking out spaces: walls with doors, windows or arches, decorated wall variants, floor tiles, columns, three kinds of stairs, a ramp and a ladder.
- Props such as a crate, a barrel, a bollard, a pipe segment and a sign.
All of them derive from ScyllaShapeBase3D, which handles the parts every shape shares. That includes the default lit material, a per-renderer tint written through a MaterialPropertyBlock, an optional collider that matches the shape, double-sided rendering for flat pieces, and per-face coloring that pulls colors from a palette. The ScyllaShapes factory spawns any of them from code in one call, and ScyllaShapeTweenExtensions animates their tint and dimensions through the tween system.
Scenes store the parameters, not the mesh. The generated mesh is never saved into a scene or prefab file, and the component rebuilds it when the object loads. A level blocked out from forty shapes therefore stays as small on disk as forty components.
Use the 3D shape utilities for:
- Greyboxing a level: rooms, corridors, stairs and doorways that can be resized without going back to a modeling tool.
- Prototype props and pickups that need to read as a crate, a barrel or a sign before the art exists.
- Debug and gameplay markers whose size is driven by data, such as a trigger radius or a spawn zone.
- Simple animated geometry, for example a pulsing pickup sphere or a portal ring that widens.
- Generating a mesh in code without a component at all, through each shape’s static
Buildmethod.
Features
- Parametric meshes. Every dimension is a property. Assigning one rebuilds the mesh, the collider and the tint immediately, in the Editor and at runtime.
- One factory for everything.
ScyllaShapes.CreateBox,CreateLinearStairs,CreateBarreland the rest spawn a new GameObject with the shape, its transform, tint, material and collider set up, and return the typed component. - Inspector support. Each shape appears under Add Component > Scylla > Geometry, and Inspector edits rebuild the mesh once the change settles, in Edit Mode and in Play Mode.
- Shared default materials. Shapes without a custom material share one cached lit material per variant, created at runtime for the active render pipeline. Core’s build step keeps the shaders those materials need in the player build.
- Tint without material instances.
Tintwrites the color to the renderer’sMaterialPropertyBlock, so tinting a hundred shapes creates no new materials. - Palette tints.
TintPaletteNameandTintEntryNametake the tint from a named entry in a registered palette, so a level’s colors can be changed in one asset. - Per-face coloring.
FaceColoringModecolors faces from a palette by cycling, hashing, a uniform entry, or a bottom-to-top gradient. Shapes with distinct parts, such as a crate’s frame and panels or a staircase’s treads and structure, add their ownColoringModethat decides how those parts are grouped. - Matching colliders.
ColliderModeattaches no collider, aMeshCollider, or the closest primitive collider (box, sphere or capsule), falling back to a mesh collider where no primitive fits. - Centered pivots. Every shape is centered on its local origin, so its bounding box is symmetric around the transform position.
- Clamped parameters. Out-of-range values are clamped. A property assignment clamps fixed ranges right away, so
Segments = 1on a cylinder reads back as 3. Limits that depend on another parameter, such as a door that must fit its wall, are applied when the mesh is built. - Static builders. Each shape exposes a public static
Build(Mesh target, ...)method that fills any mesh with the same geometry, without a component. - Tween helpers.
TweenTintworks on every shape, and fourteen primitives can tween their dimensions. - Extensible. A custom shape subclasses
ScyllaShapeBase3D, implementsBuildMesh, and inherits tinting, materials, colliders and coloring.
Components
All shape types live in the Scylla.Core.Util.Geom namespace. The tables group them by family and list the properties each one exposes on top of the shared base class properties.
Primitives
| Component | What it builds | Tunable properties |
|---|---|---|
ScyllaBoxShape | A hard-edged box with flat normals per face. | Size (Size3D), Width, Height, Depth |
ScyllaRoundedBoxShape | A box whose edges are quarter-cylinders and whose corners are eighth-spheres. | Size, Width, Height, Depth, CornerRadius, CornerSegments, ColoringMode |
ScyllaSphereShape | A UV sphere built from latitude and longitude rings. | Radius, Segments, Rings |
ScyllaIcoSphereShape | A subdivided icosahedron with near-uniform triangle sizes. | Radius, Subdivisions |
ScyllaCapsuleShape | A Y-aligned cylinder with hemispherical caps. | Radius, Height, Segments, CapRings |
ScyllaCylinderShape | A Y-aligned cylinder with optional end caps. | Radius, Height, Segments, Capped |
ScyllaConeShape | A cone with a circular base and faceted sides. | Radius, Height, Segments |
ScyllaPyramidShape | A pyramid with a regular N-sided base. | BaseSides, Radius, Height |
ScyllaTriangularPrismShape | A prism with equilateral triangle ends, standing along Y. | Radius, Height |
ScyllaOctahedronShape | Eight triangular faces meeting at six axis-aligned points. | Radius |
ScyllaTorusShape | A donut lying in the XZ plane. | MajorRadius, MinorRadius, RingSegments, TubeSegments |
ScyllaRingShape | A flat annulus extruded to a height, with hard edges. | OuterRadius, InnerRadius, Height, Segments |
ScyllaPipeShape | A hollow tube with an inner wall and optional end rings. | OuterRadius, InnerRadius, Height, Segments, Capped |
ScyllaPlaneShape | A horizontal plane in XZ, facing up, with optional subdivisions. | Size (Size2D), Width, Depth, Subdivisions |
ScyllaQuadShape | A vertical quad in XY, facing +Z. | Size (Size2D), Width, Height |
ScyllaDomeShape | The upper half of a UV sphere, optionally closed by a base disc. | Radius, Segments, Rings, BuildBase |
ScyllaSkyDomeShape | An inside-out hemisphere for sky backgrounds, visible from within. | Radius, Segments, Rings |
Walls, floors and columns
All walls run along X (length), stand up along Y (height) and take their thickness along Z. The decorated variants put their detail on the +Z side and keep the back face flat.
| Component | What it builds | Tunable properties |
|---|---|---|
ScyllaWallShape | A plain wall slab with optionally rounded edges. | Length, Height, Thickness, BevelRadius, BevelSegments |
ScyllaFloorTileShape | A thin slab in the XZ plane with optionally rounded edges. | Width, Depth, Thickness, BevelRadius, BevelSegments |
ScyllaWallWithDoorShape | A wall with a rectangular opening that reaches the floor. | WallLength, WallHeight, WallThickness, DoorWidth, DoorHeight, DoorOffsetX |
ScyllaWallWithWindowShape | A wall with a rectangular opening bounded on all four sides. | WallLength, WallHeight, WallThickness, WindowWidth, WindowHeight, WindowSillHeight, WindowOffsetX |
ScyllaArchwayShape | A wall with a floor-level opening topped by a half-circle arch. | WallLength, WallHeight, WallThickness, OpeningWidth, OpeningHeight, ArchSegments, OffsetX |
ScyllaCappedWallShape | A wall with a wider cornice band along the top. | Length, Height, Thickness, CapHeight, CapExtension, ColoringMode |
ScyllaSkirtedWallShape | A wall with a wider baseboard band along the bottom. | Length, Height, Thickness, SkirtHeight, SkirtExtension, ColoringMode |
ScyllaShelvedWallShape | A wall with evenly spaced horizontal shelves. | Length, Height, Thickness, ShelfCount, ShelfDepth, ColoringMode |
ScyllaPaneledWall1Shape | A wall with one or more nested recessed picture-frame grooves. | Length, Height, Thickness, FrameMargin, FrameThickness, FrameDepth, FrameCount, FrameSpacing, ColoringMode |
ScyllaPaneledWall2Shape | A wall with a grid of raised, beveled square panels. | Length, Height, Thickness, PanelRows, PanelCols, PanelBaseSize, PanelTopSize, PanelExtrusion, ColoringMode |
ScyllaColumnShape | A cylindrical shaft with an optional wider capital and base. | ShaftRadius, ShaftHeight, ShaftSegments, CapitalRadius, CapitalHeight, BaseRadius, BaseHeight, TotalHeight (read-only) |
Stairs, ramps and ladders
| Component | What it builds | Tunable properties |
|---|---|---|
ScyllaLinearStairsShape | A straight, solid flight that climbs toward +Z. | StepCount, StepHeight, StepDepth, Width, BuildSides, ColoringMode |
ScyllaCurvedStairsShape | A solid flight swept around the Y axis through a set angle. | InnerRadius, StepCount, StepHeight, StepWidth, SweepAngle, ColoringMode |
ScyllaSpiralStairsShape | Wedge treads winding around a central column, able to pass a full turn. | InnerRadius, StepCount, StepHeight, StepWidth, StepsPer360, CounterClockwise, ColoringMode |
ScyllaRampShape | A wedge that rises from zero height at -Z to full height at +Z. | Length, Width, Height, BuildSides |
ScyllaLadderShape | Two vertical rails joined by horizontal rungs. | RailSpacing, RungCount, RungSpacing, RailRadius, RungRadius |
Props
| Component | What it builds | Tunable properties |
|---|---|---|
ScyllaCrateShape | A box whose six faces each have a raised frame around a recessed panel. | Size (Size3D), TrimInset, TrimDepth, ColoringMode |
ScyllaBarrelShape | A lathed barrel with chamfered rims, belt hoops and flat lids. | Radius, Height, ChamferInset, ChamferHeight, BeltOutset, BeltHeight, BeltDistanceFromCap, Segments, ColoringMode |
ScyllaBollardShape | A cylindrical post with a hemispherical top. | Radius, BodyHeight, Segments, CapRings, ColoringMode |
ScyllaPipeSegmentShape | A solid pipe body with wider flanges at both ends. | BodyRadius, FlangeRadius, Height, FlangeThickness, Segments, ColoringMode |
ScyllaSignShape | A thin rectangular panel mounted on a round post. | PostHeight, PostRadius, PostSegments, PanelWidth, PanelHeight, PanelThickness, ColoringMode |
Supporting types
| Type | Role |
|---|---|
ScyllaShapeBase3D | Abstract base class of every shape. Owns the mesh, the material choice, the tint, the collider and the face coloring. |
ScyllaShapes | Static factory. One Create* method per shape, plus CreateCube, CreateMesh for a caller-supplied mesh, and ApplyColor for tinting any renderer. |
ShapeMaterialCache | Lazily creates the four shared shape materials (lit, lit double-sided, vertex-lit, vertex-lit double-sided) and writes tints through a shared MaterialPropertyBlock. |
ColliderMode | None, Mesh or MatchingPrimitive. |
FaceColoringMode | None, Cycle, Hash, GradientByY or Uniform. Decides which palette entry each face receives. |
IFaceColorResolver | The per-face color callback that the static builders accept. UniformColorResolver and PaletteFaceColorResolver implement it. |
| Shape coloring enums | RoundedBoxColoringMode, CrateColoringMode, BarrelColoringMode, BollardColoringMode, PipeSegmentColoringMode, SignColoringMode, StairsColoringMode, CappedWallColoringMode, SkirtedWallColoringMode, ShelvedWallColoringMode, PaneledWall1ColoringMode and PaneledWall2ColoringMode. Each picks how a shape’s parts are grouped into face indices. |
ScyllaShapeTweenExtensions | Tween helpers in Scylla.Core.Util.Tween. See Tween. |
Placement conventions
Every shape is centered on its local origin, including the ones that look like they should sit on the ground. A 3-unit wall spans y = -1.5 to y = 1.5, a staircase is centered on half its total rise, and a floor tile’s top face sits at +Thickness / 2. To stand a piece on the floor at y = 0, raise it by half its height.
A few axes are worth remembering:
- Plane and floor tile lie in XZ and face +Y.
- Quad stands in XY and faces +Z.
- Walls run along X and take their thickness along Z. Decorations such as caps, skirts, shelves and panels face +Z.
- Linear stairs and the ramp climb toward +Z.
- Cylinders, capsules, cones, pipes, columns, domes and the torus are built around the Y axis.
Recipes
The recipes are independent of each other. All of them assume using Scylla.Core.Util.Geom; and using UnityEngine;.
Spawn a shape from code and resize it
Problem. A shape is needed at runtime, at a known position, with a size that comes from game data.
Solution. Call the matching ScyllaShapes.Create* method. The leading parameters are shared by all of them (parent, name, position, rotation, scale, tint, material, collider mode), and the shape-specific parameters follow. Named arguments keep the call readable. The returned component can be changed later, and every property assignment rebuilds the mesh.
/* Spawn a vivid orange box, 2 x 1 x 1, with a matching BoxCollider. */
var box = ScyllaShapes.CreateBox(
parent: transform,
name: "Cargo Block",
position: new Vector3(0f, 0.5f, 4f),
tint: new Color(1f, 0.45f, 0f),
colliderMode: ColliderMode.MatchingPrimitive,
width: 2f,
height: 1f,
depth: 1f);
/* Later: stretch it. Each property assignment rebuilds the mesh and the collider. */
box.Width = 3f;
/* Set all three at once to rebuild only once. */
box.Size = new Size3D(3f, 2f, 1f);
A shape can also be added to an existing GameObject with AddComponent. The MeshFilter and MeshRenderer it needs are added automatically.
var sphere = gameObject.AddComponent<ScyllaSphereShape>();
sphere.Radius = 0.75f;
sphere.Segments = 32;
sphere.Tint = new Color(0f, 0.8f, 1f);
Tint shapes from a palette
Problem. A greybox uses a few colors across dozens of shapes, and changing one of them should not mean visiting every shape.
Solution. Point each shape at a palette entry with TintPaletteName and TintEntryName. The tint is looked up through the ColorAssetRegistry. When both names resolve, the palette color replaces the inline Tint. When either is empty or does not resolve, the shape falls back to Tint, so the inline color doubles as a safe default.
/* "Blockout" stands for a palette asset of your own, with entries named "Wall", "Floor" and "Accent". */
wall.Tint = new Color(1f, 0f, 0.6f); /* fallback if the palette is missing */
wall.TintPaletteName = "Blockout";
wall.TintEntryName = "Wall";
floor.TintPaletteName = "Blockout";
floor.TintEntryName = "Floor";
The same two fields are in the Inspector as Tint Palette Name and Tint Entry Name. Changing them re-applies the tint without rebuilding the mesh. The Palette page covers creating and registering palettes.
Color each face from a palette
Problem. A prop should read as more than one solid color, for example a crate with a light frame and dark recessed panels, or a sky dome that shifts from horizon to zenith.
Solution. Set a FaceColoringMode and give the shape a palette, either by registry name through ColorPaletteName or directly through FaceColoringPalette. The shape then writes a vertex color per face and switches to the vertex-lit material, which multiplies the vertex colors by the tint. For shapes with named parts, the shape’s own ColoringMode decides how faces are grouped before the palette is applied.
using Scylla.Core.Util.Palette;
/* A two-entry palette built in code. FaceColoringPalette takes precedence over ColorPaletteName. */
var cratePalette = new ColorPalette("Crate", "Frame and panels", new[]
{
new ColorPaletteEntry("Frame", new Color(1f, 0.55f, 0f)),
new ColorPaletteEntry("Panel", new Color(0.55f, 0.1f, 0f))
});
var crate = ScyllaShapes.CreateCrate(name: "Supply Crate", position: new Vector3(2f, 0.5f, 0f));
/* FrameAndPanels groups the crate into two face indices: 0 is the frame, 1 is the panels.
Cycle maps face index i to palette entry i % count, so the frame takes "Frame" and the
panels take "Panel". */
crate.ColoringMode = CrateColoringMode.FrameAndPanels;
crate.FaceColoringPalette = cratePalette;
crate.FaceColoringMode = FaceColoringMode.Cycle;
/* A sky dome with a vertical gradient: deep blue at the horizon to violet at the zenith. */
var sky = ScyllaShapes.CreateSkyDome(radius: 80f);
sky.FaceColoringPalette = new ColorPalette("Sky", "Horizon to zenith", new[]
{
new ColorPaletteEntry("Horizon", new Color(0f, 0.45f, 1f)),
new ColorPaletteEntry("Zenith", new Color(0.45f, 0f, 0.9f))
});
sky.FaceColoringMode = FaceColoringMode.GradientByY;
The five modes behave as follows:
Cyclegives faceithe palette entry ati % count.Hashscatters entries pseudo-randomly but deterministically. ChangeColorSeedto reshuffle.GradientByYblends neighboring entries by each face’s height within the shape.Uniformgives every face the entry atUniformColorIndex.None(the default) emits no vertex colors, and the shape renders with the plain lit material and its tint.
Faceted shapes such as the box, crate, walls, stairs and props color one region at a time, so each face gets one flat color. Smooth shapes such as the sphere, capsule, cylinder, dome and sky dome color each vertex, which turns GradientByY into a smooth ramp.
Block out a room
Problem. A level prototype needs a room with a floor, four walls, a doorway and a staircase, built from code so it can be regenerated from a layout.
Solution. Combine the floor tile, wall, wall-with-door and linear stairs factories under one parent. Because every shape is centered, each piece is raised by half its height to sit on y = 0.
const float roomWidth = 8f;
const float roomDepth = 6f;
const float wallHeight = 3f;
const float wallThickness = 0.2f;
var room = new GameObject("Room").transform;
var wallY = wallHeight * 0.5f;
/* Floor: 0.1 thick, top face flush with y = 0. */
ScyllaShapes.CreateFloorTile(room, "Floor",
position: new Vector3(0f, -0.05f, 0f),
tint: new Color(0f, 0.55f, 0.6f),
colliderMode: ColliderMode.MatchingPrimitive,
width: roomWidth,
depth: roomDepth,
thickness: 0.1f);
/* North wall runs along X. */
ScyllaShapes.CreateWall(room, "Wall North",
position: new Vector3(0f, wallY, roomDepth * 0.5f),
tint: new Color(1f, 0.45f, 0f),
colliderMode: ColliderMode.MatchingPrimitive,
length: roomWidth,
height: wallHeight,
thickness: wallThickness);
/* South wall with a doorway. Openings fall back to a MeshCollider, so the gap stays walkable. */
ScyllaShapes.CreateWallWithDoor(room, "Wall South",
position: new Vector3(0f, wallY, -roomDepth * 0.5f),
tint: new Color(1f, 0.45f, 0f),
colliderMode: ColliderMode.MatchingPrimitive,
wallLength: roomWidth,
wallHeight: wallHeight,
wallThickness: wallThickness,
doorWidth: 1.2f,
doorHeight: 2.2f);
/* East and west walls: the same wall turned 90 degrees around Y. */
var sideRotation = Quaternion.Euler(0f, 90f, 0f);
for (var side = -1; side <= 1; side += 2)
{
ScyllaShapes.CreateWall(room, side < 0 ? "Wall West" : "Wall East",
position: new Vector3(side * roomWidth * 0.5f, wallY, 0f),
rotation: sideRotation,
tint: new Color(1f, 0.45f, 0f),
colliderMode: ColliderMode.MatchingPrimitive,
length: roomDepth,
height: wallHeight,
thickness: wallThickness);
}
/* Stairs along the east wall, climbing toward +Z. Ten steps of 0.25 rise 2.5 units. */
ScyllaShapes.CreateLinearStairs(room, "Stairs",
position: new Vector3(3f, 1.25f, 0f),
tint: new Color(0.9f, 0f, 0.55f),
colliderMode: ColliderMode.Mesh,
stepCount: 10,
stepHeight: 0.25f,
stepDepth: 0.3f,
width: 1.5f);
Animate a shape with tweens
Problem. A pickup should pulse, a pressure plate should flatten, or a warning shape should flash.
Solution. ScyllaShapeTweenExtensions adds tween helpers to the shapes. TweenTint works on every shape. The dimension helpers, such as TweenRadius, TweenSize or TweenCornerRadius, exist for the fourteen primitives: box, rounded box, sphere, icosphere, cylinder, cone, capsule, torus, ring, plane, quad, pyramid, triangular prism and octahedron. Each helper returns a tween that has not started yet, so finish the chain with PlayManaged().
using Scylla.Core.Util.Tween;
/* Pulse a pickup. The sphere rebuilds its mesh on every tick. */
pickup.TweenRadius(0.6f, 0.25f)
.SetEase(EaseType.OutBack)
.SetPingPong()
.PlayManaged();
/* Flash a hazard marker red. Tint changes only the property block, so no mesh is rebuilt. */
hazard.TweenTint(new Color(1f, 0.05f, 0f), 0.1f)
.SetPingPong(loops: 6)
.PlayManaged();
/* Every helper sets the shape as the tween target, so this stops all of them. */
ScyllaTween.KillTweensOf(pickup);
The Tween page lists every helper and covers easing, looping and sequences. Integer parameters such as segment counts have no tween helper, because they change the mesh topology in steps.
Build a mesh without a component
Problem. A system needs the geometry of a shape, for example to combine many tiles into one mesh or to feed a custom renderer, but does not want a component per piece.
Solution. Every shape has a public static Build method that fills a Mesh with the same geometry the component produces. Clear the mesh first and recalculate the bounds afterwards, because the component normally does both for you. ScyllaShapes.CreateMesh then spawns a GameObject that renders the result with the shared shape material.
var mesh = new Mesh { name = "Portal Ring" };
/* Pass a resolver to emit vertex colors, or null for none. */
ScyllaTorusShape.Build(mesh, majorRadius: 2f, minorRadius: 0.15f, ringSegments: 64, tubeSegments: 12);
mesh.RecalculateBounds();
var portal = ScyllaShapes.CreateMesh(transform, mesh, "Portal",
position: new Vector3(0f, 2f, 0f),
rotation: Quaternion.Euler(90f, 0f, 0f),
tint: new Color(0.2f, 1f, 0f));
Write a custom shape
Problem. A project needs its own parametric piece, such as a plinth or a bespoke wall module, and it should behave like the built-in shapes in the Inspector and at runtime.
Solution. Subclass ScyllaShapeBase3D, keep the parameters in serialized fields, store each one through SetAndRebuild in its property setter, and fill the mesh in BuildMesh. SetAndRebuild rebuilds only when the value actually changes. Forwarding ActiveFaceColorResolver to the builder gives the shape per-face coloring for free. The static builders of the existing shapes are a good starting point.
[AddComponentMenu("My Game/Plinth")]
public sealed class PlinthShape : ScyllaShapeBase3D
{
[SerializeField]
private float _size = 1f;
[SerializeField]
private float _height = 0.4f;
public float Size
{
get => _size;
set => SetAndRebuild(ref _size, value);
}
public float Height
{
get => _height;
set => SetAndRebuild(ref _height, value);
}
protected override void BuildMesh(Mesh target)
{
/* The base class clears the mesh before this call and recalculates bounds after it. */
ScyllaBoxShape.Build(target, _size, _height, _size, ActiveFaceColorResolver);
}
protected override bool TryAttachPrimitiveCollider()
{
/* Called under ColliderMode.MatchingPrimitive on every rebuild. GetOrAddManagedCollider
reuses the collider from the previous rebuild, so only its size needs updating.
Returning false falls back to a MeshCollider. */
var box = GetOrAddManagedCollider<BoxCollider>();
box.size = new Vector3(_size, _height, _size);
return true;
}
}
API Sketch
Everything is in Scylla.Core.Util.Geom except the tween helpers, which are in Scylla.Core.Util.Tween.
/* --- Base class of every shape. --- */
[ExecuteAlways, DisallowMultipleComponent]
[RequireComponent(typeof(MeshFilter)), RequireComponent(typeof(MeshRenderer))]
abstract class ScyllaShapeBase3D : MonoBehaviour
{
/* Appearance. Tint and the tint palette fields re-apply the tint without a rebuild. */
Color Tint { get; set; } /* MaterialPropertyBlock color; default white keeps a custom material's color */
string TintPaletteName { get; set; } /* registry palette for the tint; empty uses Tint */
string TintEntryName { get; set; } /* entry in TintPaletteName; overrides Tint when both resolve */
bool DoubleSided { get; set; } /* swaps to the double-sided cached material */
/* Per-face coloring. Every setter rebuilds the mesh. */
FaceColoringMode FaceColoringMode { get; set; } /* default None */
string ColorPaletteName { get; set; } /* registry palette for face colors */
ColorPalette FaceColoringPalette { get; set; } /* runtime palette, wins over ColorPaletteName; not serialized */
int ColorSeed { get; set; } /* Hash mode only */
int UniformColorIndex { get; set; } /* Uniform mode only; clamped to the palette */
/* Physics. */
ColliderMode ColliderMode { get; set; } /* default None; applied immediately and on every rebuild */
/* Mesh access. */
Mesh SharedMesh { get; } /* the owned mesh; do not destroy it */
AABB3D Bounds { get; } /* local-space bounds of the last build; AABB3D.Empty before the first */
bool IsManuallyEdited { get; set; } /* reserved for tooling; when true, Rebuild does nothing */
void Rebuild(); /* regenerate mesh, collider, material and tint */
Mesh RebuildAndGet(); /* Rebuild, then return SharedMesh */
/* Extension points for subclasses. */
protected IFaceColorResolver ActiveFaceColorResolver { get; } /* null when face coloring is off */
protected abstract void BuildMesh(Mesh target); /* fill the already-cleared mesh */
protected virtual bool TryAttachPrimitiveCollider(); /* default false: use a MeshCollider */
protected T GetOrAddManagedCollider<T>() where T : Collider; /* the shape's own collider, reused across rebuilds */
protected bool SetAndRebuild(ref float field, float value); /* store and rebuild unless approximately equal */
protected bool SetAndRebuild<T>(ref T field, T value); /* store and rebuild unless equal */
protected virtual string GetMeshName(); /* default: the type name */
protected virtual void Awake(); /* call base.Awake() when overriding */
protected virtual void OnDestroy(); /* call base.OnDestroy() when overriding */
protected virtual void OnValidate(); /* call base.OnValidate() when overriding */
}
enum ColliderMode { None, Mesh, MatchingPrimitive }
enum FaceColoringMode { None, Cycle, Hash, GradientByY, Uniform }
interface IFaceColorResolver
{
Color32 Resolve(int faceIndex, int faceCount, Vector3 faceCenter);
}
sealed class UniformColorResolver : IFaceColorResolver
{
UniformColorResolver(Color32 color);
}
sealed class PaletteFaceColorResolver : IFaceColorResolver
{
PaletteFaceColorResolver(ColorPalette palette, FaceColoringMode mode, int seed = 0,
int uniformIndex = 0, float minY = -0.5f, float maxY = 0.5f);
}
static class ShapeMaterialCache
{
Material GetDefault(); /* pipeline Lit material, shared */
Material GetDoubleSided();
Material GetVertexLit(); /* multiplies _BaseColor by vertex color; falls back to GetDefault */
Material GetVertexLitDoubleSided();
bool IsCachedShapeMaterial(Material material);
void ApplyTint(Renderer renderer, Color color); /* writes _BaseColor, or _Color on Built-in Standard */
void ClearTint(Renderer renderer); /* removes the renderer's whole property block */
}
/* --- Factory. Every Create* method starts with the same parameters:
(Transform parent = null, string name = "<Shape>",
Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null,
Color? tint = null, Material material = null,
ColliderMode colliderMode = ColliderMode.None, ...shape parameters)
position and rotation are world space; scale is local. --- */
static class ScyllaShapes
{
ScyllaBoxShape CreateBox(..., float width = 1f, float height = 1f, float depth = 1f);
ScyllaBoxShape CreateCube(..., float size = 1f);
/* ...one Create* per shape type, with the defaults listed below... */
GameObject CreateMesh(Transform parent, Mesh mesh, string name = "Mesh",
Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null,
Color? tint = null, Material material = null,
bool addCollider = false, bool doubleSided = false);
void ApplyColor(GameObject target, Color color); /* tints the first Renderer found */
}
Every shape also has a public static Build(Mesh target, ...) method that takes the same parameters as its properties, plus an optional IFaceColorResolver. The tunable properties, with their defaults and the ranges they are clamped to. Fixed ranges are also applied when the property is set; ranges that depend on another parameter only at build time:
/* --- Primitives --- */
ScyllaBoxShape Size3D Size = (1, 1, 1); float Width, Height, Depth;
ScyllaRoundedBoxShape Size3D Size = (1, 1, 1); float Width, Height, Depth;
float CornerRadius = 0.1; /* clamped to half the smallest dimension */
int CornerSegments = 4; /* 1..32 */
RoundedBoxColoringMode ColoringMode = FrameAndPanels; /* | PerSurfaceGroup */
ScyllaSphereShape float Radius = 0.5; int Segments = 24; int Rings = 16; /* 3..256 */
ScyllaIcoSphereShape float Radius = 0.5; int Subdivisions = 2; /* 0..5 */
ScyllaCapsuleShape float Radius = 0.5; float Height = 2; /* total height, caps included */
int Segments = 24 /* 3..256 */; int CapRings = 8 /* 2..64 */;
ScyllaCylinderShape float Radius = 0.5; float Height = 2; int Segments = 24 /* 3..256 */; bool Capped = true;
ScyllaConeShape float Radius = 0.5; float Height = 1; int Segments = 24; /* 3..256 */
ScyllaPyramidShape int BaseSides = 4 /* 3..256 */; float Radius = 0.5 /* base circumradius */; float Height = 1;
ScyllaTriangularPrismShape float Radius = 0.5; float Height = 1;
ScyllaOctahedronShape float Radius = 0.5;
ScyllaTorusShape float MajorRadius = 1; float MinorRadius = 0.3;
int RingSegments = 32; int TubeSegments = 16; /* 3..256 */
ScyllaRingShape float OuterRadius = 1; float InnerRadius = 0.7; float Height = 0.2; int Segments = 32;
ScyllaPipeShape float OuterRadius = 0.5; float InnerRadius = 0.35; float Height = 1;
int Segments = 24; bool Capped = true;
ScyllaPlaneShape Size2D Size = (1, 1); float Width, Depth; int Subdivisions = 0; /* 0..128 */
ScyllaQuadShape Size2D Size = (1, 1); float Width, Height;
ScyllaDomeShape float Radius = 0.5; int Segments = 24; int Rings = 8 /* 2..128 */; bool BuildBase = true;
ScyllaSkyDomeShape float Radius = 50; int Segments = 32; int Rings = 12;
/* --- Walls, floors and columns --- */
ScyllaWallShape float Length = 2; float Height = 3; float Thickness = 0.2;
float BevelRadius = 0; /* clamped to half of min(Height, Thickness) */
int BevelSegments = 3; /* 1..32 */
ScyllaFloorTileShape float Width = 1; float Depth = 1; float Thickness = 0.1;
float BevelRadius = 0; int BevelSegments = 3;
ScyllaWallWithDoorShape float WallLength = 4; float WallHeight = 3; float WallThickness = 0.2;
float DoorWidth = 1; float DoorHeight = 2; float DoorOffsetX = 0;
ScyllaWallWithWindowShape float WallLength = 4; float WallHeight = 3; float WallThickness = 0.2;
float WindowWidth = 1.2; float WindowHeight = 1;
float WindowSillHeight = 1; float WindowOffsetX = 0;
ScyllaArchwayShape float WallLength = 4; float WallHeight = 3; float WallThickness = 0.2;
float OpeningWidth = 1.6; /* arch diameter */
float OpeningHeight = 1.2; /* straight section below the arch */
int ArchSegments = 12 /* 2..64 */; float OffsetX = 0;
ScyllaCappedWallShape float Length = 4; float Height = 3; float Thickness = 0.2;
float CapHeight = 0.3; float CapExtension = 0.1;
CappedWallColoringMode ColoringMode = WallAndCap; /* | PerRegion */
ScyllaSkirtedWallShape float Length = 4; float Height = 3; float Thickness = 0.2;
float SkirtHeight = 0.3; float SkirtExtension = 0.1;
SkirtedWallColoringMode ColoringMode = WallAndSkirt; /* | PerRegion */
ScyllaShelvedWallShape float Length = 4; float Height = 3; float Thickness = 0.2;
int ShelfCount = 4 /* 0..32 */; float ShelfDepth = 0.25;
ShelvedWallColoringMode ColoringMode = WallAndShelves; /* | PerShelf */
ScyllaPaneledWall1Shape float Length = 4; float Height = 3; float Thickness = 0.2;
float FrameMargin = 0.25; float FrameThickness = 0.15; float FrameDepth = 0.04;
int FrameCount = 1 /* 1..8 */; float FrameSpacing = 0.1;
PaneledWall1ColoringMode ColoringMode = FrameAndPanel; /* | PerRegion */
ScyllaPaneledWall2Shape float Length = 4; float Height = 3; float Thickness = 0.2;
int PanelRows = 2; int PanelCols = 2; /* 1..8 */
float PanelBaseSize = 1.2; float PanelTopSize = 0.8; float PanelExtrusion = 0.15;
PaneledWall2ColoringMode ColoringMode = WallAndPanels; /* | WallSidesTops */
ScyllaColumnShape float ShaftRadius = 0.3; float ShaftHeight = 3; int ShaftSegments = 24 /* 3..128 */;
float CapitalRadius = 0.4; float CapitalHeight = 0.2; /* 0 omits the capital */
float BaseRadius = 0.4; float BaseHeight = 0.2; /* 0 omits the base */
float TotalHeight { get; }
/* --- Stairs, ramps and ladders --- */
ScyllaLinearStairsShape int StepCount = 8 /* 1..128 */; float StepHeight = 0.2; float StepDepth = 0.3;
float Width = 1.5; bool BuildSides = true;
StairsColoringMode ColoringMode = TreadsAndStructure; /* | PerStep */
ScyllaCurvedStairsShape float InnerRadius = 1; int StepCount = 8 /* 1..128 */; float StepHeight = 0.2;
float StepWidth = 1; float SweepAngle = 90; /* degrees */
StairsColoringMode ColoringMode = TreadsAndStructure;
ScyllaSpiralStairsShape float InnerRadius = 0.3; int StepCount = 12 /* 1..256 */; float StepHeight = 0.22;
float StepWidth = 1; float StepsPer360 = 12; bool CounterClockwise = false;
StairsColoringMode ColoringMode = TreadsAndStructure;
ScyllaRampShape float Length = 2; float Width = 1; float Height = 1; bool BuildSides = true;
ScyllaLadderShape float RailSpacing = 0.5; int RungCount = 8 /* 1..256 */; float RungSpacing = 0.3;
float RailRadius = 0.04; float RungRadius = 0.03;
/* --- Props --- */
ScyllaCrateShape Size3D Size = (1, 1, 1); float TrimInset = 0.08; float TrimDepth = 0.03;
CrateColoringMode ColoringMode = FrameAndPanels; /* | PerSide */
ScyllaBarrelShape float Radius = 0.4; float Height = 1;
float ChamferInset = 0.06; float ChamferHeight = 0.1;
float BeltOutset = 0.03; float BeltHeight = 0.06; float BeltDistanceFromCap = 0.18;
int Segments = 24 /* 3..256 */;
BarrelColoringMode ColoringMode = LidsBeltAndBody; /* | PerSection */
ScyllaBollardShape float Radius = 0.2; float BodyHeight = 0.8;
int Segments = 24 /* 3..128 */; int CapRings = 6 /* 2..64 */;
BollardColoringMode ColoringMode = BodyAndCap; /* | PerRegion */
ScyllaPipeSegmentShape float BodyRadius = 0.3; float FlangeRadius = 0.38; float Height = 1;
float FlangeThickness = 0.08; int Segments = 24;
PipeSegmentColoringMode ColoringMode = FlangesAndBody; /* | PerSection */
ScyllaSignShape float PostHeight = 1.5; float PostRadius = 0.04; int PostSegments = 12 /* 3..64 */;
float PanelWidth = 0.8; float PanelHeight = 0.5; float PanelThickness = 0.04;
SignColoringMode ColoringMode = PostAndPanel; /* | PerRegion */
Under ColliderMode.MatchingPrimitive, the box, rounded box, crate, wall, floor tile and plane attach a BoxCollider, the sphere and icosphere attach a SphereCollider, and the capsule attaches a CapsuleCollider. Every other shape uses a MeshCollider.
See the API reference for full signatures and remarks on every member.
Best Practices
- Set several dimensions through the compound property when there is one.
box.Size = new Size3D(...)rebuilds once, while assigningWidth,HeightandDepthin turn rebuilds three times. - Use the factory for runtime spawning and
AddComponentfor authored objects. The factory sets the transform, tint, material and collider in one call. A component added in the Editor keeps its parameters in the scene and can be tuned in the Inspector. - Raise pieces by half their height. Every shape is centered on its pivot, so a wall of height 3 stands on the floor at
y = 1.5. Keeping that in a helper avoids a scene full of half-buried walls. - Prefer
MatchingPrimitivefor colliders. A box or sphere collider is much cheaper for physics queries than a mesh collider, and shapes without a primitive equivalent fall back to a mesh collider on their own. - Leave decorative geometry without a collider.
ColliderMode.Noneis the default and costs nothing in physics. - Keep segment counts modest. A cylinder at 24 segments reads as round at normal viewing distances. Raise counts for hero objects and close-ups, not for every pipe in a level.
- Tint through palettes.
TintPaletteNameandTintEntryNamekeep a level’s color scheme in one asset, and the inlineTintremains as the fallback. - Animate the tint or the transform when the size does not need to change. A tint or transform tween rebuilds nothing, while every dimension tween rebuilds the mesh on each tick.
- Use
DoubleSidedfor planes and quads seen from both sides. It switches to the shared double-sided material instead of creating a new one. - Reuse the static builders. A custom shape or a mesh-combining tool can call
ScyllaBoxShape.Build,ScyllaRoundedBoxShape.Buildand the others instead of writing mesh code from scratch.
Pitfalls
Related
- 01 Scylla Core
- 33 Core Utilities – Geometry
- 28 Core Utilities – Tween
- 40 Core Utilities – Palette
- 39 Core Utilities – Color
- API reference:
Scylla.Core.Util.Geom.ScyllaShapeBase3D - API reference:
Scylla.Core.Util.Geom.ScyllaShapes - API reference:
Scylla.Core.Util.Geom.ShapeMaterialCache - API reference:
Scylla.Core.Util.Geom.ColliderMode - API reference:
Scylla.Core.Util.Geom.FaceColoringMode - API reference:
Scylla.Core.Util.Geom.IFaceColorResolver - API reference:
Scylla.Core.Util.Geom.UniformColorResolver - API reference:
Scylla.Core.Util.Geom.PaletteFaceColorResolver - API reference:
Scylla.Core.Util.Geom.ScyllaBoxShape - API reference:
Scylla.Core.Util.Geom.ScyllaRoundedBoxShape - API reference:
Scylla.Core.Util.Geom.ScyllaSphereShape - API reference:
Scylla.Core.Util.Geom.ScyllaIcoSphereShape - API reference:
Scylla.Core.Util.Geom.ScyllaCapsuleShape - API reference:
Scylla.Core.Util.Geom.ScyllaCylinderShape - API reference:
Scylla.Core.Util.Geom.ScyllaConeShape - API reference:
Scylla.Core.Util.Geom.ScyllaTorusShape - API reference:
Scylla.Core.Util.Geom.ScyllaRingShape - API reference:
Scylla.Core.Util.Geom.ScyllaPlaneShape - API reference:
Scylla.Core.Util.Geom.ScyllaQuadShape - API reference:
Scylla.Core.Util.Geom.ScyllaPyramidShape - API reference:
Scylla.Core.Util.Geom.ScyllaTriangularPrismShape - API reference:
Scylla.Core.Util.Geom.ScyllaOctahedronShape - API reference:
Scylla.Core.Util.Geom.ScyllaDomeShape - API reference:
Scylla.Core.Util.Geom.ScyllaSkyDomeShape - API reference:
Scylla.Core.Util.Geom.ScyllaPipeShape - API reference:
Scylla.Core.Util.Geom.ScyllaPipeSegmentShape - API reference:
Scylla.Core.Util.Geom.ScyllaWallShape - API reference:
Scylla.Core.Util.Geom.ScyllaFloorTileShape - API reference:
Scylla.Core.Util.Geom.ScyllaWallWithDoorShape - API reference:
Scylla.Core.Util.Geom.ScyllaWallWithWindowShape - API reference:
Scylla.Core.Util.Geom.ScyllaArchwayShape - API reference:
Scylla.Core.Util.Geom.ScyllaCappedWallShape - API reference:
Scylla.Core.Util.Geom.ScyllaSkirtedWallShape - API reference:
Scylla.Core.Util.Geom.ScyllaShelvedWallShape - API reference:
Scylla.Core.Util.Geom.ScyllaPaneledWall1Shape - API reference:
Scylla.Core.Util.Geom.ScyllaPaneledWall2Shape - API reference:
Scylla.Core.Util.Geom.ScyllaColumnShape - API reference:
Scylla.Core.Util.Geom.ScyllaLinearStairsShape - API reference:
Scylla.Core.Util.Geom.ScyllaCurvedStairsShape - API reference:
Scylla.Core.Util.Geom.ScyllaSpiralStairsShape - API reference:
Scylla.Core.Util.Geom.ScyllaRampShape - API reference:
Scylla.Core.Util.Geom.ScyllaLadderShape - API reference:
Scylla.Core.Util.Geom.ScyllaCrateShape - API reference:
Scylla.Core.Util.Geom.ScyllaBarrelShape - API reference:
Scylla.Core.Util.Geom.ScyllaBollardShape - API reference:
Scylla.Core.Util.Geom.ScyllaSignShape - API reference:
Scylla.Core.Util.Tween.ScyllaShapeTweenExtensions