【unity 代码升华篇】委托进阶、回调函数(三)

11 篇文章 0 订阅
【unity 代码升华篇】委托进阶、回调函数(三) ...
http://www.unitymanual.com/thread-25527-1-1.html

(出处: -游戏开发者社区【游戏蛮牛】unity3d官网)

本来这篇帖子应该昨天完成的,可是 工作上有些事情要处理,然后又开一下午会
所以就没太多时间来写这个,so,推迟到现在,望见谅、望理解。
下面开始委托进阶部分的分享
在此我分3个部分来说明表述
1.带返回值的委托
2.泛型委托
3.委托的异步处理
下面正式进入我们的主题
委托进阶
一、带有返回值的委托
问:委托需要承载哪些信息呢?
通过前面与大家分享的委托帖子中,不难答出,它存储了方法名,还有参数列表(方法签名).
如:
//============================
public delegate void testDelegate(int num);
//============================
其实,仔细看看上面语句,就会发现 委托还同时承载了返回的类型,我把上面语句格式化下,相信大家就会明白了
//=================================
public delegate 返回类型 ProcessDelegate(int num);
//=================================
上面委托定义的蓝色部分是声明委托的关键字红色部分是返回的类型黑色部分为委托的类型名,最后小括号中的就是参数部分啦.
因此,要实现该委托就得满足下面2个条件:
1、方法的返回类型和委托的返回类型必须一致;
2、方法的参数也必须跟委托相同,这里是int类型.
OK,就然我们一起尝试下吧!
代码如下:

using UnityEngine;
using System.Collections;
 
public class babyTest : MonoBehaviour
{
    // 定义具有返回值bool的委托 
    public delegate bool ComparisonEventHandler(int cryid);
 
    public int cryid = 0;
    public GameObject[] objs;
        // Use this for initialization
        void Start () 
    {
        ComparisonEventHandler _Comparison = Comparison01; //new ComparisonEventHandler(new Test().Comparison01);
        _Comparison(cryid);
        }
    /// <summary>
    /// 方法01
    /// </summary>
    /// <param name="cryid"></param>
    /// <returns></returns>
    public bool Comparison01(int cryid)
    {
        //...操作一些东西
        int num = 1;
        if (num == cryid)
        {
            objs[0].SetActive(false);
            objs[1].SetActive(false);
            Debug.Log(string.Format("返回为true,恭喜找到baby哭的原因."));
            return true;
        }
        else
        {
            Debug.Log(string.Format("返回为false,未找到baby哭的原因"));
            return false;
        }
    }
}



运行如下:
当我设置babyid为2的时候,获取返回值为false.
 

当我设置设置baby为1的时候,获取返回值为true.
二、泛型委托
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性。泛型为.NET框架引入了类型参数(type parameters)的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数T,写一个类MyList<T>,客户代码可以这样调用:MyList<int>, MyList<string>或 MyList<MyClass>。这避免了运行时类型转换或装箱操作的代价和风险。 -来至百度百科
泛型的委托,就是参数的类型不确定,例如代码改写为:
<pre>using UnityEngine;
using System.Collections;
 
public class delegateTest : MonoBehaviour
{
    // 定义具有返回值bool的委托 ,参数采用泛型改写
    public delegate bool ComparisonEventHandler<T>(T cryID);
 
    public int cryid = 0;
    public GameObject[] objs;
    // Use this for initialization
    void Start()
    {
        // 给委托类型的变量赋值
        ComparisonEventHandler<int>  _Comparison = Comparison01; 
       bool iscry= _Comparison(cryid);
       //给此委托变量再绑定一个返回bool的方法
        ComparisonEventHandler<bool> _Comparisons = Comparison02;
        _Comparisons(iscry);
    }
    /// <summary>
    /// 方法01
    /// </summary>
    /// <param name="cryid"></param>
    /// <returns></returns>
    public bool Comparison01(int cryid)
    {
        //...操作一些东西
        int num = 1;
        if (num == cryid)
        {
            objs[0].SetActive(false);
            objs[1].SetActive(false);
            Debug.Log(string.Format("返回为true,恭喜找到baby哭的原因."));
            return true;
        }
        else
        {
            Debug.Log(string.Format("返回为false,未找到baby哭的原因"));
            return false;
        }
    }
 
    /// <summary>
    /// 方法02
    /// </summary>
    /// <param name="cryid"></param>
    /// <returns></returns>
    public bool Comparison02(bool iscry)
    {
        //...操作一些东西
        if (iscry)
        {
            Debug.Log(string.Format("baby心情 不错,增加亲密度+60."));
            return true;
        }
        else
        {
            Debug.Log(string.Format("baby心情 很差,降低亲密度-20."));
            return false;
        }
    }
}
 
 
注: 在上面代码中
//==================
ComparisonEventHandler <int>  _Comparison = Comparison01;
ComparisonEventHandler <bool>  _Comparisons = Comparison02;
//==================
红色的 部分, 泛型委托,在给 委托变量绑定方法时,委托 ComparisonEventHandler后 一定要跟一个类型参数,且该参数与将要被绑定方法的参数一样的类型。
如果不写,将报错
 

运行 如下:
 

ok,没问题
使用泛型类型可以最大限度地 重用代码、保护类型的安全以及提高性能
三、方法和委托异步调用
通常情况下,如果需要异步执行一个耗时的操作,我们会新起一个线程,然后让这个线程去执行代码。
但是对于每一个异步调用都通过创建线程来进行操作显然会对性能产生一定的影响,同时操作也相对繁琐一些。
c# 中可以通过委托进行方法的异步调用,就是说客户端在异步调用方法时,本身并不会因为方法的调用而中断,而是从线程池中抓取一个线程去执行该方法,自身线程(主线程)在完成抓取线程这一过程之后,继续执行下面的代码,这样就实现了代码的并行执行。
使用线程池的好处就是避免了频繁进行异步调用时创建、销毁线程的开销。

而当使用异步调用时,更多情况下是为了提升系统的性能,而在这种情况下使用异步编程时,就需要进行更多的控制,
比如:当异步执行方法的方法结束时通知客户端、返回异步执行方法的返回值等。
这里我就对BeginInvoke()方法、EndInvoke()方法和其相关的IAysncResult做一个简单的介绍。
先看一段不使用异步调用的通常情况
<pre>using UnityEngine;
using System.Collections;
using System.Threading;
using System;
 
public class test : MonoBehaviour
{
 
    // Use this for initialization
    void Start()
    {
Debug.Log("程序开始运行:");
 Thread.CurrentThread.Name = "Main Thread";
        Calculator cal = new Calculator();
        int result = cal.Add(6, 8);
        Debug.Log(string.Format("结果为: {0}\n", result));
        // 做某些其它的事情,模拟需要执行3 秒钟
        for (int i = 1; i <= 3; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Debug.Log(string.Format("线程:{0}:  执行了 {1} s 时间(s).", Thread.CurrentThread.Name, i));
        }
        Debug.Log("其它的事情完成");
    }
    public class Calculator
    {
        public int Add(int x, int y)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Thread.CurrentThread.Name = "Pool Thread";
            }
             Debug.Log(string.Format("开始计算{0}+{1}=?",x,y));
            // 执行某些事情,模拟需要执行2 秒钟
            for (int i = 1; i <= 2; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Debug.Log(string.Format("线程:{0}: 添加 执行了{1}s 时间(s).", Thread.CurrentThread.Name, i));
            }
            Debug.Log("计算成功!");
            return x + y;
        }
    }
}
 
  
注: 如果你确实执行了这段代码,会看到这些输出并不是一瞬间输出的,而是执行了大概 十多秒 的时间才显示
那是因为线程是串行执行的,所以在执行完 Add() 方法之后才会继续Start中剩下的代码。
当你关掉,再次运行游戏会报错“InvalidOperationException: Thread.Name can only be set once. ”,说线程名只能设置一次
因为方便测试我就直接写了,因此大家再测试的可以 
1.关掉unity 再重新打开运行
2.第二次运行前,打开代码 找到start中改成//  Thread.CurrentThread.Name = "Main Thread";即可
当然自己写个判断也行
运行如下:
 
上面模拟了一个计算器,进行数学运算的过程...用到了 几个关于对于线程的操作,不是unity的 StartCoroutine协程,而是c#的 Thread线程,在此他们区别暂不多说,以后我会专门讲解。
好了,先看看 Thread的几个方法
1. Thread.Sleep(),它会让执行当前代码的线程暂停一段时间(如果你对线程的概念比较陌生,可以理解为使程序的执行暂停一段时间),以毫秒为单位,比如Thread.Sleep(1000),将会使线程暂停1 秒钟。在上面我使用了它的重载方法,个人觉得使用TimeSpan.FromSeconds(1),可读性更好一些。
2. Thread.CurrentThread.Name,通过这个属性可以设置、获取执行当前代码的线程的名称,值得注意的是这个属性只可以设置一次,如果设置两次,会抛出异常。
3. Thread.IsThreadPoolThread,可以判断执行当前代码的线程是否为线程池中的线程。
通过这几个方法和属性,有助于我们更好地调试异步调用方法。


接下来我们定义一个AddDelegate 委托,并使用BeginInvoke()方法来异步地调用它。
在上面已经介绍过,BeginInvoke()除了最后两个参数为AsyncCallback 类型和Object 类型以外,前面的参数类型和个数与委托定义相同。
另外BeginInvoke()方法返回了一个实现了IAsyncResult 接口的对象(实际上就是一个AsyncResult 类型实例,注意这里IAsyncResult 和AysncResult 是不同的,它们均包含在.NET Framework 中)。
AsyncResult 的用途有这么几个:传递参数,
它包含了对调用了BeginInvoke()的委托的引用;
它还包含了BeginInvoke()的最后一个Object 类型的参数;
它可以鉴别出是哪个方法的哪一次调用,因为通过同一个委托变量可以对同一个方法调用多次。

EndInvoke()方法接受IAsyncResult 类型的对象(以及ref 和out 类型参数,这里不讨论了,对它们的处理和返回值类似),所以在调用BeginInvoke()之后,我们需要保留IAsyncResult,以便在调用EndInvoke()时进行传递。
这里最重要的就是EndInvoke()方法的返回值,它就是方法的返回值。
除此以外,当客户端调用EndInvoke()时,如果异步调用的方法没有执行完毕,则会中断当前线程而去等待该方法,只有当异步方法执行完毕后才会继续执行后面的代码。
所以在调用完BeginInvoke()后立即执行EndInvoke()是没有任何意义的。
我们通常在尽可能早的时候调用BeginInvoke(),然后在需要方法的返回值的时候再去调用EndInvoke(),或者是根据情况在晚些时候调用。

说了这么多,我们现在看一下使用异步调用改写后上面的代码吧:

using UnityEngine;
using System.Collections;
using System.Threading;
using System;
 
public class test : MonoBehaviour
{
    //声明委托,返回值为int类型
    public delegate int AddDelegate(int x, int y);
    // Use this for initialization
    void Start()
    {
        Debug.Log("程序开始运行:");
        //Thread.CurrentThread.Name = "Main Thread";
        Calculator cal = new Calculator();
        //int result = cal.Add(6, 8);
        //Debug.Log(string.Format("结果为: {0}\n", result));
        AddDelegate del = new AddDelegate(cal.Add);
        IAsyncResult asyncResult = del.BeginInvoke(2, 5, null, null); // 异步调用方法
 
        // 做某些其它的事情,模拟需要执行3 秒钟
        for (int i = 1; i <= 3; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Debug.Log(string.Format("线程:{0}:  执行了 {1} s 时间(s).", Thread.CurrentThread.Name, i));
        }
        int rtn = del.EndInvoke(asyncResult);
       Debug.Log(string.Format("计算结果为: {0}\n", rtn));
        Debug.Log("其它的事情完成");
    }
    public class Calculator
    {
        public int Add(int x, int y)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Thread.CurrentThread.Name = "Pool Thread";
            }
             Debug.Log(string.Format("开始计算{0}+{1}=?",x,y));
            // 执行某些事情,模拟需要执行2 秒钟
            for (int i = 1; i <= 2; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Debug.Log(string.Format("线程:{0}: 添加 执行了{1}s 时间(s).", Thread.CurrentThread.Name, i));
            }
            Debug.Log("计算完成,返回结果!");
            return x + y;
        }
    }
}


?
运行如下:
 

现在执行完这段代码,速度明显提升了几秒钟时间,两个for 循环所产生的输出交替进行,这也说明了这两段代码并行执行的情况。
可以看到Add() 方法是由线程池中的线程在执行, 因为Thread.CurrentThread.IsThreadPoolThread 返回了True,同时我们对该线程命名为了Pool Thread。
另外我们可以看到通过EndInvoke()方法得到了返回值。
有时候,我们可能会将获得返回值的操作放到另一段代码或者客户端去执行,而不是向上面那样直接写在BeginInvoke()的后面。
比如说我们在Program 中新建一个方法GetReturn(),此时可以通过AsyncResult 的AsyncDelegate 获得del 委托对象,然后再在其上调用EndInvoke()方法,这也说明了AsyncResult 可以唯一的获取到与它相关的调用了的方法(或者也可以理解成委托对象)。
所以上面获取返回值的代码也可以改写成这样:
<pre>static int GetReturn(IAsyncResult asyncResult)
    {
        AsyncResult result = (AsyncResult)asyncResult;
        AddDelegate del = (AddDelegate)result.AsyncDelegate;
        int rtn = del.EndInvoke(asyncResult);
        return rtn;
    }
 
 

然后再将int rtn = del.EndInvoke(asyncResult);语句改为int rtn = GetReturn(asyncResult);。
注意上面IAsyncResult 要转换为实际的类型AsyncResult 才能访问AsyncDelegate 属性,因为它没有包含在IAsyncResult 接口的定义中。
BeginInvoke()的另外两个参数分别是AsyncCallback 和Object 类型,其中AsyncCallback 是一个委托类型,它用于方法的回调,即是说当异步方法执行完毕时自动进行调用的方法。

它的定义为:

public delegate void AsyncCallback(IAsyncResult ar);


Object 类型用于传递任何你想要的数值,它可以通过IAsyncResult 的AsyncState 属性获得。
下面我们将获取方法返回值、打印返回值的操作放到了OnAddComplete()回调方法中:
<pre>using UnityEngine;
using System.Collections;
using System.Threading;
using System;
using System.Runtime.Remoting.Messaging;
 
public class test :MonoBehaviour
{
    //声明委托,返回值为int类型
    public delegate int AddDelegate(int x, int y);
    // Use this for initialization
    void Start()
    {
        Debug.Log("程序开始运行:");
        //Thread.CurrentThread.Name = "Main Thread";
        Calculator cal = new Calculator();
        //int result = cal.Add(6, 8);
        //Debug.Log(string.Format("结果为: {0}\n", result));
        AddDelegate del = new AddDelegate(cal.Add);
        //IAsyncResult asyncResult = del.BeginInvoke(2, 5, null, null); // 异步调用方法
        AsyncCallback callBackee = new AsyncCallback(OnAddComplete);
        // 异步调用方法
        del.BeginInvoke(2, 2, callBackee, 6);
 
 
        // 做某些其它的事情,模拟需要执行3 秒钟
        for (int i = 1; i <= 3; i++)
        {
            Thread.Sleep(TimeSpan.FromSeconds(i));
            Debug.Log(string.Format("线程:{0}:  执行了 {1} s 时间(s).", Thread.CurrentThread.Name, i));
        }
       // int rtn = del.EndInvoke(asyncResult);
       //Debug.Log(string.Format("计算结果为: {0}\n", rtn));
        Debug.Log("其它的事情完成");
    }
    static void OnAddComplete(IAsyncResult asyncResult)
    {
        AsyncResult result = (AsyncResult)asyncResult;
        AddDelegate del = (AddDelegate)result.AsyncDelegate;
        int data = (int)asyncResult.AsyncState;
        int rtn = del.EndInvoke(asyncResult);
 
        Debug.Log(string.Format("线程:{0}: 结果为, {1}; 继续相乘 {2}结果为:{3}\n", Thread.CurrentThread.Name, rtn, data, rtn*data));
    }
    public class Calculator
    {
        public int Add(int x, int y)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Thread.CurrentThread.Name = "Pool Thread";
            }
             Debug.Log(string.Format("开始计算{0}+{1}=?",x,y));
            // 执行某些事情,模拟需要执行2 秒钟
            for (int i = 1; i <=2; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Debug.Log(string.Format("线程:{0}: 添加 执行了{1}s 时间(s).", Thread.CurrentThread.Name, i));
            }
            Debug.Log("计算完成,返回结果!");
            return x + y;
        }
    }
}

 
 
运行结果:
 


ok,委托进阶到此就基本结束了,感谢大家一致的关注支持


明天,回调函数继续

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中,回调函数是一种常见的编程模式,用于在特定事件发生时执行特定的代码回调函数通常用于处理异步操作、事件触发和消息传递等情况。 Unity中的回调函数可以通过以下几种方式来实现: 1. 事件委托和事件:Unity中的事件系统允许你定义和触发自定义事件,并将回调函数附加到这些事件上。你可以使用事件委托来定义事件的签名,然后使用事件关键字创建事件。当事件被触发时,附加的回调函数将被调用。 2. MonoBehaviour生命周期函数:MonoBehaviour是Unity中所有脚本的基类,它提供了一系列生命周期函数,如Start、Update、FixedUpdate等。你可以重写这些函数,并在特定的时间点执行你的代码。例如,在Start函数中初始化游戏对象,在Update函数中更新游戏逻辑。 3. 委托和Lambda表达式:Unity也支持使用委托和Lambda表达式来实现回调函数。你可以定义一个委托类型,并将其作为参数传递给其他方法。然后,你可以使用Lambda表达式来创建匿名方法,并将其作为回调函数传递给委托。 4. UnityEvent:Unity还提供了UnityEvent类,它是一种可序列化的事件类型。你可以在脚本中声明一个UnityEvent类型的公共字段,并在Inspector面板中将回调函数附加到该事件上。当事件被触发时,附加的回调函数将被调用。 总结一下,Unity中使用回调函数的方式包括事件委托和事件、MonoBehaviour生命周期函数、委托和Lambda表达式以及UnityEvent。这些方法可以帮助你在特定的事件发生时执行特定的代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值