经验
1.WebClient.DownloadString(new Uri(url));
的方式加载网页信息,大多数网页会有限制,证书是无效的.这应该是从最基本的信息安全考虑的, 那些搜索类的页面一般是可用的,比如http://www.baidu.com http://www.google.cn 而http://www.qq.com,是无法加载的.
2.使用 dotween时,如果动画是能连续触发的,那在上一次动画结束前,触发了下一次,那动画会以上一次动画被打断时的状态为基准,而不是原始的状态为基准.这样在连续触发的时候,动画就会走形.btn.transform.DOPunchScale(new Vector3(0.1f, 0.1f, 0), 0.3f);
,那怎么避免呢?①在动画前手动代码将状态复原.②连续触发给时间间隔,保证动画已经播放完.
3.对于小型游戏,整个游戏需要一个人完成立项,策划,程序,美术找人合作,这种情况下,一个游戏的立项是很重要的.①确定核心玩法意味着游戏的可玩性②罗列游戏中的元素(所有对象)③罗列游戏中的数据④确认游戏流程⑤编排归类所有信息,配表开发的框架性.
4.SendMessage的方法,可以有效的解耦合,但问题是,不方便查看,不知道被调用的方法,是从哪里调用的. SendMessage应该少用,以方便开发过程中的调试成本,以及后续的维护成本. 通常在OnTrigger 和 OnCollition 配合使用. 如果使用了,最好在 调取的方法那里备注好是从哪个类调取的, 其实也很鸡肋了, 最好还是少写,不然找起来真的难受.
5.Time.timeScale = 0;的运用
time.timescale 是控制时间缩放的,如果=0,那 time deletime 都会停止.FixedUpdate也会停掉, 但是 updata 是运行的.
6.开发流程小控制
当思路比较灵活,能一口气写完代码固然是好的, 但一个功能涉及到的脚本系统比较多的时候,最好不要一次性全写完了再验证,一旦里面出了一个错误,从结果来看是很难查清是哪里出错了,排查起来头疼的很.开发一个小功能的时候,从数据库声明数据,初始化数据,获取数据,数据传递,实例化对象,初始化对象的数据,等一系列操作,都一步完成,结果报错了,开始排查问题,起先以为string[]的类型不能直接赋值,我就奇怪了, 这不引用类型吗,.赋值直接就地址赋值过来了,要按角标索要是没问题了,但出了问题让我怀疑引用 类型是不是不能直接赋值,而是要一个角标一个角标的赋值!!!,当我经过反复尝试终于确定 string[] 作为引用类型是能直接赋值,又开始找下一个问题,以为在start里面实例化对象,对象一旦实例化出来,就会执行生命周期函数awake onenable , 以至于我在 实例化之后,再获取组件脚本进行初始化函数的调用,是时机太晚了. 其实不是的, 在一个start语句中,把对象实例化出来, 这个对象的确会执行awake , onenable, 但是 start 是要等到 本地的start执行完后,才会执行到的.所以也不是生命周期函数的问题. 之后又耗费了不少时间到处打断点,结果发现是自己的== 写成了!=, 这种错误简直了.
总结就是: 在开发过程中,一个功能涉及到的脚本较多,流程较多,应分段进行测试,验证逻辑是通顺的,再接着往下执行.
对整体而言,做一个小游戏,先从对象出发,把主要对象生成,以最简化功能实现为导向,然后再一点点添加的功能,比如做太空飞机大战, 先创建场景让场景动起来,然后创建主角,让主角动起来, 然后创建子弹,让子弹飞起来, 然后创建敌机,让敌机动起来, 然后让他们之间生效, 碰到会掉血 死掉之类的,这里面创建的过程并不一定要按这个次序, 我想说的是,创建最佳小单给一个基本功能,然后往下,这样效率会很高.当然这种开发方式只适用于小游戏.
7.序列化[System.Serializable]掉的坑
序列化到底意味着什么?? 既然 一个类 是可序列化的, 那么把这个类公开,它会自动 初始化, 那为啥我在awake里面调用,还是报空的异常呢? 不是已经初始化了吗? 于是我在 onenable start 里面都尝试, 全是报空异常, 直到在 update 里面 用按键执行,才没有报异常, 据此推测,一个可序列化类型的数组,如果进行new 初始化, 数组里面的元素 是可序列化的, 那么在 数组初始化的时候, 元素的确会进行初始化, 但是, 初始化需要时间, 并不是马上完成初始化, 而是在start之后.
8.使用 static应注意的坑
一个 event 可以注册事件,如果 是static的.那么要注意这event 是不会随着object销毁而销毁的.除非是退出运行了. 那会有什么影响呢? 会导致注册的内容由于销毁了而报空. 比如你A场景中的Aobbiect对象的 awake()里面 给 event +=AFunc, 按照正常的逻辑那我从B场景切换到A场景,Aobbiect对象是全新的,里面的awake()都会执行一次, event +=AFunc也是全新的. 问题就在这, Afunc是全新的, 但是 event不是, 因为 它是static的, 即便Aobbiect对象销毁了, event也是依旧躺在运行空间地址中,那当再次进入awake的时候 又对这个 event注册了一个 Afunc,这时候 event里面就有2个 Afunc了 , 可是 第一个Afunc由于对象销毁里面的对象已经置空了.所以会报空异常. 要解决这种问题,两个思路①不设置 static的event ②每当销毁对象的时候 对event
进行清空.
9.关于学习
入了程序员这一行,就离不开学习了.当我们面对一个新的技术新的需求,我们如何最好最快的掌握,这就是程序员的能力功底了.当然学习也分很多种,简单划分可以是零碎的知识,和系统的知识, 对于系统的知识最有效的学习方式是培训,其次是课程,再其次是书本,对于零碎的知识最有效的网络搜索,其次是问人,再其次是书本.
10.mask遮罩
蒙版的作用是使用一张贴图的alpha作为剪切区域,被显示的图片要作为蒙版的子物体,和蒙版的alpha值不为0的部分重叠才会被显示. 如果贴图本身无alpha通道,但可以设置sprite 的alpha source 为from gray scale 通过灰色产生alpha
11.Raw Image
这是原始图像和image功能类似,不同的是它可以接受任何类型的2D贴图, Render Texture 渲染贴图,就像是一张染布,摄像机裁剪到的画面可以染到 Render Texture上,RenderTexture 可以赋给Raw Image.这样就能用图片的形式展现摄像机的实时摄图.
12.unity知识是无穷尽的
如何去面对海量的知识体系和丰富的插件,unity商店有海量的优秀插件,不过大多是要钱的.就目前作为一个开发新人,接触到的游戏类型还很少,需要用到的功能也不多,如果为了学习插件而练习插件,其实是得不偿失的,因为它没有运用场景.对于书本上许多知识,需要做到留有印象,不一定要记得怎么使用,但应该记得有一些什么样功能,可以解决什么问题,当在实际开发中遇到类似的问题能够想起来有对应的知识和插件可以利用,然后再针对性的学习,运用到项目当中,这才是有效的学习方式.不必为了可能一直都用不上的插件,煞费苦心,却没能派上用场.当然了这些是针对海量的知识体系,插件,API而言的,没必要什么都去搞明白记下来,这没必要.但针对基础知识常用的知识体系,那就需要熟练掌握了,尤其是计算机语言的基础面向对象的思维, unity的UI系统 物理系统 动画机系统这些重量级的功能模块是需要好好捯饬,插件中 Dotween 这种最最常用的插件也是需要灵活运用的.
13.写博客于我而言
我是一个比较喜欢文字输出但又不太愿意花时间的人,大多数情况都是写给自己看的,偶尔想到输出变现,就忍不住又多想一下,怎么写给别人看,可好景不长,写给别人看需要考虑观众需求,写出来的东西就得打磨,而我不喜欢打磨,于是又陷入了洋洋洒洒随意写写的状况.所以写博客于我到底意味着什么?①巩固知识点和心得,以便在回顾的时候能在脑海里关联起来,加深印象,不至于一个东西又重新学习一边.②如果可以,把一些好的点输出出去,让他人看到,对他人有点用处,但对于这一点,我不奢求,毕竟我没有花功夫在上面.
14. 重新加载场景与不销毁游戏对象
通常情况下游戏场景重新加载时,场景中所有的对象都重新开始了,意味着,awke start 等生命周期函数都是从头执行的.但是当一个对象A是 DontDestroyOnLoad(gameObject);时,随着场景切换,又回到这个场景,对象A在这个场景中本来就有1个,再加DontDestroyOnLoad(gameObject);的那个也到这个场景里来了,那现在这个场景中就会有2个对象A. 如果再次切换,又切回来就会有3个,不断增加.这不是我们想看到的.该如何解决呢? 起初我认为只要给A对象做个判断,if (_instance == null)DontDestroyOnLoad(gameObject);在单例为空的时候才不销毁,这样,起初给单例赋值后,之后就不会为空了.的确对象A再切换回来就没有累加了.可是还有一个问题,就是 对象A还会有2个!! 因为即便你做了是否为空的判断,只是让A不要再执行不销毁的指令,可原场景中本就有1个A,只不过这个A不会执行不销毁的指令,随着切换 A就只有一个,但切回到原场景就又有两个了.!! 这也不是我们想要的.所以最佳的解决方案是给一个初始场景,这个场景只在游戏进行时执行一次,之后再不会进入到这个场景.那A就可以永远只有1个存在,除非退出游戏重新进来了.
最好的办法: 将需要dontdestroy的物体放到一个过渡空场景中,游戏启动后经过该场景一次但以后不会再经过,也就是一个临时空场景用来创建dontdestroy物体.
15. 摄像机做插值跟随的坑
在项目中我的摄像机跟随算法会分两种情况计算,起初摄像机有个初始位置A,是不动的. 当对象T往上快要越过屏幕时这个点设为B,开始做插值跟随T,保持一个位置偏差这个偏差就是B-A=a的向量.所以这时候插值的目标值为 T-a,也就是始终与对象T保持一个向量a的差距。但是当对象从临界位置B往下的时候,跟随的方式不同了 这时候摄像机做差值跟随,目标是自己的初始位置A, 两种的目标位置是不同的。所以在切换算法的一瞬间,插值目标从T-a 马上变为A,摄像机的位置也会猛然变化。尤其是在插值运算时间短的情况下更突出。减弱这种情况,把插值运算时间延长至2秒或者更多,那切换的时候,这种状况就几乎看不出来了。因为我每一帧(一个固定时间)要求移动的距离短,短到几乎看不出差别,这样看起的效果就是平滑的.
16.unity的生命周期函数执行顺序
在做项目时,有常用到awake 和 onenable ,有时候在B脚本的Onenable里面调用A脚本里面在Awake就初始化的部件a, 但是调用不到 a报空,一直以为awake一定在onenable之前,其实不然.unity生命周期大概有三个阶段:
①Awake和OnEnable ,一个脚本Awake之后马上执行OnEnable。
②Start
③Update
所以,不能保证一个脚本的Awake一定比所有脚本的OnEnable要早。但是可以保证一个脚本的Awake一定比所有脚本的Start早。同样道理,可以保证一个脚本的Start一定比所有脚本的Update早。
Awake和OnEnable实在Instantiate这一行运行的时候直接被调用的
在Awake和Start中创建的物体,它的Start是在同一帧执行的,它的Update也在同一帧中执行
一个物体如果是从Update的时候创建的,它的Start也是在同一帧执行,但是它的Update是在下一帧执行.
17.生成一系列元素,元素的初始化问题
比如说商店里面的商品,一般所有商品的对象都是统一生成的,在其父类或者生成类里面 进行生成,这些商品生成的时候,还伴随着数据的赋予,比如这个商品售价多少,等级几级,是否达到解锁条件等等,这些数据,是在生成类里面调用商品的Init初始化函数,传递过去数据,还是商品自己在Onenable里面 自己初始化.这取决于 这部分数据的类别,如果数据具有特殊性要由创建者统一调配,那就在创建者里面Init传递, 如果数据只是商品自身的一些普遍情况那就在Onenable调配.另外这也涉及到数据的耦合性,商品相关的数据是否要与商品自身相关联,还是由父物体统一管理,由父物体传递给商品.
18.数据处理的操作不宜过于频繁
在我的商店系统中,元素升级的一瞬间总会很卡,查其原因是频繁使用io操作导致的,PlayerPrefs.SetString(playerData, jsondata)的操作不应该频繁使用, 这样IO存取会十分耗费性能,解决思路就是,开辟数据实体类,存储字典或者集合结构,当程序退出或者定时10分钟,存一次就行。
16. canvasgroup的使用
关于UI元素的 raycast target性质, 有时候一个元素挡在前面,而无法操作后面的UI元素,我们可以取消raycast target的属性,还可以给这个UI元素添加一个 canvasgroup组件,然后把组件上的intertable 和blocksraycasts 属性 取消掉,这两种方式有什么差别吗?,感觉前者更方便。
在窗口的GameObject上添加一个CanvasGroup,通过控制它的Alpha值来淡入或淡出整个窗口;
通过给父级GameObject添加一个CanvasGroup并设置它的Interactable值为false来制作一整套没有交互(灰色)的控制;
通过将元素或元素
17.MVC下数据的处理
M层初始时获得数据,一般来讲都是从本地磁盘或网络服务器获得,获得第一次后,C层就能调用和 使用了,但是数据是会发生变化的,当数据变化时,比如说一把武器的等级提升了, 金币数量减少了,在这个时间点上容易想到的是把数据上传更新,但这是一种很差劲的方法,时时去调用IO操作或者服务器访问,会导致大量性能浪费,最好的方式是,在游戏退出时存储一下数据,或者每隔10分钟存一下数据.那中途的数据变化,一般发生在 页面切换的时候,使用Onenable Disenable 函数来处理数据, 这时候要特别注意谁的页面处理了数据,就先将谁的状态切换,以致先调用这个页面的Disenable 的函数.
18.配置数据表的规范性
数据表配置一定要规范,不要节外生枝,尤其不要多写或少写一行一列数据什么的,容易报错,差不出问题.
18.本地数据PlayerPrefabs存储的特性
unity 使用 PlayerPrefabs存储的数据 是以如下的形式存放到windows 的注册表里面的,一般来说每个项目的存储路径是不一样的.
路径为 HKEY_CURRENT_USER ---->Software ---->Unity ---->CompanyName ---->ProjectName
其中的CompanyName和ProjectName可以在Unity->Edit->Project Settings->Player中查看和设置。
前面的路径都是相同的 除了CompanyName ---->ProjectName, 因为这两个内容是你在新建一个unity项目时,unity编辑器自动生成的(如果你不修改的话),CompanyName =DefaultCompany. ProjectName=你的工程名称
所以这样每一个项目的playerprefabs存的位置都不一样,即便关键字是一样的,但是位置不同,因为 ProjectName 不同, 如果你改了CompanyName ,那CompanyName 也不会同,就存在了不同的地址. 可是还有一种情况是特别需要注意的,那就是拷贝别人的项目,将他人项目拷贝过来,unity里面的CompanyName 和ProjectName 也跟着一同拷贝过来了。然后你对这个项目进行了调整和修改,又把项目发回给对方,对方另存了一份你发来的项目时,情况就有点意思了,因为你会发现,对方原有的项目,和你发过来的项目,所使用的playerprefabs数据是相同的, 也就是数据地址相同。原因就是 这两个项目的CompanyName和ProjectName一直没有变过。 假如要规避这种情况,只需修改某一个项目的CompanyName或者ProjectName即可,这样数据就互不影响了。
疑惑
1.数据存放与处理
数据的处理是否严格按照静态和动态来划分,动态的从服务器下载,而静态的放在本地.
如果是这样,我感觉有点不妥.
比如我在客户端有一组数据,是关于道具解锁的道具B,的解锁条件是道具A升级到7级时则道具B自动解锁, 这个需求等级7级是静态的, 如果我把这个数据放在本地, 那么本地就可能串改该数据,比如改成1级, 那串改后就能不费力气的把 道具B解锁.还有,比如角色的基础数据移动速度,反弹系数等,都是静态的,这是基础静态的,那放在本地的话,很明显一旦修改,那对角色的属性影响是巨大的.
由此,是否 数据还是按照静态 动态分, 但是,是放在服务器还是客户端,不是依照动态就在服务器,静态就在客户端来分的, 而是根据此数据牵涉到的项目内容来定的,影响到游戏进程这种重要信息得放在服务器端,而放在客户端的是哪些无关痛痒的数据,比如 道具底图 图标的 名称,加载路径 这些数据 就放在客户端.
2.对象销毁还是失活
我们在一个游戏中为什么有些对象让他生成后,用完就让他销毁,而有一些是让他失活隐藏.这面的规则是什么? 我能想到的是 这得根据对象的使用频次来看,如果一个对象出现后使用完,之后再也不会用到它了,那就完全可以把它销毁掉,反正以后也不会用了嘛,留着还占内存, 那如果该对象使用完这一次后,只是暂且不用了,之后还会用到它,而且使用的频次还挺高的,这时候如果还是销毁的话,那反复要用它就得反复的加载实例化它,这样会很耗性能,采用失活的方式就可以避免下次再用的时候要加载实例化,直接 激活就可以了.但有个问题,失活的对象是保留在进程中的,它会占用内存资源,假如这个对象,占存非常大,使用也只是偶然,那是否也可以将它销毁,用的时候再加载实例化呢?? 隐约记得重复销毁和实例化会增加垃圾内存,不知道对性能影响有多大?? 之前看 智哥的粒子对比, 发现数量极大的对象 用失活激活对比实例化性能还是会提升许多的. 那如果这款游戏内运行内存的要求很高,保持一个失活的对象在进程中,是不是会影响进程内存利用率呢???①整个游戏只使用1次的对象,实例化后销毁 ②使用多次的对象,体量小,实例化后失活,对象池管理起来 ③使用多次的对象,体量大,如果对内存利用率有高要求甚于反复实例化该对象时CPU的要求,那就销毁吧,如果不高于那还是失活缓存的好.
3.选项卡切换刷新显示物品卡顿
在游戏中有非常多的地方会用到选项卡展示对应内容,比如背包,分为武器/战袍/坐骑等等,每一个栏目下面有许多cell, 并且需要对这些cell进行操作,比如购买,升级,操作后数据就会发生变化,而且数据要同步更新到服务器(我这里是palyerprefab存储).对应的显示信息就得刷新.通常都是采用整页刷新的方式,在逻辑上比较容易处理.但这样处理在切换页面的时候会有点卡顿, 能想到的原因就是每次刷新页面内的所有cell都需要从服务器(palyerprefab)在加载数据,这样CPU会比较慢是这样的吧.那该如何处理呢?采用什么方式刷新展示物品,既能同步更新数据,又能实时更新显示.
4.协程中的按键监测转成手机端的按钮
在项目中很多时候需要开启一个有限时长的协程,在期间内,监测用户输入,一般PC端就直接input.getkeydown了, 但手机端只能通过新出现的选项框,点击对应按钮,那有什么办法能在协程中监测按钮的相应呢? 我现在想到的就是,声明bool变量, btn的点击事件绑定bool=true的方法,然后协程中监测的是bool 的值.这样可以解决问题,但略显繁琐,不知道还有没有其它办法.
5.项目中的数据类该如何架构
一个项目一般都会有数据,数据还不少,获取或存储对应的数据,一般都会写成静态方法,外界直接在任何地方都可存取数据.那这样这个数据不就和所有的类都可能耦合吗?还是说由静态方法调用的形式,不算耦合.这样的情况到处都能看到数据类加静态方法的身影.这种情况是否是可取的,是不是应该让这些数据的操作,又比较规范的地方执行,不能让每个对象都有机会获得?比如商场里面的商品购买,商品身上的购买按键是否能直接调取到数据类中扣除金币的静态方法,还是说通过商品的父类统一调配方法, 商品购买其实调用的是父类的方法,这个方法操作数据类??该以何种形式进行,两者又有什么优劣? 更新一个金币数量得把整个玩家数据包拿过来,这种感觉很不好.
6.能否通过反射获得Resources下的预制体
项目有时候要动态加载一些资源,资源很多都是预制体,那可以通过反射,写入字符串来加载 预制体吗?
7.那些成熟的游戏公司的框架是怎么来的
框架都是人写的吧,那一款游戏有了框架意味着什么?我们公司是一家新公司,都没有框架的,项目都是从零开始写,是不是一个项目成型了,其实也就有了框架,框架并不是什么高深的东西,但是框架有好坏优劣之分,一个好的框架稳定/易维护/易扩展, 一个坏的框架牵一发而动全身,难以维护.那写出一个像样的框架程序员是什么样的水准.一个游戏框架到底会包含什么部分呢?
8.数据存储前校验的规划怎样比较合适
在游戏里面,时常需要存储数据,但是存储之前,需要校验该数据以什么方式进行存储,比如 我要存一个角色的碎片数量15个,假如这个角色之前没有碎片,那这次碎片的存储就需要存15个,另外还要解锁该角色,并给一个初始等级1级, 但是如果该角色已经有碎片了,要存15个,要做的就不一样了 ,只需要在原有碎片数量的基础上加15个即可. 就是这种数据的检验过程模块,应当是在前端进行判断还是后端,比较好啊.
9.数据层应该怎么规划
制作UI系统时,各个页面都有各自的MVC,M层是数据层,但是不同的页面有不同的数据,还有很多页面有重复的数据,比如角色相关数据,武器相关数据,这些数据在多个页面都会使用,该怎么处理呢,数据以什么样的形式存在,在一个通用数据库里面 使用静态吗还是在主页面的数据层,全部获得,也使用静态,或者单例之类的。这样设计的话MVC层的耦合关系又会更加蔓延到更多页面。