Modular props and grid snapping
Assets/
Scenes/
Arena.unity
Scripts/
Prefabs/
Project settings
you already havethis lesson adds
You already built a kit like this once, in tab 01: P_Cube, P_Sphere, P_Capsule. Same idea, bigger pieces, and this time they have to fit against each other.
Professional levels are not modelled as one giant mesh. They are assembled from a small kit of pieces that snap together on a grid.
Every piece is 1, 2 or 4 metres, so anything fits against anything.
Set the grid before you model anything
- 1
Open and set Move to 1, 1, 1 and Rotate to 90.
- 2
Hold Ctrl/Cmd while dragging a handle to snap in those steps.
- 3
Model every module so its pivot sits at a corner, not the centre. This is the same trap as the cube at Y = 0.5 in tab 01 — pivot placement decides everything.
- 4
Every module faces the same way (+Z forward) so rotating by 90° always works.
A starter kit
Floor 4×4, wall 4×3, wall with doorway, wall with window, corner pillar, stair 2×3.
Six pieces build an entire building. Ten pieces build a village.
ProBuilder (free from Package Manager) lets you model these inside Unity without leaving the editor.
Prefab variants — one kit, many themes
Make the grey blockout first. Then create variants that only change the material. Fix a bug in the base prefab and every variant inherits the fix.
- 1
Right-click P_Wall → Create → Prefab Variant → name it P_Wall_Stone.
- 2
Open the variant, change only the material. Everything else stays linked to the parent.
- 3
Later, adding a collider to P_Wall adds it to P_Wall_Stone and P_Wall_Wood at the same time.
Scatter props without hand-placing 300 rocks
using UnityEngine;
public class PropScatter : MonoBehaviour
{
[SerializeField] private GameObject[] prefabs;
[SerializeField] private int count = 120;
[SerializeField] private float radius = 40f;
[SerializeField] private LayerMask groundMask;
[SerializeField] private Vector2 scaleRange = new Vector2(0.8f, 1.4f);
[ContextMenu("Scatter")] // right-click the component → Scatter
public void Scatter()
{
Clear();
for (int i = 0; i < count; i++)
{
Vector2 flat = Random.insideUnitCircle * radius;
Vector3 from = transform.position + new Vector3(flat.x, 50f, flat.y);
// drop a ray straight down to find the ground
if (!Physics.Raycast(from, Vector3.down, out RaycastHit hit, 200f, groundMask))
continue;
// skip cliffs — props on a vertical wall look broken
if (Vector3.Angle(hit.normal, Vector3.up) > 30f) continue;
GameObject prefab = prefabs[Random.Range(0, prefabs.Length)];
GameObject go = Instantiate(prefab, hit.point, Quaternion.identity, transform);
go.transform.rotation = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
go.transform.localScale = Vector3.one * Random.Range(scaleRange.x, scaleRange.y);
}
}
[ContextMenu("Clear")]
public void Clear()
{
for (int i = transform.childCount - 1; i >= 0; i--)
DestroyImmediate(transform.GetChild(i).gameObject);
}
}[ContextMenu] adds the method to the component's right-click menu, so you can run it in the editor without pressing Play.
Raycasting down onto the ground means props follow the terrain exactly, including after you resculpt.
Random rotation and scale is what stops 120 identical rocks from looking like 120 identical rocks.
DestroyImmediate, not Destroy — Destroy does nothing outside Play mode.