How I CoDE IN unity

-OR-

How I've been using unity in Stupid ways for 5 years


-A talk / cautionary tale by-

CRATESMITH

(Kieran Lord)

Foreword


This talk isn't by any means the 
definitive "how you should use unity". 

It's just the mistakes I've made and some of the 
processes I use when I'm writing code in unity.

EVEN MORE Foreword


Also, lets keep it informal
feel free to ask questions during the talk.

These slides are already online .
(http://slides.com/cratesmith/how-to-use-unity)

Who AM I?


Cratesmith!
(aka Kieran Lord)



What do I do?

I make games!

Things I've contributed to...
  • PS2/XB: Destroy All Humans 2
  • 360/PS3: The Dark Knight (Unreleased)
  • Wii: Next big thing (Unreleased)
  • Web: Alternator 
  • PC/PS3: Vessel
  • iOS/Droid: Sim Cell
  • iOS/Droid: Codebreakers
  • PC/Mac: Kinect & VR support for Tail Drift

What else DO I do?


Unity middleware such as Autopilot 
(Testflight build/SDK integration for unity) 

VR/Tech R&D 

and custom plugins. 


What I'm here to talk about


I started using unity almost 5 years ago. 

At that time there wasn't any considered
 "best practice" for using the engine.

Like most people, I made a lot of mistakes.
But lets just focus on the big ones

THe First mistake I made


Making everything out of "generic" components.

It's very tempting to think you can create 
every object in your game out of "reusable" 
"well written" "modular" components by assembling 
collections of them in the editor.

This is what happens

Why this is silly


Your "clean" and "modular" code instantly goes to hell as you try to cover all the edge cases needed by every object in your game.

The inspector gets filled with parameters that don't relate to the object you're working on.

Your project becomes a nightmare made from "order of Update/Awake" bugs.

THE SECOND MISTAKE I MADE


SendMessage and Editor Logic.

On the surface, SendMessage doesn't seem like a bad idea. You SendMessage an "event name" and if any component on that object has a method with the same name, it gets called.

Editor logic also seems like a great idea, you have a button in your level or in the user interface. Instead of linking them by code you put a UnityEvent or EventDelegate in the inspector. 

That'd keep things simple right?

(source: http://wango911.deviantart.com/)

Lets just say it wasn't good


SendMessage is great in theory, but a pain to debug and work with. I've had problems where it wouldn't appear in callstacks, it misreads null as "no parameter". 

And editor logic and visual scripting tools are great until a point, after which they become complex, impossible to debug... and completely indecipherable to anyone but the person who set them up. 

Short version. I only use these features when I have to, and even then I do so very sparingly.

THE THIRD MISTAKE I MADe


Like nearly everyone in the games industry, 
I didn't make use of automated testing. 

There are awesome testing frameworks around. Unity even has one on the asset store that is deeply integrated with the engine.

Automated testing = you spend your time making game, instead of spending time figuring out why it's not working.

I'm a firm believer that it's more fun 
to be writing tests than fixing bugs.

My FOURTH, and biggest mistake


Assuming that in 24 months time I would
remember what components I need to manually put onto
an object so that it works as intended.


Without fail I will always forget this stuff.


From now on all my code is written so that future Kieran 
(or anyone else) can pick it up and actually get on with things.

HOW I do this now


I have a process for writing programming objects in unity.

  1. Each object has a primary, "Actor" component.
  2. All "non-actor" components are automatically added
  3. Parameters must be specific types and "safe"
  4. Create game object, add actor component, press play.
  5. You're allowed to break these rules when they're overkill

ACTOR COMPONENTs


Each object gets a "core" component 
called ActorObejectType

This component is the place for
 all your object-specific code. 

ACTOR COMPONENTS


This code is single purpose!


It can be as dirty, over-specific 
and hacked up as it needs to be.


After all, we're making games here,
not pretty code.

Actor component Example


using UnityEngine;
using System.Collections;

/*
 * Actor for the Yacht player type
 * Used in mission 3 where a wizard turns the player into a yacht
 */

// Note: all components we need are "Required"
[RequireComponent(typeof(Collider))]
[RequireComponent(typeof(RigidBody))]
[RequireComponent(typeof(Damagable))]
[RequireComponent(typeof(PlayerInput))]
[RequireComponent(typeof(WeaponController))]
public class ActorPlayerYacht : MonoBehaviour
{
    // use serializefield instead of public where possible
    [SerializeField] ActorYachtWeapon[] weapons;

    // Always cache off required components on awake.
    Damagable            damagable;
    PlayerInput          playerInput;
    WeaponController     weaponController;

    // Awake is only for setting up this object's values.
    //
    // Initializing in awake leads to horrible order of
    // initialization/existance problems
    void Awake()
    {
        damagable = GetComponent<Damagable>();
        playerInput = GetComponent<PayerInput>();
        weaponController = GetComponent<WeaponController>();
    }

    // Start is for initalizing this object and others
    void Start()
    {
        foreach(var weapon in weapons)
        {
            weaponController.Add(weapon);
        }
    }    
}


non-Actor components


Copy and pasting is evil, 
even in actor components. 
I don't do it.

Instead: Common functionality gets put 
in non-actor shared components.

how I write a shared functionality/

non-actor component


I start out by writing all functionality 
into an actor component.

If I later find that the same functionality would be needed
while writing another actor component. I refactor the functionality into a shared component.

I never write a shared non-actor component from scratch. 

USING [REQUIRECOMPONENT]


But remember!

One of the rules for Actor components is that I never manually add non-actor components in the inspector.

Instead I use [RequireComponent].

What is [requireComponent]


It's an attribute that you attach to a monobehaviour class .

It will add the required component automagically 
and prevent users from manually removing it using the inspector.

It basically guarantees that 
your actor will have that component.

Using [RequireComponent]


// All shared code is required
[RequireComponent(typeof(Damagable))]        
[RequireComponent(typeof(PlayerInput))]      
[RequireComponent(typeof(WeaponController))] 
[RequireComponent(typeof(RigidBody))]
[RequireComponent(typeof(Collider))]
public class ActorPlayerYacht : MonoBehaviour
{
    // Always cache your required components, especially on mobile!
    // GetComponent isn't free. And overusing makes code harder to read.
    Damagable            damagable;
    PlayerInput          playerInput;
    WeaponController     weaponController;

    // Awake is for setting up this object's values.
    // Always cache off required components on awake.
    void Awake()
    {
        damagable = GetComponent<Damagable>();
        playerInput = GetComponent<PlayerInput>();
        weaponController = GetComponent<WeaponController>();
    }
}


parameters


The way you expose parameters to the inspector can make all the difference for your programming and design workflows.

A good parameter is...

  1. Public only if it needs to be.
  2. Only accepts the types of object it can use.
  3. It's something you ACTUALLY 
    need to tweak in the inspector.


Public  and [SerializeField] Parameters 


There are two ways to put a variable in the inspector

Prefix it with public
- OR -
Prefix it with [SerializeField]

There's an important difference!

PUBLIC  AND [SERIALIZEFIELD] PARAMETERS 


[SerializeField] members appear in the inspector but  are NOT accessible from other classes/components.

Public members also appear in the inspector but CAN be accessed from the code of other classes & components.

General rule, make all parameters as [SerializeField] you can convert them to public if you need to later.

Why? 


Unnecessary public members cause problems:

They clog up your code auto-complete windows with variables you're not interested in. 

They require more work & testing as they need to be able to be changed at any time without breaking anything.

USE Specific parameter types


Object parameters in the inspector can accept a component/asset/object of that type... or any object derived from it.

A very common practice is to reference just about every asset/prefab/object as GameObject.

This tragedy has to stop.

NOT USING SPECIFIC TYPES

Common example; I have a cannon.

The cannon has a parameter for the 
prefab for the object it will create when it fires. 

Whatever, lets make it a GameObject 
so it could fire "anything" 

So the code creates it then... and immediately has to check if it has a bunch of components, rigidbody, damage type, bullet owner.
That's a lot of assumptions. 

USING SPECIFIC TYPES

So instead, you make the parameter ActorCannonShot.

It's an actor component. 

It already has all the components garunteed cached, no calls to GetComponent needed.

Hell, lets just give ActorCannonShot a FireMe() method. Firing your cannon just became a 2 line job.

You can even change the parameter to be a base class for various CannonShot components and it'll still be like this!

DO I really need aN INSPECTOR parameter for this?


If you're a programmer there are two places you
can put variables to make them tweakable.

In your code as constants.
- OR -
As inspector parameters.

Try to only use inspector parameters for values that need them. Otherwise your inspector window can get a mile long.

HOW DO I KNOW IF I NEED AN INSPECTOR PARAMETER?


It's fine to temporarily make anything an inspector parameter while I'm working on something new but before I check any new code in I ask myself the following two questions.

Will designers need to play with this variable?
Will this variable need to change for different objects/prefabs?

If the answer to both is no, it's better to be a code constant for now.

Make it work out of the box


If it's possible to do so,
adding an actor component to an empty GameObject and 
pressing play should work with basic behaviour.

If not, 
it should give very clear warnings as to what needs to
be done to make it work.

(this is a great thing to use automated testing on!)

Know when to break the rules


If following the rules isn't going to help the game
get done faster, it's probably time to ignore them.

SERIALIZABLE Classes in the inspector


One neat thing you can do in your components is
have internal classes that show up as collapsable groups
int the inspector.


I don't have a fixed rule about how I use them, 
but they're really handy

So lets do some examples.

A basic example


using UnityEngine;
using System.Collections;

public class SerializeExample : MonoBehaviour 
{
    // Class doens't need to be public.
    // But won't be acessable outside SerializeExample if it's not.
    // (Depends on what you want to do with it)
    [System.Serializable]
    public class SerializeClass
    {
        // if you're just storing data, your parameters here should be public
        public SerializeExample objectParam;
        public float            floatParam;
    }

    // example  uses of our class
    [SerializeField] SerializeClass singleParam;
    [SerializeField] SerializeClass[] arrayParam;
}

A basic example


This is what the above code looks like in the inspector.

A not so basic example


using UnityEngine;
using System.Collections;

[RequireComponent(typeof(NR_UIDisableOnHide))]
[RequireComponent(typeof(NR_UIView))]
public class NR_DredgeMinigameManager : MonoSingleton<NR_DredgeMinigameManager>
{
    System.Action onDone;

    public class State
    {
        public virtual void OnEnter()     {}
        public virtual void OnUpdate()     {}
        public virtual void OnExit()    {}
        public NR_DredgeMinigameManager manager 
        {
             get { return NR_DredgeMinigameManager.Instance; } 
        }
    }


    [System.Serializable]
    public class StateIntroduction : State
    {
        public NR_PopupOldMan oldGuy;

        public override void OnEnter ()
        {
            oldGuy.Show(new string[]
            { "Lets find some of the things you lost"}, delegate {
                //Debug.Log("We should start the dredge game here");
                manager.SetState(manager.gameState);
            });
        }
    }
    public StateIntroduction introductionState;


    [System.Serializable]
    public class StateGame : State
    {
        public NR_DredgeMinigame         minigame;
        int numCoins = 0;
        int givePieceWhenTurnsRemaining = -1;

        public void Init(int numCoins, int givePieceWhenTurnsRemaining)
        {
            this.numCoins = numCoins;
            this.givePieceWhenTurnsRemaining = givePieceWhenTurnsRemaining;
        }

        public override void OnEnter ()
        {
            minigame.StartMinigame(manager.tilesToRecover, numCoins, givePieceWhenTurnsRemaining, delegate {
                manager.SetState(manager.resultsState);
            });
        }

    }
    public StateGame gameState;

    public RaftTileDataManager.RaftCellData[]     tilesToRecover    
    {
        get; private set;
    }

    [System.Serializable]
    public class StateResults : State
    {
        public NR_PopupOldMan oldGuy;
        
        public override void OnEnter ()
        {
            oldGuy.Show(new string[]{ "All Done?\nLet's go!"}, delegate {
                manager.EndDredgeGame();
            });
        }
    }
    public StateResults resultsState;

    public bool CheckItemsToDredge(RaftTileDataManager.RaftCellData[] tilesToRecover)
    {
        return gameState.minigame.CheckPiecesToRecover(tilesToRecover);
    }
    
    public void StartDredgeGame(RaftTileDataManager.RaftCellData[] tilesToRecover, int numberOfCoins, int givePieceWhenTurnsRemaining, System.Action onDone)
    {
//        Debug.Log ("Starting dredge game");
        this.onDone = onDone;
        this.tilesToRecover = tilesToRecover;
        gameObject.SetActive(true);
        gameState.Init(numberOfCoins, givePieceWhenTurnsRemaining);
        SetState(introductionState);
    }

    public void EndDredgeGame()
    {
//        Debug.Log ("Ending dredge game");
        if(onDone!=null)
        {
            onDone();
            onDone = null;
        }
        gameObject.SetActive(false);
        this.tilesToRecover = null;
    }

    State m_currentState;    
    public void SetState(State newState)
    {
        if(m_currentState!=null)
        {
//            Debug.Log("Dredge ending state: "+m_currentState.GetType().Name);
            m_currentState.OnExit();
        }

        m_currentState = newState;

        if(newState!=null)
        {
//            Debug.Log("Dredge starting state: "+m_currentState.GetType().Name);
            m_currentState.OnEnter();
        }
    }

    void Update()
    {
        if(m_currentState!=null)
        {
            m_currentState.OnUpdate();
        }
    }
}
    

A NOT SO BASIC EXAMPLE

There's a couple of things going on in here
  • Technically this is an Actor class! 
  • A state machine made of Serializable classes
  • Each state is it's own class, and it's own code
  • Each state exposes it's parameters in the inspector


This is how I handle complex actor classes

CUSTOM ASSET TYPES


One MAJOR problem in unity is sharing information
 between prefabs.

Prefabs are atomic, so if you have DudeWithGun and DudeWithGunB you often have to make sure you always
update all your prefabs.

Custom assets


The method I use to get around this 
is to have custom asset types.

These are assets you can create in the project window
that can store whatever you want
and prefabs/objects can reference them to share values.

In fact you can do insane things with these
(like augmenting the Mecanim Animation system)

Custom assets & prefabs

Back to that example from before.

So instead, we give create new asset called GunInfo and give it some values.

In our ActorDudeWithGun class we add a parameter that accepts a GunInfo.

Now everything in the GunInfo asset can be shared between multiple prefabs!

A simple custom asset From scratch


using UnityEngine;

// Custom assets have to derive from ScriptableObject
public class ExampleCustomAsset : ScriptableObject
{
    // like serialized classes, 
    // if it's just data storage make the variables public
    public float floatParam;
    public ExampleCustomAsset objectParam;

#if UNITY_EDITOR
    // Now we need a builder
    [UnityEditor.MenuItem("Assets/Create/ExampleCustomAsset")]
    public static void BuildAsset()
    {
        CustomAssetUtil.CreateTheAsset<ExampleCustomAsset>();
    }

#endif
}

#if UNITY_EDITOR
public static class CustomAssetUtil
{
    public static void CreateTheAsset<T> () where T : ScriptableObject
    {
        T asset = ScriptableObject.CreateInstance<T> ();
        
        string path = UnityEditor.AssetDatabase.GetAssetPath (UnityEditor.Selection.activeObject);
        if (path == "") 
        {
            path = "Assets";
        } 
        else if (System.IO.Path.GetExtension (path) != "") 
        {
            path = path.Replace (System.IO.Path.GetFileName (UnityEditor.AssetDatabase.GetAssetPath (UnityEditor.Selection.activeObject)), "");
        }
        
        string assetPathAndName = UnityEditor.AssetDatabase.GenerateUniqueAssetPath (path + "/New " + typeof(T).ToString() + ".asset");
        
        UnityEditor.AssetDatabase.CreateAsset (asset, assetPathAndName);
        
        UnityEditor.AssetDatabase.SaveAssets ();
    	UnityEditor.EditorUtility.FocusProjectWindow ();
		UnityEditor.Selection.activeObject = asset;
	}
}
#endif
    

What you get


Give them Icons

put a png image in Assets/Gizmos named 
"{Classname} Icon.png" 

the name of your custom asset type and it'll use it as an icon.


Now they're no different from in built unity types!

Use a custom asset builder


I'm even lazier than that. 
Adding a custom asset builder method every time
was a hassle so I built a tool to automatically do this.

You can grab it here.

(https://s3.amazonaws.com/cratesmith/CustomAssetBuilder.unitypackage)


Just make classes that inherit from CustomAsset
or give them the [CustomAsset] attribute and they'll
automatically appear in the create menu.



QUestions?

How To use unity

By cratesmith

How To use unity

  • 35,167