Lessons learnt from making a hit mobile game with Unity 3D
description
Transcript of Lessons learnt from making a hit mobile game with Unity 3D
Lessons learnt from making a hit mobile game with Unity 3D
David Jefferies, Technical DirectorPaperSeven 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
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
Made In Chelsea Game
Made In Chelsea Game
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• MacMini server 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
Exceptionsvoid 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
Scenes & Prefabs
Custom Setup StepsAwake
Load AssetBundles
Load Prefabs
HandleInitFirstTime
SetupDependencies
HandleInit
Update
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 Structurepublic class GameTimeInjector : GameResourcesInjector<GameObject> {
[SerializeField] private string _pauseControllerObject = "PauseEventController";
public override void Inject (){
GameTime _gameTime = GetComponent<GameTime>();PauseEventController pauseController =
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 Structurepublic class DriveAutoSaverInjector : GameResourcesInjector<GameObject> { [SerializeField] private string _saveStateDataObject = "StateManager"; [SerializeField] private string _gameStateObject = "Game";
public override void Inject() { AutoSaver autoSaver = gameObject.GetComponent<AutoSaver>(); GameState gamestate = 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