IP类和端口类

IP类和端口类用来做什么?

如果要进行网络通信、网络连接首先需要找到对应的设备,IP和端口号是定位网络中设备必不可少的关键元素

C#中提供了对应的IP和端口相关的类


IPAddress类

命名空间:System.Net

类名:IPAddress

初始化

使用byte数组进行初始化

byte[] ipAddress = new byte[] {192,168,1,1};
IPAddress ip = new IPAddress(ipAddress);

使用long长整型进行初始化

//4字节对应的长整型 一般不建议使用
IPAddress ip = new IPAddress(0xC0A80101);//192.168.1.1

使用字符串转换(推荐)

IPAddress ip = IPAddress.Parse("192.168.1.1");

特殊的IP地址

127.0.0.1代表本机地址

获取可用的IPV6地址

IPAddress.IPv6Any

IPEndPoint类

命名空间:System.Net

网络游戏通信方案概述

弱联网和强联网游戏

弱联网游戏

这种游戏不会频繁的进行数据通信,客户端和服务端之间每次连接只处理一次请求,服务端处理完 客户端的请求后返回数据后就断开连接了

一般的三消类休闲游戏、卡牌游戏等都会是弱联网游戏,这些游戏的核心玩法都由客户端完成,客 户端处理完成后只是告诉服务端一个结果,服务端验证结果即可,不需要随时通信 比如:开心消消乐、刀塔传奇、我叫MT等等

强联网游戏

这种游戏会频繁的和服务端进行通信,会一直和服务端保持连接状态,不停的和服务器之间交换数 据

一般的MMORPG(角色扮演)、MOBA(多人在线竞技游戏)、ACT(动作游戏)等等都会是强 联网游戏,这些游戏的部分核心逻辑是由服务端进行处理,客户端和服务端之间不停的在同步信息 比如:王者荣耀、守望先锋、和平精英等等


长连接和短连接游戏

长连接和短连接游戏是按照网络游戏通信特点来划分的

我们甚至可以认为

弱联网游戏——>短连接游戏

强联网游戏——>长连接游戏

短连接游戏:

需要传输数据时,建立连接,传输数据,获得响应,断开连接

通信特点:需要通信时再连接,通信完毕断开连接

通信方式:HTTP超文本传输协议、HTTPS安全的超文本传输协议(他们本质上是TCP协议)

长连接游戏:

不管是否需要传输数据,客户端与服务器一直处于连接状态,除非一端主动断开,或 者出现意外情况(客户端关闭或服务端崩溃等)

通信特点:连接一直建立,可以实时的传输数据

通信方式:TCP传输控制协议 或 UDP用户数据报协议


Socket、HTTP、FTP

Socket网络套接字:

是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象,一个套接字就是网 络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制

主要用于制作长连接游戏(强联网游戏)

Http/Https(安全的)超文本传输协议:

是一个简单的请求-响应协议,它通常运行在TCP协议之上,它指定了 客户端可能发送给服务端什么样的信息以及得到什么样的响应。

主要用于制作短连接游戏(弱联网游戏),也可以用来进行资源下载

FTP文件传输协议:

是用于在网络上进行文件传输的一套标准协议,可以利用它来进行网络上资源的下载和 上传。它也是基于TCP的传输,是面向连接的,为文件传输提供了可靠的保证


总结

网络游戏的通信方案大体上可以根据游戏的实际情况分为两种

长连接(强联网)游戏 和 短连接(弱联网)游戏

  1. Socket网络套接字 主要用来完成 长连接网络游戏需求
  2. Http超文本传输协议 主要用来完成 短连接网络游戏需求 (或者资源下载相关)
  3. Ftp文件传输协议 主要用来完成 资源的下载和上传等需求

Linux 文件权限管理:chown 和 chmod

在 Linux 系统中,文件和目录的权限管理是一个至关重要的操作。本文将介绍两条重要命令:chown(更改文件所有者和用户组)以及 chmod(更改文件权限),帮助你轻松管理文件的访问权限。


一、chown 命令

chown 命令用于更改文件或目录的所有者和用户组。

基本语法

chown [选项] 用户:用户组 文件或目录
  • 用户:文件的新所有者。
  • 用户组:文件的新用户组。

常用示例

1. 更改文件所有者

chown user1 文件名

将文件的所有者更改为 user1

2. 更改文件所有者和用户组

chown user1:group1 文件名

将文件的所有者更改为 user1,用户组更改为 group1

Linux文件夹压缩与解压

在 Linux 系统中,压缩和解压文件夹是日常运维中常见的操作。本文将详细介绍如何使用 ziptar 命令进行文件夹的压缩与解压,包括 .zip.tar.gz.tar.bz2 等格式。

压缩文件夹

使用 zip 压缩文件夹

zip -r 压缩文件名.zip 文件夹名

压缩到指定目录

zip -r /path/to/destination/myfolder.zip 文件夹名

使用 tar 压缩文件夹

tar 命令可以将文件夹压缩为 .tar.gz.tar.bz2 格式。

压缩为 .tar.gz

tar -czvf 压缩文件名.tar.gz 文件夹名

压缩为 .tar.bz2

tar -cjvf 压缩文件名.tar.bz2 文件夹名

解压文件夹

解压 .zip 文件

使用 unzip 命令解压 .zip 文件。

Socket网络通信

Socket套接字的作用

它是C#提供给我们用于网络通信的一个类(在其它语言当中也有对应的Socket类)

类名:Socket

命名空间:System.Net.Sockets

Socket套接字是支持TCP/IP网络通信的基本操作单位

一个套接字对象包含以下关键信息

  1. 本机的IP地址和端口
  2. 对方主机的IP地址和端口
  3. 双方通信的协议信息

一个Sccket对象表示一个本地或者远程套接字信息

它可以被视为一个数据通道

这个通道连接与客户端和服务端之间

数据的发送和接受均通过这个通道进行

一般在制作长连接游戏时,我们会使用Socket套接字作为通信方案

通过它连接客户端和服务端,收发消息等


Socket的类型

Socket套接字有3种不同的类型

  1. 流套接字

    主要用于实现TCP通信,提供了面向连接、可靠的、有序的、数据无差错且无重复的数据传输服务

  2. 数据报套接字

    主要用于实现UDP通信,提供了无连接的通信服务,数据包的长度不能大于32KB,不提供正确性检查,不保证顺序,可能出现重发、丢失等情况

  3. 原始套接字(不常用)

    主要用于实现IP数据包通信,用于直接访问协议的较低层,常用于侦听和分析数据包


Socket的声明

Socket s = new Socket();

参数一

AddressFamily 网络寻址 枚举类型,决定寻址方案

常用:

  1. InterNetworkIPv4寻址
  2. InterNetwork6 IPv6寻址

其他:

  1. UNIXUNIX本地到主机地址
  2. ImpLinkARPANETIMP地址
  3. IpxIPX或SPX地址
  4. IsoISO协议的地址
  5. OsiOSI协议的地址
  6. NetBiosNetBios地址
  7. Atm本机ATM服务地址

参数二:

SocketType 套接字枚举类型,决定使用的套接字类型

常用:

  1. Dgram支持数据报,最大长度固定的无连接、不可靠的消息(主要用于UDP通信)
  2. Stream支持可靠、双向、基于连接的字节流(主要用于TCP通信)

其他:

  1. Raw支持对基础传输协议的访问
  2. Rdm支持无连接、面向消息、以可靠方式发送的消息
  3. Seqpacket提供排序字节流的面向连接且可靠的双向传输

参数三:

常用:

  1. TCPTCP传输控制协议
  2. UDPUDP用户数据报协议

其他:

xlua

Lua解析器

Lua解析器能够让我们在Unity中执行Lua

默认的Lua脚本放在Resources文件夹下,并且要加上.txt后缀 才能被执行

LuaEnv env = new LuaEnv();
env.DoString("print('你好世界')");
//执行一个lua脚本
env.DoString("require('Main')");

垃圾回收

帮助我们清楚Lua中我们没有手动释放的对象 垃圾回收

帧更新中定时执行 或者 切场景时执行

env.Tick();

销毁Lua解析器

env.Dispose();

Lua文件加载重定向

xlua提供的一个 路径重定向 的方法

允许我们自定义 加载 Lua文件的规则

当我们执行Lua语言 require时 相当于执行一个lua脚本

Lua语法

变量

变量声明

  • 不需要声明变量的类型
  • 变量的默认值均为nil
  • 变量不声明就可以使用
--局部变量
local a = 1
-- 全局变量
b = '变量的声明'

变量类型

简单的四种变量类型:

  • number 数字
  • string 字符串
  • boolean 布尔
  • nil 空

复杂的4中变量类型

  • function函数
  • table 表
  • userdata数据结构
  • thread协同程序

得到变量类型

type(a) 

lua可以对多个变量赋值

a, b = 0, 1 --如果有多余的补空

字符串操作

获取字符串的长度

Unity视频播放

视频剪辑导入相关设置

sRGB 是否开启sRGB sRGB是微软联合惠普、三菱、爱普生等厂商联合开发的通用色彩标准 它的主要作用就是避免在不同设备出现色差 一般默认让它勾选即可

Transcode 是否开启转码

Dimensions 控制源文件内容的大小调整方式

  • Original 保持原始大小
  • Three Quarter Res 将源大小调整为其原始宽度和高度的四分之三
  • Half Res 将源大小调整为其原始宽度和高度的一半
  • Quarter Res 将源大小调整为其原始宽度和高度的四分之一
  • Square(1024x1024) 将源大小调整为1024x1024正方形图像。宽高比可控
  • Square(512x512) 将源大小调整为512x512正方形图像。宽高比可控
  • Square(256x256) 将源大小调整为256x256正方形图像。宽高比可控
  • Custom 将源大小调整为自定义分辨率。宽高比可控

Codec 选择用于转码的解编码器

  • Auto 为目标平台选择最合适的视频编解码器
  • H264 MPEG–4 高级视频编码 (AVC) 视频编解码器,受大多数平台上的硬件支持
  • H265 MPEG-H Part 2 或高效视频编码 (HEVC) 视频编解码器,受某些平台上的硬件支持
  • VP8 VP8视频编解码器,受到大多数平台上的软件支持,并受到 Android 和 WebGL 等几个平台上的硬件支持。

Bitrate Mode 相对于所选编解码器的基线配置文件的低、中或高比特率

Spatial Quality 此设置决定视频图像在转码过程中是否压缩大小,缩小意味着它们占用的存储空间更少。但是,调整图像大小也会导致在播放期间出现模糊。

Keep Alpha 保留Alpha透明通道并在转码期间将其编码 仅对具有Alpha通道的源显示此属性

注意:Android平台的原生 VP8 支持中不包括透明度支持,这意味着必须启用转码,以便 Unity 使用其内部 Alpha 表示方法。

Deinterlace(去交错设置) 控制隔行扫描源在转码期间如何解除隔行扫描

Addressables可寻址资源系统

创建Addressables包

导入Addressables包

  1. 打开Window——>Package Manager窗口
  2. 设置包为Unity Registry
  3. 找到Addressables包 进行安装

让资源变为可寻址资源

方法一:选中资源,勾选Inspector窗口中的Addressable

方法二:选中资源,拖入Addressables Groups窗口中

注意:

  1. C#代码无法作为可寻址资源
  2. Resources文件夹下资源如果变为寻址资源,会移入Resources_moved文件夹中

原因:Resources文件夹下资源会最终打包出去,如果变为可寻址资源意味着想通过Addressables进行管理

那么它就没有必要通过Resources方式去加载和打包,所以会自动迁移,避免重复打包,浪费空间

右键选择资源时菜单内容

  • Move Addressables to Group:将该资源放入到现有的另一个组中
  • Move Addressables to New Gourp:使用与当前组相同设置创建一个新租,并将该资源放入该新组中
  • Rmove Addressables:移除资源,该资源会变为不可寻址资源
  • Simplify Addressable Names:简化可寻址资源名,会删除名称中的路径和拓展,简化缩短名称
  • Copy Address to Clipboard:将地址复制到剪贴板
  • Change Address:改名
  • Create New Group:创建新租

资源组窗口

资源信息

  1. GroupName\Addressable Name:分组名\可寻址名(可重名,描述资源)
  2. Path:路径(不可重复,资源定位)
  3. .Labels:标签(可重复、可用于区分资源种类,例如青铜装备、黄金装备)

创建分组相关

Create——>Group

Packed Assets:打包资源分组

Blank(no schema):空白(无架构)

区别:Packed Assets默认自带默认打包加载相关设置信息,Blank没有相关信息需要自己关联

组对于我们来说意义重大,之后在资源打包时,一个组可以作为一个或多个AB包

选中某一组后右键

  • Remove Group(s):移除组,组中所有资源恢复为不可寻址资源
  • Simplify Addressable Names:简化可寻址名称,会删除名称中的路径和拓展,简化缩短名称
  • Set as Default:设置为默认组,当直接勾选资源中的Addressable时,会自动加入该组
  • Inspect Group Setting:快速选中关联的组相关配置文件
  • Rename:重命名
  • Create New Group:创建新组

配置概述相关

Manage Profiles:管理配置文件

日期和时间(Time)

时间转换

  • 1s秒 = 1000ms毫秒
  • 1ms毫秒 = 1000μs微妙
  • 1μs微妙 = 1000ns纳秒

格里高利历

格里高利历一般指公元

公元,即公历纪年法

目前我们所说公历,就是格里高利历

2022年就是从公元元年开始算起的两千二十二年

格林尼治时间(GMT)

格林尼治标准时间

是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线

地球每天的自转是有些不规则的,而且正在缓慢减速

所以,格林尼治时间已经不再被作为标准时间使用

现在的标准时间──协调世界时(UTC)

协调世界时(UTC):

又称世界统一时间、世界标准时间、国际协调时间

UTC协调世界时即格林尼治平太阳时间,是指格林尼治所在地的标准时间,

也是表示地球自转速率的一种形式

UTC基于国际原子时间,通过不规则的加入闰秒来抵消地球自转变慢的影响,是世界上调节时钟和时间的主要时间标准

时间戳

从1970年1月1日(UNIX时间戳的起点时间)到现在的时间

计算机时间和众多的编程语言的时间都是从1970年1月1日开始算起

是因为很多编程语言起源于UNIX系统,而UNIX系统认为1970年1月1日0点是时间纪元

所以我们常说的UNIX时间戳是以1970年1月1日0点为计时起点时间的

DateTime

命名空间:System

DateTime 是 C# 提供给我们处理日期和时间的结构体

对象的默认值和最小值是0001年1月1日00:00:00(午夜)

最大值可以是9999年12月31日晚上11:59:59

初始化

主要参数:

年、月、日、时、分、秒、毫秒

ticks:以格里高利历00:00:00.000年1月1日以来的100纳秒间隔数表示,一般是一个很大的数字

次要参数:

  • DateTimeKind:日期时间种类
  • Local:本地时间
  • Utc:UTC时间
  • Unspecified:不指定
  • Calendar:日历 使用哪个国家的日历,一般在Unity开发中不使用
DateTime dt = new DateTime(2022, 12, 1, 13, 30, 45, 500);
print(dt.Year + "-" + dt.Month + "-" + dt.Day + "-" + dt.Hour + "-" + dt.Minute + "-" + dt.Second + "-" + dt.Millisecond);

以格里高利历00:00:00.000年1月1日以来的100纳秒间隔数表示,一般是一个很大的数字

 print(dt.Ticks);

一年的第多少天

C#8新增语法

Using 声明

Using 声明就是对using()语法的简写

当函数执行完毕时 会调用 对象的 Dispose方法 释放对象

在使用using语法时,声明的对象必须继承System.IDisposable接口

因为必须具备Dispose方法,所以当声明没有继承该接口的对象时会报错

using StreamWriter s2 = new StreamWriter("文件路径");
//对该对象进行逻辑操作
s2.Write(5);
s2.Flush();
s2.Close();
//利用这个写法 就会在上层语句块执行结束时释放该对象

静态本地函数

静态本地函数就是在本地函数前方加入静态关键字

它的作用就是让本地函数不能够使用访问封闭范围内(也就是上层方法中)的任何变量

public int CalcInfo(int i)
{
    bool b = false;
    i += 10;
    Calc(ref i, ref b);
    return i;
    static void Calc(ref int i, ref bool b)
    {
        i += 10;
        b = true;
    }
}

Null 合并赋值

回顾空合并操作符:左边值 ?? 右边值

如果左边值为null 就返回右边值 否则返回左边值

C#7新增语法

字面值改进

基本概念:在声明数值变量时,为了方便查看数值

可以在数值之间插入_作为分隔符

主要作用:方便数值变量的阅读

int i = 9_9123_1239;
print(i);
int i2 = 0xAB_CD_17;
print(i2);

out变量的快捷使用 和 弃元

以前使用带out函数的写法

int a;
int b;
Calc(out a, out b);

现在的写法

Calc(out int x, out int y);

结合var类型更简便(但是这种写法在存在重载时不能正常使用,必须明确调用的是谁)

Calc(out int a, out var b);

可以使用 _弃元符号 省略不想使用的参数

异常筛选器和nameof运算符

异常筛选器用法

在异常捕获语句块中的Catch语句后通过加入when关键词来筛选异常

when(表达式)该表达式返回值必须为bool值,如果为ture则执行异常处理,如果为false,则不执行

作用:用于筛选异常

好处:帮助我们更准确的排查异常,根据异常类型进行对应的处理

try
{
    //用于检查异常的语句块
}
catch (System.Exception e) when(e.Message.Contains("301"))
{
    //当错误编号为301时  作什么处理
    print(e.Message);
}
catch (System.Exception e) when (e.Message.Contains("404"))
{
    //当错误编号为404时  作什么处理
    print(e.Message);
}
catch (System.Exception e) when (e.Message.Contains("21"))
{
    //当错误编号为21时  作什么处理
    print(e.Message);
}
catch (System.Exception e)
{
    //当错误编号为其它时  作什么处理
    print(e.Message);
}

nameof运算符

用法:nameof(变量、类型、成员)通过该表达式,可以将他们的名称转为字符串

作用:可以得到变量、类、函数等信息的具体字符串名称

int i = 10;
print(nameof(i));

print(nameof(List<int>));
print(nameof(List<int>.Add));

print(nameof(UnityEngine.AI));

List<int> list = new List<int>() { 1,2,3,4};
print(nameof(list));
print(nameof(list.Count));
print(nameof(list.Add));

静态导入

静态导入

用法:在引用命名空间时,在using关键字后面加入static关键词

作用:无需指定类型名称即可访问其静态成员和嵌套类型

好处:节约代码量,可以写出更简洁的代码

using static UnityEngine.Mathf;
public class Test3
{
    public void Test()
    {
       Max(10, 20); 
    }
}

AB包加载类封装

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Events;

public class AssetBundleManager : Single<AssetBundleManager>
{
    /// <summary>
    /// AB包路径
    /// </summary>
    private string PathURL
    {
        get
        {
            return $"{Application.streamingAssetsPath}/ab/{MainName}"; 
        }
    }

    /// <summary>
    /// 主包名字
    /// </summary>
    private string MainName
    {
        get
        {
#if UNITY_IOS
                return "IOS";
#elif UNITY_EDITOR_OSX
                return "StandaloneOSXUniversal";
#else
            return "StandaloneWindows";
#endif
        }
    }

    /// <summary>
    /// 依赖信息
    /// </summary>
    private AssetBundleManifest mainABM;

    /// <summary>
    /// AB包字典
    /// </summary>
    private readonly Dictionary<string, AssetBundle> abDict = new();

    /// <summary>
    /// 加载中的AB包
    /// </summary>
    private readonly HashSet<string> loadingName = new();


    /// <summary>
    /// 加载AB包资源
    /// </summary>
    /// <typeparam name="T">资源类型</typeparam>
    /// <param name="abName">AB包名</param>
    /// <param name="resName">资源名</param>
    /// <param name="callBack">回调函数</param>
    public void Load<T>(string abName, string resName, UnityAction<T> callBack, bool isAsync = true) where T : Object
    {
        PublicMono.Instance.StartCoroutine(LoadRes(abName, resName, callBack, isAsync));
    }

    /// <summary>
    /// 加载AB包资源携程
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <param name="callBack"></param>
    /// <returns></returns>
    private IEnumerator LoadRes<T>(string abName, string resName, UnityAction<T> callBack, bool isAsync) where T : Object
    {
        yield return LoadAB(isAsync, MainName);//加载主包
        if (mainABM == null)//加载依赖信息
        {
            if (isAsync)
            {
                var mainABMRq = abDict[MainName].LoadAssetAsync<AssetBundleManifest>("AssetBundleManifest");
                yield return mainABMRq;
                mainABM = mainABMRq.asset as AssetBundleManifest;
            }
            else
            {
                mainABM = abDict[MainName].LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            }
        }
        string[] strs = mainABM.GetAllDependencies(abName);//加载依赖
        if (strs.Length > 0) yield return LoadAB(isAsync, strs);
        yield return LoadAB(isAsync, abName);//加载AB包
        if (isAsync)//加载资源
        {
            var rq = abDict[abName].LoadAssetAsync<T>(resName);
            yield return rq;
            callBack?.Invoke(rq?.asset as T);
        }
        else
        {
            callBack?.Invoke(abDict[abName].LoadAsset<T>(resName));
        }
    }

    /// <summary>
    /// 加载AB包携程
    /// </summary>
    /// <param name="abNames"></param>
    /// <returns></returns>
    private IEnumerator LoadAB(bool isAsync, params string[] abNames)
    {
        foreach (var abName in abNames)
        {
            while (loadingName.Contains(abName)) yield return null;//如果这个ab包正在加载 就等他加载完成
            if (abDict.ContainsKey(abName)) continue;//如果字典中有直接进入下一次循环
            loadingName.Add(abName);
            if (isAsync)
            {
                var rq = AssetBundle.LoadFromFileAsync($"{PathURL}/{abName}");
                yield return rq;
                abDict.Add(abName, rq.assetBundle);
            }
            else
            {
                abDict.Add(abName, AssetBundle.LoadFromFile($"{PathURL}/{abName}"));
            }
            loadingName.Remove(abName);
        }
    }

    /// <summary>
    /// 卸载AB包
    /// </summary>
    /// <param name="abName">包名</param>
    /// <param name="callback">回调函数</param>
    /// <param name="unloadObjects">是否卸载场景上的资源</param>
    public void UnLoad(string abName, UnityAction callback = null, bool unloadObjects = false)
    {
        PublicMono.Instance.StartCoroutine(UnLoadAB(abName, callback, unloadObjects));
    }

    /// <summary>
    /// 卸载AB包携程
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="callback"></param>
    /// <param name="unloadObjects"></param>
    /// <returns></returns>
    private IEnumerator UnLoadAB(string abName, UnityAction callback, bool unloadObjects)
    {
        //如果这个ab包正在加载 就等他加载完成后卸载
        while (loadingName.Contains(abName)) yield return null;
        if (abDict.ContainsKey(abName))
        {
            var op = abDict[abName].UnloadAsync(unloadObjects);
            yield return op;
            abDict.Remove(abName);
        }
        callback?.Invoke();
    }

    /// <summary>
    /// 卸载所有AB包
    /// </summary>
    /// <param name="callback">回调函数</param>
    /// <param name="unloadObjects">是否卸载场景上的资源</param>
    public void UnLoadAll(UnityAction callback = null, bool unloadObjects = false)
    {
        PublicMono.Instance.StartCoroutine(UnLoadAllAB(callback, unloadObjects));
    }

    /// <summary>
    /// 卸载全部AB包携程
    /// </summary>
    /// <param name="callback"></param>
    /// <param name="unloadObjects"></param>
    /// <returns></returns>
    private IEnumerator UnLoadAllAB(UnityAction callback, bool unloadObjects)
    {
        //等待所有的AB包加载完成后卸载
        while (loadingName.Count > 0) yield return null;
        AssetBundle.UnloadAllAssetBundles(unloadObjects);
        abDict.Clear();
        callback?.Invoke();
    }
}

Unity编辑器扩展-PrefabUtility公共类

PrefabUtility公共类是什么

它是 Unity 编辑器中的一个公共类

提供了一些用于处理 Prefab(预制体或称预设体)的方法

主要功能包括 实例化预制体、创建预制体、修改预制体 等等

只要有对预制体操作的相关需求

它可以在编辑器开发的任何地方对其进行使用

常用API

动态创建预设体

路径从Assets/…开始

//  PrefabUtility.SaveAsPrefabAsset(GameObject对象, 路径);
if(GUILayout.Button("动态创建预设体"))
{
    GameObject obj = new GameObject();
    obj.AddComponent<Rigidbody>();
    obj.AddComponent<BoxCollider>();
    PrefabUtility.SaveAsPrefabAsset(obj, "Assets/Resources/TestObj.prefab");
    DestroyImmediate(obj);
}

加载预制体对象

不能用于创建,一般用于修改,会把预设体加载到内存中

路径从Assets/…开始

//这里的加载 本质上其实已经把预设体进行了实例化了 只不过该实例化对象并不是在传统的Scene窗口中(是在一个看不见的独立的场景中)
if (GUILayout.Button("加载预制体对象"))
{
    //加载 到内存中 不能用来实例化 一般加载出来是进行修改的
    GameObject testObj = PrefabUtility.LoadPrefabContents("Assets/Resources/TestObj.prefab");
    testObj.AddComponent<MeshRenderer>();
    //这种加载方式 是可以进行脚本移除 子对象创建的
    DestroyImmediate(testObj.GetComponent<MeshRenderer>());
    GameObject obj = new GameObject("新建子对象");
    obj.transform.parent = testObj.transform;
    PrefabUtility.SaveAsPrefabAsset(testObj, "Assets/Resources/TestObj.prefab");
    //释放 一定要配合使用
    PrefabUtility.UnloadPrefabContents(testObj);
}

修改已有预设体

配合AssetDatabase.LoadAssetAtPath使用

Unity编辑器扩展-AssetImporter和AssetPostprocessor

AssetPostprocessor是什么?

AssetPostprocessor(资源后处理器类)

它主要用于处理资源导入时的通用逻辑

我们可以通过继承该类实现其中的一些回调方法来自定义处理资源

我们一般会进行以下处理:

  1. 进行某种类型资源的通用设置
  2. 对某种类型资源进行统一批量的处理

继承它后的常用属性:

  • AssetImporter assetImporter:对应类型的资源导入器对象
  • string assetPath: 导入资源的路径

继承它后的常用回调方法有:

纹理相关

  • void OnPreprocessTexture()

    导入纹理资源之前调用,允许修改纹理的导入设置

  • void OnPostprocessTexture(Texture2D texture)

    导入纹理资源之后调用,允许你对导入后为其进行后处理,比如 修改纹理格式、尺寸、压缩等等

模型相关

  • void OnPreprocessModel()

    导入模型资源之前调用,允许修改纹理的导入设置

  • void OnPostprocessModel(GameObject obj)

    导入模型资源之后调用,允许你对导入后为其进行后处理,比如 修改网格、材质、动画等

音频相关

  • void OnPreprocessAudio()

    导入音频资源之前调用,允许修改纹理的导入设置

  • void OnPostprocessAudio(AudioClip clip)

    导入音频资源之后调用,允许你对导入后为其进行后处理,比如 修改音频格式、质量等

等等等等

更多内容:https://docs.unity3d.com/ScriptReference/AssetPostprocessor.html

注意: 如果只想对某种资源中的某些内容进行处理 可以自己加命名规则

public class Test : AssetPostprocessor
{
     //纹理相关
    // 导入纹理资源之前调用,允许修改纹理的导入设置
    void OnPreprocessTexture()
    {
        //1.资源导入批量设置(对导入的资源进行统一设置)
        Debug.Log("纹理设置回调" + assetPath);
        TextureImporter improter = assetImporter as TextureImporter;
        improter.textureType = TextureImporterType.Sprite;
        improter.mipmapEnabled = false;
    }
    //  导入纹理资源之后调用,允许你对导入后为其进行后处理,比如 修改纹理格式、尺寸、压缩等等
    void OnPostprocessTexture(Texture2D texture)
    {
        //2.资源导入后处理(对导入的资源进行统一的预处理)
        Debug.Log("纹理后处理回调" + texture.name);
        EditorUtility.CompressTexture(texture, TextureFormat.ETC_RGB4, TextureCompressionQuality.Fast);
    }

    //模型相关
    //  导入模型资源之前调用,允许修改纹理的导入设置
    void OnPreprocessModel()
    {
        ModelImporter improter = assetImporter as ModelImporter;
    }
    //  导入模型资源之后调用,允许你对导入后为其进行后处理,比如 修改网格、材质、动画等
    void OnPostprocessModel(GameObject obj)
    {

    }

    //音频相关
    //  导入音频资源之前调用,允许修改纹理的导入设置
    void OnPreprocessAudio()
    {
        AudioImporter improter = assetImporter as AudioImporter;

    }
    //  导入音频资源之后调用,允许你对导入后为其进行后处理,比如 修改音频格式、质量等
    void OnPostprocessAudio(AudioClip clip)
    {

    }
    
}

AssetImporter公共类

特定资源类型的资源导入程序的基类

Unity编辑器扩展-CompilationPipeline公共类

CompilationPipeline公共类是用来做什么的

它是 Unity 编辑器中的一个公共类,用于处理代码编译相关的操作和事件的

对于我们来说,我们最常用的是利用它得知代码是否编译结束

比如动态生成脚本时,我们需要在编译结束后才能使用新的脚本

常用API

当一个程序集编译结束会主动调用的回调函数

传入的两个参数分别是: 1. string arg1 : 编译完成的程序集名 1. CompilerMessage[] arg2 : 编译完成后产生的编译消息数组,包括编译警告和错误信息

private void OnEnable()
{
    CompilationPipeline.assemblyCompilationFinishedCompilationPipeline_assemblyCompilationFinished;      
}
private void CompilationPipeline_assemblyCompilationFinished(string arg1, CompilerMessage[] arg2)
{
Debug.Log("程序集名:" + arg1);
}

当所有程序集编译结束会主动调用的回调函数

参数:

object obj: ActiveBuildStatus 活动生成状态对象

private void OnEnable()
{
    CompilationPipeline.compilationFinished += CompilationPipeline_compilationFinished;
}
private void CompilationPipeline_compilationFinished(object obj)
{
    Debug.Log("所有程序集编译结束"); 
}

Unity编辑器扩展-EditorApplication公共类

EditorApplication公共类是用来干什么的?

它是 Unity 编辑器中的一个公共类,它主要提供了一些和编辑器本身相关的一些功能

比如 编辑器事件监听(播放、暂停等)、生命周期判断(是否运行中、暂停中、编译中)

编辑器进入播放模式、退出播放模式 等等功能

常用API

监听编辑器事件

  • EditorApplication.update:每帧更新事件,可以用于在编辑器中执行一些逻辑
  • EditorApplication.hierarchyChanged:层级视图变化事件,当场景中的对象发生变化时触发
  • EditorApplication.projectChanged:项目变化事件,当项目中的资源发生变化时触发
  • EditorApplication.playModeStateChanged:编辑器播放状态变化时触发
  • EditorApplication.pauseStateChanged:编辑器暂停状态变化时触发
private void OnEnable()
{
    EditorApplication.update += ()=>
    {
        if(EditorApplication.isPlaying)
        {
            Debug.Log("处于运行播放状态");
        }
    };
}

管理编辑器生命周期相关

  • EditorApplication.isPlaying:判断当前是否处于游戏运行状态。
  • EditorApplication.isPaused:判断当前游戏是否处于暂停状态。
  • EditorApplication.isCompiling:判断Unity编辑器是否正在编译代码
  • EditorApplication.isUpdating:判断Unity编辑器是否正在刷新AssetDatabase

获取Unity应用程序路径相关

  • EditorApplication.applicationContentsPath:Unity安装目录Data路径
  • EditorApplication.applicationPath:Unity安装目录可执行程序路径

常用方法

退出Unity编辑器

if (GUILayout.Button("退出"))
{
    EditorApplication.Exit(0);
}

退出播放模式,切换到编辑模式

Unity编辑器扩展-AssetDatabase公共类

AssetDatabase公共类是用来做什么的?

它是 Unity 引擎中的一个编辑器类

用于在编辑器环境中管理和操作项目中的资源(Assets)

它提供了一系列静态方法

使得开发者能够在编辑器脚本中进行资源的创建、拷贝、移动、删除等操作

在哪里使用AssetDatabase公共类中的相关内容

在编辑器相关处都可以使用AssetDatabase公共类中的相关内容

它主要提供的是一些资源相关的辅助功能,可以在编辑器拓展开发的任意地方使用

但一定注意,它属于编辑器功能,无法被打包出去,只能在Unity编辑器中使用

AssetDatabase API

创建资源

我们可以通过代码动态创建一些资源

路径从 Assets/…开始

AssetDatabase.CreateAsset(资源,路径);

注意:

不能在StreamingAssets中创建资源,

不能使用此方法创建预设体

只能创建资源相关,例如材质球等

路径需要写后缀

if(GUILayout.Button("创建资源"))
{
    Material mat = new Material(Shader.Find("Specular"));
    AssetDatabase.CreateAsset(mat, "Assets/Resources/MyMaterial.mat");
}

创建文件夹

路径从 Assets/…开始

AssetDatabase.CreateFolder(父文件夹,新文件夹名)

if (GUILayout.Button("创建文件夹"))
{
    AssetDatabase.CreateFolder("Assets/Resources", "MyTestFolder");
}

拷贝资源

路径从 Assets/…开始

AssetDatabase.CopyAsset(源资源,目标路径)