Unity3D技巧在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信

作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明。如果你喜欢这篇文章,请点【推荐】。谢谢!

QQ20140529123319_thumb1

引子

在前面两篇文章:

【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. }

在游戏中的应用

我们通过一个小鸟撞倒管子来作为事例说明如何进行通信:

bird1

在这个情景下,我们首先为小鸟设定两个事件(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(00);
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(00);
22. }
23.  
24. }
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值