330 likes | 461 Views
Lessons learnt from making a hit mobile game with Unity 3D. David Jefferies, Technical Director PaperSeven Ltd. A Bit Of History. Used Unity to prototype Split/Second 2 The game + engine was large ~2mil lines code Turnaround times were slow
E N D
Lessons learnt from making a hit mobile game with Unity 3D David Jefferies, Technical Director PaperSeven Ltd
A Bit Of History • Used Unity to prototype Split/Second 2 • The game + engine was large ~2mil lines code • Turnaround times were slow • Unity helped the developers test out gameplay ideas quickly
A Bit Of History • At one point we even considered making S/S 2 with Unity • Unity renderer was about 80% at efficient rendering models as S/S 2 • Replace editor, asset pipeline and game engine with Unity
2010 - 2011 • Unity was widely seen as a capable prototyping tool • Definitely not a commercial game engine
Unity • I think the reality is different • Model Importer • Dependency Checker • Renderer • Extensible Editor • C# code This is what I want. I don’t want to write these modules again.
Made In Chelsea Game • 9 months to develop • 4 coders, 4 artists plus contractors • Producer/GD • iOS and Facebook initially, Android update • Free with IAPs • #2 in the AppStore charts • 19k ratings at 4.5 stars • 10 hours in length if you rushed through • Won ‘Best Game’ in the Broadcast Digital Awards 2014
How We Made It • Exclusively C# • Used Prime31 plugins for most native code - IAPS, Twitter and Facebook authentication etc • Coders used a mixture of MonoDevelop and .Net • MacMiniserver build machine that built iOS, Facebook and eventually Android • Mainly developed on PC except for the iOS specific bits which were kept to a minimum
Learnings • It’s easy to get something up and running • This is a great strength of Unity • However, not all features scale well from prototype to full production • It’s this understanding which is vital
Memory • Rule [1] • Never write code that allocates while the game is running • All allocations at load time • Same rules as C++ but it’s trickier with C# • Unity’s memory profiler is your friend
Memory • Know the difference between class and struct • We never use foreach loops • Use a heap safe Dictionary • Raycast was only exception • Use object pools. No runtime instantiation
Exceptions • Rule [2] • Always use Fast but no Exceptions in production code • Speeds up managed to native calls by 3X • Managed code exceptions are still thrown • Write the exception to a log file and crash • Send the log file to your metrics server
Exceptions void HandleLog(string logString, string stackTrace, LogType type) { string log = DateTime.UtcNow.ToString("HH:mm:ss") + " " + type.ToString() + " :\t" + logString; if (!string.IsNullOrEmpty(stackTrace)) { log = log + "\n\tstackTrace :\n" + stackTrace; } using (StreamWriter file = new StreamWriter(Application.persistentDataPath + "/logs" + "/LogFile_" + logSessionID + ".log", true)) { file.WriteLine(log); } if (type == LogType.Exception) { PlayerPrefs.SetInt(APP_CRASHED_KEY, 1); } }
Scenes & Prefabs • Prefabs are a great way of encapsulating code and data • When placed directly in a scene they make it un-mergeable • If a prefab is edited in a scene and not applied it becomes unique to that scene • References were forever being lost • Rule [4] No references between different prefabs
Scenes & Prefabs • Rule [5] Have a scene structure that determines which prefabs to load & initialise • Load all the prefabs in the level folder • Custom intialisation steps to manage initialisation dependencies • Inject dependencies into the components • Start game
Code Structure • No Singletons • Loaded prefabs are available as a dictionary to all classes • Dependency injectors push dependencies into the components • Injectors are destroyed after being run
Code Structure publicclassGameTimeInjector : GameResourcesInjector<GameObject> { [SerializeField] privatestring _pauseControllerObject = "PauseEventController"; publicoverridevoid Inject () { GameTime_gameTime = GetComponent<GameTime>(); PauseEventControllerpauseController = GameResources.GetLoadedObject(_pauseControllerObject).GetComponent<PauseEventController>(); pauseController.AddPauseableObjects(_gameTime); } }
Code Structure • Common Library shared between all games • Lives in the Plugins Folder • Enforces the rule that game code cannot exist in the library • We use a repository within a repository • Warnings as Errors using gmcs.rsp & smcs.rsp
Code Structure • Listener pattern • Reduces dependencies between modules • Uses the dictionary of objects to connect listeners and distributors
Code Structure publicclassDriveAutoSaverInjector : GameResourcesInjector<GameObject> { [SerializeField] privatestring _saveStateDataObject = "StateManager"; [SerializeField] privatestring _gameStateObject = "Game"; publicoverridevoid Inject() { AutoSaverautoSaver = gameObject.GetComponent<AutoSaver>(); GameStategamestate = GameResources.GetLoadedObject(_gameStateObject).GetComponent<GameState>(); gamestate.PostEndOfRaceListeners.Add(autoSaver); } }
Over Air Update • Rule [6] • All prefabs are built as asset bundles • This allows us to update all of the game data over the air • Versions.xml exists on Amazon AWS and serves new prefabs to the game • Next time the game runs it loads the new downloaded prefabs rather than the bundles prefabs • Use [SerializeField] attribute for private variables
Out Of Memory • Write guard file when app launches • Delete it when app enters the background • If you launch and the file is there then ran out of memory • Tell user to reset their device • Log with metrics server • Use assembly stripping
Source Control • Internal Git repository hosted on our network • Everyone in the studio uses the Git client • Problems with empty directories / meta files
Auto Build • Hook into Unity’s build process • Have a config file that defines all the build settings • We use TeamCity • Building iOS more difficult than it should be • Upload to TestFlight / HockeyApp • Signed with Enterprise profile
Auto Build • We use Mind Candy’s Teamcity Unity3D runner • https://github.com/mindcandy/Teamcity-unity3d-build-runner-plugin • iOS Build is fiddly • Write build number • Build Project • Build Xcode Archive • Build IPA • Upload to TestFlight • Lots of messing around with Provisioning Profiles
Performance • Scaled some features depending on device • Fast but no Exceptions • No memory allocations • Do all possible calculations at build time
That’s It • Any Questions david@paperseven.com