unity的几种委托写法

【Unity】Delegate, Event, UnityEvent, Action, UnityAction, Func 傻傻分不清

Delegate 委托,函数指针

一个简单的例子:一对一依赖

一个简单的例子:一对多依赖

所以话说……委托有啥用呢?

事件 Event,特殊的委托

UnityEvent

Action,一个委托

UnityAction,一个委托

Func,带返回值的 Action

使用匿名函数 / Lambda 来监听回调函数

学习打开别人一个魔塔的项目,看到了满页的 Action 代码,而本人委托那一块自己写的时候压根不会用……遂学习相关知识。

多数学习自知乎

本文可能会有知识点错误,欢迎讨论。

Delegate 委托,函数指针

首先,Delegate是C#的内容,简单来说委托是一种回调机制,被广泛应用在观察者模式中。

回调机制貌似挺复杂,这里可以简单理解为允许使用回调函数,而在这里的回调函数可以简单理解为函数指针。

观察者模式没有学习过的可以看其他的博客。

一个简单的例子:一对一依赖

  • 学习打开别人一个魔塔的项目,看到了满页的 Action 代码,而本人委托那一块自己写的时候压根不会用……遂学习相关知识。

  • 多数学习自知乎

  • 本文可能会有知识点错误,欢迎讨论。

Delegate 委托,函数指针

  • 首先,Delegate是C#的内容,简单来说委托是一种回调机制,被广泛应用在观察者模式中。
    回调机制貌似挺复杂,这里可以简单理解为允许使用回调函数,而在这里的回调函数可以简单理解为函数指针
    观察者模式没有学习过的可以看其他的博客。

一个简单的例子:一对一依赖

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMyDelegate : MonoBehaviour
{
    public delegate void DeleFunc(int x);
    // Start is called before the first frame update
    void Start()
    {
        DeleFunc deleFunc;
        deleFunc = show;
        deleFunc(10);
        deleFunc(8);
        deleFunc = doubleShow;
        deleFunc(10);
    }

    void show(int x)
    {
        Debug.Log(x);
    }
    void doubleShow(int x)
    {
        Debug.Log(x * 2);
    }
}

我们声明了一个委托DeleFunc(就如一个函数指针),使用到关键字 delegate

然后,这个委托我们只告诉了它的形参和返回类型。我们需要实例化,实例化了 deleFunc

然后,我们声明一个新的函数 show,注意这里形参和返回值需要和委托的一致

然后,我们把 deleFunc 指向 show,进行方法回调

然后,我们申明了一个新的函数 doubleShow ,同理,进行方法回调

进行测试,正常运行。

一个简单的例子:一对多依赖

  • 我们使用 += 为同一个委托监听多个方法回调
    对应的,使用 -= 删除一个监听方法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMyDelegate : MonoBehaviour
{
    public delegate void DeleFunc(int x);
    // Start is called before the first frame update
    public DeleFunc deleFunc;
    void Start()
    {
        deleFunc += show;
        deleFunc += doubleShow;
        deleFunc(5);
        deleFunc -= doubleShow;
        deleFunc(5);
    }

    void show(int x)
    {
        Debug.Log(x);
    }
    void doubleShow(int x)
    {
        Debug.Log(x * 2);
    }
}

  • 注意,不能写成

    public delegate void DeleFunc(int x);
    // Start is called before the first frame update
    void Start()
    {
        DeleFunc deleFunc;    // 不能写到里面来,会报错 [使用了未赋值的局部变量“deleFunc”]
        deleFunc += show;
        deleFunc += doubleShow;
        deleFunc(5);
    }

所以话说……委托有啥用呢?

  • 委托不是必须用的,它的产生是随着OO,设计模式等产生的,用于代码解耦

  • 场景一:你有很多种行为,比如 eat(),drink(),sleep(),行为越来越多,相应的调用代码也越来越长

class Service{
    void eat(){// something}
    void drink(){// something}
    void sleep(){// something}
    public void service(string name){
        switch (name){
            case "eat":eat();break;
            case "drink":drink();break;
            case "sleep":sleep();break;
        }
    }
}

若使用委托进行封装,便可以

class Service{
    void eat(){// something}
    void drink(){// something}
    void sleep(){// something}
    public delegate void action();
    public void service(action act){
        act();
    }
}

调用的时候,这样:

    action act = eat();
    Service.service(act);
    Service.service(cook);
  • 场景二:代码解耦
    一个游戏,玩家死亡后会调用函数 GameOver(),但我目前还不知道该函数里面还需要一些代码和方法
    比如,我们可能会如下实现

public void GameOver(){
    GamePause();
    PlaySound("death");
    ClearFlags();
    AddDeathCount(1);
    ShowGameOverPanel();
    // ……
}
  • 然后这里面的每个方法又有很多代码实现。若发现死亡后的播放音效需要更改,还需要去这个代码里面单独修改。若增加了一个功能,需要在这个函数内调用,还要跑到这个函数里进行增添代码,十分麻烦,难以维护。

  • 一个做法:使用委托,即:

    public delegate void GameOver();
    public GameOver gameOver;

接下来,比如在音效系统的代码中,为其增添回调函数

class VoiceManager{
    // ……
    public void addSounds(){
        gameOver += PlaySound("death");
    }
    public void PlaySound(string name){
        // ……
    }
}

其他系统同理。这样,对于需要更改音效的地方,就集中统一管理到了相应的类。

但注意,这里的 gameOver 的委托实例获取方式仍然有些耦合。需要更解耦貌似还需要使用后续提到其他的内容。

事件 Event,特殊的委托

仍然,事件是 C# 的内容,并且事件是一种特殊的委托

怎么理解呢?先看代码

这里有两个类,一个为 TestMyDelegate ,代码同上述

一个为 AnotherDelegate ,相对第一个类,为一个外部类(这里指委托没有声明在该类)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMyDelegate : MonoBehaviour
{
    public delegate void DeleFunc(int x);
    // Start is called before the first frame update
    public DeleFunc deleFunc;
    void Start()
    {
        deleFunc += show;
        deleFunc += doubleShow;
        deleFunc(1);
    }

    void show(int x)
    {
        Debug.Log(x);
    }
    void doubleShow(int x)
    {
        Debug.Log(x * 2);
    }
}

/*****************************************/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnotherDelegate : MonoBehaviour
{
    void Start()
    {
        TestMyDelegate test = GetComponent<TestMyDelegate>();
        test.deleFunc += myfunc;
        test.deleFunc(100);
    }
    void myfunc(int x)
    {
        Debug.Log(x * 3);
    }

}

进行测试,结果如下:

  • 很明显,首先外部类为其增加监听器,然后先输出了 300
    然后内部类增加了两个监听器,并相应输出了三个数字

  • 接下来,我们在内部类,把委托的实例增加 event 关键字,改成事件
    public event DeleFunc deleFunc;
    然后发现外部类报错了

没错,事件相较于委托,即事件只能在创建类中进行调用

外部类可以对事件进行增加、删除监听器,但是不能使用 = 。等于的功能即令该委托/事件只监听这个回调函数。

UnityEvent

  • 自然,该为 Unity 中的内容,为 Unity 做的一个小封装。
    我们在代码中,首先引入头文件 UnityEngine.Events,然后写一下 UnityEvent,然后 F12 查看源代码

哦,我们发现,UnityEvent 相较于委托,它规范化了 +=和-=,以 OO 的方式改为了 AdListener(UnityAction call) 和 RemoveListener(UnityAction call),除此之外好处是更容易调试debug,编辑器可视化等。

还有一点,由于委托是多播设计,可能会导致重复添加同一监听器。这里 UnityEvent 与其他系统 (如 UnityAction, EventSystem 等)结合,更加方便。

我们试一下代码

这里使用了三个 UnityEvent 事件,分别监听了无参函数、一参函数和三个参数函数

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class TestUnityEvent : MonoBehaviour
{
    public UnityEvent myEvent;
    public UnityEvent<int> anotherEvent;
    public UnityEvent<int, int, int> alsoEvent;
    // Start is called before the first frame update
    void Start()
    {
        myEvent.AddListener(noArg);
        myEvent.Invoke();

        anotherEvent.AddListener(show);
        anotherEvent.Invoke(5);

        alsoEvent.AddListener(threeArgs);
        alsoEvent.Invoke(1, 2, 3);
    }

    void show(int x)
    {
        Debug.Log(x);
    }

    void noArg()
    {
        Debug.Log("No Arg");
    }

    void threeArgs(int a, int b, int c)
    {
        Debug.Log(a + " " + b + " " + c);
    }
}

Action,一个委托

  • 首先,ActionC#System 库中的内容
    我们引入该头文件,然后输入 ActionF12 查看原码

额,好简单,所以 Action 就是一个委托。

若你输入 Action<>F12 进去看,则会显示

  • 所以,若你自己写代码 public delegate void myAction<in T>(T obj); ,那么该 myActionAction 就没有区别。
    那么,该测试部分和之前的委托就没什么差异了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System;
public class TestAction : MonoBehaviour
{
    public Action<int> myAction;
    // Start is called before the first frame update
    void Start()
    {
        myAction = show;
        myAction += show;
        myAction(100);
    }

    void show(int x)
    {
        Debug.Log(x);
    }
}

UnityAction,一个委托

  • 在看这个,这个明显就是 Unity 中的一个 Action
    头文件在 UnityEngine.Events中,我们进入查看原码

  • 呃呃呃,你们都那么简单,好吧。
    那对比,ActionUnityAction 只有头文件不同的区别了,其他的部分都一样啊。

Func,带返回值的 Action

  • 该内容在系统库 System,我们查看原码

  • 沃耶,对比 ActionFunc 即多了一个返回值的地方,原来他们都这么简单…
    好吧确实,因为它的定义即如此,有时候自己实现一个内容还能有更多的功能…
    我们照常测试一下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System;
public class TestFunc : MonoBehaviour
{
    public Func<string, int> func;    // 这里的 int 就是返回类型
    // Start is called before the first frame update
    void Start()
    {
        func += show;
        Debug.Log(func("Hello World"));
    }

    int show(string x)
    {
        Debug.Log(x);
        return 12;
    }
}

完美实现

使用匿名函数 / Lambda 来监听回调函数

  • 除了上述声明函数并直接给委托监听外,也可以用匿名函数和 lambda表达式来进行处理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class TestOtherMethods : MonoBehaviour
{
    public UnityAction<int, int> myAction;
    // Start is called before the first frame update
    void Start()
    {
        // anony-
        myAction = delegate (int x, int y)
        {
            Debug.Log(x + " " + y);
        };
        myAction(1, 2);

        // lambda
        myAction = (int x, int y) =>
        {
            Debug.Log(x + " " + y);
        };
        myAction(3, 4);
    }

}
  • 学了这么多了,试了这么多例子了,大致也应该了解各个内容了
    在项目开发中,具体用到哪种其实都可以,虽然有人是推荐 UnityEvent,有人说 Action/Func 直接用,之类的。

转载自:https://blog.csdn.net/weixin_45775438/article/details/128449023#comments_25671721

Unity中的序列化(Serialization)是指将对象转换为字节流的过程,而反序列化(Deserialization)则是将字节流转换为对象的过程。Unity提供了一些机制来实现对象的序列化和反序列化。 Unity的序列化机制主要用于保存和加载游戏对象的状态,或者在网络传输中传递对象。以下是一些常见的序列化和反序列化方法: 1. Unity的内置序列化:Unity提供了内置的序列化机制,使得你可以将脚本中的变量标记为可序列化。通过在变量前面添加 `[SerializeField]` 属性,可以将该变量标记为可序列化。例如: ```csharp [SerializeField] private int score; ``` 2. XML 和 JSON 序列化:Unity还支持使用XML或JSON格式进行序列化和反序列化。你可以使用 `System.Xml.Serialization` 命名空间下的类来进行XML的序列化和反序列化,或者使用JsonUtility类来进行JSON的序列化和反序列化。 ```csharp // XML序列化和反序列化示例 using System.Xml.Serialization; // 序列化为XML XmlSerializer serializer = new XmlSerializer(typeof(MyClass)); using (StreamWriter writer = new StreamWriter("data.xml")) { serializer.Serialize(writer, myObject); } // 从XML反序列化 using (StreamReader reader = new StreamReader("data.xml")) { MyClass myObject = (MyClass)serializer.Deserialize(reader); } // JSON序列化和反序列化示例 using UnityEngine; using UnityEngine.Networking; // 序列化为JSON string json = JsonUtility.ToJson(myObject); // 从JSON反序列化 MyClass myObject = JsonUtility.FromJson<MyClass>(json); ``` 3. 二进制序列化:如果需要更高效的序列化和反序列化操作,可以使用二进制格式。Unity提供了BinaryFormatter类来进行二进制的序列化和反序列化。 ```csharp // 二进制序列化和反序列化示例 using System.Runtime.Serialization.Formatters.Binary; // 序列化为二进制 BinaryFormatter formatter = new BinaryFormatter(); using (FileStream stream = new FileStream("data.bin", FileMode.Create)) { formatter.Serialize(stream, myObject); } // 从二进制反序列化 using (FileStream stream = new FileStream("data.bin", FileMode.Open)) { MyClass myObject = (MyClass)formatter.Deserialize(stream); } ``` 这些是Unity中常用的序列化和反序列化方法,你可以根据具体的需求选择适合的方法来实现对象的序列化和反序列化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值