40.性能优化-CPU-脚本-禁用未使用的脚本和对象
40.1 知识点
禁用未使用的脚本和对象
禁用未使用的脚本和对象 指:在特定游戏类型中,对那些不在玩家视野内或离玩家过远的游戏对象,采用“不处理它们”的优化方式,从而节约性能。在项目体量大、场景复杂或设备性能有限时,这种做法很实用。
注意: 该方案只适用于“禁用这些对象不会对游戏流程产生明显影响”的游戏;若逻辑强依赖远处或视野外对象,则不宜盲目禁用。
Unity 的视锥剔除和遮挡剔除
- 视锥剔除:自动剔除摄像机视野外的对象,只影响渲染,被剔除的对象不会被绘制。
- 遮挡剔除:剔除虽在视野内但被其他物体挡住的对象,同样只影响渲染,避免 GPU 绘制不可见物体。
也就是说,Unity 的视锥剔除和遮挡剔除只优化 GPU 渲染,对象上的各种组件(脚本、碰撞等)仍会照常执行,无法达到“禁用未使用的脚本和对象、优化 CPU”的目的。若希望减少 CPU 消耗,需要自己在脚本里根据视野或距离去禁用组件/对象。
OnBecameVisible 和 OnBecameInvisible
- OnBecameVisible:当该物体上的 Renderer 对任意摄像机(主摄像机、UI 摄像机、小地图摄像机、Scene 视图摄像机等)变为可见时调用。
- OnBecameInvisible:当该物体上的 Renderer 对所有上述摄像机都不可见时调用。
可在可见时启用脚本(或对象)、不可见时禁用,从而减少视野外对象的 CPU 消耗。
注意:
- Scene 视图摄像机也会触发这两次回调,编辑时切到 Scene 视图可能导致频繁触发。
- 对象上必须有 Renderer 组件(如 MeshRenderer、SkinnedMeshRenderer),否则不会收到回调。
- 若 **GameObject 被 SetActive(false)**,渲染器随之失活,这两个函数不再被调用。
- 若只是本脚本所在组件被禁用(enabled = false),只要同一物体上的 Renderer 仍激活,回调仍会触发。
示例:可见时启用脚本,不可见时禁用脚本。
// 对任意摄像机可见时调用
private void OnBecameVisible()
{
print("出现在视野里");
enabled = true;
}
// 对所有摄像机都不可见时调用
private void OnBecameInvisible()
{
print("离开视野里");
enabled = false;
}
运行后,物体进入任意摄像机视野时控制台会打印“出现在视野里”,离开视野后打印“离开视野里”,脚本被禁用。下图分别为进入视野时与离开视野后脚本被禁用时的界面与控制台输出。


利用距离禁用对象
通过判断“待禁用对象”与玩家或摄像机之间的距离,当超过设定阈值时主动禁用对象或脚本。例如由怪物管理器、子弹管理器等统一维护列表,每帧或定时判断与主角/摄像机的距离,超过需求值则执行禁用逻辑(失活组件、失活对象、放回对象池等)。
注意: 距离判断时可用距离的平方与阈值的平方比较,避免使用 Vector3.Distance 带来的开方运算,减少消耗。
public Transform targetTransform; // 玩家或摄像机
void Update()
{
// 方式一:直接比距离(含开方)
if (Vector3.Distance(transform.position, targetTransform.position) >= 50f)
{
// 禁用组件、对象失活、放入对象池等
}
// 方式二:用平方比较,避免开方(50 * 50 = 2500)
if ((transform.position - targetTransform.position).sqrMagnitude >= 2500f)
{
// 同上
}
}
主动判断视锥范围
Unity 的 GeometryUtility 提供两个常用方法:
- **GeometryUtility.CalculateFrustumPlanes(Camera)**:获取该摄像机视锥体的六个平面。
- **GeometryUtility.TestPlanesAABB(planes, bounds)**:判断一个 AABB 包围盒是否与这组平面相交或被包含(即是否在视锥内)。
常用于自定义可见性检测、自定义剔除、镜头内检测,以及根据是否在视锥内决定是否启用脚本或逻辑。
用法: 先用当前摄像机计算平面数组,再取物体 Renderer 的 bounds,用 TestPlanesAABB 判断在不在视锥内。
注意: 场景中对象不多时可以每帧对少量物体做该判断;若对象很多,应对检测做分帧、分层等优化,避免单帧开销过大。
示例:在 Update 中根据是否在视锥内打印不同信息(实际项目中可改为启用/禁用脚本或对象)。
private Renderer objectRenderer;
private Plane[] frustumPlanes;
void Start()
{
objectRenderer = GetComponent<Renderer>();
}
void Update()
{
// 获取主摄像机视锥体平面(若摄像机不变可缓存 planes,仅摄像机移动时重算)
frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
if (GeometryUtility.TestPlanesAABB(frustumPlanes, objectRenderer.bounds))
{
print("在摄像机视锥体范围内");
}
else
{
print("不在摄像机视锥体范围内");
}
}
当物体在视锥外时,控制台会持续打印“不在摄像机视锥体范围内”;当物体在视锥内时,会打印“在摄像机视锥体范围内”。下图分别为两种情况的界面与控制台输出。


40.2 知识点代码
Lesson40_性能优化_CPU_脚本_禁用未使用的脚本和对象.cs
using UnityEngine;
public class Lesson40_性能优化_CPU_脚本_禁用未使用的脚本和对象 : MonoBehaviour
{
public Transform otherTransform;
private Renderer renderer;
Plane[] planes;
void Start()
{
#region 知识点一 禁用未使用的脚本和对象
//所谓的禁用未使用的脚本和对象
//是指在 特定游戏类型中
//对那些不在玩家视野内,或者离玩家太远的游戏对象
//采用不处理他们的优化方案
//达到节约性能的目的
//尤其是项目内容庞大,场景复杂或者设备性能有限的情况下
//这种优化手段非常实用
//注意:
//这种优化方案
//只适用于那些禁用对象不会对游戏过程产生明显影响的游戏
#endregion
#region 知识点二 Unity的视锥剔除和遮挡剔除
//视锥剔除:
//自动剔除摄像机视野之外的对象
//只影响渲染系统,不会绘制被剔除的对象
//遮挡剔除:
//剔除虽然在摄像机视野内、但被其他物体挡住的对象
//同样是只影响渲染系统,防止浪费 GPU 绘制不可见的物体
//也就是说
//Unity中的视锥剔除和遮挡剔除其实只会优化GPU渲染层面的性能
//对象身上依附的各种组件,还是会照常执行
//并不能起到禁用未使用的脚本和对象 优化CPU性能的目的
#endregion
#region 知识点三 OnBecameVisible和OnBecameInvisible函数
//OnBecameVisible:
//在渲染器(Renderer)对 任何摄像机(主摄像机、UI摄像机、小地图摄像机、Scene视图摄像机等) 可见时调用
//OnBecameInvisible:
//在渲染器(Renderer)对 任何摄像机(主摄像机、UI摄像机、小地图摄像机、Scene视图摄像机等) 都不可见时调用
//注意:
//1.Scene 视图摄像机也会导致调用该函数
//2.所依附的对象上必须有Renderer渲染器相关组件
//3.对象失活后,由于渲染器也会失活,这两个函数不再响应
//4.即使这两个函数所在的组件失活,只要渲染器存在,就会响应
#endregion
#region 知识点四 利用距离禁用对象
//通过判断需要禁用的对象和玩家或摄像机距离过远时主动禁用对象
//比如我们通过怪物、子弹等等管理器,不停判断他们和主要目标的距离
//当超过需求值时主动禁用对象
if (Vector3.Distance(this.transform.position, otherTransform.position) >= 50f)
{
//禁用相关的逻辑
//比如失活组件、对象失活、放入缓存池等等
}
//注意:
//在进行距离判断时,可以采用距离的平方进行比较
//这样可以减少开方的消耗
if ((this.transform.position - otherTransform.position).sqrMagnitude >= 2500)
{
}
#endregion
#region 知识点五 主动判断视锥范围
//Unity中提供了一个几何工具类
//其中有两个重要方法:
//1.GeometryUtility.TestPlanesAABB
// 用于判断一个AABB包围盒是否与一组平面(如视锥体)相交或包含
//2.GeometryUtility.CalculateFrustumPlanes
// 用于获取摄像机视锥体六个平面
//常用与:
//1.自定义检测
//2.自定义剔除
//3.镜头内检测
//4.优化游戏对象激活与逻辑执行
//等等
//用法
//1.获取摄像机视锥体平面
planes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
//2.利用摄像机视锥体平面和对象的包围盒进行相交判断
renderer = GetComponent<Renderer>();
if (GeometryUtility.TestPlanesAABB(planes, renderer.bounds))
{
print("在摄像机视锥体范围内");
}
else
{
print("不在摄像机视锥体范围内");
}
//注意:
//场景中少量对象,可以放心使用该方法判断
//若场景中对象过多,因采用分帧处理等等优化手段降低开销
#endregion
}
void Update()
{
if (GeometryUtility.TestPlanesAABB(planes, renderer.bounds))
{
print("在摄像机视锥体范围内");
}
else
{
print("不在摄像机视锥体范围内");
}
}
//注意:
//1.Scene 视图摄像机也会导致调用该函数
//2.所依附的对象上必须有Renderer渲染器相关组件
//3.对象失活后,由于渲染器也会失活,这两个函数不再响应
//4.即使这两个函数所在的组件失活,只要渲染器存在,就会响应
//在渲染器(Renderer)对 任何摄像机(主摄像机、UI摄像机、小地图摄像机、Scene视图摄像机等) 可见时调用
private void OnBecameVisible()
{
print("出现在视野里");
enabled = true;
}
//在渲染器(Renderer)对 任何摄像机(主摄像机、UI摄像机、小地图摄像机、Scene视图摄像机等) 都不可见时调用
private void OnBecameInvisible()
{
print("离开视野里");
enabled = false;
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com