Unity基础:使用Transform控制物体缩放------localScale与lossyScale的区别

大家好,欢迎回到Unity教程系列。前两篇文章我们系统学习了Transform的移动和旋转操作,今天我们来完成Transform三部曲的最后一部分------缩放(Scale)。
缩放看起来简单------不就是让物体变大变小吗?但实际上,缩放涉及一个很容易混淆的概念:localScale和lossyScale的区别。此外,缩放在父子层级中的传递、非均匀缩放的副作用、以及负缩放的镜像效果,都是实际开发中会遇到的"坑"。这篇文章会把缩放相关的所有知识点一次性讲清楚。
一、缩放的基本操作
1.1 Inspector中的Scale
在Inspector的Transform组件中,Scale由X、Y、Z三个值组成。默认值为(1, 1, 1),表示物体的原始大小。
1.2 代码中操作缩放
csharp
// 获取局部缩放
Vector3 scale = transform.localScale;
// 设置等比例缩放
transform.localScale = Vector3.one * 2f; // 放大2倍
// 设置不同轴的不同缩放
transform.localScale = new Vector3(1, 2, 1); // 只在Y轴拉长
// 增量缩放
transform.localScale += Vector3.one * 0.1f * Time.deltaTime;
// 乘以系数
transform.localScale *= 1.5f;
1.3 缩放的基本单位
缩放是乘法因子,不是绝对尺寸:
- localScale = (1, 1, 1):物体的原始模型尺寸
- localScale = (2, 2, 2):物体在每个轴上都变为原始尺寸的2倍
- localScale = (0.5, 0.5, 0.5):物体缩小到原始尺寸的一半
📝 Cube默认边长是1单位(米)。将其localScale设为(3, 1, 2),它的实际尺寸就是3米宽、1米高、2米深。
二、localScale vs lossyScale
2.1 核心区别
💡 这是本文最重要的知识点。理解localScale和lossyScale的区别:
localScale(局部缩放):
- 物体自身的缩放值
- Inspector中显示的就是localScale
- 可读写
- 相对于父物体
lossyScale(世界缩放/实际缩放):
- 物体在世界空间中的"实际"缩放
- 只读!
- 等于该物体和所有祖先物体localScale的乘积
2.2 计算示例
场景结构:
GrandParent: localScale = (2, 2, 2)
└── Parent: localScale = (3, 3, 3)
└── Child: localScale = (4, 4, 4)
Child的lossyScale = (2 × 3 × 4, 2 × 3 × 4, 2 × 3 × 4) = (24, 24, 24)
csharp
void Start()
{
// 子物体的localScale
Debug.Log($"localScale: {transform.localScale}"); // (4, 4, 4)
// 子物体在世界中的实际缩放(考虑了所有父物体的缩放)
Debug.Log($"lossyScale: {transform.lossyScale}"); // (24, 24, 24)
}
2.3 为什么lossyScale是只读的
lossyScale不能直接设置,因为它是从localScale和所有祖先物体的缩放计算出来的。要改变物体的实际大小,你只能修改localScale(或者调整祖先物体的缩放)。
2.4 lossyScale的陷阱
⚠️ 当父物体有非均匀缩放(如(2, 1, 1))且子物体有旋转时,lossyScale可能包含"剪切"(Shear)变形,无法用简单的(Vector3.x, Vector3.y, Vector3.z)表示。这种情况下lossyScale的值可能不准确,一般应该避免这种组合。
三、缩放在父子层级中的传递
3.1 父物体缩放影响子物体
父物体的缩放会成倍地影响子物体的实际大小:
csharp
// 父物体
parentTransform.localScale = new Vector3(2, 2, 2);
// 子物体的localScale保持(1, 1, 1)
// 但实际显示为2倍大小
3.2 缩放的"奇怪"效应------子物体位置也被缩放
💡 这一点很多人容易忽略:父物体的缩放不仅影响子物体的大小,还影响子物体的位置。
如果父物体缩放为(2, 2, 2),子物体的localPosition为(1, 0, 0),那么子物体到父物体的实际距离是 1 × 2 = 2单位(而不是1单位)。
csharp
// 父物体在原点
parentTransform.position = Vector3.zero;
parentTransform.localScale = new Vector3(2, 2, 2);
// 子物体的localPosition = (1, 0, 0)
childTransform.localPosition = new Vector3(1, 0, 0);
// 子物体的实际世界位置 = 父物体位置 + 父物体旋转 × (子物体localPosition × 父物体localScale)
// = (0,0,0) + (1×2, 0, 0) = (2, 0, 0)
3.3 避免缩放传递带来的问题
在以下情况建议将物体的缩放保持为(1,1,1):
- 有子物体的容器节点(避免子物体受到意外的影响)
- 需要精确控制碰撞检测的场景(缩放会影响碰撞器的实际大小)
- UI元素的父节点(避免子UI元素位置和大小计算混乱)
如果父节点需要缩放来整体调整大小,考虑:
- 在3D建模软件中直接调整模型大小
- 使用专门的Scale脚本控制每个需要缩放的物体
四、负缩放------镜像效果
4.1 负缩放的效果
将localScale的某个轴设为负值,物体会沿该轴镜像翻转:
csharp
// 水平镜像(左右翻转)
transform.localScale = new Vector3(-1, 1, 1);
// 垂直镜像(上下翻转)
transform.localScale = new Vector3(1, -1, 1);
// 前后镜像
transform.localScale = new Vector3(1, 1, -1);
4.2 2D角色的左右翻转
负缩放在2D游戏中常用------让角色面向左或右:
csharp
public class CharacterFlip : MonoBehaviour
{
private bool facingRight = true;
void Update()
{
float horizontal = Input.GetAxis("Horizontal");
if (horizontal > 0 && !facingRight)
{
Flip();
}
else if (horizontal < 0 && facingRight)
{
Flip();
}
}
void Flip()
{
facingRight = !facingRight;
Vector3 scale = transform.localScale;
scale.x *= -1;
transform.localScale = scale;
}
}
4.3 负缩放的注意事项
⚠️ 负缩放会影响:
- 法线方向:镜像后物体表面的法线也会翻转,可能导致光照计算异常(物体内部被照亮,外部变暗)
- 碰撞检测:某些物理计算在负缩放下可能不准确
- 子物体:父物体的负缩放会传递给子物体
对于法线翻转的问题,可以在Shader中启用"双面渲染"(Cull Off),或在材质设置中调整。
五、缩放的实际应用
5.1 呼吸灯/脉冲效果
csharp
using UnityEngine;
public class PulseEffect : MonoBehaviour
{
public float pulseSpeed = 2f;
public float pulseAmount = 0.2f;
private Vector3 originalScale;
void Start()
{
originalScale = transform.localScale;
}
void Update()
{
// 使用正弦波做缩放脉冲
float scale = 1f + Mathf.Sin(Time.time * pulseSpeed) * pulseAmount;
transform.localScale = originalScale * scale;
}
}
5.2 伸缩挤压效果
csharp
// 物体被挤压时变扁变宽
public IEnumerator SquashAndStretch(float duration)
{
Vector3 originalScale = transform.localScale;
Vector3 squashedScale = new Vector3(1.3f, 0.7f, 1.3f);
Vector3 stretchedScale = new Vector3(0.7f, 1.3f, 0.7f);
float halfDuration = duration / 2f;
float elapsed = 0;
// 挤压阶段
while (elapsed < halfDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / halfDuration;
transform.localScale = Vector3.Lerp(originalScale, squashedScale, t);
yield return null;
}
elapsed = 0;
// 拉伸恢复阶段
while (elapsed < halfDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / halfDuration;
transform.localScale = Vector3.Lerp(squashedScale, originalScale, t);
yield return null;
}
transform.localScale = originalScale;
}
5.3 血条缩放
csharp
public class HealthBar : MonoBehaviour
{
public Transform fillBar; // 血条填充部分的Transform
public void SetHealth(float currentHealth, float maxHealth)
{
float percentage = currentHealth / maxHealth;
// 通过缩放控制血条长度
Vector3 scale = fillBar.localScale;
scale.x = Mathf.Clamp01(percentage);
fillBar.localScale = scale;
}
}
5.4 进度条动画
csharp
public IEnumerator AnimateProgressBar(float targetProgress, float duration)
{
Vector3 currentScale = fillBar.localScale;
Vector3 targetScale = new Vector3(targetProgress, 1, 1);
float elapsed = 0;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
fillBar.localScale = Vector3.Lerp(currentScale, targetScale, t);
yield return null;
}
fillBar.localScale = targetScale;
}
六、缩放与碰撞器
6.1 缩放对碰撞器的影响
Collider碰撞器的实际大小受Transform缩放的影响:
csharp
// BoxCollider的size是(1, 1, 1)
// localScale是(2, 2, 2)
// 实际碰撞体积 = size × localScale = (2, 2, 2)
6.2 避免缩放影响碰撞检测
如果不想碰撞器受缩放影响,可以:
- 在场景中保持物体的缩放为(1,1,1)
- 只调整Collider的size属性
- 使用空父节点控制缩放,将碰撞器放在缩放为(1,1,1)的子节点上
七、常见问题
7.1 缩放后碰撞检测不准
过大的缩放可能导致物理计算精度下降。尽量将物体缩放在一个合理范围内(通常0.1到10倍之间)。
7.2 缩放后的渲染问题
非均匀缩放(不同轴的缩放不同)可能导致:
- 法线贴图效果异常
- 光照看起来"不对劲"
- 模型的包围盒计算偏差
解决方案:在可能的情况下保持等比例缩放,或使用GPU Skinning等技术。
八、本篇总结
✅ 本文核心知识点回顾:
- transform.localScale是物体自身的缩放(相对于父物体),可读写
- transform.lossyScale是物体在世界中的实际缩放(考虑所有祖先),只读
- 父物体的缩放会影响子物体的位置(距离被缩放)
- 负缩放产生镜像效果,但可能影响法线和光照
- 缩放影响碰撞器的实际大小
- 使用正弦波和Lerp可以实现脉冲和动画缩放效果
📝 至此,Transform三部曲(移动、旋转、缩放)全部完成。下一篇文章将学习Instantiate实例化------在运行时动态创建游戏物体。
动手任务 :
① 创建一个多层级的父子结构,观察localScale和lossyScale的差异
② 用负缩放实现2D角色的左右翻转
③ 用缩放做一个脉冲呼吸灯效果
关于作者:拥有多年Unity开发经验的游戏开发者,专注Unity系统化教学与最佳实践分享。