Unity Workflow LibraryUWLAbout

Topic 12 · Authored content

Importers and baking into editable assets

ScriptedImporter, AssetPostprocessor and bake steps that write inspectable, committed assets instead of hidden runtime state.

Some content starts outside Unity (a spreadsheet, a JSON level, a model) or is too large to place by hand (a forest, a road network). Both are fine. What matters is where the result lands: in an asset a human can select and inspect, not in objects that only exist while the game runs.

Pick the right tool

Source of the content Tool What a human sees
A custom file format (.csv, .json, a level text file) ScriptedImporter The source file, its importer settings in the Inspector, and the imported objects under it
A standard asset that needs consistent settings (models, textures, audio) AssetPostprocessor The normal importer Inspector, with the enforced values
Parameters plus an algorithm (scatter, layout, tables) An editor bake step A committed prefab, scene or .asset holding the output
Content that must differ every session Runtime generation from authored rules The rules asset, plus a debug view of each result

ScriptedImporter

  • The file is the source, the import is visible. The ScriptedImporter reference describes importers registered with [ScriptedImporter] for given file extensions, invoked by the asset pipeline to convert those files into assets. A designer edits the source file in their own tool, and Unity reimports it.
  • Importer settings are serialized fields. The same page states the importer's C# fields are serialized like a MonoBehaviour's, shown in the Inspector, and control the import per asset. Put scale, defaults and options there, not in constants inside OnImportAsset.
  • Stable sub-asset identifiers. Outputs are added with ctx.AddObjectToAsset(identifier, obj) and ctx.SetMainObject. The reference's example notes the identifier must be unique and consistent across imports, and that temporary objects not passed to the context must be destroyed. A changing identifier breaks every reference to that sub-asset on reimport.
  • Bump the version when output changes. The first argument of [ScriptedImporter(version, ext)] is the importer version; change it when the import logic changes so existing assets reimport.
  • Declare dependencies. When the import reads other assets, implement GatherDependenciesFromSourceFile, which the reference lists for setting up artifact dependencies and import order.
  • Changing settings from code. The reference's sequence is AssetImporter.GetAtPath, cast to the importer type, change values, EditorUtility.SetDirty, then SaveAndReimport.

AssetPostprocessor

  • Enforce import rules, do not hide them. An AssetPostprocessor hooks the import pipeline before or after import (OnPreprocessTexture, OnPostprocessModel, OnPostprocessAllAssets and others). Use it to apply the project's agreed settings, so the values a human sees in the importer Inspector are the ones that ship.
  • Scope by path. Check assetPath and act only on the folders the rule is for. The Assets and serialization page records why a postprocessor's path filter is its blast radius.
  • Version it. GetVersion returns the postprocessor's version; bump it when its behaviour changes so affected assets reimport.
  • Write down what it enforces. Each postprocessor gets a line in the project's pipeline record (see Recording a content pipeline), because a value that silently reverts after a human edits it is otherwise a mystery.

Bake into committed assets

  • BAKE-TO-AN-ASSET (HARD). A generator that produces content for the shipped game runs in the Editor and writes its output to prefabs, scenes or .asset files that are committed. The game loads the baked asset. It does not run the generator again at startup.
  • Parameters are an asset too. The generator reads its seed, ranges and rules from a ScriptableObject, so a human can change them and press bake again.
  • The output is hand-editable. After a bake, a human may move, delete or retune individual results. Decide up front what re-baking does to hand edits: bake into a separate asset that hand-placed content sits beside, or record which entries are locked. Never re-bake silently over hand edits; the Editor tooling page requires a tool to name what it would overwrite.
  • Deterministic and diffable. Same parameters and seed produce the same asset, so a re-bake with no changes shows no diff, and a parameter change shows only the entries it really moved.
  • Save through the asset database. Create with AssetDatabase.CreateAsset or PrefabUtility.SaveAsPrefabAsset, call EditorUtility.SetDirty on changed objects, then save. The ScriptableObject Manual page states that edits made from script in Edit mode are not saved without SetDirty.

When runtime generation is the right call

  • A run that must differ every session (a roguelike layout, a random reward) generates at runtime. Its tables, weights and rules are still authored assets, and the generator exposes what it produced (an in-Editor debug view or an export to an asset) so a bad result can be inspected and reproduced from its seed.
  • Generation that is only there to save an agent from placing things by hand is not a runtime system; bake it.