How to upgrade a Unity 4 project to Unity 6
How to move a Unity 4 project to Unity 6 without losing scenes: text conversion in Unity 2019.4, a UnityScript to C# port that keeps GUIDs and a recovered HUD.

Don't open a Unity 4 project directly in Unity 6, and above all don't save anything in it. Unity 6 only partly loads the old binary scenes and saving makes the loss permanent; the safe route goes through Unity 2019.4, which can both read the old format and write it back as text, and only then on to Unity 6.
This applies to projects with scenes and prefabs stored in the binary format and scripts written in UnityScript, the JavaScript-like language Unity used to support alongside C#. The example throughout is Onager, a catapult physics game written in Unity 4.1.2 and UnityScript. The project had 126 scripts, 75 scenes and 78 prefabs. After the conversion it runs on Unity 6000.5 with URP (Universal Render Pipeline) and is free on the App Store and Google Play.
What happens when you open the project in Unity 6
The simplest approach is to open the project in Unity 6 and switch serialization to Force Text, which stores assets as readable YAML. That is not a straight file conversion. Unity loads every asset through its importer and writes it back as YAML, so everything passes through the Unity 6 loader. On every scene it reports a Transform that is not assigned to a GameObject and deletes the "dangling" component. Saving makes that permanent.
You can measure the loss with binary2text, a tool that ships with the editor and reads a serialized file without the importer. In Onager we compared its output with the resulting YAML and counted only components that disappeared from GameObjects that survived:
| Component | Lost |
|---|---|
| Camera | 130 |
| ParticleSystem | 132 |
| Transform | 167 |
| BoxCollider | 104 |
| Rigidbody | 100 |
| GUITexture / GUIText | 303 / 179 |
Not one of the 75 scenes came through without loss. Whole HUD cameras vanished and the main camera was replaced with a new default one. The important part is that the damage happens on load, not on save. Until the project is converted, Unity 6 must not save it: not a single scene, not a single prefab.
Step 1: Record the original state
Before you touch anything, take a screenshot of every scene and save a binary2text dump of every original scene file. You will check cameras against those dumps in step 3 and rebuild the HUD from them in step 5. Keep the original binary files in your git history; don't delete them.

Step 2: Convert to text through Unity 2019.4
Unity 2019.4 is the last editor that can both read and write the Unity 4.1 format. Do the conversion with the editor closed, in a separate copy of the repository:
- Project version. Set the original version (4.1.2 for Onager) in the project version file so the editor takes its old-project path. Unity overwrites it on open; it only has to hold for the first import.
- Empty packages. Empty the package dependency list. A single compile error means no menu item from editor scripts works, which looks as if the menu never appeared at all.
- Import from scratch. Delete the Library folder and let the project import.
- Force Text and a forced rewrite. After switching to Force Text, Unity 2019.4 does not rewrite existing files by itself. You have to trigger it with the asset reserialization function, for the assets only, not their meta files. Rewriting every meta file makes Unity 6 reimport in a loop afterwards.
There is one trap in git. If your git attributes mark scenes as text with LF line endings, git strips bytes that look like line endings out of binary scenes when you add them to the repository. In Onager that damaged eight scenes. Don't put a line ending attribute on Unity assets; Unity writes LF anyway.
Step 3: Verify the result in the editor, not by reading YAML
Reading the converted files leads to confident wrong conclusions. Shortened prefab instance records look like a loss, and so does a camera that has only moved into a prefab. What counts is what Unity builds in memory. Open every converted scene and compare its cameras with the dump of the original from step 1.
In Onager all 75 scenes ended up with the same cameras in the same order. The only losses were things no current editor knows: 569 GUITexture, 179 GUIText and the legacy particle systems Unity removed in 2018.
Step 4: UnityScript to C# with the same GUID
Unity phased UnityScript out starting with 2017.2, so a manual port to C# is the only way. Scenes reference scripts through the GUID, a unique identifier Unity stores in every asset's meta file, and store field values by field name. Keep both during the port and every reference reconnects by itself:
for every UnityScript script X:
create a C# script with a class named exactly X
copy the GUID from the old script's meta file into the new one's
public fields: same name, same type, typos included
rename nothing until the project runs
In Onager, after porting all 126 scripts this way, 12,935 components in 73 scenes reconnected and every value set in the Inspector stayed.
A text search of the project will not tell you which scripts are used. A binary prefab stores the GUID as bytes, not as text. In Onager twenty-one scripts looked unused and every one of them was live. Watch out for leftovers from earlier port attempts too: one wooden block prefab pointed at two scripts from such an attempt, and that prefab alone accounted for 1,140 missing components. If you need to change a GUID in a binary file, swap it right in the bytes, sixteen for sixteen, so the file does not shift.
Step 5: Rebuild the HUD from the original binary scenes
GUITexture and GUIText do not exist in Unity 6, and the whole HUD built on them goes with them. You don't have to draw it again. binary2text reads the original binary scenes from your git history whether or not the classes still exist. Pull every HUD image and text out of them with its texture, colour, position and alignment, and have a script put them back on the same objects in the scenes. In Onager that was 577 images and 180 texts.
This also saves strings that exist nowhere else. In Onager that was 111 strings: every tutorial caption and the whole credits board. Writing the HUD from scratch would have meant making them up. When you carry colours over, keep in mind something the documentation does not say: the old GUITexture doubled the colour when drawing. All 577 images had a colour of exactly 0.5, so grey meant white.
After the conversion: replace old workarounds
Old projects often worked around the limits of the Unity of their day, and those workarounds are worth rebuilding once the project is converted:
- Floor reflection. Onager had a shiny floor without a real reflection. Below the floor stood a second, mirrored world: a copy of every object whose position a script copied from the original every third frame, 50 to 70 clones in one level. One extra camera drawing the reflection at half resolution replaces all of that. Particles and debris then reflect too, which the cloned world could not do.
- Aiming. If the trajectory preview tries to predict where a force added in the first physics step will send the shot, the shot lands somewhere other than the marker. It is more reliable to compute the velocity once, draw the preview from exactly that and assign it to the shot directly.
- Performance. Measure on a weak phone. On a Motorola G52, Onager dropped from 20 to 6 fps just because of the wall shader, more than resolution, shadows and the reflection combined. That is why the walls use a simpler material on low and medium detail.
If you deal with physics for thousands of objects in current Unity, see also the fake ragdoll for thousands of enemies in Unity DOTS. How to add glow and 2D lights to an old 2D game in URP after the upgrade is covered in a separate tutorial.
What this process does not solve
Components that current Unity does not know will not reconnect. GUITexture and GUIText have to be rebuilt as in step 5; legacy particle systems are lost. Class and field names stay exactly as they were in the original project, typos and whatever language someone once wrote them in included. Scenes bind to them by name, so renaming only makes sense through the editor, which rewrites the references itself.
If you have an old Unity project that needs to reach a current version, get in touch.


