Unity is like a big, old playground. It’s easy to play with all of the toys and have fun, but you can very easily hurt yourself. Unity’s biggest advantage and biggest flaw is that it’s very easy to get up and running very quickly. This is a flaw because it’s almost too easy, to the point where it’s easy to just start building your game, without thinking about the best way to organize it.
So, without further ado, let’s get started:
Tip #1: Use Singletons and Statics wherever you can
Singletons are a very handy construct, where you declare that only one instance of your class is ever allowed, and you return it whenever somebody asks for it. The Unity code for it will look something like this:
1
2
3
4
5
6
7
8
9
10
11
12
|
private
static
SingletonComponent This =
null
;
public
static
SingletonComponent Instance
{
get
{
if
(This ==
null
)
{
This = FindObjectOfType(
typeof
(SingletonComponent))
as
SingletonComponent;
}
return
This;
}
}
|
In order for this code to work, you must add the code to a Monobehaviour (in this case, I’ve called it SingletonComponent, but you’ll want to change it to whatever the name of your component is), and add the component to a GameObject. The “get” for the static Instance member does a lazy-binding lookup for the first object it finds of the type, and returns that. You can now access the class via code like:
1
|
SingletonComponent.Instance.Foo();
|
In some cases, though, you don’t want or need your object to live in a GameObject as a component. In those cases, use a static class, like so:
1
2
3
4
5
6
7
|
public
static
class
StaticClass
{
static
StaticClass()
{
// Static constructor is called before the first access to the static class
}
}
|
No extra setup is required, and you can call any methods in this class using:
1
|
StaticClass.Foo();
|
The biggest difference between the two is that the Singleton is actually an instance, while the static class is not (and requires all of its member methods to be static as well). These are both handy constructs that we’ve used all over the place in both Ace Attack and the Remnant demo.
Tip #2: Use generic versions of functions and classes wherever you can
.NET 1.1 was a fine piece of work, but one of the biggest features added in .NET 2.0 was generics, which, similar to templates in C++, will automatically encode type data. Thus, your code that would ordinarily look like this:
1
|
MyComponent c = GetComponent(
typeof
(MyComponent))
as
MyComponent;
|
Becomes:
1
|
MyComponent c = GetComponent<MyComponent>();
|
This applies to all of the Unity functions, as well as the collections objects, such as List<>, Dictionary<>, and everything else in the System.Collections.Generic namespace. You can also create your own generic classes and methods, which can save you lots of work.
Tip #3: Build and use a debug console
I spent an afternoon building a debug console using the Unity GUI system, with tab completion and command history. Each command calls a delegate in a static class. It was one of the handiest things I ever created, and so, to save you having to go through the same pain, I’ll include it here. It’s by no means a fully-featured console, but it does do quite a bit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
// --------------------------------------------------------------------------------
// Copyright (C)2010 BitFlip Games
// Written by Guy Somberg guy@bitflipgames.com
//
// I, the copyright holder of this work, hereby release it into the public domain.
// This applies worldwide. In case this is not legally possible, I grant any
// entity the right to use this work for any purpose, without any conditions,
// unless such conditions are required by law.
// --------------------------------------------------------------------------------
using
UnityEngine;
using
System;
using
System.Collections.Generic;
public
class
DebugConsole
{
private
string
ConsoleText =
""
;
private
bool
displayConsole =
false
;
public
bool
DisplayConsole
{
get
{
return
displayConsole; }
set
{
displayConsole = value;
if
(!DisplayConsole)
{
ConsoleText =
""
;
PreviousCommandIndex = -1;
}
}
}
private
List<
string
> PreviousCommands =
new
List<
string
>();
private
int
PreviousCommandIndex = -1;
private
string
AutoCompleteBase =
""
;
private
List<
string
> AutoCompleteOptions =
new
List<
string
>();
private
int
AutoCompleteOptionsIndex = -1;
private
ConsoleCommands.ConsoleCommand GetCommand(
string
CommandText)
{
foreach
(ConsoleCommands.ConsoleCommand Command
in
ConsoleCommands.Commands)
{
if
(Command.CommandText.Equals(CommandText, StringComparison.CurrentCultureIgnoreCase))
{
return
Command;
}
}
return
null
;
}
private
void
ExecuteCommand(
string
CommandText)
{
CommandText = CommandText.Trim();
PreviousCommands.Add(CommandText);
string
[] SplitCommandText = CommandText.Split(
new
char
[] {
' '
}, StringSplitOptions.RemoveEmptyEntries);
ConsoleCommands.ConsoleCommand Command = GetCommand(SplitCommandText[0]);
if
(Command !=
null
)
{
Command.Callback(SplitCommandText);
}
}
private
void
AutoComplete()
{
string
AutoCompleteText = AutoCompleteBase.Trim().ToLower();
if
(AutoCompleteOptionsIndex < 0)
{
AutoCompleteOptions.Clear();
foreach
(ConsoleCommands.ConsoleCommand Command
in
ConsoleCommands.Commands)
{
if
(Command.CommandText.ToLower().StartsWith(AutoCompleteText))
{
AutoCompleteOptions.Add(Command.CommandText);
}
}
AutoCompleteOptions.Sort();
if
(AutoCompleteOptions.Count > 0)
{
AutoCompleteOptionsIndex = 0;
PreviousCommandIndex = -1;
}
}
else
{
if
(AutoCompleteOptions.Count > 0)
{
AutoCompleteOptionsIndex = (AutoCompleteOptionsIndex + 1) % AutoCompleteOptions.Count;
}
else
{
AutoCompleteOptionsIndex = -1;
}
}
if
(AutoCompleteOptionsIndex >= 0)
{
ConsoleText = AutoCompleteOptions[AutoCompleteOptionsIndex];
}
}
private
void
ClearAutoComplete()
{
AutoCompleteBase =
""
;
AutoCompleteOptions.Clear();
AutoCompleteOptionsIndex = -1;
}
public
void
OnGUI()
{
if
(DisplayConsole)
{
string
BaseText = ConsoleText;
if
(PreviousCommandIndex >= 0)
{
BaseText = PreviousCommands[PreviousCommandIndex];
}
Event CurrentEvent = Event.current;
if
((CurrentEvent.isKey) &&
(!CurrentEvent.control) &&
(!CurrentEvent.shift) &&
(!CurrentEvent.alt))
{
bool
isKeyDown = (CurrentEvent.type == EventType.KeyDown);
if
(isKeyDown)
{
if
(CurrentEvent.keyCode == KeyCode.Return || CurrentEvent.keyCode == KeyCode.KeypadEnter)
{
ExecuteCommand(BaseText);
DisplayConsole =
false
;
return
;
}
if
(CurrentEvent.keyCode == KeyCode.UpArrow)
{
if
(PreviousCommandIndex <= -1)
{
PreviousCommandIndex = PreviousCommands.Count - 1;
ClearAutoComplete();
}
else
if
(PreviousCommandIndex > 0)
{
PreviousCommandIndex--;
ClearAutoComplete();
}
return
;
}
if
(CurrentEvent.keyCode == KeyCode.DownArrow)
{
if
(PreviousCommandIndex == PreviousCommands.Count - 1)
{
PreviousCommandIndex = -1;
ClearAutoComplete();
}
else
if
(PreviousCommandIndex >= 0)
{
PreviousCommandIndex++;
ClearAutoComplete();
}
return
;
}
if
(CurrentEvent.keyCode == KeyCode.Tab)
{
if
(AutoCompleteBase.Length == 0)
{
AutoCompleteBase = BaseText;
}
AutoComplete();
return
;
}
}
}
GUI.SetNextControlName(
"ConsoleTextBox"
);
Rect TextFieldRect =
new
Rect(0.0f, (
float
)Screen.height - 20.0f, (
float
)Screen.width, 20.0f);
string
CommandText = GUI.TextField(TextFieldRect, BaseText);
if
(PreviousCommandIndex == -1)
{
ConsoleText = CommandText;
}
if
(CommandText != BaseText)
{
ConsoleText = CommandText;
PreviousCommandIndex = -1;
ClearAutoComplete();
}
GUI.FocusControl(
"ConsoleTextBox"
);
}
}
}
public
static
class
ConsoleCommands
{
public
delegate
void
Command(
string
[] Params);
public
class
ConsoleCommand
{
public
ConsoleCommand(
string
CommandText,
string
HelpText, Command Callback)
{
this
.CommandText = CommandText;
this
.HelpText = HelpText;
this
.Callback = Callback;
}
public
string
CommandText;
public
string
HelpText;
// Currently not used
public
Command Callback;
}
public
static
ConsoleCommand[] Commands =
new
ConsoleCommand[] {
// Fill this with your commands:
new
ConsoleCommand(
"MyCommand"
,
"This is the help text for MyCommand"
, MyCommandDelegate),
};
public
static
void
MyCommandDelegate(
string
[] Params)
{
// Do stuff
}
}
public
class
DebugManager : MonoBehaviour
{
private
DebugConsole DebugConsole =
null
;
public
bool
ConsoleEnabled
{
get
{
return
(DebugConsole !=
null
) ? DebugConsole.DisplayConsole :
false
; }
}
void
Awake()
{
DebugConsole =
new
DebugConsole();
}
void
Update()
{
if
(Input.GetKeyDown(KeyCode.Escape))
{
if
(DebugConsole.DisplayConsole)
{
DebugConsole.DisplayConsole =
false
;
}
}
else
if
(Input.GetKeyDown(KeyCode.Tab))
{
DebugConsole.DisplayConsole =
true
;
}
}
void
OnGUI()
{
DebugConsole.OnGUI();
}
}
|
Tip #4: Unity supports threading, but isn’t thread-safe
You are welcome, in Unity, to create a thread using the System.Threading namespace. However, be very careful that you do NOT do anything with a GameObject or any components of GameObjects in a separate thread. Unity will spit out some incomprehensible error message, and may actually crash on you. Unity won’t manage your threads, though, so be sure to shut them down in OnApplicationQuit().
In order to do work on a separate thread, you will have to copy off the pertinent data, work on that, and then call a callback on the main thread once it’s complete. This will require some diligence on your part by using Critical Sections, Mutexes, or other threading constructs to lock the data as it gets copied across threads, but once it’s set up, it’s pretty easy to use.
Note that, while you cannot work on any GameObject-related stuff, you can definitely perform operations on Quaternions, Matrices, Vectors, Bounds, and other mathematical constructs.
Tip #5: Do NOT, under any circumstances, change any source code while Unity is playing your game
Unity WILL crash, or, at the very least, be VERY unhappy with you.
Tip #6: Use iTween. It’s very handy
iTween is a tweening library that is built for Unity. It has the ability to smoothly transition values over time, with various types of curves. While it does have a few issues and limitations, we used it very heavily on both our Remnant demo and in Ace Attack to create smooth animations and fades.
Tip #7: Unity is good practice for “printf debugging”
Because Unity does not support breakpoints or any sort of debugger, one of the things that I found myself getting a lot of practice in was so-called “printf-debugging”, where you put up a set of debug logs to tell you what’s going on, and then further and further refining the data that’s displayed, and the locations that it’s displayed, until you track down the problem.
Tip #8: The infinite loop is your bane
While the Unity editor is generally fairly stable (modulo the odd crash), the kiss of death for Unity is the infinite loop. If, through accident or malice, your code gets into an infinite loop, Unity will lock up and refuse to respond to any input. Your only recourse is to forcibly crash it with Task Manager, and hope that you didn’t lose too much work.
Tip #9: Use Only one monitor
Unity allows you to detach and dock its sub-windows in any configuration you please. However, a combination of some voodoo magic that Unity does, along with a latent bug in some driver software somewhere, will cause a very frequent blue screen crash if you have your Game screen and Editor screen on different monitors. Moving them both onto one monitor fixes the issue.
Tip #10: System.Serializable will make your structures editable
The Unity editor will automatically display and allow you to edit any public member variables that it knows how to create an editor for. Sometimes, you want to create a structure which is displayed in the editor, but if you just create the struct or class and create a public member variable of that type, Unity will not display it. The way to make Unity display it is to use the [System.Serializable] attribute thus:
1
2
3
4
5
|
[System.Serializable]
public
class
MyClass
{
public
int
Foo;
}
|
The preceding tips are general rules of thumb or quick tips that we found to be very helpful during our development. Take them with as big a grain of salt as you require. Next time, I’ll wrap up this series with a set of tips about integrating Unity Free with Unity iPhone.