Unity问题记录
本文最后更新于1 天前,其中的信息可能已经过时,如有错误请留言

材质是紫色的

原因:Shader 丢失 / 不兼容

解决方法:

  • 选中材质:Shader → Universal Render Pipeline/Lit

https://www.youtube.com/watch?v=PBqafb_PwRA

https://www.youtube.com/playlist?list=PLrMEhC9sAD1zprGu_lphl3cQSS3uFIXA9

https://www.youtube.com/watch?v=XpG3YqUkCTY

https://www.youtube.com/watch?v=PBqafb_PwRA

Placer Tool along the spline

用途:创建一个脚本轻松的沿着物体放置对象

  • Hierachy – Create – spline

首先引用spline曲线搭配Spline引用

如果物体的+Z方向是斜着的,是因为面本身就是斜着的,朝向面的法向量方向

如果想统一保持垂直分布就启用USE GLOBAL UP

左右偏移check

using UnityEngine;
using UnityEngine.Splines;
using System.Collections.Generic;
using Unity.Mathematics;

[ExecuteInEditMode] // 让脚本在编辑器模式下也能运行,不必进入 Play 模式
public class SplinePlacer : MonoBehaviour
{
    [Header("运行控制")]
    public bool run; // 勾选后执行一次摆放逻辑,执行完会自动改回 false
    public bool deleteObjects; // 勾选后立刻删除当前脚本记录的所有已摆放物体
    public bool keepObjects; // 勾选后清空记录列表,但场景里的物体会保留下来

    [Header("基础引用")]
    public SplineContainer spline; // 要沿着它进行摆放的样条曲线

    [Header("预制体设置")]
    public List<GameObject> prefabVariants = new();
    // 可用于摆放的预制体列表
    // 如果只放 1 个预制体,就始终生成它
    // 如果放多个,就会随机挑一个生成

    public int prefabMaxSize;
    // 最大尺寸
    // 在固定尺寸模式下:它会被当成“物体间距”的参考值
    // 在可变尺寸模式下:它表示单个物体允许扩展到的最大长度

    public int prefabMinSize;
    // 最小尺寸
    // 仅在可变尺寸模式下更重要,决定最小步进单位

    public bool fixedSize = true;
    // true  = 固定尺寸摆放
    // false = 可变尺寸摆放(会根据曲线弯曲程度自动拉长/缩短物体)

    [Header("可变尺寸模式参数")]
    public float angleDeviance = 3f;
    // 当曲线方向变化超过这个角度时,就认为不能继续把同一个物体拉长了
    // 只在“可变尺寸模式”下生效
    // 值越小,物体越容易被切成更短的小段
    // 值越大,物体越容易被拉得更长

    [Header("朝向与高度")]
    public bool useGlobalUp = false;
    // 是否强制使用世界坐标的 Y 轴作为“向上方向”
    // true:物体更偏向水平摆放,适合地面道路、围栏等
    // false:使用样条自身的 up 方向,适合跟随曲面、翻转轨道等情况

    public bool useTransformYAsHeight = false;
    // 是否忽略样条采样点本身的 y 高度
    // true:统一使用 spline 物体自身 Transform 的 y 值作为高度
    // false:使用样条实际采样点的 y 值

    [Header("调试信息(只读观察用)")]
    [SerializeField] private float totalLength; // 当前样条总长度
    [SerializeField] private float stepSize;    // 归一化步长(0~1 之间)
    [SerializeField] private int totalObjects;  // 当前总共摆放了多少个物体

    private List<GameObject> placedObjects = new();
    // 用来记录“本脚本生成过的物体”
    // 这样后续可以删除、重建,或者选择把它们留在场景里

    // 每帧都会执行
    // 因为有 ExecuteInEditMode,所以在编辑器里也会不断调用
    void Update()
    {
        // 手动触发执行
        if (run)
        {
            if (fixedSize)
                PlaceObjectsFixedSize();     // 固定尺寸摆放
            else
                PlaceObjectsVariableSize();  // 可变尺寸摆放

            run = false; // 执行一次后自动关闭,防止每帧重复生成
        }

        // 删除当前记录里的所有已生成物体
        if (deleteObjects)
        {
            foreach (GameObject obj in placedObjects)
                DestroyImmediate(obj);

            deleteObjects = false;
        }

        // 只清空记录,不删场景中的物体
        // 相当于“这些物体以后不归这个脚本管了”
        if (keepObjects)
        {
            placedObjects.Clear();
            keepObjects = false; // 建议这里顺手复位,避免一直重复清空
        }
    }

    /// <summary>
    /// 固定尺寸摆放:
    /// 思路很直接——按固定步长沿样条往前走,
    /// 每一段中点放一个物体,所有物体尺寸基本一致。
    /// </summary>
    void PlaceObjectsFixedSize()
    {
        totalLength = spline.CalculateLength(); // 先计算整条样条的总长度

        float step = totalLength / prefabMaxSize;
        // 计算可以分成多少段
        // 例如:总长 100,prefabMaxSize = 5,则 step = 20
        // 意思是大概会放 20 个物体

        stepSize = 1 / step;
        // 样条 Evaluate 使用的是 0~1 的归一化参数
        // 所以要把“按长度算出来的步长”转成 0~1 范围的步长

        // 重新生成前,先把上一次摆的全部删掉
        foreach (GameObject obj in placedObjects)
            DestroyImmediate(obj);

        placedObjects.Clear();

        float t = 0f; // 当前沿样条采样的位置(0~1)
        while (t < 1f)
        {
            // 起点信息
            float3 startPos;  // 起点世界坐标
            float3 startTang; // 起点切线方向(曲线前进方向)
            float3 startUp;   // 起点向上方向

            // 中点信息
            float3 midPos;
            float3 midTang;
            float3 midUp;

            // 终点信息
            float3 endPos;
            float3 endTang;
            float3 endUp;

            // 获取当前 t 位置的样条信息
            spline.Evaluate(t, out startPos, out startTang, out startUp);

            // 确保下一段没有越界
            if (t + stepSize <= 1f)
            {
                spline.Evaluate(t + stepSize, out endPos, out endTang, out endUp);
                spline.Evaluate(t + (stepSize / 2f), out midPos, out midTang, out midUp);
            }
            else
            {
                break;
            }

            // 用起点和终点的中间位置作为新物体的摆放位置
            Vector3 worldSpaceMiddle = Vector3.Lerp(startPos, endPos, 0.5f);

            GameObject newObject = null;

            // 是否强制高度使用 spline 物体自身的 Y 值
            if (useTransformYAsHeight)
            {
                worldSpaceMiddle.y = spline.gameObject.transform.position.y;
            }

            // 生成物体:多个预制体时随机选一个,只有一个时直接生成
            if (prefabVariants.Count > 1)
            {
                int randomIndex = UnityEngine.Random.Range(0, prefabVariants.Count);
                newObject = Instantiate(prefabVariants[randomIndex], worldSpaceMiddle, Quaternion.identity);
            }
            else
            {
                newObject = Instantiate(prefabVariants[0], worldSpaceMiddle, Quaternion.identity);
            }

            // 设置朝向
            if (useGlobalUp)
            {
                // 使用世界 Y 轴作为向上方向
                // 这里把方向压平到 XZ 平面,适合地面上的道路/围栏
                Vector3 flattenedDirection = new Vector3(endPos.x - startPos.x, 0f, endPos.z - startPos.z).normalized;
                newObject.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, Vector3.up) * flattenedDirection,
                    Vector3.up
                );
            }
            else
            {
                // 使用样条中点的切线和 up 方向
                // 这样物体会更贴合样条本身的空间朝向
                newObject.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, midUp) * midTang,
                    midUp
                );
            }

            // 记录生成物体,方便后续统一删除或保留
            placedObjects.Add(newObject);

            // 前进到下一段
            t += stepSize;
        }

        totalObjects = placedObjects.Count; // 记录生成数量
    }

    /// <summary>
    /// 可变尺寸摆放:
    /// 从最小步长开始尝试往前“拼接”,
    /// 只要曲线弯折还不大,就继续延长当前物体;
    /// 一旦弯得太厉害,就停止延长,生成一个刚好适配这段曲线长度的物体。
    /// </summary>
    void PlaceObjectsVariableSize()
    {
        // 重新生成前,先清空旧物体
        foreach (GameObject wall in placedObjects)
            DestroyImmediate(wall);

        placedObjects.Clear();

        // 先计算样条总长度,再根据最小尺寸得到基础步长
        totalLength = spline.CalculateLength();
        float step = totalLength / prefabMinSize;
        stepSize = 1 / step;

        // 这个值表示:最大尺寸大约等于多少个最小尺寸单位
        // 例如 max=6, min=2,那么 stepsBetweenMinMax = 3
        int stepsBetweenMinMax = Mathf.RoundToInt(prefabMaxSize / prefabMinSize);

        float objScale = prefabMinSize; // 先给一个初始值,后面会被实际长度覆盖

        // while 循环的控制变量
        float t = 0f;           // 当前起点参数
        int breakCounter = 0;   // 紧急保险,防止死循环
        bool killLoop = false;  // 对闭合样条做收尾时使用

        while (t < 1f && !killLoop)
        {
            // 紧急中断,防止某些特殊参数导致无限循环
            breakCounter++;
            if (breakCounter > 10000) break;

            // 当前段起点信息
            float3 startPos;
            float3 startTang;
            float3 startUp;

            // 当前段最终决定的终点信息
            float3 endPos = new();
            float3 endTang;
            float3 endUp;

            spline.Evaluate(t, out startPos, out startTang, out startUp);

            int scaleFactor = 0; // 目前代码里没有实际参与计算,更多像是调试残留变量
            float startT = t;    // 当前物体的起始参数位置
            float endT = t + stepSize; // 当前物体的结束参数位置,后面会逐步修正

            // 尝试把一个物体从最小长度开始不断往前延长
            for (int i = 1; i < stepsBetweenMinMax; i++)
            {
                float tempT = t + stepSize * i;

                // 如果是闭合样条,超出 1 就回到 0
                if (tempT > 1f && spline.Spline.Closed)
                    tempT = 0f;

                scaleFactor += 1;

                float3 currentPos;
                float3 currentTang;
                float3 currentUp;

                spline.Evaluate(tempT, out currentPos, out currentTang, out currentUp);

                // 如果闭合样条已经绕回起点,说明到头了,结束整个流程
                if (tempT == 0f)
                {
                    endPos = currentPos;
                    endTang = currentTang;
                    endUp = currentUp;
                    killLoop = true;
                    break;
                }

                // 第一段至少先给一个有效终点,避免后面没有 endPos
                if (i == 1)
                {
                    endPos = currentPos;
                    endTang = currentTang;
                    endUp = currentUp;
                }

                // 比较起点切线和当前位置切线的夹角
                // 如果弯曲超过允许范围,就停止延长当前物体
                if (Vector3.Angle(startTang, currentTang) > angleDeviance && i != 1)
                {
                    t = tempT - stepSize;    // 下次从上一个安全位置继续
                    endT = tempT - stepSize; // 当前物体终点停在上一个安全位置
                    break;
                }
                else
                {
                    // 还没超角度,说明这段可以继续延长
                    endPos = currentPos;
                    endTang = currentTang;
                    endUp = currentUp;

                    // 如果已经到达允许的最大尺寸,就停止延长
                    if (i == stepsBetweenMinMax - 1)
                    {
                        t = tempT;
                        endT = tempT;
                        break;
                    }
                }
            }

            // 创建当前这一个物体
            GameObject obj = null;
            if (prefabVariants.Count > 1)
            {
                int randomIndex = UnityEngine.Random.Range(0, prefabVariants.Count);
                obj = Instantiate(prefabVariants[randomIndex]);
            }
            else
            {
                obj = Instantiate(prefabVariants[0]);
            }

            placedObjects.Add(obj);

            // 计算这一段的中点
            // 位置上使用中点,是为了让物体正好居中覆盖 start 到 end 这一整段
            Vector3 start = startPos;
            Vector3 end = endPos;
            Vector3 midpoint = Vector3.Lerp(start, end, 0.5f);

            float midT = (startT + endT) / 2;
            float3 midPos;
            float3 midTang;
            float3 midUp;
            spline.Evaluate(midT, out midPos, out midTang, out midUp);

            // 设置位置
            if (useTransformYAsHeight)
            {
                // 高度固定为 spline 对象自身的 y
                obj.transform.position = new Vector3(midpoint.x, spline.transform.position.y, midpoint.z);
            }
            else
            {
                // 高度跟随样条中点
                obj.transform.position = midPos;
            }

            // 设置朝向
            if (useGlobalUp)
            {
                Vector3 flattenedDirection = new Vector3(endPos.x - startPos.x, 0f, endPos.z - startPos.z).normalized;
                obj.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, Vector3.up) * flattenedDirection,
                    Vector3.up
                );
            }
            else
            {
                obj.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, midUp) * midTang,
                    midUp
                );
            }

            // 设置缩放
            // 这里把物体 X 轴长度拉成 start 到 end 的距离
            // 也就是说:这个脚本默认你的预制体“长度方向”是本地 X 轴
            objScale = Vector3.Distance(start, end);
            obj.transform.localScale = new Vector3(objScale, 1, 1);
        }

        totalObjects = placedObjects.Count;
    }
}
using UnityEngine;
using UnityEngine.Splines;
using System.Collections.Generic;
using Unity.Mathematics;

[ExecuteInEditMode]
public class SplinePlacer : MonoBehaviour
{
    public bool run;
    public bool deleteObjects;
    public bool keepObjects;

    public SplineContainer spline;

    public List<GameObject> prefabVariants = new();

    public int prefabMaxSize;
    public int prefabMinSize;

    public bool fixedSize = true;

    public float angleDeviance = 3f;

    public bool useGlobalUp = false;
    public bool useTransformYAsHeight = false;

    public float lateralOffset = 0f;
    // 左右偏移量
    // 正数:往右
    // 负数:往左
    // 0:不偏移

    [SerializeField] private float totalLength;
    [SerializeField] private float stepSize;
    [SerializeField] private int totalObjects;
    private List<GameObject> placedObjects = new();

    void Update()
    {
        if (run)
        {
            if (fixedSize) PlaceObjectsFixedSize();
            else PlaceObjectsVariableSize();
            run = false;
        }

        if (deleteObjects)
        {
            foreach (GameObject obj in placedObjects) DestroyImmediate(obj);
            deleteObjects = false;
        }

        if (keepObjects)
        {
            placedObjects.Clear();
            keepObjects = false;
        }
    }

    private Vector3 GetRightDirection(Vector3 forward, Vector3 up)
    {
        Vector3 right = Vector3.Cross(up.normalized, forward.normalized);

        if (right.sqrMagnitude < 0.0001f)
            return Vector3.zero;

        return right.normalized;
    }

    void PlaceObjectsFixedSize()
    {
        totalLength = spline.CalculateLength();

        float step = totalLength / prefabMaxSize;
        stepSize = 1 / step;

        foreach (GameObject obj in placedObjects) DestroyImmediate(obj);
        placedObjects.Clear();

        float t = 0f;
        while (t < 1f)
        {
            float3 startPos;
            float3 startTang;
            float3 startUp;

            float3 midPos;
            float3 midTang;
            float3 midUp;

            float3 endPos;
            float3 endTang;
            float3 endUp;

            spline.Evaluate(t, out startPos, out startTang, out startUp);

            if (t + stepSize <= 1f)
            {
                spline.Evaluate(t + stepSize, out endPos, out endTang, out endUp);
                spline.Evaluate(t + (stepSize / 2f), out midPos, out midTang, out midUp);
            }
            else break;

            Vector3 worldSpaceMiddle = Vector3.Lerp(startPos, endPos, 0.5f);

            if (useTransformYAsHeight)
            {
                worldSpaceMiddle.y = spline.gameObject.transform.position.y;
            }

            Vector3 forwardDir;
            Vector3 upDir;

            if (useGlobalUp)
            {
                forwardDir = new Vector3(endPos.x - startPos.x, 0f, endPos.z - startPos.z).normalized;
                upDir = Vector3.up;
            }
            else
            {
                forwardDir = ((Vector3)midTang).normalized;
                upDir = ((Vector3)midUp).normalized;
            }

            worldSpaceMiddle += GetRightDirection(forwardDir, upDir) * lateralOffset;

            GameObject newObject = null;

            if (prefabVariants.Count > 1)
            {
                int randomIndex = UnityEngine.Random.Range(0, prefabVariants.Count);
                newObject = Instantiate(prefabVariants[randomIndex], worldSpaceMiddle, Quaternion.identity);
            }
            else
            {
                newObject = Instantiate(prefabVariants[0], worldSpaceMiddle, Quaternion.identity);
            }

            if (useGlobalUp)
            {
                Vector3 flattenedDirection = new Vector3(endPos.x - startPos.x, 0f, endPos.z - startPos.z).normalized;
                newObject.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, Vector3.up) * flattenedDirection,
                    Vector3.up
                );
            }
            else
            {
                newObject.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, midUp) * midTang,
                    midUp
                );
            }

            placedObjects.Add(newObject);
            t += stepSize;
        }

        totalObjects = placedObjects.Count;
    }

    void PlaceObjectsVariableSize()
    {
        foreach (GameObject wall in placedObjects) DestroyImmediate(wall);
        placedObjects.Clear();

        totalLength = spline.CalculateLength();
        float step = totalLength / prefabMinSize;
        stepSize = 1 / step;
        int stepsBetweenMinMax = Mathf.RoundToInt(prefabMaxSize / prefabMinSize);

        float objScale = prefabMinSize;

        float t = 0f;
        int breakCounter = 0;
        bool killLoop = false;

        while (t < 1f && !killLoop)
        {
            breakCounter++;
            if (breakCounter > 10000) break;

            float3 startPos;
            float3 startTang;
            float3 startUp;

            float3 endPos = new();
            float3 endTang;
            float3 endUp;

            spline.Evaluate(t, out startPos, out startTang, out startUp);

            float startT = t;
            float endT = t + stepSize;

            for (int i = 1; i < stepsBetweenMinMax; i++)
            {
                float tempT = t + stepSize * i;
                if (tempT > 1f && spline.Spline.Closed) tempT = 0f;

                float3 currentPos;
                float3 currentTang;
                float3 currentUp;
                spline.Evaluate(tempT, out currentPos, out currentTang, out currentUp);

                if (tempT == 0f)
                {
                    endPos = currentPos;
                    endTang = currentTang;
                    endUp = currentUp;
                    killLoop = true;
                    break;
                }

                if (i == 1)
                {
                    endPos = currentPos;
                    endTang = currentTang;
                    endUp = currentUp;
                }

                if (Vector3.Angle(startTang, currentTang) > angleDeviance && i != 1)
                {
                    t = tempT - stepSize;
                    endT = tempT - stepSize;
                    break;
                }
                else
                {
                    endPos = currentPos;
                    endTang = currentTang;
                    endUp = currentUp;

                    if (i == stepsBetweenMinMax - 1)
                    {
                        t = tempT;
                        endT = tempT;
                        break;
                    }
                }
            }

            GameObject obj = null;
            if (prefabVariants.Count > 1)
            {
                int randomIndex = UnityEngine.Random.Range(0, prefabVariants.Count);
                obj = Instantiate(prefabVariants[randomIndex]);
            }
            else
            {
                obj = Instantiate(prefabVariants[0]);
            }

            placedObjects.Add(obj);

            Vector3 start = startPos;
            Vector3 end = endPos;
            Vector3 midpoint = Vector3.Lerp(start, end, 0.5f);

            float midT = (startT + endT) / 2;
            float3 midPos;
            float3 midTang;
            float3 midUp;
            spline.Evaluate(midT, out midPos, out midTang, out midUp);

            Vector3 basePosition;
            if (useTransformYAsHeight)
            {
                basePosition = new Vector3(midpoint.x, spline.transform.position.y, midpoint.z);
            }
            else
            {
                basePosition = midPos;
            }

            Vector3 forwardDir;
            Vector3 upDir;

            if (useGlobalUp)
            {
                forwardDir = new Vector3(endPos.x - startPos.x, 0f, endPos.z - startPos.z).normalized;
                upDir = Vector3.up;
            }
            else
            {
                forwardDir = ((Vector3)midTang).normalized;
                upDir = ((Vector3)midUp).normalized;
            }

            obj.transform.position = basePosition + GetRightDirection(forwardDir, upDir) * lateralOffset;

            if (useGlobalUp)
            {
                Vector3 flattenedDirection = new Vector3(endPos.x - startPos.x, 0f, endPos.z - startPos.z).normalized;
                obj.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, Vector3.up) * flattenedDirection,
                    Vector3.up
                );
            }
            else
            {
                obj.transform.rotation = Quaternion.LookRotation(
                    Quaternion.AngleAxis(90f, midUp) * midTang,
                    midUp
                );
            }

            objScale = Vector3.Distance(start, end);
            obj.transform.localScale = new Vector3(objScale, 1, 1);
        }

        totalObjects = placedObjects.Count;
    }
}

学习笔记如有侵权,请提醒我,我会马上删除
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇