GameObject
s / Resources available to scripts.One of Unity's defining features is its Inspector (the big pane on the right side of this image).
While the game is being played, certain properties of every object
can be edited during runtime (!). This is useful for finding, for example, just the right speed for player movement, or Just the right amount of energy / mana. All of this without needing to restart the game or recompile. Very powerful.
In the above screen, you can see that the player_controller component makes the following fields available in the inspector...
Sprinting
Energy
Gravity
To make fields like these accessible, a component (also known as a script) must simple create public variables, like so...
public class player_controller : MonoBehaviour { public bool Sprinting = false; public float Energy = 1.0f; public float Vector2 inputs = Vector2.zero; // etc void Start() { } void Update() { } }
Note how the type of the public variable determines how it shows up in the inspector. bool
s are checkboxes, while float
s are values, while Vector2
s are pairs of values.
But what if we want to interact with particular GameObject
s already in the scene?
Use cases for this might be a missile GameObject
which chases the player. The Missile
component needs a reference to the player so it can know where it is and follow it! This is very simple. Simply create a public GameObject
variable...
public class Missile : MonoBehaviour { public GameObject player_object_ref; void Update() { // move towards player_object_ref.transform.position } }
...Then, place this component on your missile GameObject
. You'll see this player_object_ref
field in the inspector under the Missile
component for the missile GameObject
. It will be null, however! So click on your player GameObject
in the scene hierarchy, and drag it into the field! It will no longer be null, and your Missile
component will now have a reference to the player object
!
This is a simple way to make resources / object
s of all kind easily available to the code in your components. You can make other GameObject
s available, you can make audio clips available (so you can play them – just drag from your project pane into the inspector), text files, etc.
It will be convenient to make certain pieces of data available throughout your codebase. For example, consider a high-score counter that must be accessible and writable from multiple places in your codebase (maybe a high-score display class needs to read it, maybe a coin-class needs to write it, etc).
You can accomplish this very quickly via a static global. Consider the following class...
public class GameSessionData { public static int high_score = 0; public static int lives = 3; public static int game_title = "My First 494 Game!" }
These data members persist across scene transitions, and are accessible throughout your codebase like so...
public class SomeTestingComponent : MonoBehaviour { void Start() { print(GameSessionData.high_score); GameSessionData.game_title = "SUPER First 494 Game!"; } }
Global, easily accessible data is typically discouraged, in that it turns a codebase into spaghetti if over-indulged in. It is fast, however, and it can be made safer via...
Static data, as shown above, can be useful for maintaining game-global data. However, it is rarely useful for maintaining game-global systems. This is because static
data cannot receive Update()
, Start()
, Awake()
function calls like a GameObject
can, unless the static
data is part of a GameObject
class
, like so...
public class GameSessionData : MonoBehaviour { public static int high_score = 0; public static int lives = 3; public static int game_title = "My First 494 Game!" void Start() { } void Update() { print("current_lives: " + lives.toString()); } }
Because the static
data is now part of a class
derived from MonoBehavior
, it may now be packaged with the automatically-called Start()
and Update()
functions. With the addition of these functions, an isolated, self-sustaining gameplay system may be constructed.
But there's a problem!
For these MonoBehavior
functions (Start()
, Update()
, etc) to be executed, this component must be attached to a GameObject
in the scene. What happens if that GameObject
gets destroyed? The system stops, dead in its tracks! If the scene transitions, this object
is guaranteed to be destroyed! More than that, we need to ensure that only one of these object
s exists in the game (We have no need for more than one GameSessionData
manager!).
We need this object
to survive scene transitions – to persist across scenes.
We can accomplish this in the following way, via the Singleton pattern.
public class YAudioManager : MonoBehaviour { private static YAudioManager instance = null; void Awake () { if (instance != null && instance != this) { Debug.LogError ("Destroying Object: Instance already exists."); Destroy (gameObject); return; } else instance = this; DontDestroyOnLoad (gameObject); // ... Other Initialization Code } }
Here, we use a private static
data member to decide whether an object
of this type has ever been created before. If there is currently an object
of this type (the reference is stored in the instance
member), then we know we must destroy ourselves and return, otherwise the singleton pattern is violated (because there would then be two YAudioManager
s).
If the instance
member is null, we know that we are the first object
of this type to be instantiated, and we claim the instance
as ours by storing a reference to ourself in it. This causes any future instantiations of YAudioManager
to know we exist, and they will destroy themselves, preserving the Singleton pattern.
The final DontDestroyOnLoad(gameObject)
tells Unity that this GameObject
shouldn't be destroyed during a scene transition, which means this object
will survive until the end of the game! (assuming none of our other code deliberately calls Destroy()
on it).
With this setup, we have singleton gameplay systems which never get destroyed during the life of the game, are capable of updating and initializing themselves (via Update()
and Start()
), and provide nice centralized APIs and data that will empower our highly-distributed set of components.
Moral of the story: It's nice to have both powerful centralized managers, and loose, simple, distributed components that use them (There's a strong analogy to be made here with the US gov and its balance of Powerful centralized agencies and the freedom of local govs / individual people).
Don't optimize until you need to. If you begin to experience lag, unresponsive controls, or periodic, annoying frame-hitches, begin optimizing with the following tips...
Accessible via Window -> Profiler
, the Unity Profiler is a convenient and powerful tool for diagnosing performance problems. Take some time to familiarize yourself with it.
Application.targetFrameRate
(and Friends)The framerate you target (60fps or 30fps) will determine how much time your game has to process everything. Choosing 30fps will get you a slightly less smooth experience, but will make it easier to prevent any annoying hitches from occurring when frames take too long. Learn more about specifying your target framerate here.
Gfx.WaitForPresent
Suggests a GPU-Bounded Frame.If Gfx.WaitForPresent
is consuming substantial amounts of time, your computer's GPU is struggling to keep up. You should attempt to reduce your shader and effect usage.
Unity's C# scripting environment is garbage collected, and this collection will consume large amounts of time during the frames in which it occurs. To prevent annoying hitches and laggy, choppy frames, minimize the creation of new objects. Note that the creation of new struct
s, such as Vector3
s, does not contribute to garbage collection. Garbage generation may be tracked in the GC Alloc
column of the profiler. Keep this value as small as possible.
GetComponent<T>()
, Find()
, Etc Are Slow.If possible, be sure to use these functions in a script's Start()
or Awake()
function, rather than in its Update()
or FixedUpdate()
functions. Store a reference to the object
s / components you need, so you don't have to invoke these expensive functions every frame.
Large scenes with 100s – 1000s of objects will put a burden on your computer. If you're smart about your transitions, you should be able to break larger areas up into a bunch of highly-performant smaller areas. For keeping data / object
s alive across scene transitions, try the singleton pattern.
Check out 494 Quest – a small, entirely complete game. It's source code is included. Feel free to explore how it accomplishes things, from controls, to collisions, to scene transitions, to enemy AI, etc.
Try some InControl tutorials.
Unless it has something you need, or your current version of Unity is buggy / unstable, I wouldn't bother. Unity has a reputation for releasing broken versions, and once your project is converted to the new version, you can't go back. Of course, if you're using version control as required by the course, your short-term risk is limited.
See my resource list and Austin's resource list.
You are authorized to purchase the following assets: InControl
For anything else, check with us in advance or it will be considered a violation of the honor code. Please see Collaboration and Honor Code in the syllabus for further details.
Not on the tutorials or on project 1. Following project 1, everyone will be working on different games, and collaboration will be encouraged within reason.
Not on the tutorials or on project 1. Following project 1, feel free to enlist others for help with art assets, music, etc – things that don't heavily impact the game's design or programming. Post to DeviantArt – it's swarming with people who will make art for you for cheap. (Be sure to tell them you're students!)
Be sure your resources are allowed and cite them properly or it will be considered a violation of the honor code. Again, see Collaboration and Honor Code in the syllabus for further details.
Try Google or Stack Overflow. If they fail you, please come to office hours.