Tips For Working With Unity #4: Coding and General Tips

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值