Skip to content

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 from AuditRule) 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 / MethodPurpose
IdPermanent string identifier (e.g. studio.category.rule-name). Never change this once deployed, as team ignore files and CI configurations rely on it.
DisplayNameHuman-readable title displayed in Scene Doctor rule headers.
DescriptionConcise summary detailing why the issue matters for runtime performance.
CategoryStandard 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 / HelperDescription
ProfileThe active OptimizerProfile containing current platform limits.
PerPlatform(mobile, vr, desktopMid, desktopHigh)Returns the appropriate value based on active target platform.
IsMobileOrVRBoolean 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.
MetricsEstimated scene metrics (draw calls, triangles, active materials, VRAM).
PipelineCurrent render pipeline kind (BuiltIn or Universal).
MainCameraThe 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 MethodIntended 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 MethodAction 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).