U
章 04世界与游戏循环7 课 — 点击打开
03 / 07 · 9 分钟
世界与游戏循环

模块化布景与网格吸附

到目前为止的项目

Assets/

Scenes/

Arena.unity

Terrain

Scripts/

PropScatter.cs

Prefabs/

module kit (6 pieces)prefab variants

项目设置

baked lightmaps

你已经有的这一课新增

你在 tab 01 已经做过一套这种东西了:P_Cube、P_Sphere、P_Capsule。同样的思路,更大的块,而且这一次它们必须能互相拼合。

专业关卡不是一整块巨大的 mesh 建模出来的,而是用一小套能在网格上拼合的零件拼出来的。

动态图解

每块零件都是 1、2 或 4 米,所以随便哪块拼哪块都对得上。

建模之前先把网格定好

  1. 1

    打开 Edit▸Grid and Snap Settings,把 Move 设为 1, 1, 1,Rotate 设为 90。

  2. 2

    拖动手柄时按住 Ctrl/Cmd,就能按这些步长吸附。

  3. 3

    每个模块都要让它的 pivot 落在角上,而不是中心。这和 tab 01 里 cube 要放在 Y = 0.5 是同一个坑 —— pivot 的位置决定一切。

  4. 4

    所有模块朝向一致(+Z 是前方),这样每次转 90° 都对得上。

一套入门零件

  • 地板 4×4、墙 4×3、带门洞的墙、带窗的墙、转角柱、楼梯 2×3。

  • 六块零件能拼出一整栋建筑,十块足够拼出一个村子。

  • ProBuilder(在 Package Manager 里免费)让你不用离开编辑器就能在 Unity 内部把这些拼出来。

Prefab Variant —— 一套零件,多种主题

先做灰色的方块草稿,然后创建只换 material 的 variant。修好基础 prefab 里的 bug,每个 variant 都自动跟着修好。

  1. 1

    右键 P_Wall → Create → Prefab Variant → 取名 P_Wall_Stone。

  2. 2

    打开 variant,只改 material。其他一切仍然连着父 prefab。

  3. 3

    以后给 P_Wall 加 collider,P_Wall_Stone 和 P_Wall_Wood 也会同时有。

不用手摆 300 块石头也能铺满装饰物

PropScatter.cs — run it from the Inspector
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 外面什么都不做。