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

news2025/1/20 3:39:00

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

  • Delegate 委托,函数指针
    • 一个简单的例子:一对一依赖
    • 一个简单的例子:一对多依赖
    • 所以话说……委托有啥用呢?
  • 事件 Event,特殊的委托
  • UnityEvent
  • Action,一个委托
  • UnityAction,一个委托
  • Func,带返回值的 Action
  • 使用匿名函数 / Lambda 来监听回调函数

  • 学习打开别人一个魔塔的项目,看到了满页的 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 直接用,之类的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/116563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Failed to read artifact descriptor for XXX.jar错误分析

今天遇到极其恶心的坑&#xff0c;自己定义编写的工具类&#xff0c;之前使用一直没有问题&#xff0c;正常引入pom文件中&#xff0c;也能正常的使用工具类中自建类。今天就是无法导入包&#xff0c;报错信息如下。 Failed to read artifact descriptor for com.yuxuntoo.www…

JavaScript篇.day03-循环结构(while, for)

目录 1.循环结构 2.例题 (1)奇数乘积(while/do-while) (2)水仙花数(while) (3)最大公约数(for) (4)最小公倍数(for) (5)打印星星(for) (6)乘法表(for) 1.循环结构 循环结构在程序中可执行多次循环结构有while循环, do-while循环, for循环 名称执行顺序循环执行次数应用…

wireshark练习抓取网络数据包用C编程完成modbus协议,从云端服务器读取温湿度数据。

文章目录前言一、疯狂聊天室1、配置网络2、创建房间3、互发消息二、wireshark抓取网络数据包1.找到对应的包2、分析抓取包中的信息三、Modbus协议概述1、Modbus主/从协议原理2、通用Modbus帧结构---协议数据单元(PDU)3、两种Modbus串行传输模式4、ModbusTCP通信结构四、C编程完…

零基础自学网络安全,如何3个月快速入门?

说到自学网络安全这一块&#xff0c;我来给大家分享一下我的经验。 一、了解相关网站 在入这行之前&#xff0c;我是先泡了一段时间网络安全相关的论坛&#xff0c;了解行业的信息&#xff0c;也可以确认自己是否真的想做这一行&#xff0c;毕竟这一行看起来很炫酷&#xff0…

Python编程 内置文件中常用方法

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.函数 &#xff08;1&#xff09;range() &#xff08;2&#xff09;zi…

做自动化测试3年,薪资为何会被应届生倒挂?

各位职场人都听说过薪资倒挂这词儿吧&#xff0c;这个情况在行业内早就不是什么稀罕事了&#xff0c;我有一个认识的&#xff0c;作为公司3年工龄的软件测试老员工&#xff0c;技术过关&#xff0c;能够独立工作&#xff0c;并且思考优化方向&#xff0c;我不吹牛的说&#xff…

【STL学习之路】vector的使用

文章目录vector介绍vector使用一、构造函数二、迭代器三、vector增删查改关于STL中参数的分析&#xff08;以push_back为例&#xff09;sort算法和仿函数使用vector介绍 vector就是顺序表的封装&#xff0c;是一个模板类&#xff0c;如上图所示。为方便vector里可以存任何数据类…

ChatGPT 免账号使用

一.ChatGPT 简介 ChatGPT是人工智能研究实验室OpenAI新推出的一种人工智能技术驱动的自然语言处理工具&#xff0c;使用了Transformer神经网络架构&#xff0c;也是GPT-3.5架构&#xff0c;这是一种用于处理序列数据的模型&#xff0c;拥有语言理解和文本生成能力&#xff0c;尤…

搭建资金运营体系提高企业的运营能力

资金运营体系是以项目资金流管理为核心&#xff0c;在项目预核算体系基础上的深度化&#xff0c;是丰富和完善全面预算管理的重要内容。资金运营体系建设嵌入到业务流程和项目实施过程&#xff0c;将资金使用成本贯穿于项目实施各个环节。 一、资金管控总体思路 1、资金运营的…

数据可视化:春节临近,拥堵模式开启,你买好回家的车票了吗

哈喽&#xff0c;大家好&#xff0c;春节将近&#xff0c;想必大家也开始准备回家过年的事情了&#xff0c;春运即将开始&#xff0c;祝愿大家都能买到回家的车票。 想必大家对春运都不陌生&#xff0c;随着春节临近&#xff0c;全国各地也都先后开启了拥堵模式&#xff0c;下…

unity打android studio项目包运行时报错

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0. 打开Cmd 进入打包的项目&#xff0c;如下 输入gradlew --warning-mode all 说的是在Build.gradle(project)的RepositoryHandler.jcenter()方法已弃用 改成mavenCentral()即可 …

【OpenCV-Python】教程:8-3 高动态范围 HDR

OpenCV Python HDR 【目标】 学习如何从曝光序列生成和显示HDR图像。使用曝光融合来合并曝光序列。 【理论】 高动态范围成像(HDRI或HDR)是一种用于成像和摄影的技术&#xff0c;用于再现比标准数字成像或摄影技术更大的动态范围的光度。虽然人眼可以适应广泛的光照条件&am…

软考中级软件设计师和系统集成项目管理工程师哪个更好考?

软件设计师&#xff1a; 计算机相关基础知识&#xff0c;常用数据结构和常用算法&#xff0c;C程序设计语言&#xff0c;以及C、Java中的一种程序设计语言&#xff0c;软件工程、软件过程改进和软件开发项目管理的基础知识&#xff0c;软件设计的方法和技术。 系统集成项目管…

【设计模式】软件开发原则

开闭原则&#xff08;OCP&#xff09; 定义&#xff1a;一个软件实体应当对扩展开放&#xff0c;对修改关闭。也就是说在设计一个模块的时候&#xff0c;应当使这个模块可以在不被修改的前提下被扩展&#xff0c;即使现在不修改源代码的情况下改变这个模块的行为。 意思&#x…

(包含源代码)2022年全国大学生数学建模竞赛E题目-小批量物料生产安排详解+思路+Python代码时序预测模型-补完

目录 前言 赛题分析 1.问题一 问题分析 物料频率 代码详细操作&#xff1a; 出现频次 需求总数 趋势标量 方法 平均每天需求额度 整合代码 熵权法 详细介绍&#xff1a; 二、使用步骤 2.计算指标信息熵 3.熵权法相关代码 得到权重&#xff1a; 只希望各位以…

决策树-sklearn

决策树 1.概述 1.1 决策树是如何工作 决策树能够从一系列有特征和标签的数据中总结出决策规则&#xff0c;并且使用树状图的结构来表现&#xff0c;从而达到解决回归和分类问题。通俗的说&#xff0c;就是我们只需要问一系列问题就可以对数据进行分类。 核心要解决的问题&am…

20221226编译Toybrick的TB-RK3588X开发板的Android12系统3

20221226编译Toybrick的TB-RK3588X开发板的Android12系统3 2022/12/26 18:27 编译指令&#xff1a; 1、cat android12-rk3588.tar.gz.* >android12-rk3588.tar.gz 2、tar -zxvf android12-rk3588.tar.gz,解压缩后生成android12-rk3588-new目录 3、android12-rk3588-new 4、…

CSS篇.day04-单位,流式布局,弹性布局(flex),网格布局,渐变,变形与过渡

目录 1. 单位 2.流式布局 3.弹性布局(flex) 4.网格布局 5.渐变 6.变形与过渡 1. 单位 px: 绝对单位 像素em: 相对单位 基准点为父节点字体大小 若自身定义了font-size按自身来计算(一般浏览器默认16px)rem: 相对单位 相对根节点html的字体大小, css3新增属性, 根元…

【Python百日进阶-数据分析】Day139 - plotly甘特图:plotly.figure_factory.create_gantt()

文章目录一、语法二、参数三、返回值四、实例4.1 普通ff甘特图4.2 将任务组合在一起4.3 按数值变量着色4.4 create_gantt4.5具有数字条目的按列索引4.6 具有字符串条目的按列索引4.7 使用颜色字典4.8一、语法 已弃用&#xff0c;请用plotly.express.timeline()代替。 plotly.…

LeetCode88. 合并两个有序数组

题目 给你两个按非递减顺序排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。请你合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按非递减顺序排列。 注意&#xff1a;最终&#xff0c;合并后数组不…