Every Unity project that survives long enough grows a 900-line PlayerController, a GameManager that knows about everything, and copy-pasted code nobody wants to touch. AI assistants are good at untangling that kind of code. They are also good at quietly wiping your Inspector values, breaking script references and renaming a method that a button calls by string. This guide shows how to refactor a Unity project with AI safely: the Unity-specific traps, git checkpoints, small prompt-sized steps, [FormerlySerializedAs] so serialized data survives, and tests that catch regressions before your players do.
Why Refactors Break Unity Projects
In a plain C# app, if it compiles after a rename, it mostly works. Unity is different because a lot of your project lives in scenes, prefabs and ScriptableObject assets that point at your code by name or by GUID. The compiler can’t see those links. An AI assistant usually can’t either, because it reads .cs files, not your .unity and .prefab YAML.
The traps that bite most often:
- Renamed serialized fields. Unity stores Inspector values by field name. Rename
speedtomoveSpeedand every prefab silently falls back to the default value. - Renamed or moved script files. A MonoBehaviour is linked to its script by the GUID in the .meta file. Rename a file inside Unity and the .meta follows. Rename it in a terminal or let an AI tool delete and recreate the file, and you get a new GUID and “missing script” everywhere.
- Class name not matching the file name. If the AI renames the class but not the file, Unity can’t attach the component.
- String-based links. UnityEvent listeners set in the Inspector (like a Button’s On Click), Animation Events,
SendMessage,Invoke("MethodName")andStartCoroutine("Name")all reference methods by name. Rename the method and nothing fails to compile. It just stops working. - [SerializeReference] types. These store the full type name, so moving the class to a new namespace breaks the data unless you add
[MovedFrom].
None of this means you shouldn’t use AI. It means the AI needs to be told about these rules, and you need a safety net.
Set Up Git Checkpoints First
Do not start an AI refactor without version control. You want to be able to throw away a bad step in one command. If you haven’t set it up yet, follow our git for Unity guide first, including a proper .gitignore and Force Text serialization so scenes and prefabs show readable diffs.
The rhythm is simple:
- Commit a clean, working state.
- Ask the AI for one refactor step.
- Let Unity recompile, open the main scenes, press Play.
- Run your tests.
- Commit if good.
git restore .andgit clean -fdif not, then try a smaller step.
Small commits also make it easy to bisect later when you notice a bug three days after the refactor. If you use an agent such as Claude Code that edits files directly, put this rhythm in your project’s CLAUDE.md so it is followed every session. Our Claude Code setup guide has a starter file with Unity rules you can extend.
Ask for a Map Before Any Edits
The first prompt should change nothing. Ask the assistant to read the code and explain it back. This catches misunderstandings before they turn into diffs.
Read Assets/Scripts/Player/PlayerController.cs and the scripts it references.
Do not edit anything yet.
1. List its responsibilities (movement, health, inventory, UI, audio...).
2. List every [SerializeField] and public field, and which other scripts
read or write them.
3. List methods that could be called by name: public methods with no
callers in code (likely UnityEvents or Animation Events), and any
Invoke/SendMessage/StartCoroutine string calls.
4. Propose a refactor plan as numbered steps. Each step must compile
and keep the game working on its own.
Point 3 matters. A public method with zero callers in code is often wired to a button or an animation clip. Tell the AI to keep those names unchanged, or plan to rewire them by hand.
If the assistant is connected to the Editor through MCP, it can also inspect scenes and prefabs to find those links. See our Unity MCP setup guide for how to connect one.
One Refactor per Prompt
Large “clean up this whole folder” prompts produce large diffs you can’t review. Use the plan from the previous step and feed it one item at a time. Good step sizes:
- Extract one method or one class.
- Rename one thing (with the serialization rules below).
- Replace one pattern, such as
GameObject.Findcalls in one script with serialized references. - Move one responsibility into a new component.
Put your standing rules at the top of each prompt, or in CLAUDE.md or your tool’s rules file so you don’t have to repeat them:
Refactor rules for this Unity 6 project:
- Behaviour must not change. This is a refactor, not a feature.
- Never rename a serialized field without adding
[FormerlySerializedAs("oldName")].
- Never rename public methods that might be UnityEvent or Animation
Event targets. Ask me first.
- Never delete and recreate a .cs file. Edit in place so the .meta
GUID is kept. If a file must be renamed, tell me and I will rename
it inside Unity.
- Class names must match file names.
- Show me the full diff and list anything I must re-check in the
Inspector.
That last line is the most useful one. A good assistant will tell you “the Jump button’s On Click points at PlayerController.Jump, which now lives on PlayerMovement, so rewire it”. If it doesn’t, you’ll find out in Play mode.
Keeping Inspector Data: FormerlySerializedAs
FormerlySerializedAs tells Unity “if you find old data under this name, load it into this field”. It lives in UnityEngine.Serialization.
using UnityEngine;
using UnityEngine.Serialization;
public class PlayerMovement : MonoBehaviour
{
[FormerlySerializedAs("speed")]
[SerializeField] private float moveSpeed = 5f;
[FormerlySerializedAs("jumpPower")]
[FormerlySerializedAs("jumpForce")]
[SerializeField] private float jumpHeight = 2f;
}
You can stack the attribute if a field has been renamed more than once. Three details people miss:
- It only works within the same class. If you move a field from PlayerController to a new PlayerHealth component, the attribute can’t carry the value across. You need to copy values by hand or with a small editor script.
- Assets keep the old name on disk until they are saved again. Remove the attribute too early and data still stored under the old name is lost. Either leave it in for good, or re-save everything first with
AssetDatabase.ForceReserializeAssets()from an editor script, commit, then remove it. - Namespace moves for [SerializeReference] classes need
[MovedFrom(true, sourceNamespace: "OldNamespace")]fromUnityEngine.Scripting.APIUpdating. Plain MonoBehaviours and ScriptableObjects are found by GUID, so a namespace change doesn’t break them.
Lock Behaviour In With Tests
A refactor is only safe if you can prove behaviour didn’t change. Before touching messy code, ask the AI to write characterization tests: tests that record what the code does today, even if today’s behaviour is a bit odd.
Before refactoring, write Edit Mode tests for DamageCalculator.Calculate
that pin down its CURRENT behaviour, including edge cases: zero armor,
armor greater than damage, critical hits, negative input.
Don't fix anything. If something looks like a bug, list it separately.
A typical result:
using NUnit.Framework;
public class DamageCalculatorTests
{
[TestCase(10, 0, false, 10)]
[TestCase(10, 4, false, 6)]
[TestCase(10, 15, false, 1)] // current code clamps to 1, not 0
[TestCase(10, 4, true, 12)]
public void Calculate_MatchesCurrentBehaviour(int dmg, int armor, bool crit, int expected)
{
Assert.AreEqual(expected, DamageCalculator.Calculate(dmg, armor, crit));
}
}
Run them before the refactor (they should pass), then after every step. Logic that is buried inside MonoBehaviours is hard to test, which is itself a good first refactor: move the math into a plain C# class the MonoBehaviour calls. Test setup, assemblies and Play Mode tests are covered in our Unity Test Framework guide.
Reviewing What the AI Changed
Read every diff. With small steps this takes a minute. Look for:
- Behaviour sneaking in. “While I was there I also fixed…” is a red flag. Revert and ask again.
- Deleted .meta files, or new .meta files for files that already existed. Run
git status. A .cs file shown as deleted plus a new .cs file with the same content means the GUID changed. - Changed Awake/Start/OnEnable order. Moving initialization between these methods, or between components, can change execution order and cause null references on the first frame.
- Removed “unused” code. Methods with no callers in C# may be called from the Inspector. Search your scenes and prefabs for the method name before accepting a deletion. With Force Text serialization,
git grep "m_MethodName: Jump"finds UnityEvent targets. - Scene and prefab diffs you didn’t expect. If an agent opened and saved a scene, check that nothing else changed in it.
📚 Refactoring is a skill, not a prompt
AI makes the typing fast, but you still decide what good structure looks like. A couple of solid books on clean code and game architecture make you much better at judging the AI’s suggestions. We collected the ones worth reading.
Worked Example: Splitting a God Class
Say PlayerController handles movement, health and coin pickups. A safe sequence looks like this, with a commit after each step:
- Tests. Characterization tests for any pure logic (damage math, coin totals).
- Extract logic. Move the health math into a plain
HealthModelclass. PlayerController still owns the fields and calls it. Nothing in the Inspector changes. - New component, old one delegates. Create a
PlayerHealthMonoBehaviour with its own serialized fields. PlayerController gets a[SerializeField] PlayerHealth healthreference and forwards its old public methods to it, so existing UnityEvents keep working. - Move data. Add PlayerHealth to the player prefab and copy maxHealth and the other values across. For one prefab, do it by hand. For many, ask the AI for a one-off editor menu item that copies the values and records Undo.
- Rewire. Point UI and UnityEvents at PlayerHealth directly. Check the list of name-based links from your map prompt.
- Remove the forwarding methods and old fields from PlayerController, once nothing uses them.
Each step is small enough that if Play mode breaks, you know exactly which change caused it. Movement and coins follow the same pattern afterwards.
Troubleshooting
“The referenced script on this Behaviour (Game Object ‘Player’) is missing!”
The script’s GUID changed or the class no longer matches the file name. Check git status for a deleted .meta. Restoring the old .meta file (with its GUID) usually fixes every reference at once. If the .meta is gone for good, you will have to re-add the component on each prefab and scene.
Inspector values reset to defaults after the refactor
A serialized field was renamed without [FormerlySerializedAs], or moved to another class. Revert the step, add the attribute, and redo it. If the field moved classes, copy values manually.
“The associated script can not be loaded. Please fix any compile errors and assign a valid script.”
Usually a compile error somewhere else in the project. Fix the Console errors first. If it persists, the class name and file name don’t match.
A button or animation event silently does nothing
A method called by name was renamed or moved. Select the Button and check On Click for a missing function, or open the animation clip’s events. Rewire them, or restore the old method name.
NullReferenceException on the first frame after splitting components
Initialization moved between Awake and Start, or a new component’s reference field was never assigned on the prefab. Check the new serialized references in the Inspector first.
The AI keeps making huge changes
Your prompt is too broad. Ask for “only step 3 of the plan, and nothing else”, and reject diffs that touch files outside that step.
FAQ
Can AI refactor a whole Unity project in one go?
It can try, but you won’t be able to review the result and the serialized-data problems compound. Small steps with commits between them are faster overall.
Do I need tests before refactoring with AI?
For logic you care about, yes. Even a handful of characterization tests catch most accidental behaviour changes. For pure renames, careful Play mode checks may be enough.
Is it safe to rename scripts with an AI agent?
Only if the .meta file is renamed with it and keeps its GUID. The simplest rule is to rename script files inside the Unity Editor yourself and let the AI edit the contents.
When can I remove FormerlySerializedAs?
After every scene, prefab and asset using the old name has been re-saved. ForceReserializeAssets does that in one go. Leaving the attribute in place is harmless if you are unsure.
Which AI tool is best for refactoring Unity code?
Tools that can read the whole project folder and show diffs, such as Claude Code, Cursor or Copilot in agent mode, beat pasting code into a chat window. The workflow in this guide matters more than the tool.




Leave a Reply