Skip to content

antfarmar/Unity-Reference

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

98 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Unity Reference Cheat Sheet

Code snippets, Techniques, Optimizations, Links

Unity: An Entity-Component Game Engine

Entity-Component Pattern: Allows a single entity to span multiple domains without coupling the domains to each other.

GameObject: The single entity that spans multiple domains (Transform, Physics, Rendering, ...).

Component: Keeps the domains isolated, where the code for each is placed in its own Component class.

In Unity, GameObject is simply a container of Components.


Entity-Component Pattern

Index

  1. Architecture, Standards, Best Practices
  2. Techniques
  3. Optimizations
    • TODO
  4. Quick Links

Architecture, Standards, Best Practices

Software Design 101
  • Use C#
    • Strongly-typed, lexical analysis = debugging easy
    • Lots of documentation, libraries, community supported
  • Stay Organized
    • Naming conventions
      • Descriptive names, standard capitilization
      • Unity handles spaces in names
    • Logical folder structure
      • Easy to locate assets
  • Zero-tolerance for:
    • Warnings & Errors
    • Runtime Memory Allocaton
  • Use the Profiler often
    • Keep code constantly optimized

Techniques


Implementing Gameplay

Inter-Object Communication

  • Public Static Classes\Methods
  • Public Instance Methods
  • Events/Messages

Managers\Controllers

Most common patterns:

  • Singletons
  • Object Pools

Singleton Pattern

Game Managers, Static Classes, Global Variables, & You

Singletons are basically an object enforced to have a single instance only and always. You can access it anywhere, any time, without needing to instantiate it. That's why it's so closely related to static. For comparison, static is basically the same thing, except it's not an instance. We don't need to instantiate it, and we can't, because it's automagically allocated. And that can and does bring problems.

The Singleton design pattern is a very specific type of single instance, specifically one that is:

  • Accessible via a global, static instance field
  • Created either on program initialization or upon first access (lazy instantiation)
  • No public constructor (cannot instantiate directly)
  • Never explicitly freed (implicitly freed on program termination)

This pattern introduces several potential long-term problems:

  • Inability to use abstract or interface classes
  • Inability to subclass
  • High coupling across the application (difficult to modify)
  • Difficult to test (can't fake/mock in unit tests)

Game Managers as Singletons

One object to control them all

Game Manager Basic Requirements:

  • Be easily extendible
  • Have only one instance allowed
  • Persist across all the scenes (shouldn’t be destroyed when a new scene is loaded)
  • Provide global variable access to other classes

So why be Singleton? A non-instantiable static class could do the job, no?!

  • You can’t extend MonoBehaviour with a static class, hence it can't be a Script Component.
  • You can’t implement an interface with a static class.
  • You can’t pass around a static class as a parameter.

GameManager Singleton Examples

Minimal Quick Hack:

public class GameManager : MonoBehaviour {
    public static GameManager instance = null;  // Static instance of GameManager which allows it to be accessed by any other script.

    void Awake() {                          // Awake is always called before any Start functions.
        if (instance == null)               // Check if instance already exists.
            instance = this;                // If not, set instance to this.
        else if (instance != this)          // If instance already exists and it's not this:
            Destroy(this.gameObject);       // Then destroy this. This enforces our singleton pattern, meaning there can only ever be one instance of a GameManager.
        DontDestroyOnLoad(this.gameObject); // Sets this to not be destroyed when loading new scenes.
    }
}
Further Reading

Coroutines


Execution Time Sharing (not multi-threading, concurrency, or parallelism)

[Video Tutorial] [Unite Talk]

  • Coroutine inherits from YieldInstruction
  • A function that can suspend its execution (yield) until the given YieldInstruction finishes.
    • Maintains local parameter references when called.
    • No return values or error handling (but can be overcome, if necessary)
  • Can be used as a way to spread an effect over a period time. It is also a useful optimization:
    • Replaces state machines elegantly
    • Prevents execution blocking

When a task does not need to be needlessly repeated quite so frequently, you can put it in a coroutine to get an update regularly but not in every single frame. Similarly, calling an expensive function every frame in Update() will introduce significant slowdown, since it would block execution. To overcome this use a coroutine to call it, say, only every tenth of a second instead of every frame update.

For example:

void Start() {
    StartCoroutine(SomeCoroutine);
}

void Update() {
    // ExpensiveFunction();     // muh framerates :(
}

IEnumerator SomeCoroutine() {
    while(true) {
        ExpensiveFunction();
        yield return new WaitForSeconds(.1f);
    }
}
  • A common pattern effectively handled by coroutines:

    • Operations that take more than 1 frame...
    • Where we don't want to block execution...
    • And want to know when finished running.
    • Examples:
      • Cutscenes, Animation
      • AI Sequences/State Machines
      • Expensive Operations
  • Coroutines also admit a slick, readable game loop:

void Start() {
    StartCoroutine (GameLoop());    // Let's play!
}


// This is called from Start() and will run each phase of the game one after another.
IEnumerator GameLoop() {
    yield return StartCoroutine (LevelStart()); // Start the level: Initialize, do some fun GUI stuff, ..., yield WaitForSeconds if setup too fast.
    yield return StartCoroutine (LevelPlay());  // Let the user(s) play the level until a win or game over condition is met, then return back here.
    yield return StartCoroutine (LevelEnd());   // Find out if some user(s) "won" the level or not. Also, do some cleanup.

    if (WinCondition) {                 // Check if game level progression conditions were met.
        Application.LoadLevel(++level); // or Application.LoadLevel(Application.loadedLevel) if using same scene
    } else {
        StartCoroutine (GameLoop());    // Let the user retry the level by restarting this (non-yielding) coroutine again.
    }
}

// The Coroutines
IEnumerator LevelStart() { Debug.Log("Start"); yield return new WaitForSeconds(1f); }
IEnumerator LevelPlay () { while(alive) yield return null; }
IEnumerator LevelEnd  () { Debug.Log("End.."); yield return new WaitForSeconds(1f); }

Usage

Use StartCoroutine methods to start a coroutine.

public Coroutine StartCoroutine(IEnumerator method);                        // Typical usage. Pass the name of the method in code.
public Coroutine StartCoroutine(string methodName, object value = null);    // Higher runtime overhead to start the coroutine this way; can pass only one parameter.

Use StopCoroutine methods to stop a coroutine.

public void StopCoroutine(IEnumerator method);  // Stops the coroutine stored in method running on this behaviour.
public void StopCoroutine(string methodName);   // Stops the first coroutine named methodName.
public void StopAllCoroutines();                // Stops all coroutines running on this behaviour.

Note: If you call multiple coroutines with the same name, even a single StopCoroutine with that name will destroy them all!

Coroutine Return Types

Normal coroutine updates are run after the Update() function returns. Different uses of Coroutines by return type:

yield                       // The coroutine will continue after all Update functions have been called on the next frame.
yield WaitForSeconds        // Continue after a specified time delay, after all Update functions have been called for the frame
yield WaitForFixedUpdate    // Continue after all FixedUpdate has been called on all scripts
yield WaitForEndOfFrame     // Continue after all FixedUpdate has been called on all scripts
yield WWW                   // Continue after a WWW download has completed.
yield StartCoroutine        // Chains the coroutine, and will wait for the MyFunc coroutine to complete first.

In actual C# code:

yield return null;
yield return new WaitForSeconds(t);
yield new WWW(url);
yield return new WaitForFixedUpdate();
yield StartCoroutine(routine)

Example usages of StartCoroutine() & StopCoroutine():

  • Passing method name as code:
IEnumerator instance = null;        // Need a reference to a specific coroutine instance to stop it.
instance = SomeCoroutine(a, b, c);
StartCoroutine(instance);           // Start coroutine
                                    // or instance = StartCoroutine(SomeCoroutine (a, b, c)); (Coroutine continue failure?)
StopCoroutine(instance);            // Stop this specific coroutine instance.
  • Passing method name as string:
IEnumerator Start() {
    StartCoroutine("DoSomething", 2.0F);
    yield return new WaitForSeconds(1);
    StopCoroutine("DoSomething");
}

IEnumerator DoSomething(float someParameter) {
    while (true) {
        print("DoSomething Loop");
        yield return null;
    }
}

Events


Events are closely related/similar to the Observer software design pattern.

Observer: gameprogrammingpatterns chapter

Observer Pattern

  • Simple, workable solution for Decoupling classes:
    • It helps us loosen the coupling between two pieces of code.
    • It lets a subject indirectly communicate with some observer without being statically bound to it.
    • It lets one piece of code announce that something interesting happened without actually caring who receives the notification.

Problems:

  • Object heavy
  • Have to implement an entire interface just to receive a notification.
  • Can’t have a single class that uses different notification methods for different subjects.

Modern approach is for an “Observer” to be only a reference to a method or function, like C# delegates:

C# Delegate Events

Delegates

Delegates (C# Programming Guide)

  • A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.
  • Delegates are used to pass methods as arguments to other methods.
  • The following example shows a delegate declaration:
    • public delegate int PerformCalculation(int x, int y);

Delegate Events: C# Publisher-Subscriber Model

Events (C# Programming Guide)

  • Events enable a class or object to notify other classes or objects when something of interest occurs.

  • The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers.

  • The publisher determines when an event is raised; the subscribers determine what action is taken in response to the event.

  • Event handlers are nothing more than methods that are invoked through delegates. You create a custom method, and a class can call your method when a certain event occurs.

  • C# has "events" baked into the language.

    • The event keyword is used to declare an event in a publisher class.
      • public event SampleEventHandler SampleEvent;
  • the "observer" you register is a "delegate" (delegates are a reference to a method).

    • public delegate void SampleEventHandler(object sender, SampleEventArgs e);

Problems:

  • Setting a delegate ALLOCATES memory.
    • Using a single delegate and constantly setting it will cause a GC call to reclaim memory.
    • Better to pre-set an array of delegates in the Awake function to overcome this.

Unity Events


Optimizations


Section One

  • TODO

Quick Links


Unity Website

Documentation

Community

Scripting

GameObject|Component|Mono|Behaviour|Transform

Math|Random|Vectors|Physics|Input

Debug|Gizmos

Optimizing

Miscellaneous

Releases

No releases published

Packages

No packages published