unity 开发中10个小知识(三)

19 篇文章 0 订阅
4 篇文章 0 订阅

目录

一、urp 如何将其他相机放入其他相机栈

二、C# File.Open 函数参数详细说明

三、C# 双问号=(??=)的用法

四、C# 通过enum 字段名字获得枚举类型值

五、C#  通过DateTime,知道日期、时、秒 的字符串 获得总秒数 

六、 System.IO.IOException: Sharing violation on path 

七、C# StringComparison.OrdinalIgnoreCase

八、C# 函数前添加new 关键字

九、C# 获取程序集带有某个Attribute的类,并创建对象

十、unity中DrawMeshInstanced 使用实例


一、urp 如何将其他相机放入其他相机栈

build-in项目时相机的渲染顺序是通过摄像机的depth深度来相机的渲染顺序的,但是在urp项目中却不是,而是通过camera stack来管理

在Unity中,使用URP (Universal Render Pipeline)时,相机属性的设置都是通过相机栈(camera stack)来管理的。

将一个相机放入另一个相机的相机栈中,需要在Inspector窗口中对相机属性进行配置。具体步骤如下:

  1. 拖拽需要放入相机栈中的相机至想加入的相机的“Additional Camera”属性中。

  2. 选中相机,切换到“Camera Stacking” 栏目,勾选一个叫做“Override Scene Camera”的选项,表示强制该相机覆盖场景内的相机。

  3. 调整”Priority“属性,确保需要产品后处理的相机栈具有更高的优先级,优先级高会保证相机先被渲染,以便后续相机的后处理能够更有效地应用电。需要注意的是,排序值越低,越先被渲染。

对于同时存在多个相机的场景,在URP中利用相机栈的功能可以很方便地处理好多个相机的优先级问题,使其在渲染的时候实现预期的效果。

可以通过代码来创建和设置多相机相机栈。下面是一个简单的示例:

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class MultiCameraStack : MonoBehaviour
{
    // 定义相机
    public Camera camera1;
    public Camera camera2;

    private void Awake()
    {
        // 获取Active的URP RenderPipeline实例,用来管理相机栈
        var urpRP = (UniversalRenderPipeline)RenderPipelineManager.currentPipeline;

        // 创建相机栈
        var stack = new CameraStack();

        // 将相机添加到相机栈中,优先级越高,越先被渲染
        stack.Add(camera1, 1);
        stack.Add(camera2, 2);

        // 设置相机栈
        urpRP.cameraStack = stack;
    }
}

 以上代码是使用C#脚本的方式在URP中实现多相机的相机栈。在代码中,我们首先获取了当前渲染管线的实例,即本例中是Universal Render Pipeline实例。接着,我们定义了两个Camera的对象,并创建相机栈,并将相机添加到栈中。最终将相机栈应用到URP中。需要注意的是设置相机的优先级,以优先渲染渲染需要后处理的相机。

二、C# File.Open 函数参数详细说明

在C#中,File.Open()方法是用于打开文件的方法,它有多种重载方式,提供了各种选项以便于操作文件。以下是一些常见参数的详细说明。

  1. path

string path参数是要打开的文件的路径和文件名。这是一个必需的参数。路径可以是绝对路径(例如“C:\example.txt”)或相对路径(例如“./example.txt”)。路径可以是具体的文件路径也可以是文件夹路径。

  1. mode

FileMode mode参数表示要打开文件的模式。它是一个可选参数,默认值为FileMode.Open。FileMode枚举定义了以下几种模式:

  • FileMode.Append:在文件末尾进行写操作,如果文件不存在则创建文件。
  • FileMode.Create:新建文件,如果文件已存在,则覆盖。
  • FileMode.CreateNew:新建文件,但必须不存在。
  • FileMode.Open:以只读方式打开文件,如果文件不存在,则抛出异常。
  • FileMode.OpenOrCreate:打开文件,如果文件不存在则新建文件。
  • FileMode.Truncate:截断文件并进行写入操作,如果文件不存在则抛出异常。
  1. access

FileAccess access参数表示要打开文件的访问权限。它是一个可选参数,默认值为FileAccess.ReadWrite。FileAccess枚举定义了以下几种访问权限:

  • FileAccess.Read:读访问。
  • FileAccess.Write:写访问。
  • FileAccess.ReadWrite:读写访问。
  1. share

FileShare share参数表示要打开文件的分享模式。它是一个可选参数,默认值为FileShare.None。FileShare枚举定义了以下几种分享模式:

  • FileShare.None:不分享文件。
  • FileShare.Read:分享文件以供其他进程读取。
  • FileShare.Write:分享文件以供其他进程写入。
  • FileShare.ReadWrite:分享文件以供其他进程读写。
  1. buffer size

int bufferSize参数表示用于进行读写操作的缓冲区大小。它是一个可选参数,默认值为4096字节。在读写大文件时,可以提高性能。

  1. options

FileOptions options参数表示打开文件的一些高级选项。它是一个可选参数,默认值为FileOptions.None。FileOptions枚举定义了以下几种选项:

  • FileOptions.Asynchronous:指示文件操作应该是异步的。
  • FileOptions.DeleteOnClose:指示文件在关闭时删除。
  • FileOptions.Encrypted:指示要对文件进行加密操作。
  • FileOptions.SequentialScan:指示文件在打开后应该进行顺序扫描。
  • FileOptions.WriteThrough:指示文件应该直接写入磁盘,而不是缓存到内存。

综上所述,通过使用File.Open()方法的各种参数,我们可以灵活地打开文件,并使用各种选项进行高级操作。

三、C# 双问号=(??=)的用法

C# ??=是C# 8.0中引入的空值合并赋值运算符。它相当于以下语法的语法糖:

x = y ?? x;

其中,如果y不为null,则将y的值赋值给x;如果y为null,则x的值保持不变。 也就是说,当左操作数的值为null时,才会使用右操作数的值进行赋值运算。

例如,如果x和y是字符串,可以这样使用空值合并赋值运算符:

string x = null;
string y = "hello";
x ??= y;
Console.WriteLine(x); // output: hello

此时x被赋值为y的值,即"hello"。如果更改y的值,则x的值不会受到影响:

y = "world";
Console.WriteLine(x); // output: hello

y = "world";
Console.WriteLine(x); // output: hello

四、C# 通过enum 字段名字获得枚举类型值

在C#中,可以通过Enum.Parse()方法将枚举的名称转换为枚举类型的值。这个方法的基本语法如下:

public static object Parse(Type enumType, string value);

其中,enumType参数是要转换成的枚举类型,value参数是要转换的枚举名称。可以通过调用返回结果的类型转换将其转换为实际的枚举值。

例如,假设我们有一个名为Color的枚举类型,其中包含了三种颜色:红色、绿色和蓝色:

enum Color
{
    Red,
    Green,
    Blue
}

我们可以使用Enum.Parse()方法将颜色名称转换为枚举类型的值: 

Color color = (Color)Enum.Parse(typeof(Color), "Green");

在上述代码中,我们首先使用typeof()操作符获取枚举类型,然后将要转换的颜色名称作为字符串传递给Enum.Parse()方法,返回值是object类型的,通过强制类型转换实现转换为对应枚举类型的值。

此时color变量会被赋值为Color.Green,表示绿色。

需要注意的是,如果我们使用Enum.Parse()方法时传入了一个无效的枚举名称,则会抛出ArgumentException异常。因此,在使用此方法进行转换时,应该确保要转换的名称是存在于枚举类型中的,或者使用合适的错误处理措施来处理无效名称的情况。

总之,Enum.Parse()方法是将枚举名称转换为枚举类型的值的简单有效方法。在实际编程中,可以使用此方法来提高代码的灵活性和可维护性。

五、C#  通过DateTime,知道日期、时、秒 的字符串 获得总秒数 

在C#中,可以将日期、时和秒的字符串合并为一个日期时间字符串,然后使用DateTime.ParseExact()方法将字符串转换为DateTime类型的对象,最后使用TimeSpan结构的TotalSeconds属性获取总秒数。下面是一个示例代码:

string dateString = "2022-11-01 12:05:30";
string format = "yyyy-MM-dd HH:mm:ss";
DateTime dateTime = DateTime.ParseExact(dateString, format, CultureInfo.InvariantCulture);
double seconds = dateTime.TimeOfDay.TotalSeconds;

在上述代码中,我们首先定义了一个字符串dateString,它表示日期、时和秒的字符串。然后,我们定义了一个格式字符串format,它指示日期时间字符串的格式。

接下来,我们调用DateTime.ParseExact()方法,将日期时间字符串和格式字符串作为参数传递。此方法将返回一个DateTime对象,包含了日期和时间的信息。

最后,我们使用从DateTime对象中获取的TimeOfDay属性获取时间部分的TimeSpan对象,然后调用其TotalSeconds属性获取总秒数。

需要注意的是,在使用DateTime.ParseExact()方法进行转换时,应该确保日期时间字符串和格式字符串匹配。如果格式字符串与日期时间字符串不匹配,则将抛出FormatException异常。因此,在实际编程中,应该确保提供正确的格式化字符串。

总之,将日期、时和秒的字符串合并为一个日期时间字符串,然后使用DateTime.ParseExact()方法将字符串转换为DateTime类型的对象,最后使用TimeSpan结构的TotalSeconds属性获取总秒数,是一种简单有效的方法。

六、 System.IO.IOException: Sharing violation on path 

C# 文件可能正在打开,如果想要可以被多个进程访问,可以使用FileShare枚举来指定允许多个进程同时访问文件。下面是一个示例代码,展示如何同时打开一个文件并向其写入内容。

using (FileStream stream = new FileStream("data.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
{
    using (StreamWriter writer = new StreamWriter(stream))
    {
        writer.WriteLine("This text is written by the first process.");
        writer.Flush();
    }
}

using (FileStream stream = new FileStream("data.txt", FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
    using (StreamWriter writer = new StreamWriter(stream))
    {
        writer.WriteLine("This text is written by the second process.");
        writer.Flush();
    }
}

在上述代码中,我们首先使用FileMode.OpenOrCreate模式和FileAccess.Write访问模式打开文件,将其作为FileStream对象stream打开,并设置FileShare枚举为FileShare.ReadWrite,允许多个进程共享该文件。

然后,在第一个using块中,我们使用StreamWriter对象writer向该文件写入一些文本,并使用writer.Flush()方法将缓冲区中的数据刷新到磁盘中。

接着,在第二个using块中,我们使用FileMode.Append模式打开同样的文件,同时使用FileAccess.Write访问模式和FileShare.ReadWrite共享模式,将其作为另一个FileStream对象stream打开。然后,我们使用StreamWriter对象writer向该文件中添加一些文本,并再次使用writer.Flush()方法将缓冲区中的数据刷新到磁盘中。

需要注意的是,两个using块中要使用不同的访问方式,以确保每个块内的StreamWriter对象是独立且具有自己的缓冲区。否则,数据可能会混淆或丢失。

总之,使用FileShare枚举可以让多个进程同时访问同一个文件。在实际编程中,可以使用这个特性,充分发挥多个进程之间的并发性能。

七、C# StringComparison.OrdinalIgnoreCase

C#中,StringComparison.OrdinalIgnoreCase表示字符串比较时忽略大小写。这个能力很有用,因为当我们想要比较两个字符串是否相等时,如果我们只关心两个字符串的内容而不关心大小写,这时忽略大小写是有帮助的。

例如,我们可以使用StringComparison.OrdinalIgnoreCase来检查用户名。在用户注册时,我们会验证用户名是否已经存在于数据库中。如果我们不考虑大小写,可以使用以下代码:

string username = "johnDoe";
if (db.Table<User>().Any(u => u.Username.Equals(username, StringComparison.OrdinalIgnoreCase)))
{
    Console.WriteLine("This username is already taken.");
}

如果我们不使用StringComparison.OrdinalIgnoreCase,则我们需要把字符串转换成小写或者大写形式 

if (db.Table<User>().Any(u => u.Username.ToLower() == username.ToLower()))
{
    Console.WriteLine("This username is already taken.");
}

这两种方式的结果是一样的,但是使用StringComparison.OrdinalIgnoreCase能使代码更为简洁易读。

在C#中,StringComparison枚举提供了一些选项,可以用于字符串比较,包括:

  • CurrentCulture: 使用当前区域信息进行比较。
  • CurrentCultureIgnoreCase:使用当前区域信息进行比较,忽略大小写。
  • InvariantCulture: 使用不受区域信息影响的比较规则。
  • InvariantCultureIgnoreCase:使用不受区域信息影响的比较规则,忽略大小写。
  • Ordinal: 按照ASCII值进行比较。
  • OrdinalIgnoreCase:按照ASCII值进行比较,忽略大小写。

使用 StringComparison.OrdinalIgnoreCase通常适用于比较字符串时不需要包含区域信息的场景,可以提高代码的效率和简洁性。

八、C# 函数前添加new 关键字

在C#中,函数前添加new关键字可以用于隐藏从基类继承的同名函数,这个过程被称为成员隐藏。

在派生类中,如果定义了与基类中同名函数的函数,则新定义的函数将隐藏基类中的函数。默认情况下,编译器会在这种情况下产生警告,提示开发人员可能不想隐藏基类中的函数。在这种情况下,可以使用new关键字消除警告。

例如,假设有一个基类MyParentClass,其中包含一个函数ParentClassFunction():

class MyParentClass
{
   public void ParentClassFunction()
   {
      Console.WriteLine("Function from MyParentClass.");
   }
}

现在,派生类MyChildClass想要声明一个同样名称和参数的函数: 

class MyChildClass : MyParentClass
{
   public new void ParentClassFunction()
   {
      Console.WriteLine("Function from MyChildClass.");
   }
}

在MyChildClass中,ParentClassFunction()函数被重新定义并添加了new关键字。这样,MyChildClass就隐藏了从基类中继承的同名函数。

使用new关键字可以帮助开发人员在重新定义函数时产生警告,并在必要时消除警告,但是需要慎重使用。使用过多new关键字可能会导致代码可读性降低或产生其他意外效果。建议使用override关键字来复写基类中的函数。

九、C# 获取程序集带有某个Attribute的类,并创建对象

在 C# 中,我们可以使用反射(Reflection)机制来获取程序集中带有某个属性(Attribute)的类,并创建对象。下面是实现的示例代码:

首先,我们需要定义一个继承于 System.Attribute 的自定义属性类 MyAttribute,用于标记需要查找的类:

[AttributeUsage(AttributeTargets.Class)]
public class MyAttribute : Attribute
{
    public MyAttribute() {}
}

然后,在程序集中创建多个类,并在其中加入 MyAttribute 属性,如下所示: 

[MyAttribute]
public class MyClassA
{
    public MyClassA() {}
    public void Say() { Console.WriteLine("This is MyAttribute A"); }
}

[MyAttribute]
public class MyClassB
{
    public MyClassB() {}
    public void Say() { Console.WriteLine("This is MyAttribute B"); }
}

public class MyClassC
{
    public MyClassC() {}
    public void Say() { Console.WriteLine("This is MyClass C"); }
}

 在 main 函数中,我们可以使用反射查找程序集中带有 MyAttribute 属性的类,并创建它们的实例。

static void Main(string[] args)
{
    Assembly asmb = Assembly.GetExecutingAssembly(); // 获取当前程序集
    Type[] types = asmb.GetTypes(); // 获取所有类

    foreach (Type t in types)
    {
        if (Attribute.IsDefined(t, typeof(MyAttribute))) // 判断是否带有 MyAttribute
        {
            object obj = Activator.CreateInstance(t); // 创建对象
            if (obj is MyClassA) ((MyClassA)obj).Say(); // 判断并调用方法
            if (obj is MyClassB) ((MyClassB)obj).Say();
        }
    }
}

上述代码中,我们使用 Attribute.IsDefined 方法查找带有 MyAttribute 属性的类,并使用 Activator.CreateInstance 方法创建类的实例,最后根据对象类型调用相应的 Say 方法。

总之,反射机制可以帮助我们获取程序集中特定的类,并在必要时创建对象。使用自定义属性可以帮助我们为想要处理的类打上标记,从而方便地查找和操作这些类。

十、unity中DrawMeshInstanced 使用实例

DrawMeshInstanced 是 Unity 引擎的一个函数,可以在屏幕上绘制实例化的网格,比普通多次绘制网格具有更高的性能。实例化绘制允许开发者使用相对较少的绘制调用来渲染大量相似(但不完全相同)的网格。

使用 DrawMeshInstanced 函数需要开发者完成以下四个步骤:

  1. 定义绘制的网格 Mesh。这是每个实例都需要使用的共享绘制数据。

  2. 定义映射到每个实例的数据。这些数据可能包括每个实例的世界矩阵、颜色、大小或其他自定义属性。

  3. 实现 Shader 来渲染网格。Shader 必须能够接受每个实例的数据,以样本各自属性。

  4. 调用 DrawMeshInstanced 函数。该函数需要传入 Mesh,以及映射到每个实例的数据。还需要指定实例的总数。

下面是一个使用 DrawMeshInstanced 函数的示例代码:

using UnityEngine;

public class Example : MonoBehaviour
{
    public Mesh mesh; // 网格
    public Material material; // 材质
    public int instanceCount = 1000; // 实例数

    private Matrix4x4[] matrices; // 存储世界矩阵的数组

    private void Start()
    {
        // 初始化世界矩阵数组
        matrices = new Matrix4x4[instanceCount];
        for (int i = 0; i < matrices.Length; i++)
        {
            matrices[i] = Matrix4x4.TRS(
                new Vector3(Random.Range(-10f, 10f), Random.Range(-10f, 10f), Random.Range(-10f, 10f)),
                Quaternion.Euler(Random.Range(0f, 360f), Random.Range(0f, 360f), Random.Range(0f, 360f)),
                new Vector3(Random.Range(0.5f, 1.5f), Random.Range(0.5f, 1.5f), Random.Range(0.5f, 1.5f))
            );
        }
    }

    private void Update()
    {
        // 绘制实例化网格
        Graphics.DrawMeshInstanced(mesh, 0, material, matrices, instanceCount);
    }
}

在上述代码中,我们使用随机数据创建了多个矩阵,每个矩阵代表一个独立的实例。然后,我们在每帧 update 中使用 DrawMeshInstanced 函数绘制所有实例。

最后需要注意的是,仅在使用大量的相似网格实例时才使用此技术才能带来更好的性能。如果在某些情况下仅需要绘制少量的网格或完全相同的网格,可以考虑使用普通的网格绘制方式。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值