Unity是一个非常强大的游戏开发工具,原因有很多,其中之一是它的组件导向式的平台设计得非常清楚、简洁。例如,很容易在一两天内把一些代码拼凑在一起做简单可用的原型。经过一段时间的学习,我发现有些东西用Unity做尤其管用。
1、把你的代码当作公司的长期资产
一位了不起的程序员曾经告诉我,当你写代码时,你应该考虑到它的价值不只体现在它作为你从事的项目的资产,它也是你的公司的资产。
记住:如果你准备花时间写代码,你也应该考虑一下如何让这些代码给你的公司和制作过程带来附加值。
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游戏中的任何场景中,不需要设置或在检查器中交接。
结论
为了便于组织,制作模块化的东西并不好,但时间久了就能看出价值了。我敢说还有其他设计模式也有助于这个过程。我希望你们能在本文中看出一些东西,但愿我的解释不太令人困惑。
更多unity2018的功能介绍请到paws3d学习中心查找。链接https://www.paws3d.com/learn/,也可以加入unity学习讨论群935714213
近期更有资深开发人士直播分享unity开发经验,详情请进入官网或加入QQ群了解