模块化布景与网格吸附
Assets/
Scenes/
Arena.unity
Scripts/
Prefabs/
项目设置
你已经有的这一课新增
你在 tab 01 已经做过一套这种东西了:P_Cube、P_Sphere、P_Capsule。同样的思路,更大的块,而且这一次它们必须能互相拼合。
专业关卡不是一整块巨大的 mesh 建模出来的,而是用一小套能在网格上拼合的零件拼出来的。
每块零件都是 1、2 或 4 米,所以随便哪块拼哪块都对得上。
建模之前先把网格定好
- 1
打开 ,把 Move 设为 1, 1, 1,Rotate 设为 90。
- 2
拖动手柄时按住 Ctrl/Cmd,就能按这些步长吸附。
- 3
每个模块都要让它的 pivot 落在角上,而不是中心。这和 tab 01 里 cube 要放在 Y = 0.5 是同一个坑 —— pivot 的位置决定一切。
- 4
所有模块朝向一致(+Z 是前方),这样每次转 90° 都对得上。
一套入门零件
地板 4×4、墙 4×3、带门洞的墙、带窗的墙、转角柱、楼梯 2×3。
六块零件能拼出一整栋建筑,十块足够拼出一个村子。
ProBuilder(在 Package Manager 里免费)让你不用离开编辑器就能在 Unity 内部把这些拼出来。
Prefab Variant —— 一套零件,多种主题
先做灰色的方块草稿,然后创建只换 material 的 variant。修好基础 prefab 里的 bug,每个 variant 都自动跟着修好。
- 1
右键 P_Wall → Create → Prefab Variant → 取名 P_Wall_Stone。
- 2
打开 variant,只改 material。其他一切仍然连着父 prefab。
- 3
以后给 P_Wall 加 collider,P_Wall_Stone 和 P_Wall_Wood 也会同时有。
不用手摆 300 块石头也能铺满装饰物
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] 后,这个方法会出现在 component 的右键菜单里,不用按 Play 就能在编辑器里运行。
向下 raycast 打地面,所以道具会严丝合缝贴着地形,包括你以后重塑地形之后。
随机旋转和随机缩放,正是让 120 块一样的石头不再看起来像 120 块一样的石头的关键。
用 DestroyImmediate 而不是 Destroy —— Destroy 在 Play mode 外面什么都不做。