这里记录的是原因隐藏比较深比较神奇 又与项目无关的bug 在写bug之前记录几点能提高查询bug效率,减少加班时间的手段,要想写代码少出错,掌握设计原则也是很重要的
在像Dictionary,List这样的存储列表里面,取值的时候最好判断是否存在,不存在则进行报错打印,以及某个函数返回空值的时候进行报错打印,因为程序发布出去以后像WebGL,iOS,安卓这样的平台报错打印是不能直接确定到哪一行的,最多确定到哪个函数,如果有了查找为空的判断,调试的时候知道更多的信息,会方便很多
使用一些字符串常量的时候,最好将这个常量用一个引用指向,而不是在使用的时候直接写个双引号将常量括住,因为每次用到的时候都重新写字符串的内容容易写错,造成不必要的检查错误时间,而且这个错误可能会影响比较大并且如果开发人员没有这个意识的话,错误检查时间检查会比较长。 而且要全局改这个常量字符串内容的时候虽然有全局替换,但是也没有单独改某个引用方便而且有改到其他地方的风险,造成不必要的时间浪费。我经历过一些比较刻骨铭心的错误说由这些字符串没有使用常量造成的,所以程序员一定一定要注意这个。
使用Dictionary或者hashTable的时候,尽量少使用xxx[xxx] = xxx这种方式,使用自带的Add方法,因为如果有key值重复会进行报错,而不是覆盖之前的值,因为key值覆盖的错误查找起来比较困难,现在语法上对这种添加方式好像也进行了限制
像gameObject.SetActive这类比较常用的变量设置,最好封装在gameObject所挂物体的某个方法内或者有个整个项目统一调用的专门设置物体的gameObject的方法,这样某个物体被意外设置的时候,才有统一函数调用处打印,这样调试快很多
变量引用最好设置为私有或保护的,再用属性字段再次封装,如果赋值的时候可以用于打印路径,在该变量被赋值为错误值的时候方便看是哪个地方设置的
查看报错信息尽量完整查看调用栈,如果报错信息不能看到调用栈,最好想办法将其弄出来,在一些地方出错的时候可以使用Debug.LogError打印看看是哪个调用栈进来的
关于出错问题的查找可以上 百度 google 必应 github UnityForum UnityBlog 码云 UnityManual stackoverflow CSDN 掘金 知乎等网站的搜索引擎进行查找,这样找到解决方案的可能性比较大,除了用中文查找外可以 也可以将关键词改成相关程度高的英文
有时我们需要比较两个对象是否相同,c#里面有个getHashCode方法,这个方法继承自object,每个实例化的对象调用这个方法返回值都有一个id,比较他们的id就可以了,unityEngine里面的Object重写了这个方法,应该调用也是可以实现实例比较的
有时在继承自monobehaviour的脚本里面出错的时候,可以考虑一下是否是这个脚本挂载
我们在找代码的时候可以在自己能够想的到的调用栈的某个环节添加打印,这样找代码会找的比较快,例如我想知道最开始的时候滑动列表是在哪个函数 一开始就选中第一个item,我知道他选中之后肯定会触发某个事件,并且某个地方有监听这个事件,可以在监听这个事件的地方添加一个打印,打印出来的信息里面由调用栈查可以找到开始时候是在哪个函数开始的。如果打印的调用栈里面找不到最开始时候是从哪个函数开始,一般是unity的组件的属性造成的,查看hierarchy面板的inspector面板。例如没有勾选togglegroup的allowSwitchOff的时候,游戏开始运行之后总会选中第一个toggle
委托类型的变量最好不要设计成public 类型,这种类型外部的添加方法的方式可能是多播方式(+=)以及直接设置方式(=),这会造成比较大的隐患,例如直接设置的方式直接将之前添加的有用的方法替换了,委托类型的变量最好是设置成private类型,通过一个public的方法或者set方法对其进行设置,至于多播(+=)还是直接设置(=)的方式由开发者自行决定,假设出现了刚刚举例问题,还有个地方查看是哪里赋值的,并且如果只规定了多播,就不会出现直接设置的代码,如果只规定了直接设置,也不会出现多播的设置, 除非这个类的内部写的,这样出错的机率会小很多
1 使用for循环遍历list并且摧毁里面的全部元素的时候 不能在for循环里面调用 list.remove方法 这样会造成摧毁元素只删除了一半的效果
2 如果使用List存储Collision 然后判断时候 不能直接通过判断两个collision是否相等来判断是否两次碰撞碰撞到了同一个物体,因为碰撞函数的Collision 都是临时生成的,所以旧的Collision 与新的Collision 比较永远是false 应该拿出里面的变量来进行判断,比如拿出 collision.collider.gameobject来进行判断
3 有些变量在父类里面定义,子类里面如果需要重新赋值的话不用重新定义相同名称的变量,需要的只是在合适的方法里面重新对其进行赋值 如果刚好在父类的一些方法里面用到了这些变量,假设是方法A,子类又刚好调用了这些方法,子类又重新定义覆盖了这些变量,则子类调用的这些方法里面使用到的其实还是父类的变量,并不是子类重新定义的,这点要注意。 如果子类是想调用方法A的时候 对里面使用的变量不再是父类的变量,则在调用之前需要对继承的父类变量进行重新赋值即可,比如重写方法A,先赋值变量再在重写的方法A里面调用父类的方法A。
4 在Editor文件夹下面的公共类,在其他文件夹是不能直接引用到这个类或者这个类的对象,会报错当前上下文找不到这个类(The type or namespace name ‘XXXX’ could not be found (are you missing a using directive or an assembly reference?)),需要将调用的类移入Editor文件夹才可以,这种类型的找不到不是命名空间不同或者类的访问性限制或者项目不同引起的,但是Editor里面的类或对象可以引用到不是Editor里面的类或对象;如果添加一个继承了monobehaviour类的脚本放在Editor文件夹下面,那么这个脚本也是不能通过AddComponent添加到游戏物体上面的。
5 如果有时有个需求就是找个tag为某某的物体,将它隐藏掉,在编辑器能够正确找到并能够隐藏,但是在打出的包里面却怎么都隐藏不了,找了很多原因包括相关物体的tag设置,可以对setActive设置一层封装或者扩展,然后对调用的时候针对性打印,最后会发现,其实做错的地方是同时为两个或以上的物体设置了同个tag,但是因为一些unity机制的原因,unity里面就每次运行都能正确找到,但是打出来的包就偏偏每次运行找不到
6 GetComponentsInChildren这个方法会把包括调用这个方法的脚本所在的物体及其子物体身上的所有符合的组件找到,所以用的时候要注意,
7 注意对于模型物体的尺寸的xyz不是1:1:1的物体不能直接使用unity_ObjectToWorld 或者unity_WorldToObject来达到法线在世界坐标系与本地坐标系的互相转换,因为这两个矩阵包含了缩放矩阵这个缩放矩阵对应xyz比例是1:1:1, 尺寸的xyz比不是这样的物体,三角面存在变形,则三角面的法线方向也会改变,所以不适合直接使用这些矩阵,Unity提供了UnityObjectToWorldNormal方法来避免相关的问题
8 在一个工程G里面使用tag A进行打tag的子物体在父物体打包成assetbundle B后,在另一个工程H里面访问B使用同tag A一样的名字来寻找对应的子物体的话 可能是找不到的 因为在打assetbundle包的时候这个tag是从字符串转换成整数类型的数据 在G工程里面可能A转换的数字是1,但是在H工程里面加载的assetbundle的1转换到的tag就不一定是A了
9 在打包assetbundle或者打出unity包的时候,如果存在一个类A继承了类B,类B的一些属性或者方法如果在#if UNITY_EDITOR 里面定义了,但是类A重写的时候没有定义在#if UNITY_EDITOR 内部,就会出现类似于说没有这个方法的报错,同样的如果类B的这些方法在其他对象里面被调用了但是调用的地方也没有在#if UNITY_EDITOR 内部也会报类似的错误,原因大概是#if UNITY_EDITOR 内部的方法在打包assetbundle或者打出unity包的时候,没有被编译,被隔离了,好像代码里从没有过这段代码一样
10 在协程中执行yield break 会立刻退出当前协程,在协程中执行StopCoroutine()结束当前协程,则当前操作协程会在再执行一次后结束
11 继承了monobehaviour的类 使用new进行初始化 本身会造成一些很诡异的问题:在继承了Mono又没有挂载到物体的情况下 使用new进行初始化会造成初始化两次
12 后面版本的unity, 注意如果在一个脚本的Awake里面设置了gameObject.SetActive为false,则整个脚本所挂的物体及其子物体的Start都不会被调用,这点要注意,或者说父物体先执行了设置gameObject.SetActive为false,则子物体如果后于执行的生命函数都会不执行,注意SetActive的调用时机 参考 Unity游戏开发核心:生命周期
13 如果必须在代码里面设置某UI a的位置,在根据某UI b的position设置a的position的情况下,容易造成不同分辨率 a的UI与设想的不符合,如下述代码
newTextPos = new Vector3(btnTrn.position.x - textSize.x / 2 - verBtnOffsetX - btnSize.x / 2, btnTrn.position.y , btnTrn.position.z);
nameTxt.transform.position = newTextPos; 1 2 3 可以改成a的localPosition 是原先的a的localPosition 加上相同的偏移
newTextPos = new Vector3(nameTxt.transform.localPosition.x - textSize.x / 2 - verBtnOffsetX - btnSize.x / 2, nameTxt.transform.localPosition.y, nameTxt.transform.localPosition.z); nameTxt.transform.localPosition = newTextPos; 1 2 14 Resources.load之后返回值并不是加载到场景中而是加载到内存中,如果需要加载到场景中返回值还需要经过Object.Instantiate处理,如果是加载UI的prefab还需要挂载到canvas下面,有必要时还需要添加recttransform组件
15 设置scale的时候注意如果后面有设置parent的语句,则设置scale可能会失败,可以放到设置parent后面,或者使用setparent并且指定第二个参数使其设置了parent之后 自身的transform信息不受影响
16 如果unity运行时突然静止不动,那么比较可能程序进入某个非协程的死循环了
17 有时属性被使用和属性初始化是放在很近的地方,要注意一些属性的初始化的顺序与这些属性被用到的时候的顺序,不要被用了之后才属性初始化
18 变量空引用的原因:
有两个变量引用同一个对象,其中一个引用把这个对象销毁了,然后另外一个引用访问的时候报空 因为方便需要,存储的时候用了一个父类来存储,在需要使用的时候转化为所需子类,但是子类类型指定错了,例如A和B都继承自C,但是本来A对象用了C声明的引用去存储,但是使用的时候转化成了B对象,这时unity也没报错,只是转化失败,返回null 某个继承自MonoBehaviour的对象忘记挂载到某个物体就直接引用 某个类静态单例模式的对象没有初始化就被引用 如果变量A是由另一个变量B通过as转换过来的,变量A是空的原因一般是转换失败 19 Value does not fall within the expected range 我遇到的这个报错其实和空引用是一样的,就是某个函数调用的时候传参是空的,这个参数需要经过挂载游戏物体但是我没有挂就直接引用单例模式调用
20 An asset is marked with HideFlags.DontSave but is included in the build: Asset: ‘Library/unity editor resources’ Included from scene: ‘Assets/XXXX’ 这个报错会出现在打包的时候,打包时候prefab引用到了UnityEditor里面的东西,可以在代码里全局搜索上述的XXXX,或者 使用Unity5 使用自带的字体导致BuildAssetbundle失败An asset is marked with HideFlags.DontSave but is included in the 里面的代码进行检测,打印出来的prefab是有引用Editor资源的,在prefab的inspector面板或者代码里面进行查找 去掉引用即可
21 transform…Rotate(Vector3 axis, float angle, Space relativeTo = Space.Self)这个函数第一个参数需要注意的是如果是围绕物体或世界的某个标准轴旋转,传的参数只能说Vector3.Forward Vector3.Up Vector3.Right这三个,然后第三个参数传Space.World或者Space.Self。假设如果我需要用这个函数围绕物体的y轴旋转,则第三个参数是Space.Self,第一个参数不能传transform.Up,因为transform.Up不一定等于Vector3.Up,后者表示y轴,如果不等于的话,虽然transform.Up字面上的意思是物体的y轴正方向,但是只要他值不等于Vector3.Up,在函数的角度来说,就表示倾斜了一定角度的轴而不是标准轴了
22 使用Newtonsoft Json插件反序列化json,当然要根据json提前定义好类的结构,要注意的是多重嵌套结构的json,三重类嵌套当然没问题,问题可能在于有些json字段是只有一个元素的数组即JArray,不要没看清楚把他当成是某个JObject,这样会报错
JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type ‘CategorySrvrJsonCls’ because the type requires a JSON object (e.g. {“name”:“value”}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {“name”:“value”}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. 其实说的明白了,就是你把某个本该是jarray的字段当成了jobject,要注意这个,不然找问题会找比较久 就像下面这个定义,本该是第一个的被定义成了第二个
public class SingleBuildingSrvrJsonCls { public List areaList { get; set; } public List categoryInfoSet { get; set; } 1 2 3 4 public class SingleBuildingSrvrJsonCls { public List areaList { get; set; } public CategorySrvrJsonCls categoryInfoSet { get; set; } 1 2 3 4 遇到这种报错要把整个json和类的结构全部对清除,因为不管是不是多重嵌套,任意一个是list的地方被定义成不是list都会报错
23 list不支持list[X]=这样的添加方法,会报错,支持的是list.add(),按照里面元素的索引添加的顺序依次增加
24 EasyTouch组件里面如果类型下面的监听代码没反应,可能因为场景里面没有物体挂上了Easy Touch组件
EasyTouch.On_DoubleTap += OnDoubleTap; EasyTouch.On_SimpleTap += OnSimpleTap; 1 2 注意在脚本销毁的时候取消事件监听,否则可能会造成空指针引用访问
25 一般情况下,unity的static变量不会显示到inspector面板,不是static的变量如果是public类型或者加了serializedfield的话会显示,如果不是public类型又没有加serializedfield的话就不会显示,
26 打assetbundle包的时候报错某个脚本里面的类或者对象不存在的话,多半是因为这个类或者对象是继承了UnityEditor,加个#if UNITY_EDITOR
27 挂载到物体上面的脚本的名字要与脚本里面继承了monobehaviour的类的名字一样,不然unity不会执行该脚本的挂载,或者是过去成功挂载过的脚本会出现识别不了的报错
28 进行事件监听的时候,注意在脚本销毁的时候取消事件监听,否则可能会造成空指针引用访问,也要注意同一个对象重复对同一个事件进行监听的时候,会不会有第一次监听因为第二次监听的添加而取消的情况,第一次监听在很久以前写的话往往会忽略掉
29 如果不是单例的脚本对一个事件进行监听,如果这些脚本所挂载的游戏物体会同时出现的话,要注意他们同时对同一个事件做出反应是不是自己想要的结果
30如果Debug.drawline或者drawray之类的看不见,则可能原因是场景里面的gizmo没打开,如果嫌gizmo过大可以改其大小
31 一个内容是子类的list不能用as转化为内容是父类的list,好像是因为as只是对于两个有继承关系变量的类才有用,List a = b as List中的两个list不是继承关系,这时可以使用a.addRange(b)的方式来实现a里面有b的内容
32 对于某个值,如果确认其取值是通过某变量进行,但是对取得的值感觉不对,可以在update函数里面对其值进行持续打印,如果有变化则说明可能是访问变量取值的时机不对,也就是说,先取了正确的值之后,又取了错误的值。
33 debug.drawray这个方法的第二个参数射线方向是考虑长度的,即如果你传入的方向的magitude是多少,射线的长度就是多少,这一点要注意。
34 假设只有一个摄像机A,A在Perspective(透视)模式下和Orthographic(正交)模式下,使用屏幕点击射线检测的传参需要注意一点, 在透视模式下,这种写法没什么问题,
Ray = Camera.main.ScreenPointToRay(Input.mousePosition); Physics.Raycast (Camera.main.transform.position, ray.direction, hit) 1 2 但是在正交模式下,这种写法会有问题,需要改成如下:
Ray = Camera.main.ScreenPointToRay(Input.mousePosition); Physics.Raycast (ray, hit) 1 2 这里面的原因是,投影模式下,摄像机的成像几何是个锥体,锥体最后集中于一个点,Input.mousePosition其实等于摄像机的位置等于那个点的位置,所以两者互换没问题
但是在正交模式下,成像几何是个矩形,Input.mousePosition不等于摄像机的位置了
35 Unity继承自monobehaviour的脚本,如果在里面走了错误代码会引发禁用,我遇到的情况是,在Awake函数里面使用了空的Key去访问Dictionary的ContainsKey方法,结果造成了整个monobehaviour脚本的禁用
36 一个物体上面的脚本执行顺序是越后挂的越先执行,这里容易造成一个问题,就是如果后挂的脚本的Awake函数上面,调用gameObject.SetActive(false)将这个物体设置为InActive,则先挂的物体的Awake以及Start函数就不会执行了,同理,后挂的脚本的Start函数执行了inActive代码之后,其前面挂的脚本的对应函数也不会执行,SetActive为false之后,Update方法也不会执行了,
37 Unexpected character encountered while parsing value: t. Path 这个报错是我使用NewtonSoft.Json解析json字符串时候出现的错误,我的原因是字段的类型定义错了,比如本来是bool类型的定义成了int类型
38 打包失败的一个比较容易忽略的原因是C盘比较满了 只剩下几百M
39 如果遍历一个list将里面的元素用dictionary.add将list的元素添加到一个对象变量dictionary,在添加之前没有做dictionary.contains判断, 则可能导致dictionary的key重叠的原因是这个地方可能从不同的调用栈走进来或者同一个调用栈走进来两次或者多次,导致添加了两次,而不是list里面的元素重叠的原因,因为list里面的add函数也会做重叠判断
40 Unity内置的 Instantiate函数有时会出现即使复制的是场景里面的物体,它的朝向也不是和原物体相同的,并不是说复制了物体,transform属性就和被复制物体相同
41 如果像VisualStudio这样的代码编辑器,全局查找只能找到当前打开脚本内的内容,一般是因为visual studio不是从unity引擎内部唤起的,而是独立打开的,这时需要关闭再从unity内部点击某个脚本唤起
42 如果有时有一些不通过的情况要用到同一个场景,比较倾向于考虑将这个场景在不同情况下复制出几份,如果在不同情况下共用一个场景,则在这个场景里面改东西的时候要考虑到多个情况,比较复杂耗时不稳定,同样,如果多个场景下要用到某个脚本,其中一种场景要改这个脚本,则最好是新写一个脚本,专门用于这个场景,如果时间紧迫,新写的脚本开始可以完全复制原脚本的代码,但是后面如果多个场景里面的共用的要改的话,则要改多个脚本,所以时间不紧迫,一般考虑使用继承的方式
43 如果项目有在WebGL端运行的情况,通常会在Unity工程的Assets下的Plugins文件夹里面有自定义的jslib文件,这个文件里面每个定义的方法后面要有逗号分开,如果有没分开的,可能会导致webgl平台的unity程序生成失败
mergeInto(LibraryManager.library, { //获取产品id getProductIdStrFromJSCode: function(){ console.log("jslib getProductIdStrFromJSCode " + window.getModelIdFromJSCode());
var id = window.getModelIdFromJSCode(); var bufferSize = lengthBytesUTF8(id) + 1; var buffer = _malloc(bufferSize); stringToUTF8(id, buffer, bufferSize); return buffer; }, }); 1 2 3 4 5 6 7 8 9 10 11 12 13 44 遍历list的时候,如果发现list的长度为0了,可能是走了个自己意想不到的地方,而那个地方刚好有把list清空的操作
45 c#代码的闭包尽量少使用,之前的代码有写过网络加载的匿名方法回调,这算是一重闭包,回调里面又调用了另外一个匿名方法,这算是二重闭包,一个函数的 闭包在一瞬间频繁调用的时候可能会出问题,网络加载回来之后 传进去的匿名 函数的外部变量已经不是原先的,这是我在项目中遇到的,当我把网络加载匿名函数改成存到以url为key的回调函数字典,每个url要加载的东西返回判断加载url有回调存储就调用的时候,错误就没了,所以我猜是c#闭包的问题
46
DontDestroyOnLoad(transform.root.gameObject); 1 这句话如果写在awake或者start方法里面,存在隐患,如果脚本所在的物体所在的场景多次加载的话,这句话就会执行多次,然后这个物体连同他的根物体会多次加载到DontDestroyOnLoad场景里面,根物体下面的脚本里面函数写的功能会执行多次,如果脚本有对某些事件进行注册的,当注册的事件发生的时候,就会多次执行,在执行DontDestroyOnLoad的时候还需要再进行判断是否有当前的物体
47 我们使用射线检测的时候判断检测的物体与目标物体是否是一个物体,不能简单的==判断,可能还要判断检测物体是不是目标物体的子物体,像下面的代码
public static bool IsThisGoSpecGoOrSub(GameObject go, GameObject parGo) { if (go == parGo) { return true; }
while (go != null && go.transform.parent != null) { //Debug.LogError("CanZhanPreUtil IsThisGoProSub go.name " + go.name); go = go.transform.parent.gameObject; if (go == parGo) { return true; } } return false; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 48 常见的赋值失败的情况有如下几种原因
设置了某个东西的值之后,又出现了其他地方对这个东西的值进行赋值 覆盖了原来的值 要对某个新生成对象进行赋值,但是因为各种原因自己访问的变量所携带的是即将要清除的旧对象,导致了赋值到旧对象了。我之前遇到过这种情况,具体原因需要查一定时间,因为项目开发时间不允许而自己额外时间又懒得去看,所以暂时没看,补救的方法是,对即将赋值的某个对象赋值之后,每帧都去检查有没有存在,如果不存在则重新赋值,这样就算赋值到的是即将要被摧毁的旧对象,被摧毁之后也会对新对象进行赋值 49 有时后端接口文档说的传空不一定是传null,可能是传空字符串,如果接口文档网页进行测试的时候某个参数什么都没写能正常返回值,可能是因为什么都没写的话,接口测试发消息的时候默认会给个空字符串。相当于代码里面对这个值进行赋空字符串值的时候,而不是对这个字段赋null或者不理这个字段,因为这俩都会造成不正常,使用WWWForm传输的时候就是这个样子。
public static void LoadBaiJianDecoJson(int page,int rows,string title, Action dataBackAct = null) { string url = LoadBaiJianExhDecoListUrl; WWWForm param = new WWWForm(); param.AddField("page", page); param.AddField("rows", rows); param.AddField("title", "");
Hashtable requestHeaderHash = NetWorkMidFrame.GenerateAuthorTbl();
NetWorkUtil.Instance.Post(url, param, NetWorkUtil.Instance.commonUWRBack, dataBackAct, requestHeaderHash); } 1 2 3 4 5 6 7 8 9 10 11 12 13
50 如果Update函数或者其他的unity生命函数没有执行,可能原因是场景里面没有一个物体挂载这个脚本,也有可能是在awake最开始的时候设置了enable为false或者gameObject.SetActive为false,导致unity没有往下执行该脚本的生命函数
51 transform.localScale assign attempt for ‘comp_1’ is not valid. Input localScale is { NaN, NaN, NaN }. 这个报错是使用dotween的时候, DOShakeScale(dur, strength)的dur这个参数为0导致的,并不是像报错那样是某个组件的localScale值是空的, 在这里写是为了提示有些报错并不是像所说的那样,要有发散性思维,多看看全部关于报错的东西,也许就能发现某个地方不对,而报错可能是刚好是因为不对的地方
52 协程中报错会导致协程停止,程序里面一些报错也会导致程序停止掉,在ios端,报错一般会导致程序崩溃退出
53 如果在代码中对UGUI的一些Image设置sprite的时候出现了白底红色问号,一般是unity的image对于设置sprite的东西识别不出来,一般是sprite的格式不对
54 ReflectionProbe反射探针作为别的物体的子物体的的时候,并且在Type为Realtime并且RefreshMode为Via Script模式时,当父物体设置Active为false时,反射探针的RenderProbe()方法即使被调用了也不会执行成功的,在这种模式的时候,调用RenderProbe()必须在反射探针物体的gameObject.activeInHierarchy为true的时候调用才有效,或者当反射探针的模式在如下所示的时候。这相当于Via Script模式时,反射探针的脚本里面添加了一个方法
private void OnEnable() { rp.RenderProbe(); } 1 2 3 4 55 is missing the class attribute ‘ExtensionOfNativeClass’! 这个报错的主要原因是之前继承monobehaviour的类后来去掉之后,在预制体里面还挂着这个脚本,在预制体里面去掉这个脚本即可
56 要注意c#里面的get 和 set属性并不一定会被编写者写到调用,所以看别人的脚本或者使用别人的插件的时候,如果在set方法里面进行了代理或者监听但是程序到这里时候运行不正常,有必要考虑这个set是否真的在这个时候被执行到了。
57 在UI适配上有一个写法是在Awake函数里面根据当前屏幕分辨率重新调整UI的尺寸,这种写法有个比较隐藏的弊端 就是如果是程序一开始的时候是竖屏 这时Screen.currentResolution 返回的宽高比是小于1的,但是在UI显示的过程中变成横屏了,那么原来的竖屏的尺寸就可能不适合了,对于这种情况,一般是在UI显示的过程中将之前设置的宽高比与当前屏幕的宽高比进行比较,如果出现变化则表示出现了横竖屏转动,则再次进行UI适配
58 注意如果整数与整数进行乘除运算,得出来的结果是整数,不能精确到小数点几位这样,例如整数的 1 / 2 = 0而不是0.5,如果需要得到理想的浮点数,应该在运算之前将每个算数转换成浮点数类型,例如改成这样,1f / 2f ,如果是整数类型的变量则改成这样 (int1 * 1f ) / (int2 * 1f)
59 继承自monobehaviour的类不能用new的方式实例化,如果开启协程遇到问题,可能是协程开启的monobehaviour子类是new出来的,这个问题要注意一下
60 像网络加载这样的异步操作里面,通常要传进一个回调方法,在回调方法里面进行访问异步操作之后需要访问的变量,而不是在进行异步操作之后直接在下一句就访问需要访问的变量,这个时候异步操作通常没有完成,所以需要访问的变量是空的
61 像inputFileld text这样的组件设置值的时候,如果设置正常但是没反应,一般情况下可能性最大的就是找错设置组件了,比如我要设置inputField A,结果我找到了B,还有个要注意的是inputfield设置值的时候,是直接更改inputfield 的text字段而不是找到其所对应的Text组件再更改Text组件的text属性
62 Directory.Exists 这个方法要注意参数里面以及目标文件夹的开头或者结尾是否有空白符(空格,换行等),我同事遇到的时候是,他调用这个函数的时候传的字符串因为是用变量引用的,不能直接在代码里面看字符串内容,在这个变量里面后面多了几个空格,导致找不到,像使用这种字符串作为参数的时候最好用trim对其处理下,这和GameObject.Find以及Transform.Find有些类似,因为这类的对字符串参数内容严格要求的函数都容易犯这种错误,调用这类函数的时候最好对要找的物体的路径以及所传参数进行严格的字符串检查
63 关于路径的操作都要想想会不会是中文路径或者中文的文件夹名字引起的错误
64 Screen.orientation设置的屏幕旋转因为unity自身机制的原因可能要下一帧或者下两帧才生效,才真的改变屏幕的旋转。如果开发的时候有什么事情是要在这个操作之后进行的话,建议延迟一点时间。举个例子,之前有过判断刘海屏UI缩进操作的,每次unity旋转之后要重新进行刘海屏危险区域的判断,unity用BangsTop表示手机从ios端获取到在某个朝向的时候,屏幕顶部的危险区域,当然这是相对的,例如手机在竖屏的时候,BangsTop就是手机真正的上部即前置摄像头的下方,但是在横屏底部朝左的时候,BangsTop是横屏时候的屏幕顶部,是手机竖屏时候的右边。如果在设置了Screen.orientation将当前屏幕从竖屏改成横屏向左之后,立刻进行危险区域判断,这时屏幕其实还是之前的竖屏,则获取到的BangsTop是有值的,但是实际上横屏底部朝左的时候BangsTop不应该有值,这时就会导致UI刘海缩进出错,这时延迟进行刘海UI缩进操作是最正常的办法
65 如果出现了异常情况,要注意会不会同时出现了两个挂载相同脚本的物体,即使这两个物体没有在同一个场景,即有可能两个不同的场景同时存在的时候,这两个场景都有挂载相同脚本的物体。
66 CollisionMeshData couldn’t be created because the mesh has been marked as non-accessible. 这个报错是因为场景里面的3D物体其原始FBX的read/writeenable属性没有开启,导致AddComponent的时候,MeshCollider不能读取mesh的数据,如果对3D预制体打assetbundle的时候,预制体对应的FBX没有开启,则assetbundle加载出来的时候,其添加meshcollider等需要用到mesh数据的时候也会报这个错误。之前做射线检测需要用到碰撞体但是这个报错虽然有出现但是自己一直没注意,因为之前自己是在assetbundle的时候已经弄好了meshcollider,所以之前一直出现这个报错但是一直没有什么异常,但是后来同事改了打包方式的时候,新的assetbundle就没有再带有meshcollider了,自己找了好久才发现是meshcollider添加上去了但是读取mesh数据失败,程序员应该对每个报错以及异常都要想想有没有关系,也许某个地方的日常小异常能引起其他地方的运行异常
67 图片属性设置中,GenerateMipMap是当图片用于3D世界的物体的材质的贴图的时候,根据物体到摄像机的距离用不同的清晰度去绘制图片从而达到节省性能的一个选项,对于用于UI的图片,这个选项是没用的,而且开启之后会造成使用这个图片的Image或者其他图片组件上的图片模糊的问题。
68 关于Material和SharedMaterial的区别,当使用Renderer.sharedMaterial的时候会直接在原material上修改,并且修改后的设置就会被保存到项目工程中,场景中所有用到这个材质的物体都会受到更改的改变。一般不推荐使用这个去修改,除非项目有特殊需求。 当使用Renderer.material的时候,每次调用都会生成一个新的material到内存中去,修改Renderer.material只会影响到获取材质的那个物体。 这在销毁物体的时候需要我们手动去销毁该material,否则会一直存在内存中。也可以在场景替换的时候使用Resources.UnloadUnusedAssets去统一释放内存。 参考链接:Unity3D中Material与ShareMaterial引用的区别
69 写shader的时候,如果在中文模式下输入了空白字符,可能会造成报错,这种报错不是语法报错,看不出来的,在觉得是某行或者某段出现了这种报错的时候,将这段代码剪切出来看看还有没有报错,如果没有的话,再手动将这段代码在英文输入法的状态下打回去,中文的报错就会没有了
70 unity打包assetbundle的时候,如果要打入assetbunble的一些shader的某些属性 如果因为加载ab包的时候暂时不需要而完全没开,由于unity打包时候的节约策略,没开的属性部分的功能可能在打包时候会被剥离,如果这部分属性在assetbundle加载的时候需要使用,则使用的时候可能会不正常,解决办法是在打包的时候不设置成完全关闭而是稍微开启,这样ab包加载出来的时候 这个功能是可以正常显示。
71 unity报错The type ‘xx’ exists in both “Version=0.0.0.0, Culture=neutral, PublicKeyToken=null” and “Culture=neutral,PublicKeyToken=” 我出现这个报错的情况是在mac环境下,git合并分支之后,与自己当前所在的分支合并的那个分支因为引用了另一个git的submodule,当这个submodule被移除的时候,git更新是不能更到的。项目的情况是移除了并且移入到了PackageManager,导致项目的两个地方拥有很多相同的变量和类等,因为重名的变量在PackageManager中,在rider中报的错仔细看是rider里面的脚本与unity工程里面的脚本的冲突问题,实际上就不是,因为这个移除是别人弄的,我自己不知情,并且自己是第一次使用mac环境下的多分支git,并且报错的脚本在rider下面,自己一度以为是自己还有一些冲突没有解决。
72 dotween插件的DOShakeXXXX DOPunchXXXX不论参数怎么填,函数的表现都是有一定的不确定性的,例如方向和力度,适合用来做随机的抖动或者弹动的表现,不适合用来做流程固定的表现。dotween的DOTween.To函数用于对某个数值按照曲线来进行控制,曲线的起伏程度不能调节。
73 Microsoft ® Visual C# Compiler version 2.9.1.65535 (9d34608e) Copyright © Microsoft Corporation. All rights reserved.
error CS0006: Metadata file ‘/Volumes/Mac3/UnityProjects/iyourcar-3d-unity/Library/PackageCache/com.iyourcar.unityfs@1.4.27/Editor/Plugins/ICSharpCode.SharpZipLib.dll’ could not be found error CS0006: Metadata file ‘/Volumes/Mac3/UnityProjects/iyourcar-3d-unity/Library/PackageCache/com.iyourcar.unityfs@1.4.27/Runtime/Plugins/Newtonsoft.Json Editor/Newtonsoft.Json.dll’ could not be found 在拉了git的资源之后,出现的这个错误,unity重启之后就可以了
74 使用UnityWebRequestTexture.GetTexture获取texture会导致一些内存释放不成功的问题,这个释放不成功对于小的texture问题不是很明显,如果是大的texture则问题会比较明显,使用UnityWebRequest的方式去加载texture,参考代码:
using (UnityWebRequest request = new UnityWebRequest(thumbFullPath, UnityWebRequest.kHttpVerbGET)) { request.downloadHandler = new DownloadHandlerTexture(); yield return request.SendWebRequest(); if (request.isNetworkError || request.isHttpError) { Debug.LogError("Error while trying to load image from streaming assets - " + thumbFullPath); videoImage.gameObject.SetActive(false); } else { videoImage.texture = DownloadHandlerTexture.GetContent(request); } request.Dispose(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 75 网络请求过程中如果发起网络请求的场景销毁了,则网络请求应该要停止掉,特别的是申请下载的网络请求,否则也可能会造成下载下来的资源占据内存的问题
75 dotween插件的一些方法如果没有使用SetEase方法设置曲线,则播放过程中可能不会正常的按照线性的方式去进行,项目中遇到的问题是iphone环境中,一些透明度渐隐的过程在最后阶段突然亮了一下,这个bug是dotween造成的,也就是说dotween
76 如果经常有场景切换的操作,如果被切换的场景在切换时里面有一些物体在协程进行中,则可以考虑将这个物体移动到DontDestroyOnLoad场景里面,如果切换掉的场景会被销毁,要注意协程里面不要访问到这个被销毁的场景里面的东西
77 有下面一段代码,这段代码的加载环境和卸载环境过程的时间上是没有什么关系的,并且两者都是需要时间,这段代码在按钮点击的时候就会执行,没有判断如果当前正在更换的情况下应该return 需求是在每次加载新场景的时候,旧场景都能及时卸载掉,在按钮疯狂点击的时候,会出现一些场景加载好之后没有被引用到而一直保留没被切换删除的情况。
if (_currentEnvironmentPath != null && UserCenter.instance.environmentBean != null) { StartCoroutine(EnvironmentHelper.UnloadEnvironment(_currentEnvironmentPath, UserCenter.instance.environmentBean.bundle)); } _currentEnvironmentPath = scenePath; StartCoroutine(EnvironmentHelper.LoadEnvironment(scenePath)); UserCenter.instance.environmentBean = resourceBean; simpleCallback.OnSuccess(null); UpdateTrackerArguments(); 1 2 3 4 5 6 7 8 9 10 11 12 13 假设现在有表示ABC三个场景的按钮,在点击A按钮时,当前没有已存在场景,直接加载A,由于A场景需要时间,在加载的时候点击场景B,这时判断有场景A引用到_currentEnvironmentPath,则想去卸载他,但是A还没加载出来,这时会unity的场景类会报错,并且A不能被阻止加载出来,点击B按钮时,_currentEnvironmentPath已经指向了B,A加载出来之后没有任何变量指向它,它的影响就不会被取消,只能被GC。 对于这种情况,如果要保持现有的时间不相关的性质去加载和卸载场景,则每个加载出来的场景都需要保存到list里面,没有被_currentEnvironmentPath指向的就要在合适的时候卸载掉。 还有一种方法规避上述的情况就是,严格控制原场景卸载后新场景才能加载,即新场景的加载写在旧场景的卸载完成的回调里面,并且这个过程需要有个变量表示,在这个过程中如果其他地方点击按钮,则直接返回。与第一种方法相比,这种方法的切换场景的时间延长了。
78 我的分支A的场景A不小心改错了,一些UI物体上面挂载的脚本卸掉了,然后我使用的是git 想用分支B的场景A去覆盖自己当前的场景,我将分支B的场景A更改了一些无关紧要的物体的位置,然后提交并且推送上去,然后分支A合并分支B,可是分支B的场景A重新挂载的物体就是不能合并过来。其实是这样的,卸掉脚本的物体是个预制体。当两个场景里的物体共用一个预制体的时候,一方的更改会影响到另外一方,所以一开始想将一个物体做成预制体然后用预制体再放到场景里调节成另一个物体的时候,别忘了将这个新的物体保存成一个新的预制体,这个两个物体的更改就不会互相影响了,这种预制体造成的影响是git合并也改不了的。
79 在嵌套unity中做微信分享的时候,ios环境中, 遇到过一分享就画面卡住程序不响应的情况,微信分享是unity调用外部程序的,为了辨别是unity引起的卡住还是外部程序引起的卡住,就在外部程序的其他地方点击了分享,结果发现也卡住了。然后ios主程就直接测试的时候,点了其他app看微信分享正不正常,结果也卡住了,然后就把原因归到了手机上,然后手机重启之后就不会了,有时扩散思维也是很重要的,测试说它的手机一开始是没有登录微信的,这样的测试方法也想得到哈哈,没登录微信测试微信分享。
80 关于git上的处理的终极办法就是重新checkout出来,将剪不断的麻一刀切,之前遇到在git更新之后出现了Texture2D doesnt contain method EncodeToJPG的方法,如果只有少部分的更改没push或者都push了,直接check out的做法比其他的快很多,如果有一些更改没push,则checkout之后将更改复制过来即可。跳出了一个圈子想解决办法。
81 使用过NatCorder这款插件,用它录制的视频在播放的时候会出现一些绿边,这是因为录制时候传入的分辨率是奇数,它的构造方法如下所示,其中前两个参数不能够是奇数,下面的参数是公司里面移动端录制比较流畅的参数。
MP4Recorder(videoRecordWidth, videoRecordHeight, 16, 0, 0, 960 * 540 * 3, 1); 1 82 在UI 物体不是screenspace-overlay的情况下,即使UI物体比一些3D世界的物体更加靠近摄像机,但是也会出现这些物体挡住UI的情况,这一般是这些物体的深度更小导致的,可以采取在这些UI物体上面使用材质的方式,将材质的渲染队列调到最大。然后将UI物体使用这个材质。材质属性如下图所示 例如在image里面使用material
83 报错 Uncaught exception: NSInvalidArgumentException: -[MTLDebugDevice UpdateQueueAdd:]: unrecognized selector sent to instance 0x2834a8500 ( 0 CoreFoundation 0x0000000184d8c128 F80FCA31-BF76-3293-8BC6-1729588AE8B6 + 1155368 1 libobjc.A.dylib 0x00000001985b2cb4 objc_exception_throw + 56 2 CoreFoundation 0x0000000184c9c9b8 F80FCA31-BF76-3293-8BC6-1729588AE8B6 + 174520 3 CoreFoundation 0x0000000184d8e758 F80FCA31-BF76-3293-8BC6-1729588AE8B6 + 1165144 4 CoreFoundation 0x0000000184d906cc _CF_forwarding_prep_0 + 92 5 UnityFramework 0x0000000107bec0cc _ZL15PrepareAudioTapPK26opaqueMTAudioProcessingTaplPK27AudioStreamBasicDescription + 120 6 MediaToolbox 0x000000018d03ca4c BB26609F-DB0D-3079-BCBE-E8B5757DEABD + 3349068 7 MediaToolbox 0x000000018d03c834 BB26609F-DB0D-3079-BCBE-E8B5757DEABD + 3348532 8 CoreMedia 0x000000018d4ea5b0 B48E8AE4-1FE4-37B0-BFC9-BF9255A835EF + 824752 9 libdispatch.dylib 0x000000010dd53db8 _dispatch_call_block_and_release + 24 10 libdispatch.dylib 0x000000010dd555fc _dispatch_client_callout + 16 11 libdispatch.dylib 0x000000010dd5c680 _dispatch_lane_serial_drain + 748 12 libdispatch.dylib 0x000000010dd5d308 _dispatch_lane_invoke + 452 13 libdispatch.dylib 0x000000010dd68b34 _dispatch_workloop_worker_thread + 1456 14 libsystem_pthread.dylib 0x00000001ca6345a4 _pthread_wqthread + 272 15 libsystem_pthread.dylib 0x00000001ca637874 start_wqthread + 8 ) 2020-09-25 12:44:47.139496+0800 iYourSuv[914:241890] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[MTLDebugDevice UpdateQueueAdd:]: unrecognized selector sent to instance 0x2834a8500’ *** First throw call stack: (0x184d8c114 0x1985b2cb4 0x184c9c9b8 0x184d8e758 0x184d906cc 0x107bec0cc 0x18d03ca4c 0x18d03c834 0x18d4ea5b0 0x10dd53db8 0x10dd555fc 0x10dd5c680 0x10dd5d308 0x10dd68b34 0x1ca6345a4 0x1ca637874) libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[MTLDebugDevice UpdateQueueAdd:]: unrecognized selector sent to instance 0x2834a8500’ terminating with uncaught exception of type NSException (lldb)
这个的原因是使用unity的videoplayer的rendermode是RenderTexture或者materialOverride并且audioOutputMode是VideoAudioOutputMode.AudioSource的时候,ios14的底层渲染框架Metal对于video写入texture的一些bug,将rendermode设置成APIOnly并且audioOutputMode设置成direct即可。
84 对于同一个物体,需要如果一个地方的代码设置他的某个状态(例如颜色)后不起效果,可能原因是在设置后紧接者的另外一个脚本的地方又对这个效果进行设置了。调试错误要注意这一点。
85 如果一些运行时候加载的物体,要注意它是否在常驻场景下,如果不是则要根据实际情况决定是否要对其进行放置在常驻场景,并且移动物体到常驻场景下的时候要注意物体是没有设置parent的,否则移动会出错。
86 dotween.to函数使用的时候要注意里面传的数值类型是类似8这样的整数类型还是浮点类型,如果初始值和结束值都是整数类型会导致函数牵引的变量是突变的,例如起始值是6结束值是8,持续时间是2秒,则在2秒期间,值不会有变化,2秒过后直接从6变成8.
87 在设置UI显隐的时候,如果通过gameObject.SetActive设置物体显隐,则 其下的所有game Object的协程都会停止。可能会带来一些协程不能开启或正常结束的bug的报错,例如一个UI正在进行协程动画,这时其父物体被设置active为false了,那么它的协程动画将不会正常结束。
88 如果在代码里面进行某个组件的初始化,然后立刻调用组件的某个函数,这个函数使用的一些属性在其start函数里面初始化,这时会导致调用这个函数的时候 属性还没初始化,可以改成每次调用这个函数的时候进行初始化。
89 如果在json文件里面遇到了在unity中的解析的错误,但是将json文本拿到json解析网站解析的时候又没有出现任何问题,则需要将一些相关网站的相似的json拷贝过来,然后在修改为自己所需的内容,这种情况可能原因是一些中文的看不见的字符被检测到了,上述解决方案是比较理想的解决方案。
90 如果一个模型A正常效果的Humanoid动画用在另一个模型B的Avatar上面有一些关节过度扭曲的效果,可以尝试调节模型B的avatar,在骨骼映射正常的情况下,查看Muscle&Settings选项,我遇到的问题是一些jogging和walking,running等动作,大腿根部和膝关节和腰部过度扭曲,我分别调节了Body里面的Spine Left-Right选项,Spine Twist Left-Right选项。Left Leg和Right Leg的Upper Leg Twist In-Out等选项,这样模型再播放模型A的动画的时候,就不会出现关节过度扭曲了。但是要注意这个调节也会影响模型B上面其他动画的效果。
91 动画一般腿部会动,但是有时一些动画即使腿部会动,两个脚掌也是固定在地面某点的,例如Idle动画。如果在场景中的人物在Idle动画状态下,脚掌会移动,那么这个Idle动画的xyz都需要勾选Back Into Pose选项,选项表示的意思是将动画带有的位移烘焙到动画内部,使得播放动画的物体的Transform实际上没有受到动画影响。影响有位移和旋转,实际需要规避哪些影响由开发者决定,然后可以选择Origin(动画自带的根节点,可以是人物中心也可以是底部)还是Feet(人物的脚步)等,这些选项代表的是需要烘焙到动画的哪个部位,看实际效果了。如果还有这种情况,则需要勾选动画状态机中动画片段的FootIK属性,IK的意思大概表示在固定某个关节的情况下,系统会自动计算反向去调节其上级的关节,如固定手腕自动反向调节手臂和肩膀,FootIK的意思是将脚掌和地面相接,不会让脚掌随意乱位移,然后基于脚掌去重新调节其他上方关节的姿势。IK的意义参考 [专栏精选]Unity动画系统的IK详解
一些空中的打斗动画播放后如果角色落地的高度就会出现异常,不播放就不会。记得将这些打斗动画的Y轴位移勾选Bake Into Pose选项。将打斗动画的y轴位移烘焙到动画里面,不会影响游戏世界中的y轴位置变化。
在观察动画属性时,一些跑步行走等动画可能是没有任何位移的,这时即使动画控制器开启了apply root motion也是没有作用的,需要动画师再调节动画或者代码中控制位移旋转等。
一些基础包和代码这些资产文件能避免共用就尽量减少共用,共用虽然在短时间内方便了一些,但是被共用的次数越多,后面一改动,出错的风险就越大,以至于被共用次数多的几乎不能更改了,在一些项目必须要更改这些不能更改的资产的时候,就很麻烦。改成直接复制然后更改的方式。
UMotionPro插件一个角色的初始设置在工程最开始指定的时候就确定了,这意味着如果场景角色在工程过程中有更改,也不会体现到工程中,只有退出工程才会将更改显示出来,要在工程中体现更改,只能重新创建一个新的工程重新指定角色。
动画Rotation的Bake Into Pose 如果选择Body Orientation如果有切换动作时候的旋转问题,可以旋转origin试试。改完之后动画状态机里面的动画要记得重新赋值才会被更新。使用UMotion编辑已有动画的时候也要注意导入的动画是哪种类型,否则改好导出的动画其Rotation的Bake Into Pose选项和导入时候的一致,可能导致因旋转问题而需要重新修改。而且导出之后即使改导出动画的这个属性也改变不了了,只能在编辑的时候重新校对计算出的身体正前方与动画师制作动画时指定的前方看偏移了多少度,手动在每一帧里面将根节点调回多少度。下图中红色箭头是计算的身体正前方方向,蓝色箭头是指定的身体前方。
或者在导出动画的这里直接修改全程的旋转值,
使用Dotween进行local运动的物体在改变parent包括设置parent为null的时候要注意,因为位置是相对父物体的,一旦改变如果没有进行DoPause设置,下一帧物体的变化可能会让自己诧异。
使用Dotween组件进行移动等进行中的时候,最好把NavMeshAgent组件关闭掉,NavMeshAgent组件会影响表现,遇到的现像是执行下面代码的匿名函数时,运动物体的y值突然就变成了让其贴到地面的接近0的值,因为这是之前自己测试过的代码,测试环境比实战环境的物体少了个NavMeshAgent组件,于是我尝试在dotween时禁用掉,就不会产生异常现像了。
transform.DOMoveY(jumpDistance + transform.position.y, duration/2f).SetEase(Ease.OutSine).onComplete = () => { transform.DOMoveY(targetTrn.Value.position.y, duration / 2f).SetEase(Ease.InSine).onComplete= () => { }; }; 1 2 3 4 5 6 7 8 99 Behavior Designer插件当在OnUpdate运行时不能满足情况而且感觉事态的进展不能达到预期时,安全机制会自动返回执行失败,如下面的一段代码,当reachDistance设置的过小时,会自动返回failure,这里的过小可以是0.1,当两个边长为1的立方体接近时,如果他们不会有重叠部分,他们的最小距离只能是1,这时安全机制会返回执行失败而不是一直执行。
if (Vector3.Distance(transform.position, targetTrn.Value.position) < reachDistance) { transform.DOPause(); transform.SetParent(null); return TaskStatus.Success; Debug.Log("Success"); } else { Vector3 offset = targetTrn.Value.position - startTargetParentPos; synchronizeParent.position = startSynchronizeParentPos + offset; Debug.Log("Running"); return TaskStatus.Running; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Behavior Designer 初心者非常需要注意中断类型的使用,当中断类型对子树有不正确的中断的时候要考虑树的结构是否正确,不正确要重组当前树,一些不太正常的中断例如Idle Wait被中断的话,一般出错的原因都是在中断类型上面。
设计由几个大模块组合的比较复杂的AI的时候,各个模块的跳转不是简单的单向跳转,而是有特定条件的灵活跳转,Behavior Designer行为树内置的跳转有中断 权重和Utility Value,这些判断方式有个特点就是会立刻打断当前正在执行的任务,如果要求转换在完成了当前的任务并在空闲期的时候才会发生,则只有权重和工具值能做到,做法是将所有模块做到一个行为树里面,然后在重写的方法里面写好判断是否是空闲以及转换条件,然后需要有专门表示空闲的一个task,这样做会让行为树变得复杂很多,转换也可能出较多bug,较好的方式是每个模块用一个行为树表示,在一个模块的行为走到尽头时,用一个action来进行模块的判断,判断是否重新执行当前行为树,还是启用另个模块的行为树。
Behavior Designer出错的时候比较大的可能是有些任务的参数没有设置好,遇到不正常情况要首先考虑这点
Behavior Designer自带的移动功能包的CanSeeObject是基于碰撞器进行检测的,所以目标物体需要带上碰撞器
订阅模式有个特例:消息的名字根据消息播告时的情况意义来定义,如果应用规模有点趋大,对一种情况可能会有若干个订阅者,这些订阅者之间可能会出现一些执行顺序的要求,按照普遍公司的订阅模式机制,注册顺序保证执行顺序,先注册先执行,一般注册的函数是在功能类的初始化函数里面,能通过各功能类的初始化函数执行顺序控制,可能有特殊情况:有类AB,A对C事件的执行顺序要求在B前,对D事件的执行顺序要求在B后,先执行A的初始化函数,能满足前者不能满足后者;先执行B的初始化函数,能满足后者不能满足前者。这种情况有三种解决办法:将AB的初始化函数分成两个,一个只注册C,一个只注册D,在初始化函数执行时,重新排列AB的这些初始化函数;C或D消息播告前重新起一个新的消息,让冲突的订阅者监听这条消息;将AB类分解成更小的类。前两者属于额外分支补丁类的代码,会显著降低程序的可维护性,这样的代码多了程序会特别的难以维护,设计时应该避免这样的设计思路,最后一种方法属于单一职责原则,这种情况阐述了单一职责原则不遵守的一个后果,单一职责原则是最重要的原则,设计的时候应该尽量想着避免职责扩散,将单一原则用到极致。
Behavior Designer的Shaderd类型变量如果直接赋值其value值,可能不会报错,但是赋值会失败,新手要注意这一点,在变量在行为节点传递的时候如果发现变量没有赋值要想到可能是这点
编码时候,UnityEngine命名空间找不到:我出现这个错误的原因是将一个项目全部拷贝到另一台电脑上打开, 整个拷贝过去要注意的地方是,IDE的解决方案打开路径也会和前一台电脑的相同,IDE识别命名空间也和这个解决方案的打开路径有关系,当整个项目全部拷贝过去时, 由于解决方案的路径不同,绝对路径往往会导致接受拷贝项目的电脑打开IDE后引用不到正确的UnityEngine命名空间,做法可以变成将Assets下的全部内容以及package.manifest拷贝而不是整个项目,在新电脑新建项目之后,只需复制Assets文件夹的内容和package .manifest即可.
在使用NavMeshAgent组件的情况下,它的存在会是的Collider与地形的碰撞使得人物与地形的接触面在烘焙的寻路地图上,如果地图上移了,则可能导致人物穿底,不清楚这个特性的开放人员检查碰撞器是正确的情况下会比较懵逼。
使用Navigation烘焙的时候如果出现特别久的情况,考虑是否自己某个条件勾选错了,我这里的情况是之前复制了一个地图扩大了100倍作为背景,被复制的地图勾选了Navigation Static,新扩大的地图需要取消勾选但是自己忘记了。
UMotion对动画解析出的每一帧与Unity实际运行时有时候是不一样的,或者说Unity实际运行时候的动画与他自己自带的动画查看器查看的动画对于极个别动画会有出入,这样的出入对于根据动画轨迹制作的特效进行位置贴合的时候是比较头疼的,明明根据UMotion已经调节好的特效轨迹,实际运行的时候却不正常。可以将动画速度调快使得不同不那么明显,也可以将特效的贴合制作在播放的时候进行,推荐后者,后者相对于UMotion调节的缺点是不能反复查看,优点是能实时调节特效的播放速度,并且是运行时为准所以调好不会有偏差,最重要能在动作播放的时候进行调节,相比来说还是方便很多的。
创建的UGUI的dropdown,其子节点第一项的位置要摆好,因为实例化出来的dropdown的第一项的位置是和他一样的,如果位置有往下偏移,那么不清楚这个的新手一开始会仔细查看scrollview以及相关的选项看哪里选错了。
继承自monobehavior的脚本,如果里面的某个成员变量用setter进行最后赋值的监控,发现明明赋值了并且update函数中打印也是存在的但是,另外一些外部脚本访问这个脚本的对象的某些公开方法的时候一访问就没有,update里面每帧访问这个对象也是一直存在的,在Awake或者Start方法里面打印脚本所挂的物体的时候,也是只显示了一个物体。 没错,这种情况比较大的可能是场景里面的还是场景里有两个物体挂了这个脚本,只是另个物体默认没有打勾,导致产生一种这个脚本所挂载的物体只有一个的假象,然后外部脚本访问的对象刚好是没打勾的对象,这就是造成这种情况产生的原因。
使用SceneManager.LoadSceneAsync的时候,设置了allowSceneActivation要注意使用它以后的特点:使用它之后 Yield Return AsyncOperation是永远不会有返回的,意味着程序会卡在这句话上,而且如果有多个AsyncOperation在排队进行,注意在进行中的如果没有结束下一个不会开始,使用UnloadSceneAsync的时候,如果当前只有一个激活的场景,另一个场景因为allowSceneActivation设置为fale等还没加载完毕,那UnloadSceneAsync不会正常执行并且会返回空, 此外如果某个AsyncOperation一直进度为0,则它前面一般是有个没有执行完的AsyncOperation,使用要特别注意特点,相关详见Unity Scripting API搜索allowSceneActivation,LoadSceneAsync和UnloadSceneAsync 仔细看它们的说明和使用示例。
ArgumentException: Scene to unload is invalid 这个报错是要卸载的场景当前并没有加载或者要加载的场景当前没有加到列表里面所以找不到,我出现这个的原因是忘记了SceneManager.LoadScene默认在加载好场景的同时会卸载掉下一个场景,但是我好久没接触这个API了所以忘了,以为它只是单纯的加载。这里虽然说是很简单,可我排查了好久,一直都以为是哪个地方调用了卸载场景的方法。
修改图片等UI元素的material是会直接改到在资产里面的对应的material属性的,并且在游戏结束时候更改会被保存下来,所以要记得在游戏结束时将属性改回来,这样的特点不像物体里面有material和sharedMaterial的区分,也许unity以后对于UI也会做这样的区分吧。
Unity鼠标隐藏后按下ESC键显示出来是Unity系统内置好的一个操作,表示的意思是开发者想退出Game视图,进行其他的查看,方便开发者,一般不是开发者自己写的,所以不用再找是哪里释放,按下ESC后鼠标的各种属性和状态还是原来的变量。
package里面的代码是可能与asset里面的一样的,如果遇到ambigious 类似的报错,可以可以将其中一方的文件夹删除 原文链接:https://blog.csdn.net/weixin_43149049/article/details/99942916