10 little knowledge in unity development (3)

Table of contents

1. How urp puts other cameras into other camera stacks

2. Detailed description of C# File.Open function parameters

3. Usage of C# double question mark =(??=)

4. C# obtains the enumeration type value through the enum field name

5. C# uses DateTime to know the string of date, hour and second to get the total number of seconds 

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

七、C# StringComparison.OrdinalIgnoreCase

8. Add new keyword before C# function

9. C# gets the class with an Attribute in the assembly and creates an object

Ten, DrawMeshInstanced usage examples in unity


1. How urp puts other cameras into other camera stacks

The rendering order of the camera is determined by the depth of the camera during the build-in project, but it is not in the urp project, but managed by the camera stack

In Unity, when using URP (Universal Render Pipeline), the settings of camera properties are managed through the camera stack.

To put a camera into another camera's camera stack, you need to configure the camera properties in the Inspector window. Specific steps are as follows:

  1. Drag the camera that needs to be placed in the camera stack to the "Additional Camera" property of the camera you want to add.

  2. Select the camera, switch to the "Camera Stacking" column, and check an option called "Override Scene Camera", which means that the camera is forced to cover the camera in the scene.

  3. Adjust the "Priority" property to ensure that the camera stack that requires product post-processing has a higher priority. A high priority will ensure that the camera is rendered first, so that subsequent camera post-processing can apply power more effectively. It should be noted that the lower the order value, the earlier it will be rendered.

For scenes with multiple cameras at the same time, using the function of the camera stack in URP can easily handle the priority of multiple cameras, so that it can achieve the expected effect during rendering.

A multi-camera camera stack can be created and set up through code. Here is a simple example:

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;
    }
}

 The above code is to use C# script to implement multi-camera camera stack in URP. In the code, we first obtain the instance of the current rendering pipeline, which is the Universal Render Pipeline instance in this example. Next, we define two Camera objects, create a camera stack, and add the camera to the stack. Finally apply the camera stack to the URP. It should be noted that the priority of the camera is set to render the camera that needs post-processing first.

2. Detailed description of C# File.Open function parameters

In C#, the File.Open() method is used to open a file. It has multiple overloads and provides various options for easy file manipulation. Below are detailed descriptions of some common parameters.

  1. path

string pathThe arguments are the path and filename of the file to open. This is a required parameter. Paths can be absolute (eg "C:\example.txt") or relative (eg "./example.txt"). The path can be a specific file path or a folder path.

  1. mode

FileMode modeThe argument indicates the mode in which to open the file. It is an optional parameter with a default value of FileMode.Open. The FileMode enumeration defines the following modes:

  • FileMode.Append: Write at the end of the file, or create the file if it does not exist.
  • FileMode.Create: Create a new file, or overwrite if the file already exists.
  • FileMode.CreateNew: Create a new file, but it must not exist.
  • FileMode.Open: Open the file read-only, throwing an exception if the file does not exist.
  • FileMode.OpenOrCreate: Open the file, and create a new file if the file does not exist.
  • FileMode.Truncate: truncates the file and writes it, or throws an exception if the file does not exist.
  1. access

FileAccess accessThe parameter indicates the access rights of the file to be opened. It is an optional parameter with a default value of FileAccess.ReadWrite. The FileAccess enumeration defines the following access rights:

  • FileAccess.Read: Read access.
  • FileAccess.Write: write access.
  • FileAccess.ReadWrite: Read and write access.
  1. share

FileShare shareThe parameter indicates the sharing mode of the file to be opened. It is an optional parameter with a default value of FileShare.None. The FileShare enumeration defines the following sharing modes:

  • FileShare.None: Do not share the file.
  • FileShare.Read: Share the file for other processes to read.
  • FileShare.Write: Share the file for writing by other processes.
  • FileShare.ReadWrite: Share files for other processes to read and write.
  1. buffer size

int bufferSizeThe parameter indicates the size of the buffer used for read and write operations. It is an optional parameter with a default value of 4096 bytes. This can improve performance when reading and writing large files.

  1. options

FileOptions optionsParameters represent some advanced options for opening the file. It is an optional parameter with a default value of FileOptions.None. The FileOptions enumeration defines the following options:

  • FileOptions.Asynchronous: Indicates that the file operation should be asynchronous.
  • FileOptions.DeleteOnClose: Indicates that the file is deleted on close.
  • FileOptions.Encrypted: Indicates that the file is to be encrypted.
  • FileOptions.SequentialScan: Indicates that the file should be scanned sequentially after opening.
  • FileOptions.WriteThrough: Indicates that the file should be written directly to disk, rather than cached in memory.

To sum up, by using various parameters of the File.Open() method, we can flexibly open files and use various options for advanced operations.

3. Usage of C# double question mark =(??=)

C# ??= is the null coalescing assignment operator introduced in C# 8.0. It is equivalent to syntactic sugar for the following syntax:

x = y ?? x;

Among them, if y is not null, the value of y is assigned to x; if y is null, the value of x remains unchanged. In other words, when the value of the left operand is null, the value of the right operand is used for assignment.

For example, if x and y are strings, the null-coalescing assignment operator can be used like this:

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

At this point x is assigned the value of y, which is "hello". If you change the value of y, the value of x is not affected:

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

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

4. C# obtains the enumeration type value through the enum field name

In C#, Enum.Parse()methods can be used to convert the name of an enumeration to a value of the enumeration type. The basic syntax of this method is as follows:

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

Among them, enumTypethe parameter is the enumeration type to be converted to, and valuethe parameter is the enumeration name to be converted. It can be converted to an actual enum value by calling a cast on the returned result.

For example, suppose we have an Colorenum type called , which contains three colors: red, green, and blue:

enum Color
{
    Red,
    Green,
    Blue
}

We can Enum.Parse()convert a color name to a value of enum type using the method: 

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

In the above code, we first use typeof()the operator to obtain the enumeration type, and then pass the name of the color to be converted to Enum.Parse()the method as a string, and the return value is objectof type, which is converted to the value of the corresponding enumeration type through forced type conversion.

At this time, colorthe variable will be assigned a value Color.Green, which means green.

It should be noted that if we Enum.Parse()pass in an invalid enumeration name when using the method, ArgumentExceptionan exception will be thrown. Therefore, when using this method for conversion, you should ensure that the name to be converted exists in the enumeration type, or use appropriate error handling measures to handle invalid names.

In summary, Enum.Parse()methods are a simple and efficient way to convert an enum name to a value of the enum type. In actual programming, this method can be used to improve the flexibility and maintainability of the code.

5. C# DateTime,gets the total number of seconds by knowing the string of date, hour and second 

In C#, you can combine date, hour, and second strings into a datetime string, then use DateTime.ParseExact()the method to convert the string to DateTimean object of type, and finally use the properties TimeSpanof the structure to get the total seconds. TotalSecondsHere is a sample code:

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;

In the above code, we first define a string dateString, which represents the string of date, hour and second. Then, we define a format string format, which indicates the format of the datetime string.

Next, we call DateTime.ParseExact()the method, passing the datetime string and format string as parameters. This method will return an DateTimeobject containing date and time information.

Finally, we get the time portion of the object using the properties DateTimewe get from the object , then call its properties to get the total seconds.TimeOfDayTimeSpanTotalSeconds

It should be noted that when using DateTime.ParseExact()the method to convert, you should ensure that the datetime string and the format string match. An exception will be thrown if the format string does not match the datetime string FormatException. Therefore, in actual programming, you should ensure that the correct format string is provided.

In summary, it is a simple and effective way to combine date, hour and second strings into a datetime string, then use DateTime.ParseExact()methods to convert the string to DateTimean object of type, and finally use the properties TimeSpanof the structure to get the total seconds.TotalSeconds

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

The C# file may be open. If you want to be accessed by multiple processes, you can use FileSharean enumeration to specify that multiple processes are allowed to access the file at the same time. Below is a sample code showing how to simultaneously open and write to a file.

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();
    }
}

In the above code, we first open the file with FileMode.OpenOrCreatethe mode and FileAccess.Writeaccess mode, open it as FileStreaman object stream, and set FileSharethe enum to FileShare.ReadWriteallow multiple processes to share the file.

Then, in the first usingblock, we use StreamWriterthe object writerto write some text to the file and writer.Flush()the method to flush the data in the buffer to disk.

Then, in the second usingblock, we FileMode.Appendopen the same file using both FileAccess.Writeaccess and FileShare.ReadWriteshare modes, opening it as another FileStreamobject stream. We then use StreamWriterthe object writerto add some text to this file and use writer.Flush()the method again to flush the data in the buffer to disk.

It should be noted that usingdifferent access methods are used in the two blocks to ensure that StreamWriterthe objects in each block are independent and have their own buffers. Otherwise, data may be confused or lost.

In short, using FileShareenumeration allows multiple processes to access the same file at the same time. In actual programming, you can use this feature to give full play to the concurrency performance between multiple processes.

七、C# StringComparison.OrdinalIgnoreCase

In C#, StringComparison.OrdinalIgnoreCase indicates that case is ignored when comparing strings. This ability is useful because ignoring case is helpful when we want to compare two strings for equality if we only care about the contents of the two strings and not case.

For example, we can use StringComparison.OrdinalIgnoreCase to check username. When a user registers, we verify that the username already exists in the database. If we don't take case into consideration, the following code can be used:

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

If we don't use StringComparison.OrdinalIgnoreCase, we need to convert the string to lowercase or uppercase 

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

The result of these two methods is the same, but using StringComparison.OrdinalIgnoreCase can make the code more concise and readable.

In C#, the StringComparison enumeration provides some options that can be used for string comparison, including:

  • CurrentCulture: Use current locale information for comparison.
  • CurrentCultureIgnoreCase: Use the current locale information for comparison, ignoring case.
  • InvariantCulture: Use comparison rules that are not affected by locale information.
  • InvariantCultureIgnoreCase: Use comparison rules that are not affected by regional information, ignoring case.
  • Ordinal: Compare according to ASCII value.
  • OrdinalIgnoreCase: Compare according to the ASCII value, ignoring case.

Using StringComparison.OrdinalIgnoreCase is usually applicable to scenarios that do not need to include region information when comparing strings, which can improve the efficiency and simplicity of the code.

8. Add new keyword before C# function

In C#, adding newkeywords before functions can be used to hide functions with the same name inherited from base classes. This process is called member hiding.

In a derived class, if a function with the same name as a function in the base class is defined, the newly defined function will hide the function in the base class. By default, the compiler produces a warning in this case, suggesting that the developer might not want to hide functions in the base class. In this case, newthe warning can be suppressed using the keyword.

For example, suppose you have a base class MyParentClassthat contains a function ParentClassFunction():

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

Now, the derived class MyChildClasswants to declare a function with the same name and parameters: 

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

In MyChildClass, ParentClassFunction()the function is redefined and newthe keyword is added. In this way, MyChildClassthe function of the same name inherited from the base class is hidden.

Using newkeywords can help developers generate warnings when redefining functions, and suppress warnings when necessary, but they need to be used with caution. Using too many newkeywords can reduce code readability or have other unintended effects. It is recommended to use overridekeywords to override functions in base classes.

9. C# gets the class with an Attribute in the assembly and creates an object

In C#, we can use the reflection (Reflection) mechanism to obtain the class with a certain attribute (Attribute) in the assembly and create an object. Here is the sample code for the implementation:

First, we need to define a custom attribute class MyAttribute inherited from System.Attribute to mark the class to be searched:

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

Then, create multiple classes in the assembly and add the MyAttribute attribute to them, as follows: 

[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"); }
}

 In the main function, we can use reflection to find classes with the MyAttribute attribute in the assembly and create instances of them.

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();
        }
    }
}

In the above code, we use  Attribute.IsDefined the method to find  MyAttribute the class with attributes, and use  Activator.CreateInstance the method to create an instance of the class, and finally call the corresponding  Say method according to the object type.

In short, the reflection mechanism can help us obtain specific classes in the assembly and create objects when necessary. Using custom attributes can help us mark the classes we want to handle, so that we can easily find and manipulate these classes.

Ten, DrawMeshInstanced usage examples in unity

DrawMeshInstanced is a function of the Unity engine that can draw an instanced mesh on the screen, which has higher performance than ordinary multi-draw mesh. Instanced drawing allows developers to render large numbers of similar (but not identical) meshes using relatively few draw calls.

Using the DrawMeshInstanced function requires developers to complete the following four steps:

  1. Define the grid Mesh to draw. This is the shared drawing data that every instance needs to use.

  2. Defines the data mapped to each instance. This data may include each instance's world matrix, color, size, or other custom properties.

  3. Implement Shader to render the mesh. Shaders must be able to accept per-instance data to sample their respective properties.

  4. Call the DrawMeshInstanced function. The function needs to pass in the Mesh, as well as the data mapped to each instance. You also need to specify the total number of instances.

Here is a sample code using the DrawMeshInstanced function:

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);
    }
}

In the above code, we created multiple matrices with random data, each representing a separate instance. We then use  DrawMeshInstanced the function update every frame to draw all instances.

As a final note, using this technique will only lead to better performance when using a large number of similar mesh instances. If you only need to draw a small number of meshes or exactly the same mesh in some cases, you can consider using the normal mesh drawing method.

Guess you like

Origin blog.csdn.net/lejian/article/details/131269856