作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明。如果你喜欢这篇文章,请点【推荐】。谢谢!
引子
在前面两篇文章:
【Unity3D基础教程】给初学者看的Unity教程(四):通过制作Flappy Bird了解Native 2D中的RigidBody2D和Collider2D 【Unity3D基础教程】给初学者看的Unity教程(三):通过制作Flappy Bird了解Native 2D中的Sprite,Animation我们了解了2D中的Sprite,Animation,RigidBody和Collider,在继续开发游戏的过程中,我们会遇到这样的问题,如何处理GameObject之间的相互调用,比如说在FlappyBird中我们在小鸟撞倒管子的时候,要把这个消息通知给许多GameObject,管子接到这个消息之后需要停止运动,UI接到这个消息要弹出GameOver的字样。接下来,我来讲一下如何合理地解决这个问题。相关源码参考:Flappy Bird的源码。
为什么要进行GameObject之间的通讯?
在游戏开发中我们经常遇到这样的问题,在游戏中发生了一个事件(event),我们如何把这个时间通知给其他GameObject:比如游戏中发生了爆炸,我们需要在一定范围内的GameObject都感知到这一事件。有的时候,我们希望摄像机以外的物体不要接到我们这一事件的通知。游戏中丰富多彩的世界正是由通信机制构成的。
有一种方法是在发生事件的GameObject的Start方法里面把对该事件感兴趣的所有GameObject当作成员变量保存在脚本组件里,那么我们把发生事件的object当作Subject,把对该事件感兴趣的object当作Observer。
将Observer作为成员变量存储在Subject中有一下缺点:
难以变更,一旦要新增一个Observer就需要更改Subject中的代码 如果Observer被销毁了,无法从Subject中移除掉这个成员变量,会发生NullReferernceException。 在发生事件时,一个个去invoke不同Observer中的相应handle方法的代码变得冗长繁杂。还好的是,我们可以通过引入观察者模式来解决这个问题,更好的是,C#内置有一个非常棒的事件/委托机制,能让我们非常方便地进行观察者模式的构建。
如果你还不了解观察者模式和事件/委托机制,可以参考以下几篇文章:
C# 中的委托和事件 -- 委托/事件入门 C#综合揭秘——深入分析委托与事件 -- 委托/事件深入 你可能不知道的陷阱:C#委托和事件的困惑 -- 委托/事件陷阱C#中标准的委托类型
我们在构建事件/委托机制的时候,首先要定义委托类型,参考在Cocos2d-x中的CCCallback,我先定义了以下三种类型的委托:
1.
// 该委托不传任何参数
2.
public
delegate
void
CallFunc();
3.
// 该委托会传入发生事件的GameObject,即sender
4.
public
delegate
void
CallFuncO(GameObject sender);
5.
// 该委托会传入发生事件的GameObject,即sender。和一个变长参数列表
6.
public
delegate
void
CallFuncOP(GameObject sender, EventArgs args);
但是我发现C#本身已经提供了一种比较好的委托类型:EventHandler,所以我就把游戏中的委托都替换成了这种委托。
1.
public
delegate
void
EventHandler(object sender, EventArgs e);
另一种更好的委托方式是使用泛型参数的委托类型:EventHandler<TEventArgs>,其签名如下:
1.
public
delegate
void
EventHandler<TEventArgs>(
2.
Object sender,
3.
TEventArgs e
4.
)
采用 EventHandler 模式发布事件
如果这个事件不产生任何额外参数(即除了事件的发送者之外),则在在调用时,向EventHandler的第二个参数传一个EventArgs.Empty即可。
如果产生额外参数,第二个参数是从 EventArgs 派生的类型并提供所有字段或属性需要保存事件数据。使用 EventHandler<TEventArgs> 的优点在于,如果事件生成事件数据,则无需编写自己的自定义委托代码。
下面我们举一个例子来证实EventHandler的用法:
01.
using System;
02.
03.
namespace ConsoleApplication1
04.
{
05.
class
Program
06.
{
07.
static
void
Main(string[] args)
08.
{
09.
Counter c =
new
Counter(
new
Random().Next(
10
));
10.
//向该事件添加了一个委托函数
11.
c.ThresholdReached += c_ThresholdReached;
12.
13.
Console.WriteLine(
"press 'a' key to increase total"
);
14.
while
(Console.ReadKey(
true
).KeyChar ==
'a'
)
15.
{
16.
Console.WriteLine(
"adding one"
);
17.
c.Add(
1
);
18.
}
19.
}
20.
21.
static
void
c_ThresholdReached(object sender, ThresholdReachedEventArgs e)
22.
{
23.
Console.WriteLine(
"The threshold of {0} was reached at {1}."
, e.Threshold, e.TimeReached);
24.
Environment.Exit(
0
);
25.
}
26.
}
27.
28.
class
Counter
29.
{
30.
private
int
threshold;
31.
private
int
total;
32.
33.
public
Counter(
int
passedThreshold)
34.
{
35.
threshold = passedThreshold;
36.
}
37.
38.
public
void
Add(
int
x)
39.
{
40.
total += x;
41.
if
(total >= threshold)
42.
{
43.
ThresholdReachedEventArgs args =
new
ThresholdReachedEventArgs();
44.
args.Threshold = threshold;
45.
args.TimeReached = DateTime.Now;
46.
OnThresholdReached(args);
47.
}
48.
}
49.
50.
protected
virtual
void
OnThresholdReached(ThresholdReachedEventArgs e)
51.
{
52.
EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
53.
if
(handler !=
null
)
54.
{
55.
handler(
this
, e);
56.
}
57.
}
58.
//添加了一个带泛型参数的事件
59.
public
event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
60.
}
61.
62.
public
class
ThresholdReachedEventArgs : EventArgs
63.
{
64.
public
int
Threshold { get; set; }
65.
public
DateTime TimeReached { get; set; }
66.
}
67.
}
在游戏中的应用
我们通过一个小鸟撞倒管子来作为事例说明如何进行通信:
在这个情景下,我们首先为小鸟设定两个事件(event),分别是分数加一(ScoreAdd)和小鸟碰到管子游戏结束(GameOver) 如下:
01.
using UnityEngine;
02.
using System.Collections;
03.
using System;
04.
05.
public
class
BirdController : MonoBehaviour {
06.
07.
public
event EventHandler GameOver;
08.
public
event EventHandler ScoreAdd;
09.
10.
//当离开Empty Trigger的时候,分发ScoreAdd事件
11.
void
OnTriggerExit2D(Collider2D col) {
12.
if
(col.gameObject.name.Equals(
"empty"
)) {
13.
if
(ScoreAdd !=
null
)
14.
ScoreAdd(
this
, EventArgs.Empty);
15.
}
16.
}
17.
18.
//当开始碰撞的时候,分发GameOver事件
19.
void
OnCollisionEnter2D(Collision2D col)
20.
{
21.
rigidbody2D.velocity =
new
Vector2(
0
,
0
);
22.
if
(GameOver !=
null
)
23.
GameOver(
this
, EventArgs.Empty);
24.
this
.enabled =
false
;
25.
}
26.
27.
}
然后在对这个事件感兴趣的GameObject会通过相应的Handler对该事件进行监听,这样就可以进行一对多的GameObject间的通信了。
01.
using UnityEngine;
02.
using System.Collections;
03.
using System;
04.
05.
06.
public
class
TubeController : MonoBehaviour {
07.
08.
09.
// Use this for initialization
10.
void
Start () {
11.
GameObject.Find(
"bird"
).GetComponent<BirdController>().GameOver += OnGameOver;
12.
}
13.
14.
void
OnDestroy() {
15.
if
( GameObject.Find(
"bird"
) )
16.
GameObject.Find(
"bird"
).GetComponent<BirdController>().GameOver -= OnGameOver;
17.
}
18.
19.
void
OnGameOver(object sender, EventArgs e)
20.
{
21.
rigidbody2D.velocity =
new
Vector2(
0
,
0
);
22.
}
23.
24.
}