目录
五、C# 通过DateTime,知道日期、时、秒 的字符串 获得总秒数
六、 System.IO.IOException: Sharing violation on path
七、C# StringComparison.OrdinalIgnoreCase
九、C# 获取程序集带有某个Attribute的类,并创建对象
十、unity中DrawMeshInstanced 使用实例
一、urp 如何将其他相机放入其他相机栈
build-in项目时相机的渲染顺序是通过摄像机的depth深度来相机的渲染顺序的,但是在urp项目中却不是,而是通过camera stack来管理
在Unity中,使用URP (Universal Render Pipeline)时,相机属性的设置都是通过相机栈(camera stack)来管理的。
将一个相机放入另一个相机的相机栈中,需要在Inspector窗口中对相机属性进行配置。具体步骤如下:
-
拖拽需要放入相机栈中的相机至想加入的相机的“Additional Camera”属性中。
-
选中相机,切换到“Camera Stacking” 栏目,勾选一个叫做“Override Scene Camera”的选项,表示强制该相机覆盖场景内的相机。
-
调整”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()方法是用于打开文件的方法,它有多种重载方式,提供了各种选项以便于操作文件。以下是一些常见参数的详细说明。
- path
string path
参数是要打开的文件的路径和文件名。这是一个必需的参数。路径可以是绝对路径(例如“C:\example.txt”)或相对路径(例如“./example.txt”)。路径可以是具体的文件路径也可以是文件夹路径。
- mode
FileMode mode
参数表示要打开文件的模式。它是一个可选参数,默认值为FileMode.Open。FileMode枚举定义了以下几种模式:
FileMode.Append
:在文件末尾进行写操作,如果文件不存在则创建文件。FileMode.Create
:新建文件,如果文件已存在,则覆盖。FileMode.CreateNew
:新建文件,但必须不存在。FileMode.Open
:以只读方式打开文件,如果文件不存在,则抛出异常。FileMode.OpenOrCreate
:打开文件,如果文件不存在则新建文件。FileMode.Truncate
:截断文件并进行写入操作,如果文件不存在则抛出异常。
- access
FileAccess access
参数表示要打开文件的访问权限。它是一个可选参数,默认值为FileAccess.ReadWrite
。FileAccess枚举定义了以下几种访问权限:
FileAccess.Read
:读访问。FileAccess.Write
:写访问。FileAccess.ReadWrite
:读写访问。
- share
FileShare share
参数表示要打开文件的分享模式。它是一个可选参数,默认值为FileShare.None。FileShare枚举定义了以下几种分享模式:
FileShare.None
:不分享文件。FileShare.Read
:分享文件以供其他进程读取。FileShare.Write
:分享文件以供其他进程写入。FileShare.ReadWrite
:分享文件以供其他进程读写。
- buffer size
int bufferSize
参数表示用于进行读写操作的缓冲区大小。它是一个可选参数,默认值为4096字节。在读写大文件时,可以提高性能。
- 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 函数需要开发者完成以下四个步骤:
-
定义绘制的网格 Mesh。这是每个实例都需要使用的共享绘制数据。
-
定义映射到每个实例的数据。这些数据可能包括每个实例的世界矩阵、颜色、大小或其他自定义属性。
-
实现 Shader 来渲染网格。Shader 必须能够接受每个实例的数据,以样本各自属性。
-
调用 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
函数绘制所有实例。
最后需要注意的是,仅在使用大量的相似网格实例时才使用此技术才能带来更好的性能。如果在某些情况下仅需要绘制少量的网格或完全相同的网格,可以考虑使用普通的网格绘制方式。