0831学习 agile Posted on Aug 31 2023 面试 unity基础 ##unity坐标系 --- 屏幕坐标系 Screen Space - 屏幕坐标系是建立在屏幕上的二维坐标系。 - 以像素来定义的,屏幕的左下角为(0,0),右上角为(Screen.width, Screen.height),z轴的坐标是相机的世界坐标中z轴坐标的负值。 - 鼠标位置坐标属于屏幕坐标,通过Input.mousePosition可以获得该位置的坐标。 - 屏幕也为屏幕坐标,Input.GetTouch(0).position可以获得单个手指触摸屏幕时手指的坐标。 - Screen.width并不一定等于Camera.pixelWidth。只有camera的ViewportRect中的width为1才一样,height类似 --- 视口坐标系 ViewPort Space - 视口坐标系是将Game视图的屏幕坐标系单位化,左下角(0,0),右上角(1,1)。z轴的坐标是相机的世界坐标中z轴坐标的负值。 - 利用比例可以方便地控制点在屏幕内的位置,而不用理会屏幕的实际大小变化,常用于自适应 --- ##坐标系之间的变换 --- 全局坐标系和局部坐标系 - `transform.Translate(translation:Vector3, relativeTo: Space = Space.Self)`:沿着translation的方向移动`|translation|`的距离,其结果将应用到relativeTo坐标系中。如果relativeTo为空,则默认为局部坐标系。 - `Transform.TransformPoint(Vector3 position)`:将一个坐标点从局部坐标系转换到全局坐标系 - `Transform.InverseTransformPoint(Vector3 position)`:将坐标点从全局坐标系转换到局部坐标系 - `Transform.TransformDirection(Vector3 direction)`:将一个方向从局部坐标系转换到全局坐标系 - `Transform.InverseTransformDirection(Vector3 direction)`:将一个方向从全局坐标系转换到局部坐标系 - `Transform.TransformVector(Vector3 vector)`:将一个向量从局部坐标系转换到全局坐标系 - `Transform.InverseTransformVector(Vector3 vector)`:将一个向量从全局坐标系转换到局部坐标系 - `Transform.forward`, `Transform.right`, `Transform.up`:当前物体的物体坐标系的z轴,x轴,y轴在`世界坐标系上的指向`。Vector3.forward ,(0,0,1)的缩写。在transform.Translate()中使用时,如果不表明坐标系,则为物体的局部坐标,即物体自身的正前方。Vector3.right,(1,0,0)的缩写。•Vector3.up ,(0,1,0)的缩写 --- 屏幕坐标系与全局坐标系 - `Camera.ScreenToWorldPoint(Vector3 position)`:将屏幕坐标转换为全局坐标 - `Camera.WorldToScreenPoint(Vector3 position)`:将全局坐标转换为屏幕坐标 - `Input.mousePosition`:获得鼠标在屏幕坐标系中的坐标 --- 屏幕坐标系与视口坐标系 - `Camera.ScreenToViewportPoint(Vector3 position)`:将屏幕坐标转换为视口坐标 - `Camera.ViewportToScreenPoint(Vector3 position)`:将视口坐标转换为屏幕坐标 --- 全局坐标系与视口坐标系 - `Camera.WorldToViewportPoint(Vector3 position)`:将全局坐标转换为视口坐标 - `Camera.ViewportToWorldPoint(Vector3 position)`:将视口坐标转换为全局坐标 --- 向量 - `Vector3.magnitude`:向量的长度 - `Vector3.sqrMagnitude`:向量长度的平方 - `Vector3.Distance(A,B)`:2个点A,B之间的距离。等同于`(B-A).magnitude`或`(A-B).magnitude` - `Vector3.Angle`:计算两个向量的夹角,0-180 - `Vector2.Dot`:向量的点乘。a·b=(ax,ay)·(bx,by)=(axbx+ayby)或a·b= |a||b|cosθ - `Vector3.Cross`:向量的叉积 ![v2-7a00a8f4a70783e52d51c1e1f0c9cdfe_b.jpg](https://tools.nxcloud.club:12500/images/2023/08/31/v2-7a00a8f4a70783e52d51c1e1f0c9cdfe_b.jpg) --- ##Camera.main.WorldToScreenPoint实现 --- ```C# Vector3 ManualWorldToScreenPoint(Vector4 localPos, Matrix4x4 localToWorldMatrix) { Camera cam = Camera.main; localPos.w = 1; var woldPos = localToWorldMatrix * localPos; var v = cam.worldToCameraMatrix; var p = cam.projectionMatrix; //mvp=>因为unity是从右边向左顺序 var temp = (p * v) * woldPos; if (temp.w == 0f) { // point is exactly on camera focus point, screen point is undefined // unity handles this by returning 0,0,0 return Vector3.zero; } // convert x and y from clip space to window coordinates temp.x = (temp.x / temp.w + 1f) * .5f * cam.pixelWidth; temp.y = (temp.y / temp.w + 1f) * .5f * cam.pixelHeight; //dis 是unity源码中这么处理 var dir = (Vector3)woldPos - cam.transform.position; var forward = cam.transform.forward; var dis = Vector3.Dot(dir, forward); return new Vector3(temp.x, temp.y, dis); } ``` --- 齐次除法和屏幕映射的过程使用下面的公式: ![Snip20230831_56.png](https://tools.nxcloud.club:12500/images/2023/08/31/Snip20230831_56.png) --- ##Struct和Class的区别 --- - class是引用类型,struct是值类型 - class作为参数类型传递,传递的是引用地址,struct作为参数类型传递,传递的是值,引用地址也是值,也就是说参数传递都是值 - `struct类型包含隐式的默认无参构造函数,且给所有的成员赋上默认值。如果显式声明struct的默认构造函数,则会报错,但是可以声明struct的带参数的构造函数`。类中没有声明任何构造函数,那么系统会默认创建一个无参的构造函数,而当你定义了带参数的构造函数以后,系统不会创建无参构造函数,这时候,如果你还想允许无参构造,就必须显式的声明一个 - class创建实例必须用new关键字,而struct可以用new,也可以不用new,区别在于用new生成的struct中,struct的成员是有初始值的,但在不使用new的情况下创建出的实例,如果需要引用某个字段,需要先给该字段初始化 - class支持继承,struct不支持继承,但支持接口,所有结构体都直接继承于抽象类 `System.ValueType`,`System.ValueType` 又继承于 `System.Object` - 不允许对结构体使用抽象(`abstract`)和密封(`sealed`)修饰符,也不允许对结构体成员使用 `protected` 或 `protected internal` 修饰符 - 结构体中的`函数成员`不能是抽象的(`abstract`)或虚的(`virtual`),重写(`override`)修饰符`只允许重写`从 `System.ValueType` 继承的方法,如`Equals(object obj)`,`GetHashCode()`,`ToString()` - struct中有struct属性,如果要直接内部struct字段的字段,不能直接改 ```C# TestStruct ts = default; var struct2 = ts.Struct2; //直接赋值修改有问题,因为ts.Strict2为临时变量 // ts.Struct2.C = 30; struct2.C = 30; //c为0 Debug.Log($"Struct2.C:===first==>{ts.Struct2.C}"); ts.Struct2 = struct2; //c为30 Debug.Log($"Struct2.C:===second==>{ts.Struct2.C}"); ``` - class的成员变量可以在声明时候赋初始值,而在struct声明中,除非字段被声明为 const 或 static,否则无法初始化 ```C# public struct Coords { public double x = 4; //错误, 结构体中初始化器不允许实例字段设定初始值 public static double y = 5; // 正确 public static double z { get; set; } = 6; // 正确 } ``` - 两个结构体实例的比较是基于值的比较,而类实例的比较则是对其引用的比较(没有实现equal方法) - 结构体可以用作 Nullable type(即:Nullable<T> 中的 T),对其赋值 null 值:` TestStruct? a = null;` --- ```C# [Serializable] public struct Nullable<T> where T : struct { private bool hasValue; internal T value; public Nullable(T value) { this.value = value; this.hasValue = true; } ... } ``` `Nullable<T>`, 它表示该类型是可以为空的一个类型。它被定义为一个`结构(struct)`而非一个类(class),C#中对于`Nullable<T>`可空类型有个简单写法:`T?`,例如:`int?` --- ###何时使用struct,何时使用class - 多数情况下,推荐使用class类,因为无论是类的赋值、作为参数类型传递,还是返回类的实例,实际拷贝的是托管堆上引用地址,也就大概4个字节,这非常有助于性能的提升 - 作为struct类型,无论是赋值,作为参数类型传递,还是返回struct类型实例,是完全拷贝,会占用栈上的空间。 在如下场景推荐使用struct: - 小于16个字节 - 偏向于值,是简单数据,而不是偏向于"面向对象" - 希望值不可变 - 它不会频繁地装箱和拆箱 --- ##值类型和引用类型区别 --- ###值类型 C#的所有值类型均隐式派生自`System.ValueType` - 结构体:struct(直接派生于System.ValueType) - 数值类型: - 整型:`sbyte(System.SByte的别名)`,`short(System.Int16)`,`int(System.Int32)`,`long(System.Int64)`,`byte(System.Byte)`,`ushort(System.UInt16)`,`uint(System.UInt32)`,`ulong(System.UInt64)`,`char(System.Char)` - 浮点型:`float(System.Single)`,`double(System.Double)` - 高精度decimal型:`decimal(System.Decimal)` - bool型:`bool(System.Boolean的别名)` - 用户自定义的结构体 - 可空类型(派生于`System.Nullable<T>`泛型结构体,`T?`实际上是`System.Nullable<T>`的别名) - 枚举:enum(派生于`System.Enum`) --- ####值类型总结 - 每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值 - 所有的值类型都是密封(seal)的,所以无法派生出新的值类型 - 引用类型和值类型都继承自`System.Object`类。不同的是,几乎所有的引用类型都直接从`System.Object`继承,而值类型则继承其子类,即 直接继承`System.ValueType` - 用Type.IsValueType属性来判断一个类型是否为值类型:`typeof(TestStruct).IsValueType` --- ###引用类型 --- C#有以下一些引用类型: - 数组(派生于`System.Array`) - 用户用定义的以下类型: - 类:class(派生于`System.Object`) - 接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。接口只是表示一种`约定`[contract]) - 委托:delegate(派生于`System.Delegate`) - object(`System.Object`的别名) - 字符串:string(`System.String`的别名) --- ###值类型部署在栈上,引用类型部署在托管堆上这句话有问题!!! --- - 数组都是引用类型,int[]数组也是引用类型,引用类型数组中的值类型元素位于堆中 - 引用类型部署在托管堆上 - 值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为`局部变量时,存储在栈上` - 闭包情况下的匿名函数和迭代器块将它们的局部变量做成了类的字段,从而存储在了`堆`上,所以这个`局部变量如果是值类型,那也是存储在堆上` 所以值类型部署在栈上有问题 --- ##总结 --- - 引用类型可以实现接口,值类型当中的结构体也可以实现接口 - 引用类型和值类型都继承自`System.Object`类。不同的是,几乎所有的`引用类型`都`直接从System.Object继承`,而`值类型`则继承其子类,即 `直接继承System.ValueType`。即`System.ValueType本身是一个类类型`,而不是值类型。其关键在于`ValueType重写了Equals()方法`,从而对`值类型按照实例的值来比较,而不是引用地址来比较` - 引用类型可以派生出新的类型,而值类型不能,因为所有的值类型都是密封(seal)的 - 引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型,如 int? a = null; ) - 引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值 --- ##字符串的内存分配与暂存池 --- ```C# var str1 = "12345"; var str2 = "123" + "45"; // str1等于str2:True Debug.Log($"str1等于str2:{ReferenceEquals(str1, str2)}"); var str3 = "45"; var str4 = $"123{str3}"; // str1等于str4:False Debug.Log($"str1等于str4:{ReferenceEquals(str1, str4)}"); var str5 = "123" + str3; // str1等于str5:False Debug.Log($"str1等于str5:{ReferenceEquals(str1, str5)}"); // str4等于str5:False Debug.Log($"str4等于str5:{ReferenceEquals(str4, str5)}"); //public unsafe String(char* value) var str6 = new string("12345"); // str1等于str6:False Debug.Log($"str1等于str6:{ReferenceEquals(str1, str6)}"); var str7 = new string(new char[] { '1', '2', '3', '4', '5' }); // str1等于str7:False Debug.Log($"str1等于str7:{ReferenceEquals(str1, str7)}"); var str8 = str1; // str1等于str8:True,可以看出string类型就是引用类型 Debug.Log($"str1等于str8:{ReferenceEquals(str1, str8)}"); // str1等于str9:True var str9 = string.Intern(str1); Debug.Log($"str1等于str9:{ReferenceEquals(str1, str9)}"); //跟str6一样 var str10 = new string(str1); // str1等于str10:False Debug.Log($"str1等于str10:{ReferenceEquals(str1, str10)}"); var tempArray = new char[] { '1', '2', '3', '4', '5' }; var str11 = tempArray.ToString(); var str12 = tempArray.ToString(); // str1等于str11:False Debug.Log($"str1等于str11:{ReferenceEquals(str1, str11)}"); // str11等于str12:False Debug.Log($"str11等于str12:{ReferenceEquals(str11, str12)}"); ``` --- - 暂存池中的所有字符串对象的值都不相同 - 只有编译阶段的文本字符常量会被自动添加到暂存池 - 运行时期动态创建的字符串不会被加入到暂存池中 - string.Intern()可以把动态创建的字符串加入到暂存池中 - 动态创建的字符串和暂存池中的某个字符串的值相等,引用也不会相等 - 动态创建的两个字符串的值相等,他们的引用依然不相等 --- 0901学习 0810学习