Your prototype matter

avoiding technical debt early on

Architecture decisions

  1. How to structure your code
  2. How to store your data
  3. Which libraries and frameworks to use
  4. How to structure your unity project

Filip Loster

1. How to structure your code

Avoid the need to do domain reloading

How much speed is there to gain

  • Unity 2021.3.9f1
  • Almost empty project
   

5.5sec vs 0.1sec

How to make it work

  • Avoid static fields
  • Watch out for registering / unregistering handlers

public class StaticCounterExampleFixed : MonoBehaviour {
	static int counter = 0;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void Init() {
        counter = 0;   
    }
}
https://github.com/marijnz/unity-toolbar-extender

var isInFastMode = EditorSettings
	.enterPlayModeOptionsEnabled;
    
if (isInFastMode) {        
    if (GUILayout.Button(new GUIContent("SPEED"), _ButtonStylesEnabled)) {
        EditorSettings.enterPlayModeOptionsEnabled 
        	= false;
        EditorSettings.enterPlayModeOptions 
        	= EnterPlayModeOptions.None;
    }    
}
else {
    if (GUILayout.Button(new GUIContent("SPEED"), _ButtonStylesDisabled)) {
        EditorSettings.enterPlayModeOptionsEnabled 
        	= true;
        EditorSettings.enterPlayModeOptions 
        	= EnterPlayModeOptions.DisableDomainReload;
    }    
}

1. How to structure your code

Spearate your model from the view

Benefits of View-Model separation

  • You'll ensure system separation
  • Encourages writing functional code with clean data flow
  • You'll be able to write edit mode tests

Tests

  • No need to have them from the start
  • No need to have 100% code coverage
  • Very usefull as a documentation
  • Actually speeds up new feature development as project grows

Data Flow

1. How to structure your code

Encapsulate systems

Goal: running pieces of the game independently

Tools to achieve encapsulation

  • Use message queues instead of referencing
  • Pass data through model
  • Inject needed dependencies

    ... more on that later

2. How to store your data

Identify player state early on

Don't leave saving later

2. How to store your data

Keeping data in Scriptable Objects

Seems like a great idea...

  • Easy to start
  • Changes update live
  • Really powerfull with Odin:
    • [Inline Editor]
    • SO variables

No need for [CreateAssetMenu]

https://odininspector.com/community-tools/540/scriptableobject-creator

Pitfalls

Editor Performance

Pitfalls

Saving

Pitfalls

Changing SO structure

3. Which libraries and frameworks to use

Simple = almost always better

3. Which libraries and frameworks to use

Beyond Zenject


public class Foo : MonoBehaviour {
    ISomeService _Service;

    public void Init(ISomeService service) {
    	_Service = service;
    }

    public void DoSomething() {
        _Service.PerformTask();
    }
}

...

foo.Init(new Service());

public class TestInstaller : MonoInstaller {
	public override void InstallBindings() {
		Container
			.Bind<ISomeService>().FromInstance(new Service());
	}
}

public class Foo : MonoBehaviour {
	[Inject]
	ISomeService _Service;

	public void DoSomething() {
		_Service.PerformTask();
	}
}

public class Context : ScriptableObject {
    
	public ISomeService Service => _Service ??= new Service();

	private ISomeService _Service;

}

public class Foo : MonoBehaviour {
	[SerializeField]
	Context _Context;

	public void DoSomething() {
		_Context.Service.PerformTask();
	}
}

public class Foo : MonoBehaviour {   
	Context Context => 
		_Context ??= Resources.Load<Context>("context");

	Context _Context;

	public void DoSomething() {
		Context.Service.PerformTask();
	}
}

How to initialise MonoBehaviuors

  • Before any game code starts
  • Guaranteed to run everytime

[InitializeOnLoadAttribute]
public static class BootLoader {
	static BootLoader() {
		EditorApplication.playModeStateChanged += (nState) => {
			if (nState != PlayModeStateChange.ExitingEditMode)
				return;

			EditorPrefs.SetString(
				KEY_RETURN_TO_SCENE, 
				EditorSceneManager.GetActiveScene().name);

			var bootScene = AssetDatabase
				.LoadAssetAtPath<SceneAsset>($"Boot.unity");

			EditorSceneManager.playModeStartScene = bootScene;
		};
	}
}
							

						private void Load() {
						    var returnScene = EditorPrefs
						    	.GetString(KEY_RETURN_TO_SCENE, null);
						    EditorPrefs.SetString(KEY_RETURN_TO_SCENE, null);

						    if (returnScene != null) {
						        EditorSceneManager.LoadScene(
						        	returnScene, 
						        	LoadSceneMode.Additive);
						    }
						}							
						

What are the benefits

  • Can run from any scene
  • Easier DI to use
  • Can run prefabs in separation

4. How to structure project

Avoid nested prefabs

Problems with nested prefabs

  • Can never be sure change propagates
  • Weird behaviour with overwritten properties
  • Requires great discipline
  • Sometimes flat out breaks

What instead

  • Keep flat prefab structure
  • Break prefab instances when making changes
  • Prefer instantiating prefabs
  • Avoid loading prefabs with scenes

4. How to structure project

Watch out for resource hog

Every referenced prefab is loaded

Addressables

  • Not as hard as you think
  • Quite more stable than you anticipate
  • Easy way to add "soft" references
  • No need for creating bundles -can still load from resources

Thank you!