游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

Unity开发者总结的5点工作经验

发布时间:2013-09-20 08:14:52 Tags:,,,

作者:John Warner

Unity是一个非常强大的游戏开发工具,原因有很多,其中之一是它的组件导向式的平台设计得非常清楚、简洁。例如,很容易在一两天内把一些代码拼凑在一起做简单可用的原型。然而,除了它的多功能以外,经过几年的实践,我发现有些东西用Unity做尤其管用。

对于我的新游戏《The Fall》,我做了多次实践。结果是,开发过程顺畅、迅速、容易,漏洞比我上一款游戏少了许多。为了庆祝《The Fall》的诞生,和为它的Kickstarter众筹造势,我写这篇文章,分享一些我最喜欢的设计方法,也许在你的游戏也能派上用场。

1、把你的代码当作公司的长期资产

一位了不起的程序员曾经告诉我,当你写代码时,你应该考虑到它的价值不只体现在它作为你从事的项目的资产,它也是你的公司的资产。例如,在《The Fall》中,有一个可能显示两名玩家角色之间的交谈的对话系统:

Evaluator_Dialogue(from gamasutra)

Evaluator_Dialogue(from gamasutra)

这个对话系统是完全独立的,几乎不需要在Unity设置。我可以毫不费力地就把这个对话系统放到我以后做的任何一款游戏中。简单地调整它的OnGUI()函数就能给理论上适用于大部分游戏的对话系统换一个外观,真的是随取随用。不用说,这节省了大量时间和资金,提高了工作效率。

记住:如果你准备花时间写代码,你也应该考虑一下如何让这些代码给你的公司和制作过程带来附加值。

2、使用全局类实例变量

这么做帮了我大忙,特别是在保持组件分离和整洁方面。主要思路是,给每一个脚本设置一个指向这个脚本的某个实例的全局变量,这样你就可以随时访问那个实例,而不必在检查器中一行一行搜索脚本。以下是它的运作方式:

在我的主要的DialogueSystem.cs脚本里,我把变量放在顶部,与它包含的类有相同的类型,如下:

public static DialogueSystem instance;
or in JavaScript:
static var instance : DialogueSystem;

然后,在这个脚本的Awake()函数,你只要写:

instance = this;

然后简单地把这个脚本依附到你的场景中的任何对象上,当Unity加载那个场景时,那个脚本实验就会给自己设置DialogueSystem.instance。这为什么有用?它意味着如果我想访问我的对话系统,我只需要写下:

DialogueSystem.instance.SomePublicFunctionName();

Unity就知道我想调用场景中的DialogueSystem的那个实例的功能。

注意:这种做法要求在给定场景中只有一个脚本实例。因此,这对于大的组件非常管用,如对话系统,但我想它对于要在场景中多次出现的对象可能没什么用,或者会出问题。如是要你对这个方法更加安全的执行办法有兴趣,那么你就自己Google一下吧。本文说得比较简单了。

3、使用callback(Delegate或Monobheaviour.SendMessage())

如果你还是新手,这对你来说可能有点复杂了。

假设我们有一个NIS (non-interactive sequence),我们希望玩家角色走几步,停下,与NPC对话,然后当对话结束控制权回到玩家手中。因为本文的第二点,我们知道可以简单地从NIS脚本中调用下面的函数,以激活这个对话系统:

DialogueSystem.instance.SomeFunctionToActivateDialogue()

但我们怎么知道对话什么时候结束,NIS脚本可以把控制权还给玩家?我们可以使用callback。当我们第一次调用DialogueSystem.instance.Whatever(),我们可以传一些参数给那个函数,当它结束调用通知调用它的脚本时就可以使用的参数。做法有几种。我更倾向于使用C#的Delegate,因为它更干净,但如果你使用的是Javascript,那就不要选它了。

Delegates (C#)

把它当作指向一个函数的变量。使用Delegate,你可以把函数A作为参数传给函数B,这样当函数B完成时就可以调用函数A了。在这里我不想详细地说怎么做Delegate,但过程是相当简单的。

如果有读者要求,我很乐意写一篇更详细的教程。

MonoBehaviour.SendMessage(string)

Unity提供了一些使用SendMessage函数的callback函数,前者可以通过函数的名称调用脚本中的函数。例如,假设我们有一个名为“Foo”的脚本,它有一个叫作“FooFunction”的函数。在另一个脚本里,我们需要指向我们的“Foo”脚本的实例的变量,假设这个变量是“ourFooVariable”。那么我们可以调用:

ourFooVariable.SendMessage(“FooFunction”);

我们可以给callback使用这个,因为我们可以传脚本的实例和函数名称给另一个函数。例如,在Javascript中:

Script A:
———–
function Start(){
ScriptB.MyFunction(this, “MyCallback”); // ‘this’ means this instane of Script A
}
function MyCallback(){
//This will get fired after script B is finished.
}
————
Script B:
————
function MyFunction(callback : MonoBehaviour, cbName : String){
//do some stuff
//…..
//call back to script A:
callback.SendMessage(cbName);
}
———–

使用callback连同全局类实例变量,可以帮助你制作或多或少是独立的组件。在《The Fall》,当玩家在NIS中,对话系统必须触发,NIS只要调用对话系统然后发给它callback,这样对话系统就运作了。当它结束时,对话在原来的NIS中运行callback函数,然后NIS继续完成它的事。一点都不会乱掉。不需要在检查器中把这些系统整理在一起。不需要NIS编码器却调用对话系统。

4、使脚本失效和启用

我们再看看刚才那个对话系统的案例。在《The Fall》中,当对话系统被启用时,它会一直在图形用户界面(GUI)上绘制。我没有在脚本中设置检查,看它是否正在显示对话。这样做管用是因为对话系统能够自行启动和失效,还因为第2点和第3点。

当我激活我的对话系统中的一个对话时,第一条代码就是让对话系统启动它自己:

this.enabled = true;

当对话完成,且callback发送后,最后一行代码是使它自己失效:

this.enabled = false;

这样做,对话系统不会占用太多资源,只在必要时才生效和。

5、避免处处使用Update——使用coroutine(协同程序)

unity开发的一个非常普遍的设计方法是,当只需要运行一次时,把简单的代码放在每一帧都运行的函数中。例如,有一个澈地的淡入和淡出法,在每一帧都检查一次看是否需要黑掉屏幕,然后在0-1之间的数字移动。它在每一帧中都这么做,总是,这完全不必考虑。

有许多时候,当你想让一个脚本等待一段比较长的时间,然后再根据指令做点什么。我发现,这时候最好使用Coroutine。

基本上,使用Coroutine,你可以让unity隔着几帧做某事,然后完成后退出。不需要淡入和淡去脚本总是更新,你可以创建一个Coroutine来淡入或淡出屏幕,当完成时就停止,所以Update不会一直进行。

在JavaScript中,简单的Coroutine如下:

var fadeAlpha : float = 0;
function FadeIn(seconds : float){
while(fadeAlpha != 1){
fadeAlpha = Mathf.MoveTowards(fadeAlpha, 1, Time.deltaTime*(1/seconds));
yield;
}
}

如果这个函数运行起来,它会把fadeAlpha的值变成1到你告诉它的秒数。然后你可以把那个fadeAlpha以类似的方式运用到脚本中,在OnGUI函数中。

整合

我们使用所有以上想法考虑一下FadeInOut脚本。我们创建一个FadeInOut脚本,然后只放一个实例到场景中,并且禁用它。确保这个脚本有一个静态变量叫作实例,当这个脚本运行它的Awake()函数,它会自己设置实例变量。

下一步,我们在这个脚本中创建几个类似于上述函数的函数,在FadeIn()的第一行启用那个脚本,在FadeOut()的最后一行禁用那个脚本。

如果我们想,我们可以执行callback系统,但这个脚本可能太简单了,用不着执行callback系统。

然后,如果我们还想让游戏屏幕淡出,我们只要调用FadeInOut.instance.FadeOut(1),游戏就会在一秒后淡出。当我们想让它再次淡入,我们可以调用FadeInOut.instance.FadeIn(1),游戏就会在一秒后淡入。

这样,我们就制作好一个简单的FadeInOut脚本,它是完全独立运作的,不会浪费资源,可以放在任何unity游戏中的任何场景中,不需要设置或在检查器中交接。

结论

为了便于组织,制作模块化的东西并不好,但时间久了就能看出价值了。我敢说还有其他设计模式也有助于这个过程。我希望你们能在本文中看出一些东西,但愿我的解释不太令人困惑。

别忘了支持一下我的《The Fall》众筹!(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

The top 5 things I’ve learned as a Unity developer

by John Warner

Unity is a fantastic game development platform for many reasons, one of which being the clean, accessible way its component-oriented platform is designed.  For example, it’s very easy to slam together some code and have a simple working prototype running in a day or two. Despite its versatility however, I’ve found over the years that there’s a few practices that work particularly well with Unity.

With my new game, The Fall, I’ve taken many of these practices and combined them. The result has been a very smooth development process that has been quick and easy to work in and has created relatively fewer bugs that my last games.  To help celebrate my announcement of The Fall and help plug its new Kickstarter campaign, I’m writing this article to share some of my favorite design practices that might help you, with your game.

If you get some value out of this article, please check out The Fall on Kickstarter!

1: Think of your code as a long term company asset

A great programmer once told me that when you write code, you should consider not just its value as an asset to the project you’re working on, but as an asset to your business. For example, in The Fall, I have a dialogue system that can display a conversation between two players with avatars:

This dialogue system is written in a way that’s completely independent and requires very little set-up in Unity. I can, with very little work, take this dialogue system and plug it into any future game that I want to make. Simple adjustments to it’s OnGUI() function could re-skin what is essentially a perfectly functional dialogue system that could theoretically work in most cases, right out of the box. Needless to say this saves a lot of time and money, and the increased speed and ease also aides the creative process as well.

Remember: If you’re going to invest the time to write code, you should be adding value to your business and creative process as well!

2: Use global class instance variables

This design pattern has helped me the most in keeping my components separate and clean.  The idea here is for each or your major scripts, you can set a global variable that points to a specific instance of that script, so that you can access that instance anywhere without having to cross-wire scripts in the inspector.  Here’s how it works, more or less:

Inside my main DialogueSystem.cs script, I have a variable at the top, that has the same type of it’s containing class, written as follows:

public static DialogueSystem instance;

or in JavaScript:

static var instance : DialogueSystem;

Then, in the script’s Awake() function, you simply write:
instance = this;

You then simply attach this script to any object in your scene, and when Unity loads that scene, that particular script instance will set DialogueSystem.instance to it’s self. Why is this helpful? Well, it means that if I want to access my dialogue system, from anywhere in my game, all I have to do is type:

DialogueSystem.instance.SomePublicFunctionName();

And Unity will know that I want to call a function on the particular instance of the DialogueSystem script in my scene.

Important note — This design pattern intends that you only have one script instance in a given scene. Therefore, this works very well for large components, such as a dialogue system, but I would imagine it would be useless or problematic to use on objects that could be instanced multiple times in a scene. If you’re interested in a more safe implementation of this pattern, you can Google “Singleton” – the implementation in this article is more simple, for accessibility’s sake.

3: Use Callbacks (delegates or Monobheaviour.SendMessage())

This one is a little more complicated if you’re a beginner. Consider this:

Lets say we have an NIS (non-interactive sequence) where we want the player’s character to walk a few steps, stop, enter dialogue with an NPC, and then return control to the player once the dialogue has ended.  Well, because of point #2 in this article, we know that we can simply activate the Dialogue System from an NIS script by calling

DialogueSystem.instance.SomeFunctionToActivateDialogue()

But how do we know when the dialogue has finished, so that the NIS script can return control to the player, or do something else that we want? Well, we can use a callback. When we first call DialogueSystem.instance.Whatever(), we can pass that function some parameters that it can use when it’s finished to notify the script that called it in the first place. There are a few ways of doing this. I very much prefer using Delegates in C#, because they’re a little cleaner, but if you use Javascript, they’re not an option.

Delegates (C#)

Think of delegates as variables that basically point to a function. Using delegates, you can pass function A to function B as a parameter, so that function B can call function A when function B completes. I won’t get into the details of creating delegates here, but it’s a fairly simple process and this is a good place to get started learning:

http://msdn.microsoft.com/en-us/library/ms173171(v=vs.80).aspx

If I get some requests, I’d be happy to write a more detailed tutorial.

MonoBehaviour.SendMessage(string)

Unity provides some callback functionality using its SendMessage function, that can basically call a function in a script, with a string that is the function’s name. For example, lets say we have a script named “Foo” with a function named “FooFunction”. Inside another script, we’d need a variable that points to an instance of our “Foo” script, lets say we call that variable “ourFooVariable”. We could then call

ourFooVariable.SendMessage(“FooFunction”);

We can use this for callbacks because we can pass an instance of a script and a function name to another function. For example, in Javascript:

Script A:
———–
function Start(){
ScriptB.MyFunction(this, “MyCallback”); // ‘this’ means this instane of Script A
}
function MyCallback(){
//This will get fired after script B is finished.
}
————
Script B:
————
function MyFunction(callback : MonoBehaviour, cbName : String){
//do some stuff
//…..
//call back to script A:
callback.SendMessage(cbName);
}
———–

Using callbacks in conjunction with global class instance variables can help you create components that are more or less independent. In The Fall, when the player is in an NIS and the dialogue system needs to be triggered, the NIS simply calls the dialogue system and sends it a callback, and then does nothing as the dialogue system works. When it’s finished, the dialogue system runs the callback function in the original NIS and the NIS then goes about it’s business. Nothing needs to be cross-wired. No work in the inspector is required to get these systems to play nicely together. No NIS scripters need to mess with the dialogue system to get it to work nicely with their code. It just works.

4. Get scripts to disable and enable themselves.

Lets look back again to our dialogue system example. In The Fall, when the dialogue system is enabled, it draws to the GUI, all the time. I have no checks in the script to see weather or not it should be displaying the dialogue, it simply always does it. This works because the dialogue system is able to enable and disable it’s self, and this works well because of points #2 and 3.

When I activate a dialogue in my dialogue system, the first line of code is for the dialogue system to enable it’s self:

this.enabled = true;

When the dialogue has completed and the callback has been sent, the last line of code disables it’s self:

this.enabled = false;

This way the dialogue system doesn’t take much in the way of resources and simply sits there and shuts its mouth, so to speak, until it’s needed.

5. Try to get away from using Update for everything — try using coroutines.

A very common design pattern with unity development is to put simple code in a function that runs every frame, when it really only needs to be run once in a while.  For example, there’s a popular Fade-In-and-Out solution that checks, every frame, weather or not it should be making the screen black, and then moves a number between 0 and 1. It does this every single frame, always, even though the vast, vast, vast, vast majority of the time, this doesn’t need to be considered at all.

There are lots of times when you want a script to wait for a long period of time, and then do something gradually on command. I have found that Coroutines are often the best way of making this happen.

Here is a simple primer on Coroutines in unity

Basically, with Coroutines, you can get unity do something over several frames, and exit when it’s done. Instead of having a fade-in-and-out script that’s always updating, you could create a coroutine to fade the screen in or out, which would simply stop on completion, so Update isn’t running constantly like the faucet in the kid’s bathroom.

In JavaScript, a simple coroutine might look like this:

var fadeAlpha : float = 0;
function FadeIn(seconds : float){
while(fadeAlpha != 1){
fadeAlpha = Mathf.MoveTowards(fadeAlpha, 1, Time.deltaTime*(1/seconds));
yield;
}
}

If this function gets run, it would change the value of fadeAlpha to 1 over how ever many seconds you tell it. You would then use that fadeAlpha value in a similar fashion to the script in the above link, in an OnGUI function.

Putting it all together

Lets consider a FadeInOut script, using all of these ideas. We create a FadeInOut script and plunk only one instance into a scene and disable it. Make sure the script has a static variable called instance, and when the script runs it’s Awake() function, it sets the instance variable to it’s self.

Next, we create a few functions in the script similar to the one above, only that enable the script in the first line of FadeIn(), and disables the script in the last line of FadeOut();

If we want, we can implement a callback system, but this script is probably too simple to warrant it.

Then, if we ever want the game screen to fade out, we just call FadeInOut.instance.FadeOut(1); and the game will fade out over one second. When we want it to fade in again, We can call FadeInOut.instance.FadeIn(1) and the game will fade back in over 1 second.

This way, we’ve created a simple FadeInOut script, that works completely independently, doesn’t waste resources, and can be plunked into any scene of any unity game and work with no setup or cross-wiring in the inspector what so ever.

Conclusion

Creating modular bits isn’t just good for the sake of organization, but it also creates value for yourself over time. I’m sure there are lots of other design patterns to aide this process.  I hope that you got some value out of this and that my descriptions weren’t too confusing. If you liked what you read, or have a question, please leave a comment and I’ll do my best to respond. (source:gamasutra)


上一篇:

下一篇: