10. Writing Custom Rules
← Previous: Profiles & Settings | Next: Showcase Scene →
Overview
Scene Doctor is engineered as an open, extensible diagnostic framework. Every audit check implements the IAuditRule interface.
FuzzyOptimizer uses Unity’s native TypeCache system to discover rules:
- Any concrete class implementing
IAuditRule(or inheriting fromAuditRule) with a default parameterless constructor is automatically registered. - Rules can live in any Editor assembly definition across your project or package dependencies.
- No manual registration or configuration file editing is required. Custom rules appear immediately in Scene Doctor, the Settings rule manager, CLI audits, and exported reports.
Minimal Custom Rule Example
Below is a complete, production-ready custom rule that detects realtime lights inadvertently illuminating UI layers:
using System.Collections.Generic;using UnityEngine;using UnityEditor;using FuzzyLogicLabs.FuzzyOptimizer.Editor.Audit;using FuzzyLogicLabs.FuzzyOptimizer.Editor.Core;
public sealed class LightsLightingUiLayerRule : AuditRule{ // Permanent, unique ID. Prefix with your studio identifier to prevent collisions. public override string Id => "mystudio.lighting.culling-mask-ui";
public override string DisplayName => "Realtime Lights Illuminating UI Layer";
public override string Description => "Realtime lights configured with default culling masks illuminate UI elements, causing wasted vertex shader evaluations.";
public override AuditCategory Category => AuditCategory.Lighting;
public override void Evaluate(AuditContext context, List<AuditIssue> issues) { int uiLayer = LayerMask.NameToLayer("UI"); if (uiLayer < 0) return;
// Efficient cached lookup of active scene lights foreach (Light light in context.FindActive<Light>()) { if (light.lightmapBakeType == LightmapBakeType.Baked) continue; if ((light.cullingMask & (1 << uiLayer)) == 0) continue;
Light targetLight = light;
// Build the issue with an automated reversible fix issues.Add(CreateIssue( AuditSeverity.Info, title: $"Light illuminates UI: {light.name}", description: "Exclude the UI layer from this light's culling mask.", target: light.gameObject, fix: FixHelpers.SceneObject( label: "Exclude UI Layer", safety: FixSafety.Safe, target: targetLight, description: $"{light.name}: Culling Mask − UI", apply: () => targetLight.cullingMask &= ~(1 << uiLayer) ), estimatedGain: "Saves redundant vertex shading on UI canvas elements" )); } }}Core Framework Classes
1. AuditRule Base Class
Inheriting from AuditRule provides standard helper methods and boilerplate reduction:
| Property / Method | Purpose |
|---|---|
Id | Permanent string identifier (e.g. studio.category.rule-name). Never change this once deployed, as team ignore files and CI configurations rely on it. |
DisplayName | Human-readable title displayed in Scene Doctor rule headers. |
Description | Concise summary detailing why the issue matters for runtime performance. |
Category | Standard enumeration: Textures, Meshes, Rendering, Shadows, Lighting, Culling, Colliders, Physics, Animation, Audio, Particles, UI, Terrain, ProjectSettings, Hierarchy, PerformanceBudget. |
Evaluate(context, issues) | Main analysis method where diagnostic checks are performed and issues appended. |
CreateIssue(...) | Constructs a typed AuditIssue stamped with the rule ID, severity, target object, and automated fix. |
2. AuditContext
The AuditContext provides cached scene data and platform helpers to ensure audits remain blazing fast:
| Property / Helper | Description |
|---|---|
Profile | The active OptimizerProfile containing current platform limits. |
PerPlatform(mobile, vr, desktopMid, desktopHigh) | Returns the appropriate value based on active target platform. |
IsMobileOrVR | Boolean shortcut indicating if target is Mobile or Standalone VR. |
FindActive<T>() | High-speed cached query returning enabled components of type T on active GameObjects across all open scenes. |
Metrics | Estimated scene metrics (draw calls, triangles, active materials, VRAM). |
Pipeline | Current render pipeline kind (BuiltIn or Universal). |
MainCamera | The active scene camera used for frustum calculations. |
GetTextureImporter(path) | Helper to query or inspect asset importers safely. |
3. FixHelpers Factory
Creating automated fixes that integrate seamlessly with the revert history journal is streamlined via FixHelpers:
| Factory Method | Intended Use Case |
|---|---|
FixHelpers.SceneObject(...) | Modifying a scene component or GameObject property. Records pre-mutation state into the journal. |
FixHelpers.Importer<T>(...) | Updating asset importer settings (TextureImporter, ModelImporter, AudioImporter). Automatically batches reimports. |
FixHelpers.ProjectAsset(...) | Modifying project assets like materials or ScriptableObjects. |
FixHelpers.ProjectSettings(...) | Modifying project configuration assets under ProjectSettings/. |
4. Custom Revert Sessions
If your fix performs complex multi-stage mutations, instantiate a DelegateFix and record operations directly into the active OptimizationSession:
var fix = new DelegateFix("Optimize Asset", FixSafety.Moderate, "Custom description", session =>{ // Record original state before changing session.RecordObject(targetComponent);
// Modify component targetComponent.property = newValue;
// Record newly generated assets so revert can delete them session.RegisterCreatedAsset("Assets/FuzzyOptimizer_Generated/Custom/mesh.asset");});| Session Method | Action Tracked for Revert |
|---|---|
RecordObject(obj) | Captures serialized state of an object prior to modification. |
RecordCreatedObject(obj) | Tracks newly instantiated GameObjects for destruction on revert. |
RecordComponentRemoval(comp) | Records component data before removal from a GameObject. |
RecordImporterChange(path) | Backs up original asset and .meta files. |
RegisterCreatedAsset(path) | Registers new file assets created on disk. |
RecordIrreversible(description) | Flags an operation that cannot be journal-reversed (informs user in UI). |