unity3d中委托事件委托怎么保证不冲突

2144人阅读
Unity3D(22)
Here I demonstrate how to create delegates, events and singletons to work in conjunction. This tutorial is written for Unity3D, However, similar code can be used for any C# or .NET application.
在这里我将展示如何结合使用委托、事件以及单例。这个教程针对Unity3D所写,然而,相似的代码可以使用到任何C#或.Net程序中。
Why would I want to know this? As a young self-taught programmer, I often found myself writing tons and tons of boolean statements to determine if some event or action has happened. I listen to those events through Coroutines and other methods to return values. If you find yourself doing this as well, STOP IT!
为什么我想要知道这个? 作为一个自学编程的人,我发现自己在代码中使用了大量的布尔值来决定一些事件或者行为的发生。我监听协同程序(Coroutines)和其他方法来得到返回值。如果你发现自己也在做同样的事情,停止它!
Welcome to Events…
欢迎来到事件…
Intro Lately, I’ve been trying to improve on my C# programming skills and found myself lacking knowledge of the basic understand for Events. So, while looking through numerous tutorials on MSDN and other blogs, I found most tutorials to be over complicated and lush with convoluted code not pertinent to the core concept. I don’t want this to happen to you!
最近我在试图提高自己的C#编程技能,发现自己对事件缺乏基本的了解。所以我阅读了很多MSDN的教程和其他博客,但我发现大多数都写的都十分繁琐并且包含了很多与核心内容不相干的代码。我不希望这些事也在你身上发生。
With that said I will try to explain the basics of Events and how they are used in a project…
正如所说的,我将试图解释事件的基础以及如何将它们应用到项目中。
Singleton? If you don’t know what a Singleton is, you probably should. A Singleton is a script that cannot be an Instanced or ‘Duplicated’. It is, well… Single.
或许你不知道单例。单例是一个不能被实例化或者“复制”的脚本,也就是说它是“唯一的”。
I recommend using Singletons for things that do not need to be copied multiple times during a game. Such as an Inventory System. Typically, the player only needs one Inventory, so we don’t want to call Instances of it, we only want one. And when we call it, we want to make sure it doesn’t get duplicated.
我建议在那些在游戏中不需要被复制很多次的东西上使用单例。例如目录系统。通常,角色只需要一个目录,我们不想去调用它的实例。当调用它时,我们希望确保它没有被多次复制。
There are many ways to create Singletons, but this method is often used because it’s simple…
有很多方式创造单例,下面是一种常用的方法,因为它比较简单…
csharp code:
public class Clicker : MonoBehaviour
private Clicker() {}
public static Clicker Instance
if (instance ==
instance = GameObject.FindObjectOfType(typeof(Clicker)) as
public void
DoSomething() { }
Here, I have a class ‘Clicker’ that is attached to my Camera. This class handles all clicks that I send into 3D Space with a Raycast.
这里,我创建了一个名为‘Clicker’的类,并将它链接到摄像机。这个类用来控制三维场景中所有基于射线的点击。
To access my ‘DoSomething’ method from another script, I simply call…
通过在其他脚本中访问‘DoSomething’方法进行简单调用,我只需要简单的调用…
csharp code: Clicker.Instance.DoSomething();
This eliminates the need to use a bunch of Static Method and Variable calls, plus gives us one instance only!
这样就排除了需要创建大量静态方法和变量的情况,好处是只给了我们一个实例。
Delegate and Event? A Delegate can be thought of as a reference pointer to an object/method. When it gets called, it notifies all methods that reference the delegate.
委托和事件? 委托可以想成是一个针对对象和方法的引用指针。当其被调用时,它会通知所有引用委托的方法。
So, first things first…
所以,第一件事首先…
Define a Delegate and the method that gets called when it fires…
定义一个委托和调用时的相应方法…
csharp code:
public class Clicker : MonoBehaviour
public delegate void OnClickEvent(GameObject g);
public event OnClickEvent OnC
The delgate called ‘OnClickEvent’ passes a ‘GameObject’ that we can use to define what game object it came from. Then, we defined an ‘event’ OnClick that gets called when the delegate is called.
例子中创建了一个名为‘OnClickEvent’的委托,它包含了一个‘GameObject’类型的参数来获取对象信息。之后,我们定义一个名为‘OnClick‘的事件,当委托被调用时它将得到消息。
Now, in the same script, we need to call the delegate and pass it our GameObject. I’ve done this through a Raycast…
现在,在同一个脚本里,我们需要调用委托来传递给GameObject。我通过使用射线完成了这一步。
csharp code:
public class Clicker : MonoBehaviour
public delegate void OnClickEvent(GameObject g);
public event OnClickEvent OnC
void Update ()
Ray ray = Camera.mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, 100))
if (Input.GetMouseButtonUp(0))
OnClick(hit.transform.gameObject);
As you can see, if the Ray has contact an Object in the scene and we Left-Mouse-Click, we call the event and pass the GameObject.
正如你看到的,如果摄像在场景中接触到了物体,点击鼠标左键,我们调用了事件并且传递了GameObject。
The last thing we must do is reference the delegate from our other scripts that are listening to the call. For this I’ve created a class called ‘GoldPile’.
最后要做的是在其他脚本中引用委托来监听调用。所以我创建了一个名为’GoldPile‘的类。
csharp code:
public class GoldPile : MonoBehaviour
void Awake ()
Clicker.Instance.OnClick += OnC
void OnClick(GameObject g)
if (g == gameObject)
Debug.Log("Hide and give us money!");
gameObject.active = false;
In our Awake() method, we’ve defined our listening Event and assigned a local method that gets called ‘OnClick’. ‘OnClick’ does not need to be the same as our delegate method, but it can be.
在Awake()方法中,我们定义了正在监听的事件并且分配了一个名为‘OnClick’的本地方法。‘OnClick’不需要和委托方法(命名)一致,但实际上可以。
Note: In our previous post we added a Singleton to our Clicker class. This allows us to use Clicker.Instance
注意:之前,我们已经在Clicker类中加入了单例。这允许我们使用Clicker.Instance。
As you can see, we’ve also created the OnClick() method that passes our GameObject we clicked on.
正如你看到的,我们已经创建了OnClick()方法并将点击到的对象用作了参数。
Note: You must use if (g == gameObject), otherwise it will hide other instances of that method in the scene as well… This is why we pass the GameObject for reference!
注意:你必须使用 if (g == gameObject),否则它也将隐藏场景中的其他实例物体。这既是为什么我们需要为引用传递GameObject参数。
Now you are free to add this method to any other script in your game if needed. Don’t forget to define the method and delegate in your Awake().
现在你可以自由地添加这个方法到你游戏中的其他脚本。不要忘记在Awake()中定义方法和委托。
You can download the complete project for your reference HERE. Enjoy! :)
你可以在下载完成的工程文件。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:26566次
排名:千里之外
原创:30篇
转载:32篇
(4)(1)(1)(1)(1)(3)(24)(25)(2)
(window.slotbydup = window.slotbydup || []).push({
id: '4740887',
container: s,
size: '250,250',
display: 'inlay-fix'[求助] 怎么取消一个事件里的全部委托【unity3d吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:77,052贴子:
[求助] 怎么取消一个事件里的全部委托收藏
感觉这段没问题啊Orz执行完以后委托还在,绝望了 T T
登录百度帐号推荐应用求大佬讲解下Unity的委托和事件的用法? - 知乎4被浏览1106分享邀请回答//定义一个委托
public delegate void HelloDelegate();
//实际的方法
public void HelloMethod()
Console.WriteLine("hello, world");
//委托作为参数
public void HelloCallBack(HelloDelegate helloMethod)
//可以添加一些处理过程。
helloMethod();
/*================================================================
最基本的用法。*/
//这里也可以写成HelloDelegate del =new HelloDelegate( HelloMethod);
HelloDelegate del = HelloMethod;
/*=================================================================
这是委托的另一个用法:作为参数(多用于实现CallBack)。*/
HelloCallBack(HelloMethod);
/*=================================================================*/
事件则稍复杂些……不过只是内容有些多而已,并不难。先从“外观”说起。一个标准.net framework事件,形如“EventName(object sender, EventArgs e)”,其中sender是事件的拥有者,e是事件的信息,除了基类EventArgs外,常见的还有MouseEventArgs之类的派生类,当然,一切都是可控的。定义最基本的一个事件,只要使用内置的EventHandler委托类型即可。它就是EventName(object sender, EventArgs e)的形式。//定义一个EventHandler委托
public event EventHandler SayHelloEventHandler;
//一个公开的方法,在实际应用中,他的作用是执行某些代码(并确定条件成立)后触发事件。
public void SayHelloMethod()
Console.WriteLine("hello, world");
SayHelloEvent(new EventArgs());
//这里不要求一定这么写,但是一个“标准”的事件都会这么写以便重载。
protected virtual void SayHelloEvent(EventArgs e)
SayHelloEventHandler?.Invoke(this, e);
/*这是简写,标准的写法是:
EventHandler handler = SayHelloEventH
if (handler!=null)
handler(this, e);
//使用方法:
//绑定事件到方法
SayHelloEventHandler += MySayHelloEventHandler;
//用户处理事件的方法。
private void MySayHelloEventHandler(object sender, EventArgs e)
//进行处理
//注册完毕后,如果执行SayHelloMethod(),那么就会触发SayHelloEvent事件了。
而实际上,EventArgs只是个基类,它没什么用,如果我们要给事件附带的更多的信息(比如MouseEventArgs就附带的鼠标的各种状态),就需要自己写一个EventArgs的派生类——当然这里只写一个更简单但不标准的形式:不要EventArgs参数的事件。注意:EventArgs或许可以无视,但sender最好不要省略,否则在多个事件绑定同一个方法的时候就完蛋了……要实现这一点,就不能直接用预置的EventHandler类型了,而是要自定义一个委托。代码非常简单,就不加注释了……使用方法同上。public delegate void SayHelloDelegate(object sender);
public event SayHelloDelegate SayHelloEventHandler;
public void SayHelloMethod()
Console.WriteLine("hello, world");
SayHelloEvent();
protected virtual void SayHelloEvent()
SayHelloEventHandler?.Invoke(this);
而当需要自定义事件附带消息的时候,最佳的方法是使用 EventHandler&T&泛型。不过我还是不想写EventArgs的派生类,所以就把这参数换成int好了,当然这样也是不标准的.....//可以用任意类型。
public event EventHandler&Int32& SayHelloEventHandler;
public void SayHelloMethod()
Console.WriteLine("hello, world");
SayHelloEvent(233);
protected virtual void SayHelloEvent(Int32 e)
SayHelloEventHandler?.Invoke(this, e);
================================其他:1.虽然我在事件写法举例的时候用了奇怪的e类型甚至省略了,但标准的事件最好还是要用EventArgs或者其派生类的,sender更不能省略。2.手动绑定事件的时候要注意生命周期防止内存泄漏。4添加评论分享收藏感谢收起0添加评论分享收藏感谢收起在Unity3D中基于订阅者模式怎样实现事件机制
今天博主想和大家分享的是在Unity3D中基于订阅者模式实现消息传递机制,我们知道Unity3D中默认提供了一种消息传递机制SendMessage,虽然SendMessage使用起来的确非常简单,可是它的这种简单是建立在付出一定的代价的基础上的。经常有朋友提及不同的模块间如何进行通信的问题,可能答案最终会落到单例模式、委托和事件机制这些关键词上,在这种情况下本文所探讨的内容可能会帮助你找到最终的答案。
从生活中得到的启示
??我们知道通过在Unity3D中通过GetComponent就可以获得某个模块的实例,进而引用这个实例完成相关任务的调用。可是显然这种方法,就像我们随身带着现金去和不同的人进行交易,每次交易的时候都需要我们考虑现金的支入和支出问题,从安全性和耦合度两个方面进行考虑,这种方法在面对复杂的设计的时候,非常容易造成模块间的相互依赖,即会增加不同模块间的耦合度。为了解决这个问题,大家开始考虑单例模式,因为单例模式能够保证在全局内有一个唯一的实例,所以这种方式可以有效地降低模块间的直接引用。单例模式就像是我们在银行内办理了一个唯一的账户,这样我们在交易的时候只需要通过这个账户来进行控制资金的流向就可以了。单例模式确保了各个模块间的独立性,可是单例模式更多的是一种主动行为,即我们在需要的时候主动去调用这个模块,单例模式存在的问题是无法解决被调用方的反馈问题,除非被调用方主动地去调用调用方的模块实例。说到这里我们好像看到了一种新的模式,这就是我们下面要提到的事件机制。
订阅者模式和事件机制
??首先这里要提到一种称为&订阅者模式&的设计模式,这种设计模式在《大话设计模式》这本书中称为&观察者模式&或者&发布-订阅(Publish/Subscribe)模式&,我们这里暂且叫做&订阅者模式&吧!该模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个对象在状态发生变化时会通知所有观察者对象,使它们能够自动更新自己。针对这个模式,我们可以考虑事件机制的实现,事件机制可以理解为在一个事件中心(Subject)保存有对所有事件(Observer)的引用,事件中心负责对这些事件进行分发,这样每个事件就可以通过回调函数的方式进行更新,这样就实现了一个事件机制。下面给出基本的代码实现:
using System.C
using System.Collections.G
using UnityE
namespace UniEventDispatcher
/// 定义事件分发委托
public delegate void OnNotification(Notification notific);
///通知中心
public class NotificationCenter
/// 通知中心单例
private static NotificationCenter instance=
public static NotificationCenter Get()
if(instance == null){
instance = new NotificationCenter();
/// 存储事件的字典
private Dictionary eventListeners
= new Dictionary();
/// 注册事件
///事件Key
///事件监听器
public void AddEventListener(string eventKey,OnNotification eventListener)
if(!eventListeners.ContainsKey(eventKey)){
eventListeners.Add(eventKey,eventListener);
/// 移除事件
///事件Key
public void RemoveEventListener(string eventKey)
if(!eventListeners.ContainsKey(eventKey))
eventListeners[eventKey] =
eventListeners.Remove(eventKey);
/// 分发事件
///事件Key
public void DispatchEvent(string eventKey,Notification notific)
if (!eventListeners.ContainsKey(eventKey))
eventListeners[eventKey](notific);
/// 分发事件
///事件Key
///通知内容
public void DispatchEvent(string eventKey, GameObject sender, object param)
if(!eventListeners.ContainsKey(eventKey))
eventListeners[eventKey](new Notification(sender,param));
/// 分发事件
///事件Key
///通知内容
public void DispatchEvent(string eventKey,object param)
if(!eventListeners.ContainsKey(eventKey))
eventListeners[eventKey](new Notification(param));
/// 是否存在指定事件的监听器
public Boolean HasEventListener(string eventKey)
return eventListeners.ContainsKey(eventKey);
??注意到在这个&通知中心&中,我们首先实现了单例模式,这样我们可以通过Get方法来获取该&通知中心&的唯一实例,其次这里利用一个字典来存储对所有事件的引用,这样保证外部可以通过AddEventListener和RemoveEventListener这两个方法来进行事件的添加和移除,对于添加的事件引用我们可以通过DispatchEvent方法来分发一个事件,事件的回调函数采用委托来实现,注意到这个委托需要一个Notification类型,对该类型简单定义如下:
using UnityE
namespace UniEventDispatcher
public class Notification
/// 通知发送者
public GameO
/// 通知内容
/// 备注:在发送消息时需要装箱、解析消息时需要拆箱
/// 所以这是一个糟糕的设计,需要注意。
/// 构造函数
///通知发送者
///通知内容
public Notification(GameObject sender, object param)
this.sender =
this.param =
/// 构造函数
public Notification(object param)
this.sender =
this.param =
/// 实现ToString方法
public override string ToString()
return string.Format(&sender={0},param={1}&, this.sender, this.param);
??对Notification的定义需要提供发送者和发送内容,这样可以保证所有的通知都按照这样的格式进行定义,如果有Socket开发经验的朋友可能会联想到通讯协议的定义,这里是比较相似啦,哈哈!
使用事件机制的一个示例
??这里以一个简单的示例来验证事件机制的可行性,我们在场景中有一个球体,默认这个球体的颜色为白色,通过调整界面中的RGB数值,可以改变球体的颜色,在这个示例中UI是事件发送者,负责UI中Slider控件的数值发生变化时向球体发送消息,传递的数据类型是Color类型;球体为事件接收者,负责注册事件及接收到消息后的处理。因为代码较为简单,所以这里写在一个脚本中:
using UnityE
using UnityEngine.UI;
using System.C
using UniEventD
public class Example : MonoBehaviour
/// R数值的Slider
private Slider sliderR;
/// G数值的Slider
private Slider sliderG;
/// B数值的Slider
private Slider sliderB;
void Start ()
//在接收者中注册事件及其回调方法
NotificationCenter.Get().AddEventListener(&ChangeColor&, ChangeColor);
//在发送者中分发事件,这里以UI逻辑为例
sliderR = GameObject.Find(&Canvas/SliderR&).GetComponent();
sliderG = GameObject.Find(&Canvas/SliderG&).GetComponent();
sliderB = GameObject.Find(&Canvas/SliderB&).GetComponent();
//注册UI事件
sliderR.onValueChanged.AddListener(OnValueChanged);
sliderG.onValueChanged.AddListener(OnValueChanged);
sliderB.onValueChanged.AddListener(OnValueChanged);
public void OnValueChanged(float value)
//获得RGB数值
float r = sliderR.
float g = sliderG.
float b = sliderB.
//分发事件,注意和接收者协议一致
NotificationCenter.Get().DispatchEvent(&ChangeColor&, new Color(r, g, b));
/// 改变物体材质颜色
public void ChangeColor(Notification notific)
Debug.Log(notific.ToString());
//设置颜色
renderer.material.color = (Color)notific.
该示例运行效果如下:
??虽然目前这个事件机制在实现和使用上没有什么问题,可是从扩展性和可优化性上来考虑,这个设计目前存在以下问题:
* 字符型的键名使用起来方便,可是对通知者和接收者由1个以上的人力来维护的时候双方需要通过沟通来确定键名,可以考虑使用GameObject或者Transform来替代现在的键名设计,可是这种设计带来的新问题是会增加不同模块间的GameObject或者Transform的相互引用。
* 通知者和接收者在传递参数和接受参数的时候需要分别进行装箱和拆箱,所以这并非一个优秀的设计,同时需要双方保证传递的参数类型一致。解决方法是针对不同的类型对通知中心进行派生或者考虑对通知中心提供泛型约束,这样做的目的是使Notification中的通知内容变成具体的类型,这样就可以解决目前需要进行装箱和拆箱而带来的性能问题。}

我要回帖

更多关于 unity3d委托与事件 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信