1. Unity相机系统深度解析
1.1 相机基础参数详解
在Unity中,Camera组件是游戏视角呈现的核心,每个参数都直接影响最终渲染效果。Clear Flags决定了背景如何处理,通常有四种模式:
- Skybox:默认模式,显示天空盒
- Solid Color:纯色背景
- Depth Only:仅清除深度信息(用于多相机叠加)
- Don't Clear:不清除任何信息(性能消耗大)
Culling Mask是开发中经常需要调整的参数,它通过Layer层级控制相机渲染哪些对象。比如制作小地图时,可以专门设置一个"Minimap"层,只渲染该层级的物体。
实际项目中发现,不当的Culling Mask设置会导致Draw Call暴增。建议将需要同时显示的对象尽量放在同一层级。
Clipping Planes的Near/Far值设置尤为关键:
- Near值过大会导致近处物体被裁剪
- Far值过大会增加渲染负担
- 通常3D游戏建议Near=0.3,Far=1000起步
Depth值控制相机渲染顺序,数值大的会覆盖数值小的。在制作画中画效果时,需要特别注意Depth的层级关系。
1.2 高级渲染技巧
Target Texture功能非常实用,以下是几种典型应用场景:
- 小地图制作:将顶视相机渲染到UI纹理
- 监控画面:多个相机渲染到不同RenderTexture
- 镜子效果:通过反射相机生成实时镜面
Occlusion Culling是大型场景优化的利器,但需要注意:
- 需要提前烘焙 occlusion 数据
- 动态物体需要设置为Occludee Static
- 对小物件集群效果最好
Viewport Rect实现分屏游戏时,坐标范围是(0,0)到(1,1)。比如双人游戏可以设置:
- 玩家1相机:(0,0,0.5,1)
- 玩家2相机:(0.5,0,0.5,1)
2. 相机编程实战
2.1 相机控制与坐标转换
获取主相机有三种可靠方式:
- Camera.main(效率较低,适合编辑器脚本)
- 提前缓存引用(推荐在Awake中获取)
- 通过FindGameObjectWithTag查找
世界坐标与屏幕坐标转换是UI交互的基础:
csharp复制// 血条跟随3D物体
Vector3 screenPos = mainCam.WorldToScreenPoint(target.position);
healthBar.transform.position = screenPos + new Vector3(0, 30, 0);
实测发现,WorldToScreenPoint在物体位于相机后方时也会返回坐标,需要额外判断z值:
csharp复制if(screenPos.z < 0) // 物体在相机后方
2.2 相机事件回调
三个关键相机事件的使用场景:
- onPreCull:最适合做动态遮挡剔除
- onPreRender:适合后处理参数设置
- onPostRender:适合截图功能实现
典型应用案例:
csharp复制void OnEnable() {
Camera.onPreRender += MyPreRender;
}
void MyPreRender(Camera cam) {
if(cam == mainCam) {
// 设置全局着色器参数
Shader.SetGlobalVector("_CameraPos", cam.transform.position);
}
}
3. 刚体物理系统精讲
3.1 碰撞检测机制
三种碰撞事件的区别:
- Enter:触发瞬间调用一次
- Stay:持续碰撞每帧调用
- Exit:分离时调用一次
Collision与Trigger的关键区别:
| 特性 | Collision | Trigger |
|---|---|---|
| 物理模拟 | 有 | 无 |
| 性能消耗 | 较高 | 较低 |
| 获取信息 | 更全面 | 较简单 |
常见问题解决方案:
- 异形碰撞体检测失败:确保父物体有Rigidbody
- 高速物体穿透:开启Continuous Detection
- 碰撞不触发:检查Layer碰撞矩阵
3.2 力的应用实践
四种力的模式对比测试数据:
| 模式 | 质量影响 | 时间影响 | 适用场景 |
|---|---|---|---|
| Force | 是 | 是 | 持续推力 |
| Acceleration | 否 | 是 | 环境力场 |
| Impulse | 是 | 否 | 爆炸、跳跃 |
| VelocityChange | 否 | 否 | 瞬移、速度重置 |
爆炸力效果实现要点:
csharp复制void ApplyExplosion() {
Collider[] colliders = Physics.OverlapSphere(center, radius);
foreach(var col in colliders) {
Rigidbody rb = col.GetComponent<Rigidbody>();
if(rb != null) {
rb.AddExplosionForce(power, center, radius, 3.0f,
ForceMode.Impulse);
}
}
}
4. 性能优化与疑难解答
4.1 相机性能优化方案
多相机系统优化策略:
- 将静态相机的Culling Mask设为最小
- 非必要相机设为Disabled,需要时启用
- 使用Camera.Render手动控制渲染时机
- 减少Post Processing Stack的使用
动态分辨率调整技巧:
csharp复制void AdjustResolution() {
float fps = 1f / Time.deltaTime;
if(fps < 30) {
mainCamera.targetTexture = lowResRT;
} else {
mainCamera.targetTexture = null;
}
}
4.2 刚体常见问题排查
刚体异常问题诊断表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 物体下坠过快 | 重力Scale过大 | 调整gravityMultiplier |
| 碰撞体穿透 | 碰撞体尺寸不匹配 | 检查Collider尺寸 |
| 物理模拟卡顿 | 过多刚体同时激活 | 启用Sleep模式 |
| 力效果不明显 | 质量值设置不当 | 调整mass合理范围 |
刚体休眠管理最佳实践:
csharp复制void FixedUpdate() {
if(rb.IsSleeping() && shouldMove) {
rb.WakeUp();
rb.AddForce(moveDirection);
}
}
5. 高级应用案例
5.1 第三人称相机实现
平滑跟随相机核心代码:
csharp复制void LateUpdate() {
Vector3 targetPos = target.position - target.forward * distance + Vector3.up * height;
transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime);
transform.LookAt(target);
}
防穿墙处理方案:
csharp复制RaycastHit hit;
if(Physics.Raycast(target.position, -transform.forward, out hit, distance)) {
transform.position = hit.point + transform.forward * 0.5f;
}
5.2 物理交互系统设计
可推动物体实现要点:
- 设置合适的mass和drag值
- 添加以下交互代码:
csharp复制void OnControllerColliderHit(ControllerColliderHit hit) {
Rigidbody rb = hit.collider.attachedRigidbody;
if(rb != null && !rb.isKinematic) {
rb.AddForceAtPosition(hit.moveDirection * pushPower, hit.point);
}
}
物理材质选择指南:
- 高摩擦:橡胶材质 (Friction 0.8+)
- 低摩擦:冰面材质 (Friction 0.1-)
- 弹性表面:设置Bounciness > 0.5