Last time I talked about what parts of a Unity project need to go into source control, and alluded to some issues that can crop up due to the way that Unity works. Today I’ll delve into some of the details of what’s going on, why it causes problems, and how to avoid those problems working in a small team.
The first problem that you’ll run into is the sheer quantity of binary files that Unity creates. Nearly every single file that the free version of Unity generates is a binary file. (Not having used the pro version, I cannot vouch for the quantity or quality of binary files therefrom.) The first thing that you’ll need is some way to make sure that you only have one person working on a file at a time. Perforce, for example, has a very strong file locking concept which works great to enforce this. We used Subversion, which has a weaker file locking mechanism, and wouldn’t work to enforce the issue. So, without some software enforcing the separation of work, we had to be very careful and make sure that we all knew what the other was working on. With three of us, it worked fairly well, since we were all working on different parts of the system, but we did have the occasional collision. When you do have these collisions, the only thing you can do is note what changes you made, revert your local changes, and then redo them after you update. Such is the nature of the beast, unfortunately.
When Unity encounters a new file in the Assets folder, it generates a new GUID for it. GUIDs are 128-bit (16-byte) identifiers that are generated through an algorithm that attempts to guarantee their uniqueness. It then generates a metadata and a cache file for that object named the same as its GUID, and places them into the appropriate subdirectories of the Library directory. Finally, it modifies the (binary) Library/guidmapper file to tell Unity that it knows about this file, and what its GUID is.
Do you see the problem? Let’s play the “What if two people tried to do this” game and see what happens. Joe Artist creates a new texture, and places it into the Assets directory somewhere. On his computer, Unity merrily chugs away, and generates GUID aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa. Meanwhile, Jane Designer creates a new prefab, places it into the Assets directory, and her computer generates GUID bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb. Back on Joe’s machine, Unity has modified the guidmapper file to indicate that SnazzyTexture.png is mapped to aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, while on Jane’s machine, guidmapper indicates that BestPrefabEver.prefab is maped to bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb. Now, Joe finishes his work first, so he checks his files in: Assets/Textures/SnazzyTexture.png, Library/metadata/aa/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, and Library/guidmapper. Jane finishes shortly thereafter, and…oh no! The source control server tells her that her guidmapper is out of date! And in a conflicted state, I might add, since she’s got local changes.
The solution to fixing this problem is to single out one person as the “Library Manager”. It is this person’s responsibility to maintain the Library directory. So, when Joe checks in his texture, all he checks in is the SnazzyTexture.png file. Joe then tells the Library Manager (let’s call him Bob) about his new texture. Bob updates his local repository with the texture, and lets Unity generate a GUID for it. Bob, then, checks in the Library/metadata/GUID file, as well as the Library/guidmapper file. Joe synchs, and then everybody is on the same page.
There’s still a problem with this setup, though. Joe can make textures all day, but a texture is a texture, and it doesn’t reference any other data. That texture isn’t really useful until Joe makes a material for it. The material file is its own asset with its own GUID, but it links to a texture, and the chances are that Joe will be checking in a material file along with his texture. Unity generates this link by storing the GUID of the linked texture in the material. Now, this works fine on Joe’s machine, because his copy of Unity has been generating the GUIDs. But what happens when Bob updates on his machine? The material file will have a GUID in it that doesn’t exist, and the link will be broken.
There are several ways to solve this problem. The first is that Joe can tell Bob what changes to make before he checks in. The second is that Joe can just let Bob check in the Library files, then fix up the links after Bob checks in his piece. The last one is that Joe can export a .unitypackage containing both the texture and the material, which Bob will import, and the link will be proper. All three of these solutions are valid, and will work just fine. In fact, we employed all three of them during the development of the Remnant gameplay demo. (Ace Attack has had a somewhat different development process, so we haven’t run into this problem.)
The problem with these solutions is that they don’t scale beyond three people or so. If I were to use Unity on a larger-scale project, I would have a single machine designated as the Library Manager machine. This machine would watch a directory on a shared drive for new UnityPackages. It would copy them locally, import them into the Unity project, and check in the new files. We won’t be using Unity for any future projects, though, so that won’t be an issue for us.
There are a couple of items left on this subject to tie the whole process in a nice bow. The first item is deleting of files. Just as people should only check their content into the Assets directory, they should only delete from the Assets directory. Unity will detect that the file is no longer there, and will remove the matching files from the Library directory. The Library Manager will be responsible for actually removing the files from source control. Here’s the catch, though: Unity will only remove the files from the Library when it is first loading the project. If you delete a file while Unity is running, it will not delete the matching Library files until the next time you run. So, it is important that the Library Manager close and open the Unity project after the initial update process.
The second is that, when people other than the Library Manager synch, they are likely to get conflicted files in their Library directory. The correct action in these situations is *always* to resolve the conflict by using the file from the repository.
Unity’s layout is optimized to work with its own Asset Server. But, armed with the above knowledge, you should be able to work in small teams without stepping on each others toes too much. Next time I’ll talk about one last workflow issue: large sub-projects.