Thursday, September 10, 2015

Scripting the Unity Editor to automatically build to Windows / OSX / Linux and packaging the files in ZIP files.


I'm getting ready to release my next gay sex game, which means a lot of builds and testing. This game, in particular, has a lot of particular framework and infrastructure that involves copying over specific files directly into the built data folder. I don't want to have to copy over the files manually over and over, so I bit the bullet and decided to write an editor script that automatically does all this stuff for me, for all 3 desktop platforms that I'm targeting. This is really nice because it saves me a lot of time when making builds, and it also makes it more the whole process more foolproof since it prevents me from forgetting any files -- Unity is automated to include them for me!

Here are the main snippets + explanations of those parts of the pipeline, with the full script at the end of this post...

1) Use EditorUtility.SaveFolderPanel to get a file path.

string path = EditorUtility.SaveFolderPanel("Build out WINDOWS to...",
    GetProjectFolderPath() + "/Builds/", "");


2) Based on the specified BuildTarget platform, I apply some hardcoded variables, like what to add to the end of the .EXE filename, or where to find the Data folder on that platform. You'll definitely want to modify this part of my script based on how you personally like naming your files, or where you want to put your files for your own game, etc.

case BuildTarget.StandaloneWindows64:
  modifier = "_windows";
  fileExtension = ".exe";
  dataPath = "_Data/";
  break;


3) Switch the active build target so that Unity re-imports / re-converts files.

EditorUserBuildSettings.SwitchActiveBuildTarget(buildTarget);

4) OK, here's when I actually build out the game files now. In the last parameter, I have an inline if() statement which decides whether to "ShowBuiltPlayer" (pop-up an Explorer / Finder window when the build is done) or not. If you're working in OSX, you might watch to switch that to BuildTarget.StandaloneOSXUniversal or something, or maybe even remove it entirely.

BuildPipeline.BuildPlayer(GetScenePaths(), playerPath, buildTarget, buildTarget ==
    BuildTarget.StandaloneWindows ? BuildOptions.ShowBuiltPlayer : BuildOptions.None);


5) Next, I copy over some specific files from my \Assets\ folder to my build data folder. I use Unity's own internal copy function, FileUtil.ReplaceDirectory which also replaces any existing files. Because it's from my Assets folder, the folder copy operation includes all the .meta files. I delete the .meta files after copying over all the files. There's probably a nicer way to do this, but I didn't do that.

FileUtil.ReplaceDirectory(Application.dataPath + "/" + assetsFolderPath, fullDataPath + assetsFolderPath ); // copy over languages

// delete all meta files
if (deleteMetaFiles) {
var metaFiles = Directory.GetFiles ( fullDataPath + assetsFolderPath, "*.meta", SearchOption.AllDirectories);
  foreach ( var meta in metaFiles ) {
    FileUtil.DeleteFileOrDirectory( meta );
  }
}


6) At the end, I compress the whole build into a .ZIP file using DotNetZip. I use this particular fork of DotNetZip that's stripped down for Unity. There's also a bug in DotNetZip that corrupts DLL binary files, which means it usually breaks UnityEngine.DLL -- a pretty important file. Make sure you include the "ParallelDeflateThreshold" fix or else all your zipped builds will be corrupted!

using (ZipFile zip = new ZipFile()) {
  // DotNetZip bugfix that corrupts DLLs / binaries
     http://stackoverflow.com/questions/15337186/dotnetzip-badreadexception-on-extract
  zip.ParallelDeflateThreshold = -1; 

  zip.AddDirectory (directory);
  zip.Save(zipFileOutputPath);
}




... And that's it. A Gist of my full script is embedded below. Make sure you put it inside an "Editor" folder, or else Unity will attempt to include it inside your game and the build process will fail.