转载:http://www.verydemo.com/demo_c92_i9022.html
Unity3D is a very nice tool in developing games or creating prototype. We have been using it for several months. It is well documented and the community is active enough for beginner. Also it provides an efficient Q&A website that you can get help of your problem.
Although Unity community are doing a lot of things to help beginner, there are still some undocumented details that can be confusing for new user. Here are some examples that you should be aware of:
We all known that Unity3D will automatically expose the public variable as property in Inspector (and in Animation Editor). It also serialize the public variable unless you add attributeSystem.NonSerialized. When you browse the properties of your GameObject in inspector, you probably will find the name of the property split into multiple words if you are using uppercase in the middle of the property name.
As the result if I define variable myFooBar, in the Inspector it will show “My Foo Bar” just like the picture below:
Before I started to use Unity, I used to name complex variable with “_” in between words. So all my variables looked like my_foo_bar at first. Too bad Unity3D would not recognize “_”, and it showed the name as “My_foo_bar” as a result:
So for a better experience in Unity3D, it’s recommended to name a variable start with lowercase character and split different words with uppercase letter.
Moving a GameObject is very common in game development. In Unity3D, it seems easy as long as you add the distance to the transform.localPosition. The code below shows how to moving an object along the x-axis 10.0 unit per seconds:
transform.localPosition += new Vector3( 10.0f * Time.deltaTime, 0.0f, 0.0f );
Be careful in using this! Assume the GameObject above have a parent, and the parent’s localScale is at (2.0, 2.0, 2.0). Your GameObject will move in 20.0 unit per second. Because your GameObject’s world position equals to:
Vector3 offset = new Vector3( my.localPosition.x * parent.lossyScale.x, my.localPosition.y * parent.lossyScale.y, my.localPosition.z * parent.lossyScale.z ); Vector3 worldPosition = parent.position + parent.rotation * offset;
To solve the problem, Unity3D provide the Transform.Translate(x,y,z), which moves GameObject by distance without scale in different space. So the more reliable script looks like this:
transform.Translate( 10.0f * Time,deltaTime, 0.0f, 0.0f );
Unity3D provides good mechanism to expose property to inspector. Designers would love those exposed properties because they can tweak them without reading any code. Here’s a little reminder that these inspector properties are also available in Animation View.
Some developers may think the Unity Animation View are just a substitute of other 3rd party animation package such as Maya and Blender3d. In fact you can use Animation View to not only manipulate skeleton, but also public variables for any of your GameObjects. For example, we use Animation View to change the alpha of our sprites over time, so that our designer can create fade in/out effect without writing code.
Unity3D allow us to use inherit for component. That means you could have a foo component that extends from MonoBehaviour, then create a bar that inherit from the foo as the code shows below:
public class foo : MonoBehaviour { ... } public class bar : foo { ... }
If I have two GameObject A and B — A contains foo, B contains bar. When we write:
foo comp1 = A.GetComponent(); bar comp2 = B.GetComponent();
The comp1 will get foo in A and comp2 will get bar in B as we expect. Since bar is the subclass of foo, some people may ask can we get the bar in B by passing the type of base class ? The answer isYES. You can write the GetComponent of B as:
foo comp2 = B.GetComponent();
In this way, you still get the bar component in B for comp2. Knowing this is good when you design complex game that you could have generic interface, and just get the base class for everything.
Pause and resume game is not quite easy in Unity3D, I’m still trying to find a perfect solution for that. I use a common practice with changing Time.timeScale to pause the game (by setting it to 0.0). This would cause problem, because a lot of functions or components in Unity3D can be affected by timeScale. Here is my current watch-out list:
I read from community that when time paused, you can use Time.realtimeSinceStartup to help user process time based functions. I don’t think timeScale is really designed for game pause/resume, it works much better when you apply it to slow motion effect. So in short, if you use timeScale = 0.0 to pause a game then operate the pause menu, don’t use the function above directly, the pause menu would never show up because it is paused as well.
My solution is writing a realtime yield function, realtime Animation Component and use them for pause scene, I will show these in my next post.
I hope Unity3D could add affectByTimeScale boolean member in Component, and yield WaitForSecondsRealtime() function, that would save our ass and is more feasible (I’m doing this in exsdk, a C and Lua game engine inspired by Unity3D).
We Unity users all learned from manual that Instantiate() is extremely powerful for creating GameObject from prefab or clone an exists GameObject. But what is not written in the manual is: for gameplay that requires a lot of object spawning, Instantiate() could cause performance issue if you instantiate too many objects in the same time, especially for iOS platform.
I learned it from using Instantiate() for a bullet hell game – to spawn bullets. But the frame rate went unstable and some times dropped down heavily. I don’t know for sure what causes the issue (garbage collecting could be involved in this case), but I avoid too many Instantiate() calls by using a GameObject-Pool – a list of pre-instantiated and recycle-able GameObjects. This gave us a nice performance boost for our game.
I was confused by these two functions when using Unity3D for the first time. I don’t quite understand what kind of initialize code should put in Awake() or Start(). To understand this, I wrote the example below:
player.cs:
private Transform handAnchor = null; private bool readyToGo = false; void Awake () { handAnchor = transform.Find("hand_anchor"); } void GetWeapon ( GameObject go ) { go.transform.parent = handAnchor; } void Start () { readyToGo = true; }
other.cs:
... GameObject go = new GameObject("player"); go.AddComponent(); // Awake invoke right after this! go.SendMessage( "GetWeapon", weapon_base.spawn("sword") ); ...
As we can see, in other.js we create the GameObject “player” and add the player Component to it. The player component’s Awake is Invoked before SendMessage is executed. So that we can guarantee the handAnchor is assigned before GetWeapon() was called. Start() would be call in the next frame after the scripts in other.js.
In my opinion, object reference stuff should be written in Awake(), and use Start() for stuff like AI character entering default state.
Unity3D allows us expose built-in array to Inspector, such as float[], GameObject[]. It also supports some generic classes, such as List. So you may think Dictionary can be exposed to Inspector too. Unfortunately, Unity3D says NO in its document. Keep this in mind.
If you want to let your designer edit a map-like structure, I suggest use built-in array to store Key-Value pairs, this allow user to add/remove an Key-Value pair in the Inspector. Then load the array and add them one by one to your Dictionary in Awake().
You may reference other GameObject in your script. First you write:
GameObject otherGo = null;
then you drag the GameObject to the Other Go property in Inspector.
What if you just want to get the “foobar” Component in this GameObject. Of course you can write this to continue your script:
otherGo.GetComponent();
This is inefficient since the GetComponent.() function travels the component list in the GameObject and compare if the one is type of T or its base class.
You could get the foobar in Awake() to enhance the performance. But why not just declare the public variable as type foobar instead of GameObject?
foobar otherFoobar = null;
By using the above code, you just need to drag the same GameObject to the Inspector, Unity3D will help you find the Component in type of foobar, and assign it to otherFoobar. A much better workflow!
This is a JavaScript(UnityScript) only feature. We know that Unity3D support C#, JavaScript and Boo as its script language. The C# is a strong type language. JavaScript on the other hand can be less strict in declaring a variable.
Unity3D requires you to use strong type declaration for iOS platform development. That means you have to use the additional syntax Unity3D provides you when writing Javascript (I prefer call this UnityScript). You must write:
var foobar:String = "Hello";
instead of
var foobar = "Hello";
However it’s really difficult to notice your syntax error until build your project for the iOS platform. Luckily Unity3D provides you the macro #pragma strict to force the compiler using strong type Javascript. Just put the macro at the top of your script and you will receive the error message right away in your editor console.
Well, those are the things I found in Unity3D programming that not well documented. I’m sure there are other dark corner in Unity3D that you may fall into. Please give us feedback or paste a link to your article if there are other stuff that not covered. We can all help to improve the programming experience for Unity community!