Unity中Scale详解

1.缩放变换
Scale代表的是缩放。 
通常意义下的缩放变换,指的是利用缩放矩阵和物体的齐次坐标相乘而进行变换,缩放矩阵形如:

 
Scalex代表x轴方向的缩放 
Scaley代表y轴方向的缩放 
Scalez代表z轴方向的缩放 
当它与物体的齐次坐标相乘后,可以得到缩放后的物体坐标 
 
可以发现缩放矩阵将物体原本的坐标依据缩放比例进行了变换。 
这是几何上的缩放变换,那么在Unity中,缩放变换又是一种什么样的作用呢。

2.Unity中的缩放变换
在Unity中,对于导入的模型以及unity中自带的那些Object(例如 Cube 、Sphere…)是不能直接改变其本身属性坐标的大小的,只有通过缩放变换来改变其自身坐标系的大小来使其大小发生改变。也就是如果物体正常的长度为2,如果你本体坐标系的缩放为2,那么物体实际看到的长度就变为了4,但是这个物体在它本体坐标系下的长度仍然为2。在Unity中,Scale的变化你可以看做是对物体的本体坐标系进行的缩放变化,在这个缩放过程中,物体在其本体坐标系下的各项属性保持不变。 
 
 
如图这是一个Cube改变Scale之前的状态,接下来我们在Z轴对它进行缩放 
 
 
可以发现这个cube在z轴上的长度变为了原来的二倍,而它自身的坐标并未发生改变。

在unity中的物体的transform里,表示缩放的一共有两个量,lossyScale和localScale。

Localscale代表的是当前物体相对于父物体的缩放,既可以作为左值也可以作为右值。

Lossyscale代表的是当前物体在全局中的缩放,为只读变量,不可修改。

Inspector界面显示的scale表示的是localScale。

3.注意
在Unity中关于Scale容易出问题的地方就在于当物体之间包含父子关系时,如果父物体的Scale改变,会导致子物体的世界坐标(即transform.position)发生改变。(注意,inspector中的position为与父物体的相对坐标)

假定在父物体的Scale为(1,1,1)的情况下,父物体的世界坐标为(x1,y1,z1),子物体的世界坐标为(x2,y2,z2)。当父物体的Scale发生改变变为(1,1,2)时,父物体的世界坐标不发生改变,而子物体的世界坐标将变为(x1,y1,z1+(z2-z1)*2)。子物体的世界坐标发生这种改变的目的,是为了保证其相对于父物体本体坐标系的相对坐标的不变性。

演示一下
令Ojbect1为父物体,object2为obj1的子物体,object3为obj2的子物体 
如果在初始情况下object1的坐标为(0,0,-2),object2的坐标为(0,0,1.5),object3的坐标为(0,0,4) 
接下来改变三个物体的localScale。 
Object1的localScale为(1,1,1.5) object2的localScale为(1,1,2) object3的localScale为(1,1,3) 
改变过后,三者的lossyScale变为(1,1,1.5),(1,1,3),(1,1,9)。 
Obj1的position为(0,0,-2),obj2的坐标为(0,0,3.25),obj3的坐标为(0,0,10.75)。

改变前后三个物体的localPosition均未发生改变。

接下来解释obj2和obj3的坐标变化是如何得来的。 
3.25= -2+(1.5-(-2))*1.5 
10.75=[-2+ (1.5-(-2))*1.5]+(4-1.5)*3 
即子物体的当前世界坐标等于父物体的世界坐标加上子物体相对于父物体的相对坐标乘以父物体的lossyScale对应的值。

public class ObstacleCollect : MonoBehaviour
{
    void Awake()
    {
        BoxCollider[] boxColliders = GetComponentsInChildren<BoxCollider>();
        for (int i = 0; i < boxColliders.Length; i++)
        {
            float minX = boxColliders[i].transform.position.x -
                         boxColliders[i].size.x*boxColliders[i].transform.lossyScale.x*0.5f;
            float minZ = boxColliders[i].transform.position.z -
                         boxColliders[i].size.z*boxColliders[i].transform.lossyScale.z*0.5f;
            float maxX = boxColliders[i].transform.position.x +
                         boxColliders[i].size.x*boxColliders[i].transform.lossyScale.x*0.5f;
            float maxZ = boxColliders[i].transform.position.z +
                         boxColliders[i].size.z*boxColliders[i].transform.lossyScale.z*0.5f;
            Debug.Log(i+ "position  " + boxColliders[i].transform.position.x);
            Debug.Log(i + " size " + boxColliders[i].size.x);
            Debug.Log(i + " lossyScaleX " + boxColliders[i].transform.lossyScale.x);
            Debug.Log(i + "minX  " + minX);
            Debug.Log(i + "maxX  " + maxX);
            Debug.Log(i + "position  " + boxColliders[i].transform.position.z);
            Debug.Log(i + " size " + boxColliders[i].size.z);
            Debug.Log(i + " lossyScaleZ " + boxColliders[i].transform.lossyScale.z);
            Debug.Log(i + "minZ  " + minZ);
            Debug.Log(i + "maxZ  " + maxZ);
 
            //Debug.Log(boxColliders[i].transform.position.x);
 
 
            IList<Vector2> obstacle = new List<Vector2>();
            obstacle.Add(new Vector2(maxX, maxZ));
            obstacle.Add(new Vector2(minX, maxZ));
            obstacle.Add(new Vector2(minX, minZ));
            obstacle.Add(new Vector2(maxX, minZ));
            Simulator.Instance.addObstacle(obstacle);
        }
    }
 
    // Update is called once per frame
    void Update()
    {
    }
}


 

<think>嗯,用户之前问过.NET和Mono的关系,现在又转向Unity中的协程。可能他们正在学习Unity开发,遇到了协程的概念,需要详细解释。之前的问题涉及技术框架的比较,这次则是具体的Unity功能,所以需要深入讲解协程的实现、用法以及常见问题。 首先,用户可能已经了解C#的基础,但协程在Unity中的工作方式与普通多线程不同,需要解释清楚Unity的协程是基于迭代器和时间控制的。要强调协程不是多线程,而是在主线程上分时执行,避免误解。 用户可能需要知道协程的适用场景,比如处理异步操作、延时执行、分帧加载等。需要举一些例子,比如等待几秒后执行,或者分步骤处理生成敌人。同时,要提醒注意事项,比如避免长时间阻塞,正确停止协程,以及生命周期管理的问题。 另外,用户可能会混淆协程和异步async/await的区别,需要比较两者的使用场景和优缺点。比如协程更适合与Unity的Yield指令配合,而async/await在不需要Unity特定等待时可能更高效。 还要考虑用户可能的误区,比如在禁用对象或场景切换时协程停止,以及性能影响,比如过多的协程可能影响帧率。需要给出最佳实践,比如使用对象池管理协程,或者结合UniTask等优化方案。 最后,提供示例代码帮助理解,比如StartCoroutine的用法,yield return不同的指令,以及如何停止协程。确保用户能够实际应用这些知识,解决开发中的问题。</think>好的!以下是关于 **Unity 中协程(Coroutine)** 的详细解析,包括原理、使用场景、实现机制和注意事项。 --- ### **1. 协程的核心概念** - **定义**:协程是一种**非抢占式的多任务处理机制**,允许在单个线程内通过“分时”执行多个任务,实现**代码的暂停与恢复**。 - **与线程的区别**: - 协程运行在**主线程**上,不涉及多线程的上下文切换或锁机制。 - 协程通过 `yield` 主动让出执行权,由 Unity 引擎调度恢复时机,**不阻塞主线程**。 --- ### **2. 协程的核心用途** - **延迟执行**:等待若干秒后执行代码(如倒计时)。 - **分帧处理**:将耗时操作分散到多帧完成(如分帧加载资源)。 - **异步等待**:等待条件满足(如网络请求返回、动画播放完毕)。 - **复杂流程控制**:实现状态机或分步骤逻辑(如 RPG 对话系统)。 --- ### **3. 协程的实现机制** #### **(1)语法基础:C# 迭代器(IEnumerator)** 协程基于 C# 的 `IEnumerator` 接口实现,通过 `yield return` 暂停执行并返回控制权。 ```csharp IEnumerator MyCoroutine() { yield return null; // 等待下一帧 yield return new WaitForSeconds(2); // 等待2秒 Debug.Log("协程恢复执行!"); } ``` #### **(2)Unity 的协程调度** - **启动协程**:通过 `StartCoroutine()` 方法。 ```csharp void Start() { StartCoroutine(MyCoroutine()); } ``` - **停止协程**: - `StopCoroutine()`:停止指定协程。 - `StopAllCoroutines()`:停止所有协程。 #### **(3)常见 Yield 指令** | Yield 指令 | 行为 | |-------------------------------|---------------------------------------| | `yield return null` | 等待下一帧 | | `yield return new WaitForSeconds(t)` | 等待 `t` 秒(受 Time.timeScale 影响) | | `yield return new WaitForFixedUpdate()` | 等待 FixedUpdate 执行 | | `yield return new WaitUntil(() => condition)` | 等待条件为 `true` | | `yield return new WaitWhile(() => condition)` | 等待条件为 `false` | | `yield return StartCoroutine(otherCoroutine)` | 嵌套执行其他协程 | --- ### **4. 协程的底层原理** - **状态机模型**:Unity 将协程编译为一个状态机类,通过 `MoveNext()` 和 `Current` 控制执行进度。 - **生命周期绑定**:协程依赖于 MonoBehaviour 对象,若对象被销毁(`Destroy(gameObject)`)或脚本禁用(`enabled = false`),协程会自动终止。 - **性能开销**:协程的调度由 Unity 主循环驱动,每帧检查恢复条件,**轻量但需避免滥用**(如同时运行数百个协程)。 --- ### **5. 使用示例** #### **(1)延迟生成敌人** ```csharp IEnumerator SpawnEnemies() { for (int i = 0; i < 5; i++) { Instantiate(enemyPrefab, transform.position, Quaternion.identity); yield return new WaitForSeconds(1); // 每隔1秒生成一个敌人 } } void Start() { StartCoroutine(SpawnEnemies()); } ``` #### **(2)分帧加载避免卡顿** ```csharp IEnumerator LoadLargeData() { for (int i = 0; i < 1000; i++) { ProcessDataChunk(i); // 处理数据块 if (i % 10 == 0) { yield return null; // 每处理10个数据块等待一帧 } } } ``` --- ### **6. 注意事项与最佳实践** - **避免长时间阻塞**:协程中不要使用 `while(true)` 或同步耗时操作,否则会冻结主线程。 - **资源释放**:若协程引用对象,需在 `OnDestroy()` 中停止协程,防止内存泄漏。 - **替代方案**: - **UniTask**:第三方库,提供更高效的异步编程支持(支持 `async/await`)。 - **C# Job System**:对多线程并行计算友好,但需处理线程安全。 - **调试技巧**:使用 `UnityEditor.EditorApplication.isPaused` 或断点调试协程执行流程。 --- ### **7. 协程 vs Async/Await** | 特性 | 协程(Coroutine) | C# Async/Await | |---------------------|---------------------------------------|------------------------------------| | **依赖引擎** | 必须运行在 Unity 主线程 | 不依赖引擎,可在任意线程使用 | | **Yield 指令** | 支持 Unity 特定指令(如 WaitForSeconds)| 依赖 `Task` 或自定义等待对象 | | **性能** | 轻量,适合小规模任务 | 更高效,适合复杂异步逻辑 | | **兼容性** | 需 MonoBehaviour 支持 | 纯 C# 特性,无 Unity 依赖 | --- ### **总结** - **协程是 Unity 异步编程的核心工具**,适合处理简单的延时、分帧或流程控制。 - **理解 `yield` 指令和生命周期管理**是避免 Bug 的关键。 - **复杂场景可结合 UniTask 或 Job System** 提升性能和代码可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值