unity3d 学习 插件

2017-11-08 09:39:15 AGroupOfRuffian 阅读数 7476
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

临近年假,好不容易有时间歇歇,偶然看到一个视频教程讲的是PlayMaker,本着认真学习的态度去Asset Store下载,结果收费,于是果断去度娘下载免费版,结果看到一篇文章讲的是Unity3D常用插件,网址:http://jingyan.baidu.com/article/7f766daf4ef2844100e1d079.html ,想想自己也有小半年unity经验了,于是整理了一下自己用过的插件并附上下载地址。

1. NGUI,这个不用多说,上官网:http://www.tasharen.com/?page_id=140,也可以去我的百度云盘:http://pan.baidu.com/s/1qWwSwFE

2. FingerGestures,顾名思义,这个是支持移动设备所有触摸事件的插件,包括点击,滑动,还有自定义手势等等,云盘:http://pan.baidu.com/s/1mgDp5Gk

3. EasyTouch,也是用于移动平台上的RPG类的游戏,这里有篇文章,简单介绍了一下该插件http://my.oschina.net/dingxiaowei/blog/205231,下载地址:http://pan.baidu.com/s/1o6xC0Z4

4. ShareSDK,功能强大的分享插件,支持一键分享并且配置起来十分简单,目前绝大多数的社交软件都支持,并且他们官方的文档写得特别清楚而且如果注册用户的话,还会有技术支持,官方文档:http://wiki.mob.com/Unity3D快速集成指南/    下载地址:http://pan.baidu.com/s/1Coc8I

5. AndroidRemote,这个主要用于安卓平台和电脑联调,省去了每次测试都要重新打包的麻烦,下载地址:http://pan.baidu.com/s/1bndGfwj

6. NGUI_HUD_Text,这个用于游戏里血条的变化,下载地址:http://pan.baidu.com/s/1jGmpsge

7. 2dToolKit,是一款2D开发组件,它具有很强的灵活性,可以让开发者在Unity中进行2D开发,下载地址:http://pan.baidu.com/s/1mgsWNA4、

8. PlayerMaker,是一个可视化脚本工具,开发者可以使用它很快的完成游戏原型制作,能够大大的提高开发效率,让你的游戏更加完美,下载地址:http://pan.baidu.com/s/1jGL1jCi

9. PathologicalGames,用于绝大多数的曲线操作,下载地址:http://pan.baidu.com/s/1bn8iowv

10. FxMaker,侧重于unity里的粒子特效系统,这款插件提供了众多特效资源和效果预览,玩家可以随意组装效果并最终合成一个预设自己使用,下载地址:http://pan.baidu.com/s/1bndagvt

11. Magical FX,魔法特效包,下载地址:http://pan.baidu.com/s/1vtc8e

12. water.unitypackage,这是一款用于水的插件,性能还比较高,用于移动设备也没问题,弥补了专业版的unity水特效资源在移动设备卡机的不足,下载地址:http://pan.baidu.com/s/1gdxQGrp

13. LitJson,看名字就知道,这是用于Json操作的一些API,下载地址:http://pan.baidu.com/s/1gdzS9H5

14. icsharpcode-SharpZipLib,用于解压缩, ZipLib组件与.net自带的Copression比较,在压缩方面更胜一筹,经过BZip2压缩要小很多,而且这个功能更加强大。使用方法:http://blog.csdn.net/wjbaiverson/article/details/6226160,下载地址:http://pan.baidu.com/s/1dDF5JKt

15. unity-lzma,也是一款用于压缩和解压文件的库,它是一个开源的类库,有C、 C++、C#、JAVA的类库,Unity里面我们自然要使用C#的类库,使用方法:http://www.tuicool.com/articles/J3Iv22I,下载地址:

http://pan.baidu.com/s/1jGorfIQ

 

2015年7月10日补充(摘自http://www.cgjoy.com/thread-47257-1-1.html)

16. uScript-Visual-Scripting-Tool-for-UnityEasyMotion2D     脚本插件

17. megafiers.unitypackage         变形插件,官方售价150美元,对各种物体任意变形

18. Unisky 1.2.6                       天空插件,方便的模拟各种天气

19. UnityScriptEditor                脚本插件,方便智能的编辑脚本

20. cave-run-3d-unity-game-starter-kit      EasyRoads3Dv1.8.1.unitypackage       寻路插件

21. chipoff-fracture-system            破碎特效插件

22. Enhanced Editor++ v2.1.unitypackage 编辑增强插件

23. MessageManager_1.1.1        消息插件

24. particle-system-collection     粒子效果插件

25. Dynamic Elements FX Pack       著名的效果插件

26. FX's+Collection            各种特效的集合,包括瀑布,雨雪,落叶等等40多个特效

27. Unity Shooter Engine  (Unity3D)U.S.E.射击游戏引擎 V1.7

28. RoadPathTool v1.1&RiverTool v1.1             寻路插件

29. SwipeControl.unitypackage                    滑动屏幕插件

30. FPS_Kit_for_3.0.unitypackage         City Damage.zip                            城市套件

31. MagicalEffects.unitypackage                 魔幻特效插件,模拟各种好看的效果

32. Sprite manager.rar                           动画插件

33. Bitmap2Material.zip                            材质贴图插件

34. substance_designer_2_0_0.rar                      材质插件

35. 硬表面着色器Hard Surface Shaders Free and Pro .rar   材质插件,各种材质轻松表现

36. TerrainToolkit               地形资源,包括各种树木等等

37. Ian S Explosion Pack         爆炸特效插件,模拟各种爆炸效果

38. TimelineFX.时光粒子编辑器     可以编辑出各种粒子效果

39. Prime31_StoreKit_for_iPhone  著名的广告插件

40. Chickens Shader Bundle  shader包,包括各种材质shader

41. shatter toolkit切西瓜插件

42. easy unity3d timer 时钟插件

43. dungeon construction kit   characterkit-csharp2 角色游戏插件
44. Enemies.unitypackage    Buoyancy toolkit.unitypackage    camera splat effects 镜头特效插件
45. cp morphing lab 人体变形插件
46. easy destructible wall 模拟墙倒塌的插件

47. shatter toolkit  著名的切西瓜插件,可制作水果忍者游戏

48. hard shaders pro 硬表面材质插件专业版
49. energfx           能量特效插件
50. edys vehicle physics 车辆游戏插件
51. etcetera.unitypackage    uscript 可视化编程插件
52. Tom TerrianTool 地形编辑插件
53. 3d anaglyph system   3d红绿插件
54. InventoryManager-js-cs  背包系统插件
55. soccertoolkit  足球插件
56. strumpyshadereditor  材编辑质插件
57. amplify shader manager  材质插件
58. unity external lightmapping tool 外部灯光插件
59. Vectrosity向量划线工具
60. Cartoon Clouds 卡通白云
61. HOTweenEditor_v0.2.101
62. 镜像反射 着色器 Mirror Reflection shader
63. 飞船模型带贴图.unitypackage
64. 二层房屋室内示例House of the Future example
65. 爆炸效果 Detonator – Explosions for Unity3D
66. Camera Splat Effects 镜头溅斑效果
67. Car Physics 汽车物理引擎包
68. Fuz(毛发)shader
69. Head Look Controller – 头部转向控制.unitypackage
70. JCar Unity3D开发的简单赛车游戏
71. Qualcomm AR – 增强实境工具.unitypackage
72. Rope BETA 4.0 链子 绳子脚本示例
73. 《魔兽世界》角色控制.unitypackage
74. 反向关节工具.unitypackage
75. 血液飞溅特效Blood Splatter FX
76. 3rd Pesron Shooter Kit 第三人称射击游戏包
77. AI 4 Enemies 人工智能.unitypackage
78. Asylum 避难所,游戏室内场景项目资源
79. 清风房车特效演示 材质 光效 动画 AirStream - Substance(含源码包.unitypackage
80. unitycar:可以无需代码制作各种赛车游戏。
81. gamedraw:可以制作模型,编辑模型,uv展开等等,无需切换到其他3d软件。
82. a pathfinding project pro:强大易用的寻路插件。
83. ship game starter kit:制作各种船类游戏
84. poolmanager:游戏性能优化插件
85. ez replay manager:帮助播你录下任何u3d游戏,重新播放,甚至以更快的速度
86. RapidUnity Extra Primitives:让你迅速的添加物体到场景
87. BrainBuilder编写游戏无需代码.unitypackage

88.lavarocks.unitypackage

mouselookdb.unitypackage

Unity3D模拟制作陨石袭击地面或者炮弹落地成坑的效果
Tazman-Audio.Fabric.v1.22f.unitypackage
89. TerrainEdge地形包
90. tower_Defense.unitypackage
91. Unity3D中实现简单的电影模式框架  Edit UV.unitypackage
92. 树木管理器小插件.unitypackage

93. CloudsToy v1.2

94. FX Camera Systems(FX 相机系统)
95. gamedraw:可以制作模型,编辑模型,uv展开等等,无需切换到其他3d软件。
96. a pathfinding project pro:强大易用的寻路插件。
97. ship game starter kit:制作各种船类游戏
98. ez replay manager:帮助播你录下任何u3d游戏,重新播放,甚至以更快的速度
99. RapidUnity Extra Primitives:让你迅速的添加物体到场景
2015-03-29 10:38:17 u011844375 阅读数 602
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

1.      skele插件

 选中骨架层(Box01)然后按 StartEdit进行编辑,可以改变姿势动作(事先要需要先安装skele插件,并且需要有一个具有骨架的模型)

第一个图片是Unity3d中的Hierarchy  (如果你已经安装了skele插件就会有StartEdit这个东西)

 

 TimeFWJ


2.      制作动画片段

 先在Window那里调出Animation的小窗口->按了Add Curve之后选择-Box01->isActive旁边的加号

 


3按小红点录制按钮


 

4.  把红线拖到第二处地方并按右键AddKey

 

5.选中上面的脚的骨骼进行旋转,形成第第二条线的动作

 

 

6.  这个window会记录你做过什么动作(可以看出进行过了脚部的某个骨骼进行了旋转的操作)

 

7.按播放按钮就可以看到模型的脚在动



2018-10-17 14:15:14 qq_22521529 阅读数 30040
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

Unity3D学习路线与学习经验分享//最后一次更新为2019.7.22日,更新了一些废掉的链接

作者:15游02 丁祺

你好,这篇文档是我的导师孙老师(以下简称老孙)指名我 书写给新手、初学者以及技能有些许缺陷的人的一篇经验分享的文档,当然如果你看到了这些文字,代表着你是一个有意愿或期望去学习这款软件的人。因人与人之间有很多的不同,以下我会尽我所能,通过不同切入点与角度,并根据以上人群的不同技术程度,来帮你提升你的技术实力,我会在结尾留下我的联系方式,有兴趣的话可以联系我。那接下来我就分享一下我的学习经验。

写给新手与初学者:

你在准备开始学习这款软件之前,可能经常会听周围的人说,“英语很重要,如果不会英语,程序是学不会的”,“学程序,就又得背单词了”,“C语言都没学好,不可能搞得懂Unity3D的”...等等这一些容易影响心态的话。我认为,学这个,开局最重要的并不是疯狂翻书疯狂看视频直接拿来就学,而是——消除恐惧,消除对程序,对Unity3D“高大上”的感觉。

所以学Unity3D需要英语吗?

那?你觉得什么程度才是会英语呢?我分享两个链接给你,这是英文演讲与纯英文文档的网页链接,如果有兴趣测试一下自己的英语实力的可以打开看一下。

【Unite 2018 柏林大会】完整版录像

https://www.bilibili.com/video/av25266964?from=search&seid=16153052159950265652

Unity3D官方API文档

https://docs.unity3d.com/Manual/index.html?_ga=2.153078527.776856565.1539157046-412574144.1536670294

对于我来说,如果你能听懂英文技术演讲录像,能看懂英语技术文档才能被称之为会英语,反之则不会,我觉得不能为你解决实际问题的技能根本不能称之为会,只是在浪费时间而已。简单说,如果你想靠Unity3d这个东西吃饭,并不需要英语水平,你不需要刻意 去记忆英文语句,按照我的话讲,纯英语界面的Unity3d,抄它的界面就好了,界面没有的,去网上找对应的功能,并抄在笔记上,下次用的时候直接去翻就足够了(如果你想尽快提升能力的话,我建议千万不要去记,用的时候直接去翻看笔记就好了,在大量的练习中,你的大脑就会自然而然的记住这些代码,我就是这样的)。

当然了,如果你想成为界内大牛,你就必须会英语了,毕竟这是老外的东西,学会英语可以帮你打开一扇新的窗户。不过你现在的英语实力绝对绝对足够去学习这款软件了。

C语言学的差,会不会差很多啊?

会C语言与不会C语言的人,同时学Unity3D中的C#的学习速度,是有区别的,毕竟他们两种语言之间还是有一些共同的部分的,但达不到很多,这种程度。但与其你自己纠结 这个问题,不如问问自己愿意付出多少学习能力在这个上面。每个人都拥有清华北大学生的学习力,只是愿意付出的量不同而已,想学就一定不会差

“我感觉Unity3D就是一个小学生引擎,画质贼差,感觉学起来没什么意义,还不如去学虚幻呢。”

相同场景下 虚幻4 VS Unity 5

https://www.bilibili.com/video/av17956467/?spm_id_from=333.338.recommend_report.2

如果你有这种感觉的话,推荐试一下《崩坏3》手机游戏与腾讯的《逆战》电脑游戏;

 

总之Unity3d只是个工具,做出来的东西的好坏,取决于用的人,就像你打游戏,如果你技术不好,再版本强势的英雄,你也没法单人打上最强王者。

 

 

如何开始学习Unity3d?

我要变学霸_如何写笔记?How I Take Notes

https://www.bilibili.com/video/av10395257

在开始学习之前,你需要一个笔记本,推荐活页的,就是那种可以拿下来单页的那种,他就是你以后学习时,你大脑的硬盘,笔记法同时适用于世界上的全部科目。具体怎么写笔记我就不说了,只需要记住两点:

  1. 千万别自以为是的认为自己的脑子很好使,你不是机器,写笔记,必须且一定要写,不然,重复查找自己找过的知识点的时候,你的心态会受到影响,且会浪费大量的无用时间,而且千万别用电子版,记录电子笔记的时候会勾起你ctrlC+V的欲望,表面看,你节约了半分钟打字时间,可你没有经过你大脑思考的这一步骤,会严重影响你的记忆,而且电子版无法像纸质笔记本那样粘书签,翻看会非常麻烦,相信我,多动手写几个字而已,不会要人命的。
  2. 写知识点的时候千万千万别抄别人教给你的话,(1)一定要尽可能的精简,(2)并尽一切可能用自己的语言表达,然后把自己的话记录到笔记本上,否则你就是白写,没有任何意义。

 

开始学习Unity的几种方向(热手用的入门期,熟悉者可以跳过)

以下我会给你列举几个我个人推荐的开始学习Unity的几个入口,当然你在学习这些东西的时候,不要忘了听课(课上尽量不要跟着敲代码,敲代码会一定程度的影响你的注意力,而是听出老师讲课的核心,并把核心知识转换成自己的话,写在笔记本上);你可以根据自己的兴趣,与自己大脑的记忆习惯来选择,从哪个位置进入。这几个入口是没有优劣的。不用觉得哪个入口高大上,重要的是,这个入口是否适合你自己,如果发现听不懂,认真再听一遍,如果还是不会,直接跳过。不过在学习期间,你是没精力玩游戏的,记得删掉你手机与你电脑里的游戏,等你自己的Unity技术足够养活自己了以后,再用Unity给你赚来的钱,充到游戏里做个天选之人吧。

以下全部视频我都看过,你大可不必担心视频的质量(我个人在看视频学习的时候,会同时播放一些没有人声的轻音乐,来降低困意)

1、暂时比较厌恶或者恐惧代码,以及零基础的人

傅老师的Unity3D教学 //这里傅老师的原视频被收购,所以他近期刚刚新出了一套新鲜的视频,供大家使用

https://www.bilibili.com/video/av57479723

这个系列是通过插件的方式,来尽量绕开代码这个大难题的(当然不代表完全没有),让你在不太了解代码的情况下就可以基本操作Unity,并且可以做一点东西出来,他还在这个系列里教学了插件画面美化,通过点击的方式就可完成3A画面的特效处理。

 

2、传统的教学方式:

这个入口主要是以不断完成微型小游戏的一种路线,有一点点难度,推荐了解一些些C语言或其他编程语言的人进入

Unity零基础入门 - 打砖块 http://www.sikiedu.com/course/77

零基础入门Unity - 古迹探险 http://www.sikiedu.com/course/84

Unity5.2入门课程 - 进入Unity开发的奇幻世界 http://pan.baidu.com/s/1mhDZO7m

Unity中的C#编程 - 零基础(Unity 2017) http://www.sikiedu.com/course/83

第一季 C#编程初级教程 链接:https://pan.baidu.com/s/1nwGyGDN 密码:memt

UGUI - Unity 5.1超强UI案例学习 http://pan.baidu.com/s/1qYENTW4

这个系列使用比较传统的教学顺序,来熟悉Unity操作的。

 

3、相比视频更喜欢看书:

《Unity游戏设计与实现 南宫梦一线程序员的开发实例》(绿色的书)   第0章

 

《Unity 5.X从入门到精通》  Unity官方制作,精品中的精品(这本书包含了入门期开始到精通这款引擎的几乎全部内容,如果你打算看这本书,坚持从头看到尾吧。

 

要尽早结束入门期,毕竟入门期只是用于热手。

 

正式学习期

入门期结束后,就要正式开始学习Unity了,以下为必学项,每个视频后我都会写上我认为必学的理由,我会尽量解释清楚推荐的理由,不会让你带着“我擦学这有啥用啊”之类的想法去看的。

以下视频我会分成块,你看的时候,各模块之间看的顺序可以随你兴趣,但所有的视频内容都必须认认真真看完。(此清单的部分内容来自于SIKI的A计划学习路线的加工整理版本)

 

代码部分(代码,与外语一样,都是一门语言,都是用于交流的工具,只不过外语是跟人交流,而代码是跟机器交流的,学会了代码你才能真正开始控制电脑,让他帮你工作;放心,代码是世界上最简单的外语,比学英语相比不知道简单了多少倍。)

第一季 C#编程初级教程(基础代码部分,这其中大部分都跟C语言,Java相同,已经学过的可以快进着看) https://pan.baidu.com/s/1nwGyGDN 密码:memt

第二季 C#编程中级教程(略微提升了点难度,但其中的内容都很常用。学这个的时候会有一种完全不知道这破玩意有什么用的感觉,没事这是正常现象,不用太担心,做好笔记,当你完全理解了面向对象以后就明白了) https://pan.baidu.com/s/1htLYalI 密码:78zv

Unity API常用方法和类详细讲解(API是Unity这个引擎提供给你的“工具”,这部视频干货特别特别多,不要用脑子记,但千万记住,一定要在做好笔记的同时,理解每一条API的大致意思。这是Unity最最最核心的部分,你以后的学习和工作中会天天用到这部分的内容。) http://www.sikiedu.com/course/59

 

物理系统(因为现实中的真正真实的物理,实在是太难模拟了(想象一下摩擦力),所以程序员使用了“碰撞器”,来大致模拟现实中的物理效果(如你去台球厅打球,人用球杆用力击打白色球,这个球撞到其他球时,可以打飞其他球。这种“打飞”其他球的效果使用了碰撞器来实现。))

Unity官方教程-3D物理系统-中文版 (这段视频中的难度是逐渐上升的,先看懂前4 课,可以不用强迫自己一口气看完) https://space.bilibili.com/28562838/#/channel/detail?cid=30384

 

UI部分

UI是我认为的Unity中最简单最容易上手的一部分了,基本不需要写任何代码,拖拖拽拽很容易就OK了。(可能有人听过NGUI,我简单解释一下,NGUI是过去人们用的非Unity官方制作的UI系统,现在已经不像过去那么流行了,个人不建议学习,当然,有兴趣者可以看看,不过UGUI一定要会)

UGUI - Unity 5.1超强UI案例学习 http://pan.baidu.com/s/1qYENTW4

 

动画系统(如果你不希望你做出来的游戏都是一堆不会动的“僵尸”,就好好学这的内容。这里放上的视频链接为3D模型的动画系统)

需要学习的量有点大,有一定难度。不过我还是那句话,不要用脑子记,抄笔记。

 

Unity中的动画系统和Timeline(Unity2017) http://www.sikiedu.com/course/82

 

光照系统 (不过多解释)

[傅老師/Unity教學] 30 - 3種烘焙光照模式(Baked Lighting)

https://space.bilibili.com/211153830/#/video?tid=0&page=5&keyword=&order=pubdate

[傅老師/Unity教學] 渲染相關知識 - 01 基礎渲染步驟

https://www.bilibili.com/video/av17250027

[傅老師/Unity教學] 渲染相關知識 - 02 室內光影基礎實驗

https://www.bilibili.com/video/av18330502

[傅老師/Unity教學] 渲染相關知識 - 03 用一顆石頭架場景!! 室外光影基礎實驗

https://www.bilibili.com/video/av18442018

 

粒子系统(这里我就不多解释什么是粒子系统了,打开视频链接,自己看吧)

 

Unity官方最新粒子特效素材包视频 https://www.bilibili.com/video/av31104362?from=search&seid=10690239211286766965

 

顺便在此推荐一下上传这个视频的B站UP主Shallot夏洛特,他的个人空间经常会上传一些关于Unity新奇功能的教程。想长期学习Unity的话,推荐关注一下他的动态。

 

Unity中的Shuriken粒子系统(Unity2017)入门级教程(想深入学习粒子的就去网上查吧)

http://www.sikiedu.com/course/79

 

 

DOTween动画插件

这个插件是所有插件中,唯一的一个我建议你必学的插件。这个插件是用来制作物体的移动(从一个位置移动到另一个位置的动画),UI的移动(从 一个位置移动到另一个位置的动画)从一个颜色渐变到另一个颜色(从一个颜色变换到另一种颜色的动画)的一种便利性的插件。嗯?没听懂?

DOTween

链接:https://pan.baidu.com/s/1i6Ud2XB 密码:6amf

 

项目制作教程(知识学累了吗?想实际做做项目练练手?我这里精选了几个我做过,并且感觉收获颇丰的视频教程给你,推荐你感觉脑子爆炸的时候食用。)写代码的时候,如果你有能力,尽量不要抄袭他的代码,使用自己的习惯去写,只要做出来的东西和他一样就行,这种自己思考代码的方式将会极大的培养你的代码写作能力。

初级案例:

Unity初级案例 - 坦克大战(Unity2017.1) (就是小时候我们玩的插卡游戏机里的坦克大战)

http://www.sikiedu.com/course/90

Unity初级案例 - 愤怒的小鸟(其中使用了2d的物理系统,使用方法和3d的基本相同)

http://www.sikiedu.com/course/134

有一定基础后的案例:

Tanks第三人称双人坦克大战视频教程

链接:https://pan.baidu.com/s/1eTbuUxO 密码:qi4b

Stealth秘密行动 (Unity最经典官方项目之一) 链接:http://pan.baidu.com/s/1miMWSas 密码:bl83

从零开始三小时速撸一款雷姆小游戏Unity3D全过程(如果你喜欢二次元的孩子推荐这部和下面那部。会稍微有点理解上的难度。)

https://www.bilibili.com/video/av5814699

从零开始的两小时速撸:使用血小板制作脑血栓游戏(跳一跳)

https://www.bilibili.com/video/av27387729

MMD功能 写实版场景配Unity娘 极乐净土(喜欢二次元的同时,又好奇MMD到底是怎么制作出来的人推荐玩一下这个视频)P1为介绍视频。P2为教学视频

https://www.bilibili.com/video/av7700841?from=search&seid=13542732114945459430

https://www.bilibili.com/video/av7715582

(在这个作者刚出雷姆小游戏的时候我还是个代码都不会的孩子,但当他出血小板的时候,我已经可以用我自己的Unity知识工作了;假如你真的想好,要走Unity这条路的话,狠下心来好好学,努力是不会辜负你的。)

 

在此推荐一下这三部视频的作者 新鲜的女尸,就跟这名字一样,这小子真跟尸体一样突发性诈尸一次,然后又进入了长期的休息期,喜欢做二次元Unity项目的人推荐关注他一下,没准哪个动漫突然火了以后又出来诈尸一次。

 

 

最后的高级提升期

因这里的内容都比较难,如果你没有上面的知识积累,看这里的视频会让你困懵逼的。这部分内容推荐已经学了新手期与大部分入门期的人再深入学习

 

高级代码部分

第三季 C#编程高级教程 这部视频可以说,已经把代码的全部内容讲完了,难度很高,可能你看完了初级和中级以后依旧看不懂,如果真的用心去看,并且反复看了好几遍依旧看不懂的话,先放下学学别的,然后再回来看。推荐有至少一年代码经验的人食用

链接:https://pan.baidu.com/s/1slBP7mH 密码:eob1

 

数据结构 数据结构是一门分析如何存储数据与数据之间逻辑关系的一门学科,这部视频里的老师讲的真的很好,你认真看能看懂的。数据结构的知识面试很容易考,你一定要认真学里面的知识点。(千万不要看上海交通大学的和清华大学的数据结构公开课,虽然这两部视频中的老师讲课很有牌面,但是全是说的书面语言,没有习惯听书面语言的人是不太可能听懂的,最好不要去看,不然会严重打击你的自信心。)

【考研】赵海英 数据结构 (这部视频就不像C#高级篇那么难了,认真听就能会)原链接的视频被取消了,如果需要查看,请在B站或者百度搜索赵海英 数据结构

【郝斌】-数据结构入门 (学历不高,但讲课极其精辟的一位老师,喜欢听白话讲课的人非常推荐,  推荐上下两位老师一起结合着看,你会有非常高的收获)

https://www.bilibili.com/video/av6159200?from=search&seid=10153396348143638381

这里我再推荐一本书 《大话数据结构》

这本书用很通俗易懂的语言写了数据结构的内容(虽然有的例子贼牵强,哈哈哈),推荐不喜欢看视频,或者看完视频后查缺补漏使用。

Unity编辑器扩展 编辑器扩展是一种提升在Unity中的脚本美观性的一种技术,学起来还是很好玩的。

编辑器扩展

在线观看: http://www.sikiedu.com/course/47

链接: http://pan.baidu.com/s/1gfHURYF

【水鸡游戏课堂】Unity编辑器扩展1至7

https://www.bilibili.com/video/av23671632?from=search&seid=4358948779151612714

算法 算法是一门研究如何利用数据存储的数据,求出计算结果,以及讨论最优计算方法的一门学科,有些地方也把算法也一并算作是数据结构。(这里的这一部视频,并未收集全部算法领域的知识,你需要在以后的工作中继续学习补充你缺失的部分)

编程内功修炼-算法 链接: https://pan.baidu.com/s/1eSgkCpk 密码:xcz2

 

设计模式 设计模式是前人写代码时,透过需求发现的一些常见的结构上的问题,并把这些问题的常见的解法,用UML图与简单的文字归纳出来的一种代码结构思想。在学习这里的知识点的时候,如果突然有一种,“卧槽,在我学设计模式之前我就用过这模式”的感觉,说明你在之前的练习中,真的用心去思考问题了,是一种非常很好的现象;在学习设计模式的时候需要注意几点。

  1. 这不是考验记忆力的学科。设计模式不要死记,重要的是学会他构建代码时候的书写模式。
  2. 不要任何地方都想用设计模式。学会设计模式后,宁可不用也不要滥用设计模式,不然会让你觉得束手束脚,无从下手,当你有了大量的代码练习基础后,你就会明白模式的好处,与应应用的场合了。
  3. 不要觉得设计模式中的例子就是绝对正确的。有时候设计模式中举的例子,并不一定是你的项目中真正需要使用的书写方式,千万不要被他的例子束缚住。
  4. 以上三点虽然我说的很轻松,但是想真正做到这些,没有大量的实验与使用基础是不太可能做到的,不要有太大压力,慢慢来别放弃。

因我自己学习时使用的是SIKI A计划课程中的视频(收费),所以不敢给你瞎推荐好的免费视频,如果你没有A计划的账号,推荐去B站搜索“设计模式”,手动搜索适合自己的视频。A计划收费视频   游戏开发中的设计模式 http://www.sikiedu.com/course/54

 

框架:MVC 这是我认为最有用的东西之一了,他可以说属于设计模式的范畴。我认为他与其说是知识,不如说是一种规划代码的一种整理方案,让你不再觉得代码乱,也会杜绝总会出现一些在奇奇怪怪位置调用的变量。

同为A计划收费课程 基于MVC架构的俄罗斯方块开发(基于Unity2017)

http://www.sikiedu.com/course/80

 

数据库 数据库的重要性不用我多说吧。

MySQL数据库从零到精通 链接:http://pan.baidu.com/s/1mh6FR5Y 密码:qlf7

http://www.sikiedu.com/course/48

 

Shader Shader是一种显卡语言,当然了,如果你真的能学到这,我想也不需要我解释什么是Shader了。

简单易懂的Unity5 Shader着色器入门教程

https://pan.baidu.com/s/1pLCwaKj 密码:7wqu

 

以上可以说就是Unity需要的基本知识了,还剩下一些零碎的知识,需要你自行查找并学习。

1、3D数学

  1. Json 数据持久化 想将数据保存在硬盘里?学这个吧。
  2. uLua热更新 链接: https://pan.baidu.com/s/1kUIZMcB 密码:x12i
  3. 网络链接Unet  或者 PhotonServer

 

结束语:

如果你真的按照我说的做,并且认认真真按照我说的路线与方法学了下来,谢谢你愿意相信我,也愿意相信你自己。带着你努力的成果与这股学习力,去找工作吧,社会不会辜负愿意努力的人。

作者:丁祺

QQ:1968548010

版本:v1.1;

2018-01-30 15:00:43 leansmall 阅读数 13543
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

原创文章如需转载请注明:转载自风宇冲Unity3D教程学院


引言:想用Unity3D制作优秀的游戏,插件是必不可少的。工欲善其事必先利其器。本文主旨是告诉使用Unity3D引擎的同学们如何根据需求选择适当的工具。为此我写了插件的经验及理论讲解,涉及插件的 学习/辨别/选择/配合。也写了插件的 评测/教程/下载。关于评测,带有一定的主观性,仅供参考。关于教程,热门插件网上已经有很多教程了,本文提供链接,网上资料少的插件本文提供了使用方法的简单介绍。至于下载,主要是学习交流为主,下载速度还是比较快的。
(PS:本文主要起一个抛砖引玉的作用。欢迎同学们积极留言交流,大牛不吝指教【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院。本文用武侠功夫这个大家比较感兴趣的内容来描述游戏开发,借此描述Unity插件学习的道理,同时激发同学们的学习兴趣,没兴趣的可以跳过相关内容。我的新浪微博 @风宇冲

本文一共分为三个部分,第一部分是插件的下载,第二部分是插件的评测,第三部分是插件的学习方法。

                              第一部分 插件的下载

下载所有插件 (下载时逐个下载比较快)
注:所有付费插件下载仅用于学习和交流用途,请在下载后24小时内删除,商业用途请购买正版。(你懂的)


                              第二部分 插件的评测

Unity插件本文分以下九个类别介绍,后面跟的是风宇冲的推荐插件:
一 界面制作 推荐:NGUI
二 2D游戏制作 推荐:2D Toolkit
三 可视化编程 推荐:PlayMaker
四 插值插件 推荐:iTween,HOTween
五 路径搜寻 推荐:Simple Path
六 美术及动画制作 推荐:RageSpline,Smooth Moves
七 画面增强    推荐:Bitmap2Material,Strumpy Shader Editor
八 摄像机管理  推荐:Security Camera
九 资源包  推荐:Nature Pack
十 其他类
一: 界面设计(UI) -  风宇冲推荐NGUI
综述:所有UI插件可以实现的功能和效果最后都是差不多的,区别是最终游戏的运行效率和内存占用量不一样,开发的速度也不一样。 NGUI和EZGUI是属于一类,核心是将UI元件图合并到一张大图(atlase)上,再根据uv去找对应的小图,最后使用2D camera绘制。 iGUI是另外一种UI插件,它可以说是Unity自带UI的升级版,也就是OnGUI的延伸。了解OnGUI()的同学都知道,它的运行效率是比较低的,没有Drawcall合并,iGUI也一样。中文等字体制作一般是用Glyph Desginer 或者 Bmfont, 这点NGUI和EZGUI是通用的。总体来说, GUI插件的功能还是略微有限, 无论是什么GUI插件想做复杂点的功能就必须得用脚本。学习维护起来还是稍微有点麻烦的,故依然建议一个团队只一个人或几个人专门负责UI。
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

【NGUI】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
细节功能  
运行效率  
内存占用   ★
开发速度   
掌握时间   
跨越平台   
文档教程  
综合评价  

优点:UI合成图(atlas)管理方便,字体RGB压缩,持续更新,支持Flash,支持语言本地化即多语言,支持图像高低清配置。
缺点:卷屏界面(ScrollList)的实现稍微麻烦。
介绍:目前Unity最好用的UI 插件。重点是 NGUI应该会一直更新,跟着Unity的脚步。 NGUI也更方便于管理atlas,至少每个Sprite在atlas中都有名字来管理可以很方便的添加删除共用,并且Sprite的位置大小信息可见也 可适当调整。
使用注意:
(1)一个界面,通常也就是一个panel,一定只能有一个atlase, 否则层级极易出现错乱。

div STYLE="color: rgb(255, 0, 0);" >【FastGUI】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
细节功能  
运行效率  与NGUI相同
内存占用   与NGUI相同
开发速度   
掌握时间   
跨越平台  与NGUI相同
文档教程  
综合评价  
NGUI插件的补充,必须先在工程里导入FastGUI。主要用途为对Photoshop的支持,可以利用Photo的分层快速制作NGUI的UI部件,直接在Unity里使用psd即可。

【EZGUI】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
细节功能 
运行效率 
内存占用 
开发速度  
掌握时间  
跨越平台  
文档教程 
综合评价 
  比较经典的UI插件,目前已经基本不更新了。 
优点:Unity的元老级UI插件,方便实现快速开发
缺点:已基本不更新,UI合成图不能单图调整。
使用中注意的几点:
1:如果是Prefab中有EZGUI的东西的话,每次修改完最好Apply一下,然后点击Build Atlases
2:如果贴图合成图(atlase)都默认在Sprite Atlases文件夹下,如果图错乱的话,直接删除贴图重新点击Build Atlases生成贴图合成图即可。
3:位于scrolllist 下的一切物体必须为 EZGUI的组件 , 否则下拉的时候会不被切图。
scroll item 显示不完整  : 调整scrollist视口x尺寸
4:假如 点pixel perfect后 仍不显示尺寸则 点击运行则尺寸恢复正常
5:Panel只 build atlas 精度不够:  把atlas的分辨率调高 然后删掉 重新build
6:如果是移动平台开发切记EZ GUI组件选项不能钩 pixel perfect,因为其有auto resize会使在iphone 尺寸错误。
7:EZ脚本里很多有用函数,其默认为protected,建议将有用函数改成public,方便调用。

【iGUI】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
细节功能  
运行效率  
内存占用  
开发速度   
掌握时间   
跨越平台   
文档教程  
综合评价  
优点:快速开发,可视化
缺点: 效率低
iGUI是所有UI制作插件里最牛的可视化工具了。它的口号是 WSYIWYG(What you see is what you get),翻译成中文就是 所见即所得。其UI的实现核心是和Unity自带UI也就是OnGUI()是一样的。所以导致了做到后期一整套UI可能会有几十甚至上百的Drawcall。
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

二:2D游戏制作 -  风宇冲推荐2D Toolkit
综述:核心都是 2d精灵(Spritte)以及帧动画的管理和使用。个人觉得2D Toolkit 比Ex2d好些,更像是制作商业2D游戏的软件。
【2D Toolkit 】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
细节功能  
运行效率  
内存占用  
开发速度  
掌握时间   
跨越平台   
文档教程  
综合评价  
优点:专业,靠谱
缺点:精细的碰撞实现较麻烦
你想做2D游戏?没错,就是它了!笔者也做过一些2D游戏,棋牌的飞行的RPG的都有,2D Toolkit还是很靠谱的。缺点是碰撞检测要么是简单的多边形(三角形四边形神马的)之间碰撞,或者是简单多边形和复杂多边形之间。复杂多边形和复杂多边形之间的碰撞是不支持的。而且复杂多边形的碰撞体需要自己去画。想做出类似像素碰撞检测的效果也是可以的,沿着图形本身的边缘去画,如果你不嫌麻烦的话。
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院








【Ex2d】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

笔者接触了一点,看了其官方提供的示例,感觉比较娱乐。不多评价了。


三:可视化编程(Visual Scripting) -  风宇冲推荐PlayMaker
综述:可视化编程在商业游戏开发里可以辅助编程开发而不是替代编程。PlayMaker可以用来做状态管理。uScript可以用来做简单游戏的开发。
【PlayMaker】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
可视化程度
细节功能  
运行效率  
开发速度   
掌握时间   
跨越平台   
文档教程  
综合评价  

优点:状态管理
缺点:实现其宣扬的无编程做游戏不靠谱,多状态功能难管理
Asset Store上面很火的插件,官方说可实现无编程制作完整的游戏。这个个人感觉,你要做个类似俄罗斯方块或者弹弹球之类的小游戏是可以的,但是完全不编程制作商业级的游戏真心不靠谱。举个例子,游戏要赚钱你要嵌入广告sdk或者是IAP等付费sdk,那么你就得写脚本去实现。 不过PlayMaker也还是有可取的地方,其核心在于 将例如站立行走死亡等等状态通过状态机简称FSM(finite state machine)来管理。简单来说一个物体就是一个FSM,一个状态对应一个state。游戏过程中能在物体上方实时显示该物体的状态。并且有很方便的图表管理。缺点在于:所有功能必须对应状态。有很多共同状态都需要的功能用Playmaker做会极其麻烦,比方说,你有一个人物,有5个状态,还有行走攻击等控制输入,每个状态都要控制输入的话,同样的代码你就要有5份,很麻烦。而正常代码的话只需要简单的一段代码几个状态的与运算就可以了。
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院



【uScript】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
可视化程度
细节功能  
运行效率  
开发速度   
掌握时间   
跨越平台   
文档教程  
综合评价  

优点:流程清晰
缺点:功能不够细致
如其宣传的,uScript模仿了UDK和CE3的开发形式,注重逻辑流程,所见即所得,逻辑性直观紧凑。但是模块还不够全面功能不细致,例如你可以给材质赋贴图,但是要改变材质颜色就找不到对应功能了,类似情况挺多的。
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

四:插值插件 (Tween tool)-  风宇冲推荐iTween / HOTween
综述:iTween和HOTween各有各的优势,核心功能就是对位移,缩放,颜色等数值进行插值。iTween和HOTween并不冲突,可以同时在一个工程里使用。建议做路径的话最好用iTween, 做非位移,缩放,颜色的数值时只能用HOTween。其他情况最好用HOTween,因为管理更具有可操作性。
【iTween】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
实用性    
简易度    ★
综合评价  
优点:免费,通用,实用
缺点:不够稳定,不能返回改变目标tweener
介绍:免费的经典tween插件。Tween,包括位移,缩放,颜色变换等。NGUI,EZGUI,PlayMaker等很多插件都使用它来实现tween部分。缺点是tween的过程中,如果物体被销毁等情况,容易产生不可预制的错误。
【iTweenPath】
可视化    ★
实用性    
简易度    ★
综合评价  
优点:免费,路径清楚,实用
介绍:iTween的补充插件,主要是可视化的绘制路径,然后在iTween里使用生成的路径。可以用于TD地图怪物的路径移动。
使用方法:(1)导入插件后 (2)随便任何一个物体,把iTweenPath拖上去 (3)inspector里设置路径点数量 (4)在选中该物体的前提下,在Scene View里拖拽各个点绘制路径 (5)给路径起名例如 'xyz' (6)代码里使用即可,例如iTween.MoveTo(obj, iTween.Hash("path",iTweenPath.GetPath("xyz"),"time",10f));
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院



【HOTween】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
实用性    
简易度    ★
综合评价  
优点:免费,管理比iTween更方便,使用范围更广
缺点:可视化补充件HOTween Edtior作用position时不能像
iTweenPath那样显示路径。
介绍:类似iTween的 tween插件,功能更为强大。目标类型不再仅仅为position,color等固定类型,任何你在脚本里定义的public, non-static的 numeric 即 color/vector/int/float/double/string 变量都可以作为目标。并且调用函数后会返回tweener,如果储存tweener可以随时 监测/停止本次tween。
使用方法:(1)导入插件
(2)使用HOTween的脚本里添加using Holoville.HOTween;
Start()里添加HOTween.Init();
(3)   TweenParms tmp = new TweenParms();
        tmp.Prop("position",new Vector3(5,0,0));
        tmp.Ease(EaseType.Linear);
        HOTween.To(obj.transform,1,tmp);
 其中 position如果换成自定义变量,obj.transform就要换成对应的脚本对象即可。

序列tween使用方法:
(1)mySequence = new Sequence(new SequenceParms().Loops(3,LoopType.Yoyo));
(2)
mySequence.Append(HOTween.To(myGameObject1.transform, 1, new TweenParms().Prop("position", new Vector3(10,20,30)).Prop("rotation", new Vector3(0,720,0)).Prop("localScale", new Vector3(4,4,4)).Ease(EaseType.EaseOutBounce)));
有Append /
 Prepend / Insert三种方法,Append是加在序列最后,Prepend是最前,Insert是即将到来的目标
注意:(1)HOTween.To 等方法是低效的,储存返回的结果tweener,然后反复使用例如tweener.Play()更高效
(2)Tweener及TweenParms相当于一个容器, 下面填的变量才是真正要改变的内容。
【HOTween Editor】(要求Unity 3.5.6以上)
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
可视化    ★
实用性    
简易度    ★
综合评价  
优点:高效,快捷
缺点:插值作用于position时,无路径显示。
使用方法:
(1)选中目标物体 (2)Component->HOTween->HOTweenComponent  (3)在Inspector下的界面点击 蓝色的‘+Add Tween’(4)选择tween目标 (5)点击蓝色的‘+’,注意你想要改变的变量点这一步才会出来。(6)选择要插值的变量 (7)填Tween To等值即可
(8) 关闭'AutoDestroy'和‘Autoplay ’ (9) 脚本加
using Holoville.HOTween;
using System.Collections.Generic;
(10)调用该tween
    HOTweenComponent tweenComponent = myGameObject.GetComponent<HOTweenComponent>();
        if (tweenComponent != null)
        {
            // Do something with the Tweeners
            List<Tweener> tweeners = tweenComponent.generatedTweeners;
           
            if(tweeners[0].id == "tweenerName")
            {
                tweeners[0].Play();
            }
        }
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院








五: 路径搜寻(Path Finding)-  风宇冲推荐SimplePath
综述:路径搜寻主要用于怪物的AI行走,以及人物点击移动。这两个路径算法核心思想都是:先将地图划分成方格区域(像棋盘一样),然后根据方格内是否有障碍物对方格进行赋值,最后生成 单位位置 与 目标位置间的格子路径。想具体学习的童鞋可以百度 A star等算法。
【SimplePath】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院


运行效率  
掌握时间   
文档教程  
综合评价  
官方介绍说支持500+agent,即支持500个单位同时寻路,下方提供的对应插件中带有展示Demo。

【A* Pathfinding Project】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
运行效率  
掌握时间   
文档教程  
综合评价  
使用标准的A star算法,下方提供的对应插件中带有展示Demo。



六:美术及动画制作 -  风宇冲推荐RageSpline,Smooth Moves
【Smooth Moves】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
实用性    
简易度    
掌握时间  
文档教程  
综合评价  
介绍:神马?你们团队没有做动画的人?那么就用它吧!2D骨骼动画制作插件,不支持Flash。骨骼动画文件可以复用。比帧动画省空间占用。
优点:动画文件复用,省空间
缺点:
使用简介:
一:制作Atlase (1)Project栏下创建Smooth Moves Texture Atlase Data (2)选中atlase 文件,点击open atlase Editor (3)把素材图往里拖。
二: 制作Animation  (1)Project栏下创建Smooth Moves Texture Atlase Data (2)选中animation文件,点击open animation Editor (3)点击Bone下面的 ‘+’图标,创建骨骼节点(腰,头,腿,足等等,支持中文)(4)点击Animation Clips下的‘+’,创建动画(站立,行走,攻击等)。(5)选中帧序列界面里的 黄色方块 (6)Type选择Image(Transform就是隐藏,所以通常设置为Image)(7)选择图 (8)选择任意同行的黑块,右键选择Duplicate First Key Frame (9)执行 6-7 (10)然后设置pivot,rotation等信息(11)搞定了!点击play看效果吧!
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

【RageSpline】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
实用性   
简易度   
综合评价 
介绍:矢量绘图工具,矢量的好处大家应该都知道,就是不会因为放大而损失质量。 适用于矢量风格的2D游戏以及2D UI的制作。程序会了这个美术就要失业了!
使用方法: (1)新建一个空的GameObject (2)贴上RageSpline脚本,然后基本形状就出来了 (3)调整形状,具体是 鼠标双击=创建节点 N=圆滑和锋利的边角模式切换 K=削薄边缘 L= 增厚边缘 delete=删除节点 (4)调整边缘颜色outline color 以及填充颜色 Fill Color,大功告成!
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

【Mega-Fiers】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
实用性   
简易度   
综合评价 
介绍:模型变形工具,简单实用,可以用于3D字体的弯曲效果等等。
使用方法:非常简单, (1)选中要变形的物体,(2)上方菜单Component->Modifiers->Modify Object (3)添加任何预制变形,例如弯曲则是 Component->Modifiers->Bend (4)之后在Inspector里调对应数值即可。

七:画面增强 -  风宇冲推荐Bitmap2Material,Strumpy Shader Editor
【Bitmap2Material】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
细节功能  
运行效率  
掌握时间   
文档教程  
综合评价  
优点:方便,实用。
缺点:尚未发现。价格过高【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
使用方法:将包里的Bitmap2Material.sbsar拖进unity工程,然后直接将你的原图拖到下方例图的'Input'上去,它会自动生成法线贴图灰度图等效果图,效果还是不错的。并且支持将随便的一张图制作成Tile图,可以无缝拼接的哦。之后就自己在Insprector里微调吧!
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院


【UniSky】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

实用性   
简易度   
效果 
    
兼容性
   
综合评价 
优点:云与日月层叠效果逼真,有云起云散
缺点:缺少下雪等特效,有紫色斑点等BUG。
介绍:24小时实时 天空盒+天气插件。天气主要是下雨和storm,不过效果一般,建议另外。画面效果不错,不过需要自己去配置, 例如大太阳的天气去下雨肯定不真实,下雨的时候至少要把天空调成阴暗的,有一种乌云密布的感觉。并且天气与地形与单位的互动,需要另外添加,比如溅射到地面的水花等等。月亮放大后在某些显卡下会有紫色斑点。
使用建议:
风:Unity的Windzone
雨:Unity官方Demo《AngryBot》 里面的雨
使用方法:链接

【Strumpy Shader Editor】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
可视化   
实用性   
简易度   
综合评价 ★★★★★
优点:实用,免费
介绍:渲染器Shader的可视化编辑器,可视化的界面和PlayMaker,uScript用起来差不多。顶点渲染,像素渲染和光照模型渲染三种模式都支持。使用前最好对 三种类型Shader的代码写法有所了解,再使用该工具能起到事半功倍的效果。
使用方法:(1)插件载入到工程里 (2)菜单Shader Editor->Stumpet Shader Editor。 (3)点击New Graph (4)在图表界面Master右方,右键创建 Tex2D 以及 Sampler2D (4)连线如下 (5)点击Update Graph,之后能看见预览图了 (6)点击Export As,生成Shader文件
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院

注意:(1)每次改动完需要点Update Graph更新预览。 (2)删除连线:鼠标移动至线上,点击右键
(3)UV x或y的单变化使用UV_Pan (4)图表视口的移动为 alt + 鼠标左键
StrumpyShader详细教程

八:摄像机管理 - 风宇冲推荐 Security Camera
【Security Camera】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
实用性   
简易度   
综合评价 
优点:简单,实用
介绍:摄像机管理插。Unity里管理多相机很麻烦,Camera Preview不实用,来回启用禁用也繁琐。Security Camera能快速查看各个相机的实际效果。
使用方法:插件载入到工程里后,直接把SecurityCamera.cs脚本拖到各个相机上,然后在Game View里就可以直接分开看单个相机的效果了。
注意:(1)相机不能重名 (2)只有一个主相机,标签tag为'MainCamera'


【Ultimate FPS Camera】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
介绍:FPS摄像机的控制插件,没什么好说的。工程里导入插件后直接按里面的文档做就行了。

九:资源包类(Models & Particles)
综述:这个就不用多说了,好多有用的资源啊,太省事了!
【Nature Pack】
介绍:大自然的树,花,草等模型
【Ruin city】
介绍:一个被破坏的城市的模型

【CartoonSnow】
介绍:卡通雪效果


十:其他类
【uniSWF】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
优点:支持元件经典动画(Classic Tween),无需转换成序列帧
缺点:不支持图元(Shape)形变,转换成序列帧
介绍:在unity里使用flash的元件(主要是MovieClip)来制作UI什么的,对有Flash开发经验或者Flash资源的可能比较有用。

【ORK】(Okashi RPG Kit)
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
介绍:看了看youtube上的教程视频,ORK没什么意思,它整套的UI包括商店,用的是Unity自带的UI,这整套东西你拿来改还不如自己做好,又快又准又能精确调整。就不评分了。这里仅供下载,没事可以看看,如果有兴趣做RPG的新手可以去下个RPG Maker XP看看它的框架是怎样的。做商业级RPG的还是根据需要自己搭框架吧。

【Tower Defense ToolKit】
【风宇冲】Unity3D教程宝典之插件篇:Unity3D插件详细评测及教学下载 - 风宇冲 - 风宇冲Unity3D教程学院
介绍:不评价了,理由前文有提到过, 仅提供下载。


还有下面一些主流插件没有找到,欢迎补充。
【Mixamo】无下载
介绍: 模型动画插件,500美刀,伤不起啊。不过Unity4的动画系统已经改进很多。所以这个插件不用也罢。
【Audio Toolkit】无下载
介绍:音频管理插件,可以提高音效质量。32.5美刀
【FX Maker】
介绍:粒子系统库。100美刀
【Easy Water】
介绍:水面模型。7.5美刀
【Easy Touch】
介绍:触屏控制插件。20美刀
【MakeYourLevel】
介绍: 环境模型库。 22.5美刀

                                第三部分 插件的学习方法

插件的学习: 学插件有如大侠学武功。先练内功根基,再练上乘武功。内功根基是指 线性代数,计算机图形学,编程基础,Unity基本API等等。基础不要求同学们要有多精通,至少基本的原理和使用要懂。上乘武功是指各种插件。如果内功 根基不扎实而强修秘籍的话,也许会像天龙八部里的鸠摩智一样走火入魔。在游戏开发里,通常表现为卡在一个地方,模模糊糊的怎么都过不去,各种纠结。假如你 遇到了类似情况,建议回过头静下心来温习下相关的基础知识,等过后头脑缓过来了再理理思绪,分析分析条理,一般问题在这个时候就解决了。

插件的辨别:游戏开发如大侠比武,比的是游戏,武功(插件)不同,实力当然也不同。 好的武功能把你的特性发挥的淋漓尽致,PK有如行云流水。差的武功会让你瞬间被秒杀,无缘Grossing榜。好的插件能很好的和你的项目本身配合,达到 提高开发效率的目的。不好的插件你研究它得花费大量时间又还得花时间,又还得花时间修改调试,很有可能反而会降低效率。所以分辨插件的优劣很重要。我总结 了以下几点,能快速分辨插件的好坏:
好的插件(1)首先,Asset Store上排名高的一般都好用,什么榜都行,当然还是Top Grossing 最有分量,不好的东西谁愿意付钱呢?如果没有排名就看评价数量和内容吧(2)国内相关介绍讨论比较多的一般都好 (3)教程详细的,特别是有语音高清教程的。
不好的插件 (1)搜索到的相关信息极少的 (2)无排名,评价少 (3)教程粗糙

插件的选择: 上乘武功需要专精,令狐冲专精剑法,独孤九剑笑傲江湖,郭靖专精掌法,降龙十八掌威震武林。你不可能把天下所有武功集于一身。Unity开发也是,不可能 所有插件你都精通,只能是选取一些对你用处比较大的插件重点学习使用。所以插件的选择就比较重要了、一般来说比较实用的是单个功能块的插件,比如做UI选 NGUI, 插值控制用iTween, 2D游戏用2D Toolkit等等。而综合类插件就不建议实用了。例如做塔防类游戏的 Tower Defense ToolKit, 做RPG游戏的Okashi RPG Kit,这类插件是专门为做一类游戏而生,集成了很多很多功能,但是每个功能又做不到很专业,所以假如你要用,还得自己改,而这类插件一般定制的调整空间 比较小,目前来说还达不到专业的级别。所以如果你做很专业领域的游戏,比如赛车,有个start kit能节省很多时间,毕竟一辆车有几十上百个参数,通常没有必要自己做物理模型。其他一般的游戏比如塔防,闯关什么的就没必要找个类似start kit的东西了。

插件的配合:上乘武功则需要配合, 例如张无忌的太极拳+九阳真经+梯云纵+乾坤大挪移。招式太极,内功九阳,轻功梯云纵,能量转移则乾坤。配置组合插件道理也一样,使用什么样的组合就要看游戏本身的定位如何了。假如使用了与游戏定位不符合的插件,效果通常只能事与愿违。例如,做个2D游戏,自然用不到 Bitmap2Material,2D游戏也不需要法线贴图灰度图什么的吧。又如做一款商业级别的移动平台游戏用了iGUI制作UI的话,在 Android和iOS各自的低端机上的表现到最后会让你头疼不已。武侠的世界里,武功会有冲突,例如九阳神功九阴真经不能调和,插件同样也是有冲突的,不尽早发现的话后期面对一堆Bug会让你欲哭无泪的。后文中陆续提到了一些兼容或冲突,有些插件在Asset Store上的介绍也会有相关信息,例如某插件会在介绍里写明与NGUI兼容,与PlayMaker兼容。更多的需要同学们事先了解或者实验出来。本段下方举了插件组合的例子供大家参考。

2D 横屏动作游戏 :2D Toolkit + NGUI
3D 第一人称射击 :NGUI + Simple Path + Bitmap2Material + PlayMaker + Security Camera + Stumpy Shader Editor
独立开发2D游戏:(1)Photoshop/Illustrator等外部绘图软件 + Smooth Moves + NGUI
(2)RageSpline + 2D Toolkit + NGUI
简易非商业开发:iGUI + iTween
塔防游戏: iTween/ Simple Path + NGUI  +其他
            
2018-08-15 18:09:08 q764424567 阅读数 4184
  • Unity 值得看的500+ 技术内容列表

    Unity3D是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

##一、前言
发现就喜欢研究这些插件,为什么呢,因为方便快捷啊。基本不用研究源代码怎么实现的,只要会有就行了。但是,光这样也不行,还是要多去看看底层代码是怎么实现的,还有人家的框架是怎么搭的。
要不说Unity3D入门容易,提升难呢,因为提升全是靠苦功夫,去研究底层代码。算了,不絮叨了

##二、参考文章
Unity3D 装备系统学习Inventory Pro 2.1.2 总结
Unity3D 装备系统学习Inventory Pro 2.1.2 基础篇

##三、正文
先上一张效果图
这里写图片描述
下载链接:
https://pan.baidu.com/s/1uxp80cu_zgHTZoESSV-NzQ
无效了记得跟博主说一声哈

###1、总体结构
这里写图片描述

####物品相关

  • 非UI相关InventoryItem 物品体系类,具体如装备,消耗品,商店物品等

  • UI相关InventoryUIItemWrapper 物品体系

  • UIItem的UI包装的Item继承体系

  • ItemCollection这样的类,因为简单的增、删和改肯定是逃不了,复杂的如交换,容器间的交换等操作

####UI窗口相关

  • UIWindow体系的窗口类,具体有角色,银行,技能,店铺等窗口

  • InventoryUIDialog系统下的对话框类,具体有确认框,买卖,通用提示

  • 特殊窗口(非继承体系窗口),如上下菜单,通知窗口等

####管理相关类

  • 配置管理

  • InvertoryManager

  • ItemManger

  • 数据库操作

####其它
应该是一些辅助类,有UI部分的,事件辅助,定义接口等等吧,这部分还没有深入去阅读应该也是挺复杂的

####剩下部分
一些第三方插件,Unity3D特性及Edit扩展等等

####具体类图
这里写图片描述
这里写图片描述

###2、使用教程
####示范项目
在你的Assets/Demos/Scenes 你会发现这些演示场景,这些演示场景会包含所有特性,一定要仔细的看一下哦
这里写图片描述
####建立一个新的项目

  • 选择1(自动)
    打开设置向导,它可以发现在Tools/Inventory Pro/Setup wizard
    对于错误“没有管理者发现的对象”;单击确定按钮和一个管理对象命名为"_managers"将自动添加到你的场景里。
  • 选择2(手动)
    创建一个空的游戏对象将inventorymanager组成,你可以找到在 库存/经理/ inventorymanager你会得到几个管理器组件包括inventorymanager,inventorysettingsmanager和更多的,我们不需要现在。

####数据库建立
itemmanager包含所有项目的项目数据库,你可以创建管理每个场景,并使用不同的数据库,每一个场景。

1. 去你的项目窗口中,找到一个地方,你想创建项目数据库。

2. 右键单击(或点击创建,或者去创建/ 库存亲 /项目数据库)来创建一个新的项目数据库。

3. 一旦创建的数据库将在你的项目中选定。

4. 将项目拖到itemmanager的试题数据库槽。

现在也让我们创建 语言数据库 ,做完全相同的操作,并将它分配给inventorymanager的 lang 。

####配置设置

首先打开主编辑器可以发现在 工具/库存亲/主编。当你打开编辑会给你一个错误消息的第一时间(见下图)。

这是因为库存亲需要知道它应该保存新项目。单击“设置”按钮,选择一个文件夹 文件夹里面你的项目。

现在让我们打开整合经理再次看到我们必须配置。

第一个选项卡“UI”包含用于渲染UI的基本要素。为了做到这一点需要至少2个项目的 项目按钮预置 Item Button Prefab和 UIROOT。

这个项目按钮预置是的预置 /槽包含的项目。根是包含所有用户界面窗口的UI画布。

几个演示器可以用来快速上手。你可以在库存/演示/资产/用户界面/ ui_prefabs / ui_item_pfb找到默认的包装

并新建一个UGUI,把UI Canvas 赋值给 GUI ROOT。

####对象编辑

  1. 项目编辑

主编辑器,包括项目编辑器可以打开通过 Tools / Inventory Pro / Main editor

1.1 创建项目Creating items

首先,单击“创建项目”按钮,一旦我们做了一个对象选择器将显示。

在对象选择器,我们可以选择的物品类型。每一项类型具有不同的行为或目的,例如当消耗品被使用时将减少1个的堆栈大小,而当武器装备被装备时出现人物在场景中得到呈现。

你可以使用键盘来选择类型来创建,使用向上和向下箭头,选择一个你想要的并回车确认。

让我们抓住一个consummableinventoryitem (可消耗品)现在,并配置。

接下来你会被提示步骤2。在这里你可以选择一个模型,以此为基础的新项目。例如,你已经有了一个预置,例如一把剑,它拥有的纹理图片、可以使用碰撞器和自定义组件,那么只需拖动物品到“Drag object here”字段位置上,或者选择使用“Select model”按钮。

假设你没有一个预先定义的模型,只是想创建一个新的对象选择“No model”,或“2D sprite with trigger“用于2D游戏。

一旦创建的项目将显示一个按键在列表的底部,点击并生成物品。

一旦项目(预制)是创造了你可以修改它通过改变纹理,模型,添加自定义组件,等。

1.2 类别Categories

使用类别编辑器你可以定义项目的具体类别,例如食物,药水,剑,斧,等你可以定义每一类的冷却时间,这样你可以使用消耗品,同一类别中的所有其他项目也将在冷却。就是说,你可以重写每个项目的冷却时间在项目编辑器”选项卡。

1.3 性能Properties

使编辑更强大的性能也增加,属性允许您创建自定义的“变量”。一旦创建,你可以指定的属性项目编辑器内的任何项目,它提供一个值。 属性也可以通过 自定义代码。

值的字符串格式: 格式允许您使用该属性的值在UI格式。 使用{ NR }符号来定义自己的格式。

基准值: 基值的初始值的属性。例如,你可能有5的强度默认和允许它从那里成长。

1.4 稀有性编辑 Rarity editor

最后但并非最不重要的你可以定义你的项目多种多样,各有它的颜色,在工具提示中显示的用户界面元素。

定义物品的稀有程度分级,在UI中显示的颜色区别,还有物品掉落时出现在场景中的预置显示(这里默认是一个袋子)

——

2.装备编辑 Equipment editor

设备系统非常灵活,可用于几乎任何装备/附件系统。人物属性是通过选择一个或多个项目类型的定义,可以使用编辑器选择哪些数据将被计算,最终显示。

除了统计也有装备的类型,这些可以被定义在“装备类型编辑器选项卡 Equip type editor tab ”。装备类型可以限制避免某些组合。例如,当装备单手剑,我确信双手的剑和斧子和匕首双手不兼容。当单手剑装备,装备一把单手匕首,匕首将被装备。

——

3 货币编辑Currency editor

货币 编辑器允许你定义,可以用在你的项目,制定蓝图,供应商的货币,等。

每一种货币可以包含一组转换。这些转换允许你将一个美元兑欧元。

自动转换的定义是否可用于汽车。货币之间转换。例如,许多游戏使用系统的金,银和铜。当跑出来的铜系统可以转换到铜银货币。基本上重新填充它从一个更高的货币,可以转换成。

自动转换可以在编辑器底部的定义。

自动转换最大:允许你转换到一个更值钱的货币一旦你达到定义的最大值。例如,你不能拥有超过100的铜,所以一旦系统发现你有100以上的铜将被转换为1银。

自动转换分数: 当“让分数”(在顶部)不启用的分数也需要转换,或丢弃。例如,当你有1.1银(这是不允许的)系统会把它降到1银10铜。

4 制作编辑

制作经理类,允许你创建“锻造”任何时候,无论是烹饪,锻造或皮。

让我们创建一个新的 category,并开始创造一些蓝图。

####制定蓝图 Crafting editor

1. 默认情况下,结果项目名称–项目成功后,工艺–将作为蓝图的名称,但你当然可以,像往常一样,配置。

2. 机会因素表明工艺成功的可能性有多大,0.5种工艺的机会有50%的机会,和1的机会的因素有100%的成功机会。

1. 加速因子是成对的制作时间,在许多游戏(哇)制作一项变快时,创建一个完整的批一次。例如,当制作10烂苹果,第一项需要5秒,第二,1.1(10%)更快,这归结为5 /(1.1 ^ N)。

3. 所需物品说明很多项目所需的工艺给定的项目,这是从布局上分,可以在底部的定义。

一旦你定义的蓝图,你可以创建一个标准或布局的基础工艺窗口允许用户做他的 事。

####语言编辑器 Language editor

语言编辑器允许你定义一个特定的动作发生在显示时的库存支持信息。

目前只有1个语言数据库的支持,为多语言数据库支持将来会增加。

如果你喜欢一个特定的消息不出现只是让它空着。

####设置编辑

搜索

请注意,您还可以使用搜索栏搜索名称的变量设置/你想改变。

###3、Demo解析
####GettingStarted.unity
这里写图片描述
首先说一下Demo1的功能,其实很简单主要是建立起来Inventory Pro的运行环境,首先项目的Demo是3d的所以创建项目时,选择是3D工程。运行环境中,使用标准插件库建立一个第三方视角跟随的角色,角色可以在Panel中自由的移动跑跳;然后才是Inventroy Pro的基础配置,主要是引入Setting,在Setting中进行一些基础的配置。具体的运行后的界面如下图所示
这里写图片描述
#####设置一个角色
第一步在Scene中添加一个Panel,然后把它设置大点,不然角色会掉下去
这里写图片描述
第二步,找到图中的角色prefab然后直接拖到场景中,reset一下即可
这里写图片描述
第三方视角相机跟随

第三方视角相机跟随,也是按照标准过程进行

1,删除原来的MainCarmar摄像机

2,从Asset中拖拽我们需要的Prefab到场景中来
这里写图片描述
第三步,设置相机的Target为我们的控制角色,这里拖拽即可
这里写图片描述
#####最后是装备系统配置创建
基础环境创建好了,下面我们需要创建下装备系统的自身的基础环境了,涉及到了Srcript,Manage文件夹中的四大基础类

装备系统配置类,

装备系统管理类

Item管理类(工厂可能不准确,欢迎指正)

装备数据Asset类

Demo1中其实要实现的就两步

第一步,创建空游戏对象,配置InventorySetting类

第二步,初始化游戏Item数据Asset

###4、实例
####Demo1:使用插件来实现一个装备窗口

功能点:

  • 1、实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能

  • 2、装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置

  • 3、窗口具有拖拽功能

  • 4、窗口物品具有拖拽,及窗口间拖拽

  • 5、可以在窗口使用物品的功能,物品有消耗扇形显示功能

具体效果图如下所示:
这里写图片描述
#####插件使用
1、具体在UGUI 中的Canvas中创建一个InventoryWindow

2、在InventoryWindow下创建空GameObject并命名Container,赋予Grid LayOut 插件

3、给InventoryWindow添加InventoryUI组件,插件将自动添加WindowUI也就是通用窗口辅助插件

4、添加拖拽功能组件DraggableWindow,这样窗口就有了拖拽功能了

至此简单的点击I键可以打开和关闭的装备窗口做好了
#####总结
最后总结下实现通用窗口的三个类,分别是WindowHelper文件夹下的,UIWindow,UIWindowPage和DraggableWindow
这里写图片描述
1、DraggableWindow有就是拖拽窗口的组件,这里还是比较赞的,也是插件编程的简单例子,这里学过UGui的同学都知道要实现拖拽功能实现IBeginDragHandler和IDargHandler接口即可,原理很简单, 源码如下

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
 
namespace Devdog.InventorySystem
{
    [AddComponentMenu("InventorySystem/UI Helpers/DraggableWindow")]
    public partial class DraggableWindow : MonoBehaviour, IBeginDragHandler, IDragHandler
    {
        public float dragSpeed = 1.0f;
 
        private Vector2 dragOffset;
 
 
        public void OnBeginDrag(PointerEventData eventData)
        {
            if (InventorySettingsManager.instance.isUIWorldSpace)
                dragOffset = transform.position - eventData.worldPosition;           
            else
                dragOffset = new Vector2(transform.position.x, transform.position.y) - eventData.position;
        }
 
        void IDragHandler.OnDrag(PointerEventData eventData)
        {
            transform.position = new Vector3(eventData.position.x + dragOffset.x * dragSpeed, eventData.position.y + dragOffset.y * dragSpeed, 0.0f);
        }
    }
}

2、UIWindow这个类是窗口的公共类,先上类图主要功能点在类图上标注了,这里就不废话了,主要就是控制的窗口的显示关闭,及组合动画效果比较难的是实现了类似组合窗口的功能(这部分有后有机会再仔细分析)
这里写图片描述
源码就不全上了,上点有亮点的部分如下:

public virtual void Hide()
{
    if (isVisible == false)
        return;
 
    isVisible = false;
 
    if (OnHide != null)
        OnHide();
 
    if (hideAudioClip != null)
        InventoryUIUtility.AudioPlayOneShot(hideAudioClip);
 
    if (hideAnimation != null)
    {
        animator.enabled = true;
        animator.Play(hideAnimation.name);
 
        if (hideCoroutine != null)
        {
            StopCoroutine(hideCoroutine);                   
        }
 
        hideCoroutine = _Hide(hideAnimation);
        StartCoroutine(hideCoroutine);
    }
    else
    {
        animator.enabled = false;
        SetChildrenActive(false);
    }
}
 
 
/// <summary>
/// Hides object after animation is completed.
/// </summary>
/// <param name="animation"></param>
/// <returns></returns>
protected virtual IEnumerator _Hide(AnimationClip animation)
{
    yield return new WaitForSeconds(animation.length + 0.1f);
 
    // Maybe it got visible in the time we played the animation?
    if (isVisible == false)
    {
        SetChildrenActive(false);
        animator.enabled = false;
    }
}

以上部分是通过协程实现的具有延时效果的动画关闭窗口的代码,有代表意义。

3、UIWindowPage类,该类是UIWindow的子类,在UIWindow有一个Page的集合用于组合显示UIWindowPage,这块Demo1中没有涉及到该功能这里就不仔细分析了,等后面的例子中出现了再研究,亮点代码如下:

/// <summary>
/// Container that olds the items, if any.
/// </summary>
public RectTransform itemContainer;
public UIWindow windowParent { get; set; }
 
public override void Awake()
{
    base.Awake();
 
    windowParent = transform.parent.GetComponentInParent<UIWindow>();
    if (windowParent == null)
        Debug.LogWarning("No UIWindow found in parents", gameObject);
 
    // Register our page with the window parent
    windowParent.AddPage(this);
}
 
public override void Show()
{
    if(isEnabled == false)
    {
        Debug.LogWarning("Trying to show a disabled UIWindowPage");
        return;
    }
 
    base.Show();
 
    windowParent.NotifyPageShown(this);
}

这里UIWindow和UIWindowPage 本身是继承关系,然又彼此引用,代码可读性有些差了,作者这里通过Page类中Awake和Show来做父类的初始化和调用,也是一种方法,我觉得还行(请高手拍砖)。

总体来说目前的UIWinow和UIWindowPage更像是容器Panel或者Group不像是窗口,等以后的Demo中有复杂的再学习吧。

4、如何通过键盘唤起窗口

这个比较简单用到了U3D的输入输出模块,关键代码如下:

/// <summary>
/// Keys to toggle this window
/// </summary>
public KeyCode[] keyCombination;
 
public virtual void Update()
{
    if (keyCombination.Length == 0)
        return;
 
    bool allDown = true;
    foreach (var key in keyCombination)
    {
        if (Input.GetKeyDown(key) == false)
        {
            allDown = false;
        }
    }
 
    if (allDown)
        Toggle();
 
}

####Demo2:通用窗口的具体实现
功能点:

  • 1、实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能

  • 2、装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置

  • 3、窗口具有拖拽功能

  • 4、窗口物品具有拖拽,及窗口间拖拽

  • 5、可以在窗口使用物品的功能,物品有消耗扇形显示功能

  • 6、通用窗口的类体系结构

具体的插件使用和功能已经在上篇中说明了这里就不多说了

1、本篇重点分析 6通用窗口的类体系结构,类组织和类图如下所示:
这里写图片描述
类的继承体系结构这里就说了,在第一篇有可以自行查阅
这里写图片描述
类的引用关系、核心字段和方法已经在类图中标记的很清楚,用简单的几句话说明下,装备窗口中的每个格子是由一个空格子具有背景的UIItem和InventoryItemBase Model组成的,而整个装备窗口是一个InventoryUI,该类继承了ItemCollectionBase类,也就是说它是具有一组UIItem的装备集合窗口,添加上UIWindow组件、DraggableWindow就具有了普通窗口的拖拽移动和显示关闭的功能了。

2、装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置

如何实现动态装备窗口主要有两个核心技术:一个是UI中的自适应排列,也就是Grid layout Group组件;一个是U3D的prefab实例化技术

动态初始化Cell数据核心代码如下

protected virtual void FillUI()
        {
            if (manuallyDefineCollection == false)
            {
                items = new InventoryUIItemWrapperBase[initialCollectionSize];

                // Fill the container on startup, can add / remove later on
                for (uint i = 0; i < initialCollectionSize; i++)
                {
                    items[i] = CreateUIItem<InventoryUIItemWrapper>(i, itemButtonPrefab != null ? itemButtonPrefab : InventorySettingsManager.instance.itemButtonPrefab);
                }
            }
            else
            {
                for (uint i = 0; i < items.Length; i++)
                {
                    items[i].itemCollection = this;
                    items[i].index = i;
                }
            }
        }

        protected T CreateUIItem<T>(uint i, GameObject prefab) where T : InventoryUIItemWrapperBase
        {
            T item = GameObject.Instantiate<GameObject>(prefab).GetComponent<T>();
            item.transform.SetParent(container);
            item.transform.localPosition = new Vector3(item.transform.localPosition.x, item.transform.localPosition.y, 0.0f);
            item.itemCollection = this;
            item.transform.localScale = Vector3.one;
            item.index = i;
        
            return item;
        }

是不是很简单 initailCollectionSize是InventoryUI基类的一个共有field也就是说这个装备格子的数量,这个可以根据自己设计的装备窗口手动设置,然后根据这个循环调用CreateUIItem泛型方法通过,GameObject.Instantiate动态实例化预设装备格子对象,并设置Parent和位置

####Demo3:通用窗口的具体实现可拖拽功能
#####窗口间物品的拖拽

自己在学习的过程中,虽然读了源码过了几天来写教程,还是有点不清楚,不能亲车熟路(这也许就是读与写的区别吧),给自己提出了几个问题,在重新去翻代码之前先给自己提出几个问题:

  • 1、拖拽的事件发起者应该是那个类?

  • 2、拖拽的事件的Drag是如何确定下方的UI元素的?

  • 3、拖拽后的逻辑操作,应该由哪个类来承接?

  • 4、窗口与窗口之间的拖拽,既有Drag又有Drop,如何更加合理的解决这个问题?

  • 5、窗口间物品拖拽的以及同窗口物品拖拽的逻辑流程是什么?

A1 拖拽的事件发起者应该是那个类?:拖拽的发起者必须是UI元素这点是必须的,目前涉及的UI元素有两种一种是窗口容器,一种数据装备格元素,显然装备格元素更有优势,因为少了一层定位逻辑判断,Drag事件直接发起,还可以做Move的相关逻辑,这里Inventory Pro2也确实是怎么做的(这里初次接触UGUI的同学可能要去学习下相关的事件系统,这里就不多说了),这里InventoryUIItemWrapper就是装备格的基类,这里继承了UGUI的相关UI事件,IBeginDragHandler, IEndDragHandler, IDragHandler, IPointerUpHandler, IPointerDownHandler, IPointerEnterHandler, IPointerExitHandler,这么多接口也预示着代码并不简单
这里写图片描述
这些就是接口实现函数,把他们都弄明白了也就明白了如何实现拖拽
这里写图片描述
A5 窗口间物品拖拽的以及同窗口物品拖拽的逻辑流程是什么?:先回答这个问题比较合理,在过去的winform的拖拽中并没有这么多接口可以实现,但是我相信拖拽操作的本身逻辑应该是不变的,也就是几个步骤,

  • 1)在物品上点击鼠标左键(记录鼠标点击的元素)->

  • 2)在鼠标不up,且move事件中确认了拖拽开始(Drag事件) –>

  • 3) mouse Move事件中获得鼠标下的元素->

  • 4)mouse up 事件触发Drop,判断鼠标位置及鼠标下元素是否可以drop如果可以进行Drop逻辑至此,这个拖拽操作结束

技术原型就是这么简单。下面看看Uintiy3d ugui Inventory Pro是如何实现的,又读了一遍代码,深深的有体会到了一把,“原理很简单,现实很残酷”,这还是在ugui为我们做了一些封装的情况下,这里其实涉及的函数其实有5个

  • OnPointerEnter:确定了点击了那个UI元素,对应1)

  • OnBeginDrag:开始拖拽,对应2)

  • OnDrag:拖拽中,对应3)

  • OnEndDrag:结束拖拽,对应4)

  • OnPointExit:清空选中元素,恢复默认值

具体代码比较多这里不再展开说了,这里庆幸的是,Inventory Pro对拖拽的逻辑进行了封装,在InventoryUIItemWrapper中接口实现函数中,主要做的参数舒适化,主要关于UI的逻辑代码封装在了InventoryUIUtility类中,

以下是主要接口实现函数的代码

public virtual void OnBeginDrag(PointerEventData eventData)
        {
            if (itemCollection == null)
                return;

            if (item != null && eventData.button == PointerEventData.InputButton.Left && itemCollection.canDragInCollection)
            {
                // Create a copy
                var copy = GameObject.Instantiate<InventoryUIItemWrapper>(this);
                copy.index = index;
                copy.itemCollection = itemCollection;

                var copyComp = copy.GetComponent<RectTransform>();
                copyComp.SetParent(InventorySettingsManager.instance.guiRoot);
                copyComp.transform.localPosition = new Vector3(copyComp.transform.localPosition.x, copyComp.transform.localPosition.y, 0.0f);
                copyComp.sizeDelta = GetComponent<RectTransform>().sizeDelta;

                InventoryUIUtility.BeginDrag(copy, (uint)copy.index, itemCollection, eventData); // Make sure they're the same size, copy doesn't handle this.
            }
        }

        public virtual void OnDrag(PointerEventData eventData)
        {
            if (item != null && itemCollection != null && itemCollection.canDragInCollection) // Can only drag existing item
                InventoryUIUtility.Drag(this, index, itemCollection, eventData);
        }

        public virtual void OnEndDrag(PointerEventData eventData)
        {
            if (item != null && itemCollection != null && itemCollection.canDragInCollection)
            {
                var lookup = InventoryUIUtility.EndDrag(this, index, itemCollection, eventData);

                // Didn't end on a button or used wrong key.
                if (lookup == null)
                    return;

                if (lookup.endOnButton)
                {
                    // Place on a slot
                    lookup.startItemCollection.SwapOrMerge((uint)lookup.startIndex, lookup.endItemCollection, (uint)lookup.endIndex);
                }
                else if (lookup.startItemCollection.useReferences)
                {
                    lookup.startItemCollection.SetItem((uint)lookup.startIndex, null);
                    lookup.startItemCollection[lookup.startIndex].Repaint();
                }
                else if(InventoryUIUtility.clickedUIElement == false)
                {
                    TriggerDrop();
                }
            }
        }

以下是InventoryUIUtility类封装的静态函数代码

public static InventoryUIDragLookup BeginDrag(InventoryUIItemWrapper toDrag, uint startIndex, ItemCollectionBase collection, PointerEventData eventData)
        {
            if (draggingItem != null)
            {
                Debug.LogWarning("Item still attached to cursor, can only drag one item at a time", draggingItem.gameObject);
                return null; // Can only drag one item at a time
            }

            if (eventData.button != PointerEventData.InputButton.Left)
                return null;


            draggingItem = toDrag;
            //draggingButtonCollection = collection;

            // Canvas group allows object to ignore raycasts.
            CanvasGroup group = draggingItem.gameObject.GetComponent<CanvasGroup>();
            if(group == null)
                group = draggingItem.gameObject.AddComponent<CanvasGroup>();

            group.blocksRaycasts = false; // Allows rays to go through so we can hover over the empty slots.
            group.interactable = false;

            var lookup = new InventoryUIDragLookup();
            lookup.startIndex = (int)startIndex;
            lookup.startItemCollection = collection;

            return lookup;
        }

        public static void Drag(InventoryUIItemWrapper toDrag, uint startSlot, ItemCollectionBase handler, PointerEventData eventData)
        {
            if(eventData.button == PointerEventData.InputButton.Left)
                draggingItem.transform.position = new Vector3(eventData.position.x, eventData.position.y, 0.0f);
        }

        public static InventoryUIDragLookup EndDrag(InventoryUIItemWrapper toDrag, uint startSlot, ItemCollectionBase handler, PointerEventData eventData)
        {
            if(eventData.button == PointerEventData.InputButton.Left)
            {
                var lookup = new InventoryUIDragLookup();
                lookup.startIndex = (int)draggingItem.index;
                lookup.startItemCollection = draggingItem.itemCollection;

                if (hoveringItem != null)
                {
                    lookup.endIndex = (int)hoveringItem.index;
                    lookup.endItemCollection = hoveringItem.itemCollection;
                }

                Object.Destroy(draggingItem.gameObject); // No longer need it

                draggingItem = null;
                //draggingButtonCollection = null;

                return lookup;
            }

            return null;
        }

        /// <summary>
        /// When the cursor enters an item
        /// </summary>
        public static void EnterItem(InventoryUIItemWrapper item, uint slot, ItemCollectionBase handler, PointerEventData eventData)
        {
            hoveringItem = item;
            //hoveringItemCollection = handler;
        }

        /// <summary>
        /// When the cursor exits an item
        /// </summary>
        /// <param name="item"></param>
        /// <param name="slot">The slot is the IButtonHandler index not the inventory index.</param>
        /// <param name="handler"></param>
        /// <param name="eventData"></param>
        public static void ExitItem(InventoryUIItemWrapper item, uint slot, ItemCollectionBase handler, PointerEventData eventData)
        {
            hoveringItem = null;
            //hoveringItemCollection = null;
        }

A2 拖拽的事件的Drag是如何确定下方的UI元素的?

这里DragDrop中的元素由 OnPointerEnter 来触发获得(有点像Mouse Move事件),具体保存在InventoryUIUtility类的一个静态变量中

public static InventoryUIItemWrapper hoveringItem { get; private set; }

A3 拖拽后的逻辑操作,应该由哪个类来承接?

也就是Drop的操作由谁来完成,首先回忆下职责InventoryUIItemWrapper类负责事件的触发,InventoryUIUtility类负责UI相关的逻辑(选中,射线,坐标系统)

再看一遍OnDragEnd函数,具体的Drop逻辑是有Drop的后查找的lookup集合类(装备格集合基类ItemCollectionBase)来处理的,具体又有交换\合并两个逻辑,触发代码如下:


if (lookup.endOnButton)
{
     // Place on a slot
     lookup.startItemCollection.SwapOrMerge((uint)lookup.startIndex, lookup.endItemCollection, (uint)lookup.endIndex);
}

当然还有一种复杂的逻辑,就是扔掉物品的操作,这个是有具体的Item装备模型类(InventoryItemBase)来处理,核心代码在TriggerDrop方法中来调用,具体如下:

public override void TriggerDrop(bool useRaycast = true)
        {
            if (item == null || itemCollection.canDropFromCollection == false)
                return;

            if(item.isDroppable == false)
            {
                InventoryManager.instance.lang.itemCannotBeDropped.Show(item.name, item.description);
                return;
            }

            Vector3 dropPosition = InventorySettingsManager.instance.playerObject.transform.position;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit, InventorySettingsManager.instance.maxDropDistance,
                InventorySettingsManager.instance.layersWhenDropping))
            {
                dropPosition = hit.point;
            }
            else
            {
                return; // Couldn't drop item
            }

            var s = InventorySettingsManager.instance;
            if (useRaycast && s.showConfirmationDialogWhenDroppingItem && s.showConfirmationDialogMinRarity.ID <= item.rarity.ID)
            {
                // Not on a button, drop it
                var tempItem = item; // Capture list stuff
                var msg = InventoryManager.instance.lang.confirmationDialogDrop;
                s.confirmationDialog.ShowDialog(msg.title, msg.message, s.defaultDialogPositiveButtonText, s.defaultDialogNegativeButtonText, item,
                    (dialog) =>
                    {
                        ItemCollectionBase startCollection = tempItem.itemCollection;
                        uint startIndex = tempItem.index;

                        var d = tempItem.Drop(dropPosition);
                        if (d != null)
                        {
                            startCollection[startIndex].Repaint();
                        }
                    },
                    (dialog) =>
                    {
                        //Debug.Log("No clicked");
                    });
            }
            else
            {
                var d = item.Drop(dropPosition);
                if (d != null)
                {
                    Repaint();
                }
            }
        }

A4 窗口与窗口之间的拖拽,既有Drag又有Drop,如何更加合理的解决这个问题?

这个问题比较绕,其实也涉及到了问题2,其实无论怎么拖拽也就是两个东西,一个是被拖拽的物体from,一个是要放的地方to,这里其实都是窗口中的格子,只要有了这两个格子类也就确定了from和to的容器,比较特殊的一种情况也就是 from和to两个容器相等,也就是同窗口拖拽了,具体这些对象InventoryUIUtilty类中都做了封装,还是很赞的具体代码如下:

public class InventoryUIDragLookup
        {
            public int startIndex = -1;
            public ItemCollectionBase startItemCollection;

            public int endIndex = -1;
            public ItemCollectionBase endItemCollection;

            public bool endOnButton
            {
                get
                {
                    return endItemCollection != null;
                }
            }
        }


        #region Variables 

        private static InventoryUIItemWrapper draggingItem;
        public static InventoryUIItemWrapper hoveringItem { get; private set; }
        public static bool isDraggingItem
        {
            get
            {
                return draggingItem != null;
            }
        }

        public static bool clickedUIElement
        {
            get
            {
                return EventSystem.current.IsPointerOverGameObject();
            }
        }


        public static bool isFocusedOnInput
        {
            get
            {
                if (EventSystem.current.currentSelectedGameObject != null)
                    if (EventSystem.current.currentSelectedGameObject.GetComponent<UnityEngine.UI.InputField>() != null)
                        return true;

                return false;
            }
        }

        #endregion

复杂的物品拖拽逻辑总结完毕,再次向我们印证了,从helloworld到现实是多么的困难,实际的情况可能更复杂比如要加入动画效果,要做网络延时验证,数据同步等等吧

####Demo4:通用窗口的具体实现物品有消耗扇形显示功能
功能点:

  • 1、实现了两个窗口,通过点击键盘I来,打开或者关闭窗口也就是Toggle功能

  • 2、装备窗口中的物品栏空格数量动态生成可控,可以在属性窗口手动配置

  • 3、窗口具有拖拽功能

  • 4、窗口物品具有拖拽,及窗口间拖拽

  • 5、可以在窗口使用物品的功能,物品有消耗扇形显示功能

  • 6、通用窗口的类体系结构

这里开动之前给自己提几个问题:

  • 1、UGui原生实现使用物品扇形消耗效果(即冷却实现)是如何实现的?

先看看最终效果
这里写图片描述

如上图,我的技能不是像LOL一样每个技能对应一个按键,而是,先选择一个技能,然后按下某一个键释放 [类似于Button Group]
技能后方有一个灰色的蒙版,有蒙版的技能即为当前选中的技能。
每个技能CD可以在脚本中自定义。
倒计时蒙版与CD会同步。
怎么实现先选择一个技能然后按下键来释放技能不是本教程的目标。
本教程只简单说明怎么实现技能CD。
我们在实现效果之前先简单了解一下UGUI的Image组件。

这里写图片描述
重点介绍一下Image Type 【显示模式】

Image组件中,Image Type(显示方式)有Simple、Sliced、Tiled、Filled四种。
下面一一介绍。

*Simple【普通】
*此显示模式下,Sprite将直接显示在控件中。如果大小不一致,将通过拉伸来填充控件。
*如果preserve Aspect勾选,图片将保持长宽比
*
*Sliced【切片】
此显示模式下,Sprite将被视为9个切片[33]组成,图片控件只显示中间切片的边缘。
*如果Fill Center勾选,将显示完整切片
*
*Tiled【平铺】
*此显示模式下,sprite尺寸不变
*[自行类比Windows桌面壁纸填充方式中的平铺]
*
*Filled【填充】
*显示模式类似于Simple,但是可以有多个选项来选择展示“从无到有”的变化
*填充方式由Fill-Method属性决定。
*本帖不一一说明各个显示模式。如果你感兴趣,请自行查找资料。

在脚本中,我们需要几个重要的变量来关联各个物体。

比如你需要一个这样的结构体或者类:

[System.Serializable]
public struct skillSprite
{
    public Sprite skill_On;
    public Sprite skill_Off;
    public float skillTimer;
    public Text timerText;
    public Image timerMask;
    public GameObject tipPanel;
    [HideInInspector]
    public bool isOn;   //冷却时间到
    [HideInInspector]
    public bool isOpen; //拥有此技能
    [HideInInspector]
    public bool isPressDown;   //是否使用了技能
}

要实现技能效果,我们会在技能面板中使用Filled模式来实现倒计时效果。

我们在Hierarchy面板中建立这样的父子关系
这里写图片描述
控制代码如下:

if(skillSprites[index].isOpen)  //如果已经获得该技能执行
{
            //Debug.Log("index = " + index);
 
            if (skillSprites[index].isPressDown)    //当技能被激活时执行
            {
                skillSprites[index].isOn = false;   //冷却时间重置
                skillSprites[index].timerMask.enabled = true;   //CD蒙版激活
                skillSprites[index].skillTimer -= Time.deltaTime;   //CD时间开始倒计时
                //startTimer[index]为该技能的冷却时间
                //因为在Image组件fillAmount的值范围是[0,1]的float值,所以这里做一个计算转化为百分比
                skillSprites[index].timerMask.fillAmount -= (Time.deltaTime / startTimer[index]);   //更新蒙版状态  
                skillSprites[index].timerText.text = skillSprites[index].skillTimer.ToString("F1"); //更新时间显示
            }
}

这样你可以继续完善脚本逻辑来实现效果。
剩下的部分比较简单啦。我就不在这里再做说明了。
这里写图片描述
这里写图片描述
OK,搞定

  • 2、装备格子是如何与1所提到的方法接合在一起的?

这里回忆下装备格子是用InventoryUItemWrapper这个UI类实现的,所以答案就在这个类里,但使用这个类并非易事,它是用ItemCollectionBase类(集合容器)动态生成的,在前篇讲过这里再温习一下,这里我们发现如果没有值得话,它取的InventorySettingsManager.itemButtonPrefab,也就是个装备格子预设

protected virtual void FillUI()
        {
            if (manuallyDefineCollection == false)
            {
                items = new InventoryUIItemWrapperBase[initialCollectionSize];

                // Fill the container on startup, can add / remove later on
                for (uint i = 0; i < initialCollectionSize; i++)
                {
                    items[i] = CreateUIItem<InventoryUIItemWrapper>(i, itemButtonPrefab != null ? itemButtonPrefab : InventorySettingsManager.instance.itemButtonPrefab);
                }
            }
            else
            {
                for (uint i = 0; i < items.Length; i++)
                {
                    items[i].itemCollection = this;
                    items[i].index = i;
                }
            }
        }

        protected T CreateUIItem<T>(uint i, GameObject prefab) where T : InventoryUIItemWrapperBase
        {
            T item = GameObject.Instantiate<GameObject>(prefab).GetComponent<T>();
            item.transform.SetParent(container);
            item.transform.localPosition = new Vector3(item.transform.localPosition.x, item.transform.localPosition.y, 0.0f);
            item.itemCollection = this;
            item.transform.localScale = Vector3.one;
            item.index = i;
        
            return item;
        }

回忆一下这个配置是一个必填配置,必须在Setting中进行设置,
这里写图片描述
找到该预设,找到真相
这里写图片描述
这里写图片描述
看的出来这个预设绑定了InventoryUIItemWrapper类,其公共的Field也以此列了出来分别是Amout Text物品数量,Item Name物品名称(None),Icon(Image)这就是物品的图标了比如苹果,Cooldown Image 就是图片表面的遮罩层,用来做雷达效果的。

  • 3、装备格子是如何与苹果等可以吃的动态物品接合在一起的?

从上面的图片我们也看的出来,其实默认的Icon应该是装备格子的背景(黑底),它是如何变成苹果梨,或者刀剑的呢?这里比较简单的线索就是通过拾取地上的包裹,然后在背包里多了一个物品(这个过程不表述了比较复杂,留在以后专门分析),顺藤摸瓜,最终还是要加入到背包里面,这样我们去看看ItemCollectionBase类中的AddItem方法,这里这个方法本身很复杂主要是有类似20个血瓶1打东西放置的时候需要重新计算格子什么的比较麻烦,核心的函数是SetItem

/// <summary>
        /// This function can be overridden to add custom behavior whenever an object is placed in the inventory.
        /// This happens when 2 items are swapped, items are merged, anytime an object is put in a slot.
        /// <b>Does not handle repainting</b>
        /// </summary>
        /// <param name="slot"></param>
        /// <param name="item"></param>
        /// <returns>Returns true if the item was set, false if not.</returns>
        public virtual bool SetItem(uint slot, InventoryItemBase item)
        {
            if (CanSetItem(slot, item) == false)
                return false;

            // _item ugly work around, but no other way to keep it safe...
            items[slot].item = item;
            return true;
        }

这里我们还记得InventoryUIItemWrapper是Item的包装,所以这里设置了其的Item为新增的Item,顺着这个思路剩下相关的就是绘制部分了,再去看看InventoryUIItemWrapper的绘制部分看看是如何显示的以及冷却效果是如何实现的

public override void Repaint()
        {
            if (item != null)
            {
                if (amountText != null)
                {
                    // Only show when we have more then 1 item.
                    if (item.currentStackSize > 1)
                        amountText.text = item.currentStackSize.ToString();
                    else
                        amountText.text = string.Empty;
                }

                if (itemName != null)
                    itemName.text = item.name;

                if(icon != null)
                    icon.sprite = item.icon;
            }
            else
            {
                if (amountText != null)
                    amountText.text = string.Empty;

                if (itemName != null)
                    itemName.text = string.Empty;

                if(icon != null)
                    icon.sprite = startIcon != null ? startIcon : InventorySettingsManager.instance.defaultSlotIcon;
            }

            //RepaintCooldown(); // Already called by update loop
        }

icon.sprite = item.icon; 这一行代码我们看到了,其实icon这个Image Field 对应Item.icon,而且是一个sprite对象(原来sprite是Image的一个部分),再看下Update Loop的代码,只是在每一帧调用了
RepaintCooldown(),也就是执行冷却刷新,具体代码如下:

public virtual void RepaintCooldown()
        {
            if (cooldownImage == null)
                return;

            if (item != null)
            {
                if(item.isInCooldown)
                {
                    cooldownImage.fillAmount = 1.0f - item.cooldownFactor;
                    return;
                }
            }

            // To avoid GC
            if (cooldownImage.fillAmount != 0.0f)
                cooldownImage.fillAmount = 0.0f;
        }

当然这里有控制cooldown的逻辑,isInCooldown是一个属性逻辑都在里面,包括同类型物品使用的冷却控制(有点复杂,这里先不表了)

  • 4、物品是如何被触发使用的?

触发使用一定是在点击的时候触发的,接合UGui的事件机制,Inventory Pro 分别在OnPointerDown和OnPointerUP中进行了实现,有一些触控相关的判断见一下源码

public virtual void OnPointerDown(PointerEventData eventData)
        {
            if (itemCollection == null)
                return;

            pointerDownOnUIElement = InventoryUIUtility.clickedUIElement;
            if (pointerDownOnUIElement == false)
                return;

            if (InventorySettingsManager.instance.useContextMenu && (eventData.button == InventorySettingsManager.instance.triggerContextMenuButton || Application.isMobilePlatform))
            {
                if (item != null)
                    TriggerContextMenu();

                return;
            }

            if (InventorySettingsManager.instance.mobileUnstackItemKey == MobileUIActions.SingleTap)
            {
                TriggerUnstack();
                return;
            }
            else if(InventorySettingsManager.instance.mobileUseItemButton == MobileUIActions.SingleTap)
            {
                TriggerUse();
                return;
            }

            if (item != null && pressing == false && Time.timeSinceLevelLoad - InventorySettingsManager.instance.mobileDoubleTapTime < lastDownTime)
            {
                // Did double tap
                if (InventorySettingsManager.instance.mobileUnstackItemKey == MobileUIActions.DoubleTap)
                {
                    TriggerUnstack();
                    return;
                }
                else if(InventorySettingsManager.instance.mobileUseItemButton == MobileUIActions.DoubleTap)
                {
                    TriggerUse();
                    return;
                }
            }

            lastDownTime = Time.timeSinceLevelLoad;
            pressing = true;
        }

物品的具体使用在TriggerUse() 实现

public override void TriggerUse()
        {
            if (item == null)
                return;

            if (itemCollection.canUseFromCollection == false)
                return;

            int used = item.Use();
            if (used >= 0)
            {
                Repaint();
            }
        }

这里最终物品的使用时调用的Item Model中的Use()来实现的,这个应该是一种基类方法,需要特定的子类Item来实现,具体实现还要和容器有一定关系,比较复杂本文不表,有机会日后再展开,反正使用后调用了Repaint()方法也就是在使用完毕物品后装备格子进行了重绘刷新操作。

####Demo5:Inventory Pro 装备拾取的实现
#####效果图
1、运行相关的例子,场景中出现4个矩形,这4个矩形是用来模拟物品掉落的包裹,移动Player靠近物品
这里写图片描述
2、使用鼠标点击物品正方体,点击I键打开包裹,这里看到3个掉落包裹正方体已经点没有了,相应的背包里多出三个苹果,至此例子演示完毕
这里写图片描述
#####插件使用
使用Inventory Pro进行装备的拾取,有很简单的例子

1、点击菜单Tool,InventorySystem,Main editor 打开自定义Editor
这里写图片描述
2、在Main manager对话框中点击Items editor选项卡,选中Item editor 点击Create Item 绿色按钮,会弹出左侧的选择子对话框,其中列出了可以创建的Item种类,第一项就是消耗品,当然还有很多别的东西,这里先试验最简单的消耗品
这里写图片描述
3、点击创建消耗品以后会出来一个Item的详情界面,这里需要添加Item的Name,Icon,Behavier里有类型,冷却时间,价格,是否可以叠放,是否可以丢弃等等属性吧,同时Editor会创建出相应的物品prefab,直接拖拽到场景中即可
这里写图片描述
#####问题
1、地上的包裹(Item容器)是如何产生的?

2、地上的包裹是如何对应Item model的,且之间的关系是什么?

3、拾取的过程是怎么样的?
#####答案

1、地上的包裹(Item容器)是如何产生的?

A1、这里例子里面是通过Unity3d Editor扩展进行添加地上的包裹的且是Cude,其中使用了动态生成预设的技术,这里涉及到很多关于Editor扩展的知识,基本与本章主题无关,Inventory Pro的作者也只是通过例子进行了展示(我想如果是实际游戏可能需要动态生成,比如生长点,怪物掉落什么的),这里列出一个动态生成Prefab的核心代码,也就是类似选择生成消耗品后点击的事件的处理函数,代码如下:

protected override void CreateNewItem()
        {
            var picker = EditorWindow.GetWindow<InventoryItemTypePicker>(true);
            picker.Show(InventoryEditorUtil.selectedDatabase);

            picker.OnPickObject += (type) =>
            {
                string prefabPath = EditorPrefs.GetString("InventorySystem_ItemPrefabPath") + "/item_" + System.DateTime.Now.ToFileTimeUtc() + "_PFB.prefab";

                var obj = GameObject.CreatePrimitive(PrimitiveType.Cube);
                var prefab = PrefabUtility.CreatePrefab(prefabPath, obj);

                AssetDatabase.SetLabels(prefab, new string[] { "InventoryItemPrefab" });

                var comp = (InventoryItemBase)prefab.AddComponent(type);
                comp.ID = crudList.Count == 0 ? 0 : crudList[crudList.Count - 1].ID + 1;
                Object.DestroyImmediate(obj);

                AddItem(comp, true);
            };
        }

2、地方的包裹是如何对应Item model的,且之间的关系是什么?
我们看到1中的代码创建好刚体,碰撞后的正方体后

var comp = (InventoryItemBase)prefab.AddComponent(type);

通过AddComponent添加了用户选择的Type,通过类型转换(InventoryItemBase)我们知道这个类型是Item类型。

然后我们再通过Unity3d 可视化环境来验证一下
这里写图片描述
通过选中预设我们看到其绑定的相关插件有碰撞和刚体属性,最后绑定的脚本是Consumable Inventory Item (Script),这样也验证了上面代码加入的Type,也就是在类选择所对应的InventoryItemBase 类型
这里写图片描述
这里需要注意的是Object Triggerer Item脚本是如何绑定到对象上的,这块是一个小技巧,我找了半天才发现的

/// <summary>
/// The base item of all the inventory items, contains some default behaviour for items, which can (almost) all be overriden.
/// </summary>
[RequireComponent(typeof(ObjectTriggererItem))]
public partial class InventoryItemBase : MonoBehaviour

用RequireComponent这个特性就可以不用手动进行相关插件的绑定了。
这里写图片描述
上图是Consumable Inventory Item(script)的public field详情了,是不是似曾相识,就是和Item Editor工具创建Prefab的时候是一样的。

到此第二个问题回答完毕。

3、拾取的过程是怎么样的?
在正式回答前,我先yy下,既然拾取必须是双向,而且应该是触发的(当点击鼠标触发),然后Play接收到触发的调用进行拾取,最后清理空背包(立方体消失),下面去看看代码是否是这么实现的。

触发器这里在2的答案里已经有了,看了下做法出奇的简单,可能这就是框架的力量吧,下面贴出源码

/// <summary>
    /// Used to trigger item pickup, modify the settings in ShowObjectTriggerer.
    /// </summary>
    [AddComponentMenu("InventorySystem/Triggers/Object triggerer item")]
    [RequireComponent(typeof(InventoryItemBase))]
    [RequireComponent(typeof(Rigidbody))]
    public partial class ObjectTriggererItem : MonoBehaviour
    {

        public InventoryItemBase item { get; protected set; }

        public bool inRange
        {
            get
            {
                return Vector3.Distance(InventorySettingsManager.instance.playerObject.transform.position, transform.position) < InventorySettingsManager.instance.useObjectDistance;
            }
        }


        public void Awake()
        {
            item = GetComponent<InventoryItemBase>();            
        }

        public virtual void OnMouseDown()
        {
            if (ShowObjectTriggerer.instance != null && ShowObjectTriggerer.instance.itemTriggerMouseClick && InventoryUIUtility.clickedUIElement == false)
            {
                if (inRange)
                    Use();
                else
                {
                    InventoryManager.instance.lang.itemCannotBePickedUpToFarAway.Show(item.name, item.description);
                }
            }
        }

        protected virtual void Use()
        {
            item.PickupItem();
        }
    }

这里我们很容易看到了OnMouseDown触发的物品拾取逻辑,直接调用绑定的InventoryBaseItem的PickupItem()拾取函数即可,这里再次重温下自动绑定的威力

[RequireComponent(typeof(InventoryItemBase))]

当需要使用的时候记得在Awake()函数中设置相关属性即可

public void Awake()
{
    item = GetComponent<InventoryItemBase>();            
}
/// <summary>
        /// Pickups the item and stores it in the Inventory.
        /// </summary>
        /// <returns>Returns 0 if item was stored, -1 if not, -2 for some other unknown reason.</returns>
        public virtual bool PickupItem(bool addToInventory = true)
        {
            if(addToInventory)
                return InventoryManager.AddItem(this);
        
            return true;
        }

上面是InventoryItemBase中的PickupItem方法,只用调用InventoryMannager类的静态方法AddItem方法即可,就是这么简单

/// <summary>
        /// Add an item to an inventory.
        /// </summary>
        /// <param name="item">The item to add</param>
        /// <returns></returns>
        public static bool AddItem(InventoryItemBase item, bool repaint = true)
        {
            if (CanAddItem(item) == false)
            {
                instance.lang.collectionFull.Show(item.name, item.description, instance.inventory.collectionName);
                return false;
            }

            //// All items fit in 1 collection
            //if (item.currentStackSize <= item.maxStackSize)
            //    return best.collection.AddItem(item, repaint);

            // Not all items fit in 1 collection, divide them, grab best collection after each iteration
            // Keep going until stack is divided over collections.
            while (item.currentStackSize > 0)
            {
                var bestCollection = instance.GetBestLootCollectionForItem(item, false).collection;
                uint canStoreInCollection = bestCollection.CanAddItemCount(item);

                var copy = GameObject.Instantiate<InventoryItemBase>(item);
                copy.currentStackSize = (uint)Mathf.Min(Mathf.Min(item.currentStackSize, item.maxStackSize), canStoreInCollection);
                bestCollection.AddItem(copy);

                item.currentStackSize -= copy.currentStackSize;
                //item.currentStackSize = (uint)Mathf.Max(item.currentStackSize, 0); // Make sure it's positive
            }

            Destroy(item.gameObject); // Item is divided over collections, no longer need it.

            return true;
        }

InventoryMannager中的AddItem,代码看似简单,却有四个逻辑,是否可以放置,选择最佳容器,叠放逻辑,销毁。

####Demo6:Inventory Pro 实现UGUI通用对话框

所谓通用对话框,如果是自己实现的话有以下几点需要解决,窗体显示控制,窗体UI布局,窗体文字显示,窗体事件回调,窗体显示动画控制,窗体显示声音控制,窗体与其他窗体的关系,功能虽然小涉及的方面和知识却不少,自己做真的很不容易,所以别再自己造轮子了。
这里写图片描述

#####插件实现的效果
简单的确认对话框提示

当扔物品的时候会提示是否确认对话框

这里写图片描述
稍微复杂一些的购买物品对话框

当购买物品时会显示出一个购买的物品,物品数量金额的对话框
这里写图片描述
简单确认对话框的使用

1、使用UGUI来设计一个自己使用的对话框,基本几个元素Title,description ,two buttons;

2、给对话框绑定Draggable Window(Script)使其具有拖拽功能

3、添加Animator,定义对话框显示的时候具有动画效果

4、添加UI Windows(script)使其具有打开关闭,声音,动画的效果

5、Confirmation Dialog(script)使其具有事件回调,model对话框的属性,文字绑定等对话框固有的属性

至此简单的对话框就做好了,这里我们充分见识了绑定技术、组件技术、UI解耦和框架的强大威力
这里写图片描述
这里写图片描述
复杂对话框的使用

这里只要知道Item Int Val Dialog(scirpt)其实是ConfirmDialog类的一个子类,剩下的东西就很自然了,这里不详细展开了。
这里写图片描述

#####分析
功能需求确定了,如何实现这些功能可能就需要用到一些模式,以及一些经验了,先看一下类图
这里写图片描述

根据前一节的脑图,类图我们逐个分析,InventoryUIDialogBase 是一个抽象类,也是与UI进行绑定的主体,其没有一个无用的属性,这里重点关注几个字段和属性,UIWindow类是通用的窗口显示和动画控制组件,InventoryMessage是字符串Message的封装类。

######1)窗体UI布局

UI布局是通过Unity3d UGUI拖拽的方式设计上去的,这个很简单,首先做到了UI分离

######2)窗体文字显示

窗体文字的显示首先是通过后台与UI做的绑定,这里使用Unity3d的组件设计时绑定技术(这里做过WPF的同学有是否有印象MVVM中的绑定),这里关键是文字信息,实际发现其实Dialog类并不关心显示的什么string,而是Inventory Pro提供的(类图中的Message类)一层封装后得到的结果,这里为什么要单独拿出来实际是为了做国际化以及一些文字性的扩展,比如颜色,字体显示的方案。

InventoryLangDataBase类对于所有的消息体文字进行了集中处理,而且本身也是Asset,这里有两种好处一种就是可以集中管理,一种就是为国际化文字。
这里写图片描述
因为Unity3d UGUI可以做文字颜色和字体的格式化操作,这里完全可以扩展添加有颜色和字体大小的文字重载
这里写图片描述
######3)窗体显示控制,窗体显示动画控制,窗体显示声音控制

窗体显示的控制,完全利用Unity3d平台的组件化功能,通过UIWindow专门拿出来控制,这里看到UIWinow类是必须加载Animator动画类的
这里写图片描述
窗体的动画控制,由主体DialogBase进行设计时的动画效果绑定,由UIWindow类在控制显示和关闭时进行动画的Play,这里还用到了协程
这里写图片描述
窗体显示声音控制,由全局类静态方法 InventoryUIUtility.AudioPlayOneShot 来播放即可

######4)窗体与其他窗体的关系

这个功能类似于网页中的遮罩或者winform里的模态(ModelDialog)对话框,这里没有现成的东西可以使用只能自己写了,这里如何关闭UGUI的事件处理主要是通过CanvasGroup这个插件来控制

这里写图片描述

######5) 窗体事件回调
窗体中的事件回调交给了Dialog子类来处理,具体是在重载的ShowDialog方法中添加了委托的事件回调函数,然后通过代码绑定的方式(这里是onClick.AddListener,而不是UI手动可视化绑定)进行了按钮事件的绑定,这里有很大的灵活性。我比较喜欢这种通过代码定义显示委托的方式,来完成事件的回调(c++系可能叫做函数指针),同比匿名委托,泛型委托(Action或者Func),Lambda表达式,代码可读性更强

这里写图片描述
####其它

这里留了一个小疑问,对话框的触发显示是如何实现的,我们的(MessageBox.Show)在哪里呢?

看过前面的文章的同学应该知道,Inevntory Pro有一个全局setting类,需要进行一些配置,其中就需要窗体元素与SettingManger脚本进行绑定,而SettingManger是一个单列全局类
这里写图片描述
最后是如何显示对话框的代码了,看到ShowDialog方法了吗,两个按钮的事件回调函数 Lambda表达式特别显眼
这里写图片描述

####写在最后
分析总结完毕后有一些想法

1、好的框架使开发变得的easy,扩展很方便,通过以上的分析和例子看的出来很容易就能扩展出来一些简单的类似Confirm对话框,而且是对修改封闭,对新增开放的;

2、一个司空见惯的小功能,如果做好了完全可以覆盖到Unity3d的许多知识,剩下的只是不断进行这样的重复,重建你的神经网络即可,总有一天Unity3d的技术就这样印在你的大脑之中;

3、如果你真的看懂了本文,分析一下其实所有的UI系统都是相通的只是API和使用的技术不同而已,只是有些API封装的死,有些封装的松散一些。换句话说如果你自己在某种UI体系中完成一种自己的实现,换到另一个UI体系一样可以实现的;

4、微软体系如Winform过渡的封装是否是好事情?有些时候是好事情,有些时候就未必。根据手上的资源合理的选择技术才是根本;

5、关于使用轮子和造轮子的纠结,这也是一组矛盾,不造轮子就不能深刻的体会技术,造轮子需要大量的时间可造出来未必有已经造好的轮子设计的好,你会选择哪一种呢?

####核心代码
UIWindow

using System;
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using System.Collections.Generic;

namespace Devdog.InventorySystem
{
    /// <summary>
    /// Any window that you want to hide or show through key combination or a helper (UIShowWindow for example)
    /// </summary>
    [RequireComponent(typeof(Animator))]
    [AddComponentMenu("InventorySystem/UI Helpers/UIWindow")]
    public partial class UIWindow : MonoBehaviour
    {

        public delegate void WindowShow();
        public delegate void WindowHide();


        #region Variables 

        /// <summary>
        /// Should the window be hidden when the game starts?
        /// </summary>
        [Header("Behavior")]
        public bool hideOnStart = true;

        /// <summary>
        /// Keys to toggle this window
        /// </summary>
        public KeyCode[] keyCombination;

        /// <summary>
        /// The animation played when showing the window, if null the item will be shown without animation.
        /// </summary>
        [Header("Audio & Visuals")]
        public AnimationClip showAnimation;

        /// <summary>
        /// The animation played when hiding the window, if null the item will be hidden without animation. 
        /// </summary>
        public AnimationClip hideAnimation;

        public AudioClip showAudioClip;
        public AudioClip hideAudioClip;


        /// <summary>
        /// The animator in case the user wants to play an animation.
        /// </summary>
        public Animator animator { get; set; }
        protected RectTransform rectTransform { get; set; }

        [NonSerialized]
        private bool _isVisible = false;
        /// <summary>
        /// Is the window visible or not? Used for toggling.
        /// </summary>
        public bool isVisible
        {
            get
            {
                return _isVisible;
            }
            protected set
            {
                _isVisible = value;
            }
        }

        private IEnumerator showCoroutine;
        private IEnumerator hideCoroutine;


        /// <summary>
        /// All the pages of this window
        /// </summary>
        [HideInInspector]
        private List<UIWindowPage> pages = new List<UIWindowPage>();

        public UIWindowPage defaultPage
        {
            get;
            private set;
        }

        #endregion

        #region Events

        /// <summary>
        /// Event is fired when the window is hidden.
        /// </summary>
        public event WindowHide OnHide;

        /// <summary>
        /// Event is fired when the window becomes visible.
        /// </summary>
        public event WindowShow OnShow;

        #endregion


        public void AddPage(UIWindowPage page)
        {
            pages.Add(page);

            if (page.isDefaultPage)
                defaultPage = page;
        }

        public void RemovePage(UIWindowPage page)
        {
            pages.Remove(page);
        }


        public virtual void Awake()
        {
            animator = GetComponent<Animator>();
            if (animator == null)
                animator = gameObject.AddComponent<Animator>();

            rectTransform = GetComponent<RectTransform>();

            if (hideOnStart)
                HideFirst();
            else
            {
                isVisible = true;
            }
        }

        public virtual void Update()
        {
            if (keyCombination.Length == 0)
                return;

            bool allDown = true;
            foreach (var key in keyCombination)
            {
                if (Input.GetKeyDown(key) == false)
                {
                    allDown = false;
                }
            }

            if (allDown)
                Toggle();
 
        }

        #region Usefull UI reflection functions 

        /// <summary>
        /// One of our children pages has been shown
        /// </summary>
        public void NotifyPageShown(UIWindowPage page)
        {
            foreach (var item in pages)
            {
                if (item.isVisible && item != page)
                    item.Hide();
            }
        }

        protected virtual void SetChildrenActive(bool active)
        {
            foreach (Transform t in transform)
            {
                t.gameObject.SetActive(active);
            }

            var img = gameObject.GetComponent<UnityEngine.UI.Image>();
            if(img != null)
                img.enabled = active;
        }

        public virtual void Toggle()
        {
            if (isVisible)
                Hide();
            else
                Show();
        }

        public virtual void Show()
        {
            if (isVisible)
                return;

            isVisible = true;
            animator.enabled = true;

            SetChildrenActive(true);
            if (showAnimation != null)
            {
                animator.Play(showAnimation.name);
                if (showCoroutine != null)
                {
                    StopCoroutine(showCoroutine);
                    
                }

                showCoroutine = _Show(showAnimation); 
                StartCoroutine(showCoroutine);
            }

            // Show pages
            foreach (var page in pages)
            {
                if (page.isDefaultPage)
                    page.Show();
                else if (page.isVisible)
                    page.Hide();
            }

            if (showAudioClip != null)
                InventoryUIUtility.AudioPlayOneShot(showAudioClip);

            if (OnShow != null)
                OnShow();
        }


        public virtual void HideFirst()
        {
            isVisible = false;
            animator.enabled = false;

            SetChildrenActive(false);
            rectTransform.anchoredPosition = Vector2.zero;
        }

        public virtual void Hide()
        {
            if (isVisible == false)
                return;

            isVisible = false;

            if (OnHide != null)
                OnHide();

            if (hideAudioClip != null)
                InventoryUIUtility.AudioPlayOneShot(hideAudioClip);

            if (hideAnimation != null)
            {
                animator.enabled = true;
                animator.Play(hideAnimation.name);

                if (hideCoroutine != null)
                {
                    StopCoroutine(hideCoroutine);                    
                }

                hideCoroutine = _Hide(hideAnimation);
                StartCoroutine(hideCoroutine);
            }
            else
            {
                animator.enabled = false;
                SetChildrenActive(false);
            }
        }


        /// <summary>
        /// Hides object after animation is completed.
        /// </summary>
        /// <param name="animation"></param>
        /// <returns></returns>
        protected virtual IEnumerator _Hide(AnimationClip animation)
        {
            yield return new WaitForSeconds(animation.length + 0.1f);

            // Maybe it got visible in the time we played the animation?
            if (isVisible == false)
            {
                SetChildrenActive(false);
                animator.enabled = false;
            }
        }


        /// <summary>
        /// Hides object after animation is completed.
        /// </summary>
        /// <param name="animation"></param>
        /// <returns></returns>
        protected virtual IEnumerator _Show(AnimationClip animation)
        {
            yield return new WaitForSeconds(animation.length + 0.1f);

            if (isVisible)
                animator.enabled = false;
        }

        #endregion
    }
}

InventoryUIDialogBase

using UnityEngine;
using System.Collections;
using Devdog.InventorySystem.Dialogs;
using UnityEngine.UI;

namespace Devdog.InventorySystem.Dialogs
{

    public delegate void InventoryUIDialogCallback(InventoryUIDialogBase dialog);

    /// <summary>
    /// The abstract base class used to create all dialogs. If you want to create your own dialog, extend from this class.
    /// </summary>
    [RequireComponent(typeof(Animator))]
    [RequireComponent(typeof(UIWindow))]
    public abstract partial class InventoryUIDialogBase : MonoBehaviour
    {
        [Header("UI")]
        public Text titleText;
        public Text descriptionText;

        public UnityEngine.UI.Button yesButton;
        public UnityEngine.UI.Button noButton;

        /// <summary>
        /// The item that should be selected by default when the dialog opens.
        /// </summary>
        [Header("Behavior")]
        public Selectable selectOnOpenDialog;

        /// <summary>
        /// Disables the items defined in InventorySettingsManager.disabledWhileDialogActive if set to true.
        /// </summary>
        public bool disableElementsWhileActive = true;
    
        protected CanvasGroup canvasGroup { get; set; }
        protected Animator animator { get; set; }
        public UIWindow window { get; protected set; }

        public virtual void Awake()
        {
            canvasGroup = GetComponent<CanvasGroup>();
            if (canvasGroup == null)
                canvasGroup = gameObject.AddComponent<CanvasGroup>();

            animator = GetComponent<Animator>();
            window = GetComponent<UIWindow>();


            window.OnShow += () =>
            {
                SetEnabledWhileActive(false); // Disable other UI elements

                if (selectOnOpenDialog != null)
                    selectOnOpenDialog.Select();
            };
            window.OnHide += () =>
            {
                SetEnabledWhileActive(true); // Enable other UI elements
            };
        }

        public void Toggle()
        {
            window.Toggle();
            if(window.isVisible)
                SetEnabledWhileActive(false); // Disable other UI elements
            else
                SetEnabledWhileActive(true); // Enable other UI elements
        }

        /// <summary>
        /// Disables elements of the UI when a dialog is active. Useful to block user actions while presented with a dialog.
        /// </summary>
        /// <param name="enabled">Should the items be disabled?</param>
        protected virtual void SetEnabledWhileActive(bool enabled)
        {
            if (disableElementsWhileActive == false)
                return;

            foreach (var item in InventorySettingsManager.instance.disabledWhileDialogActive)
            {
                var group = item.gameObject.GetComponent<CanvasGroup>();
                if (group == null)
                    group = item.gameObject.AddComponent<CanvasGroup>();

                group.blocksRaycasts = enabled;
                group.interactable = enabled;
            }
        }
    }
}

ConfirmationDialog

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

namespace Devdog.InventorySystem.Dialogs
{
    public partial class ConfirmationDialog : InventoryUIDialogBase
    {
    
        /// <summary>
        /// Show this dialog.
        /// <b>Don't forget to call dialog.Hide(); when you want to hide it, this is not done auto. just in case you want to animate it instead of hide it.</b>
        /// </summary>
        /// <param name="title">Title of the dialog.</param>
        /// <param name="description">The description of the dialog.</param>
        /// <param name="yes">The name of the yes button.</param>
        /// <param name="no">The name of the no button.</param>
        /// <param name="yesCallback"></param>
        /// <param name="noCallback"></param>
        public virtual void ShowDialog(string title, string description, string yes, string no, InventoryUIDialogCallback yesCallback, InventoryUIDialogCallback noCallback)
        {
            SetEnabledWhileActive(false);

            window.Show(); // Have to show it first, otherwise we can't use the elements, as they're disabled.

            titleText.text = title;
            descriptionText.text = description;
            yesButton.GetComponentInChildren<Text>().text = yes;
            noButton.GetComponentInChildren<Text>().text = no;

            yesButton.onClick.RemoveAllListeners();
            yesButton.onClick.AddListener(() =>
            {
                if (window.isVisible == false)
                    return;

                SetEnabledWhileActive(true);
                yesCallback(this);
                window.Hide();
            });

            noButton.onClick.RemoveAllListeners();
            noButton.onClick.AddListener(() =>
            {
                if (window.isVisible == false)
                    return;

                SetEnabledWhileActive(true);
                noCallback(this);
                window.Hide();
            });
        }

        /// <summary>
        /// Show the dialog.
        /// <b>Don't forget to call dialog.Hide(); when you want to hide it, this is not done auto. just in case you want to animate it instead of hide it.</b>
        /// </summary>
        /// <param name="title">The title of the dialog. Note that {0} is the item ID and {1} is the item name.</param>
        /// <param name="description">The description of the dialog. Note that {0} is the item ID and {1} is the item name.</param>
        /// <param name="yes">The name of the yes button.</param>
        /// <param name="no">The name of the no button.</param>
        /// <param name="item">
        /// You can add an item, if you're confirming something for that item. This allows you to use {0} for the title and {1} for the description inside the title and description variables of the dialog.
        /// An example:
        /// 
        /// ShowDialog("Are you sure you want to drop {0}?", "{0} sure seems valuable..", ...etc..);
        /// This will show the item name at location {0} and the description at location {1}.
        /// </param>
        /// <param name="yesCallback"></param>
        /// <param name="noCallback"></param>
        public virtual void ShowDialog(string title, string description, string yes, string no, InventoryItemBase item, InventoryUIDialogCallback yesCallback, InventoryUIDialogCallback noCallback)
        {
            ShowDialog(string.Format(string.Format(title, item.name, item.description)), string.Format(description, item.name, item.description), yes, no, yesCallback, noCallback);
        }
    }
}

###5、其他补充

####前言

Inventory Pro插件中的基于预设(Prefab)的泛型对象池实现
本文讲的就是基于UI的局部(从某个Scene,到某个Dialog)对象池,范围不同,需要的对象池就有不同的要求。

####演示

左下角信息提示窗口NoticeUI中的信息提示比较多时,具有滚动条和超出自动隐藏的功能,就是通过对象池技术实现,从而提高性能和效率
这里写图片描述
下面代码是在NoticeUI中 设置对象池的相关代码
这里写图片描述
####分析
老规矩先上类图,不过其实没有什么好分析的,就是对象池的标准接口,取Get和回收这里是Destroy。还有出现两个类一个是泛型的,一个是非泛型的,至于为什么是结构体而不是类,这块我也没有深究。
这里写图片描述
下面我们仔细分析下这个UI对象池的类,首先看下类的

####声明和构造函数
这里写图片描述
1、类注释写的很清楚,告诉你这个对象池支持的对象是GameObjects,限制了必须是Unity3D的类型,该类的功能仅为了提高速度。

它并不完善,建议使用小规模的数据。明白了这点很重要,也就是我在背景里说的每种场景使用的对象池是不一样的,一定不能混用和滥用。

2、泛型结构体的声明,这里我们T限定了类型是UnityEngine.Component也就是Unity3D的组件基类,所以为什么注释里面说了只适合GameObjects了,剩下的就是new()关键字约束,确保类是可以new的。IPoolableObject是面向接口的一种实现约束(框架层考虑就是不一样),实际是强制要求在代码级别做好对象池类的管理。

3、构造函数是用了缺省值StatSize64很贴心,下面三句初始化代码,创建了一个_PoolParent GameObject,并挂到了全局的组件树上,我觉得的这点特别重要,这使得在Edit下可以看见池对象的动态创建和状态,特别是让组件树特别清晰,点个赞,第三句,也就是保存了泛型对象

poolParent = new GameObject("_PoolParent").transform;
poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
this.baseObject = baseObject;

####泛型池对象的初始化
池对象初始化创建
这里写图片描述
泛型池对象的初始化其实是调用了GameObject.Instantiate的泛型方法重载,也就是深克隆。这也就是解释了为什么可以完成对于预设(Prefab)的对象池了,然后设置其父对象,挂接到组件树上,最后调用gameObject.SetActivie(false)让UI对象不可见,其实是一个开关。

####池对象的获取和销毁

  • 池对象的获取
    这里写图片描述
    有了创建时候的开关,获取UI池对象就可以通过这个开关来判断对象是否已经使用了,然后如果active是false的,通过设置成true打开就完成了池对象的创建,这里createWhenNoneLeft参数默认是true,用来解决当构造函数预设池对象都被使用后如何扩展的问题,这里其实很简单了当所有预设都用完了直接再创建一个即可,多亏了c#的动态数组List,不然还要像c++那样动态调整数组的大小。

  • 池对象的销毁
    这里写图片描述
    对象的销毁其实是回收,这里对象最简单的一步就是SetActive(false)也就是把UI开关给关上,让对象不可见。其实重要的是调用IPoolObject接口的Reset函数实现,进行一些善后工作,让然也要通过 transform.SetParent()进行组件树的重置。所有对象的销毁和回收就很简单了只需要循环遍历销毁即可。

####核心代码

using System;
using System.Collections.Generic;
using Devdog.InventorySystem;
using Devdog.InventorySystem.Models;
using UnityEngine;

namespace Devdog.InventorySystem
{
    /// <summary>
    /// Only supports GameObjects and no unique types, just to speed some things up.
    /// It's not ideal and I advice you to only use it on small collections.
    /// </summary>
    public struct InventoryPool<T> where T : UnityEngine.Component, IPoolableObject, new()
    {
        private List<T> itemPool;
        private Transform poolParent;
        private T baseObject;

        public InventoryPool(T baseObject, int startSize = 64)
        {
            poolParent = new GameObject("_PoolParent").transform;
            poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
            this.baseObject = baseObject;

            itemPool = new List<T>(startSize);
            for (int i = 0; i < startSize; i++)
            {
                Instantiate();
            }
        }


        /// <summary>
        /// Create an object inside the pool
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        public T Instantiate()
        {
            var a = GameObject.Instantiate<T>(baseObject);
            a.transform.SetParent(poolParent);
            a.gameObject.SetActive(false); // Start disabled

            itemPool.Add(a);
            return a;
        }
    

        /// <summary>
        /// Get an object from the pool
        /// </summary>
        public T Get(bool createWhenNoneLeft = true)
        {
            foreach (var item in itemPool)
            {
                if(item.gameObject.activeSelf == false)
                {
                    item.gameObject.SetActive(true);
                    return item;
                }
            }

            if (createWhenNoneLeft)
            {
                Debug.Log("New object created, considering increasing the pool size if this is logged often");
                return Instantiate();
            }

            return null;
        }
    

        /// <summary>
        /// Mark an object as inactive so it can be recycled.
        /// </summary>
        /// <param name="item"></param>
        public void Destroy(T item)
        {
            item.Reset(); // Resets the item state
            item.transform.SetParent(poolParent);
            item.gameObject.SetActive(false); // Up for reuse
        }

        public void DestroyAll()
        {
            foreach (var item in itemPool)
                Destroy(item);
        }
    }

    /// <summary>
    /// InventoryPool only good for gameObjects
    /// </summary>
    public struct InventoryPool
    {
        private List<GameObject> itemPool;
        private Transform poolParent;
        private GameObject baseObject;

        public InventoryPool(GameObject baseObject, int startSize = 64)
        {
            poolParent = new GameObject("_PoolParent").transform;
            poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
            this.baseObject = baseObject;

            itemPool = new List<GameObject>(startSize);
            for (int i = 0; i < startSize; i++)
            {
                Instantiate();
            }
        }


        /// <summary>
        /// Create an object inside the pool
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        public GameObject Instantiate()
        {
            GameObject a = null;
            if (baseObject != null)
                a = GameObject.Instantiate<GameObject>(baseObject);
            else
                a = new GameObject();

            a.transform.SetParent(poolParent);
            a.gameObject.SetActive(false); // Start disabled

            itemPool.Add(a);
            return a;
        }


        /// <summary>
        /// Get an object from the pool
        /// </summary>
        public GameObject Get(bool createWhenNoneLeft = true)
        {
            foreach (var item in itemPool)
            {
                if (item.gameObject.activeInHierarchy == false)
                {
                    item.gameObject.SetActive(true);
                    return item;
                }
            }

            if (createWhenNoneLeft)
            {
                Debug.Log("New object created, considering increasing the pool size if this is logged often");
                return Instantiate();
            }

            return null;
        }


        /// <summary>
        /// Mark an object as inactive so it can be recycled.
        /// </summary>
        /// <param name="item"></param>
        public void Destroy(GameObject item)
        {
            item.transform.SetParent(poolParent);
            item.gameObject.SetActive(false); // Up for reuse
        }


        public void DestroyAll()
        {
            foreach (var item in itemPool)
                Destroy(item);
        }
    }
}

###6、Inventory Pro学习总结
####背景
游戏中的UI系统或者叫做GUI窗口系统主要有:主要装备窗口(背包,角色窗口也是一种特殊窗口)、确实提示窗口(如购买确认)、信息提示窗口(一遍没有按钮,ContexntMenu)和特殊窗口(聊天记录或者技能树),前篇已经介绍分析了Inventory Pro确认提示窗口的设计和实现方式,这篇主要讲一下信息提示窗口的实现。本以为提示窗口是比较简单的,毕竟没有按钮事件交互的问题,但是分析了下源代码还是让我有些惊讶,插件作者在提示窗口中考虑到了性能问题,由于本人一直在PC端开发程序没有移动端的经验,所以在移动端对于性能优化还是比较关注的。
####插件效果及使用
左下角即为信息提示窗口NoticeUI,当信息提示比较多时,具有滚动条和超出自动隐藏的功能,是通过对象池技术实现,提高性能和效率
这里写图片描述
通过拖拽的方式创建好UI界面,红框中我们看到了组件树的结构和类型
这里写图片描述
在NoticeUI上绑定NoticeUI脚本,设置好每一行显示的预设NoticeMessageUI,ScrollRect等相关属性,基本就已经完成了关于信息提示窗口的实现了
这里写图片描述
####源代码分析
这里写图片描述
####类图分析
经过这段时间的学习,我真的慢慢爱上了VS的类图分析了,希望新手同学也能习惯这点。VS的类图很强大能自动生成关联关系和继承接口等信息,是特别舒心的类图工具。

A、先看下Message模型(Model)类,InventoryNoticeMessage继承了InventoryMessage,继承后拥有的字段有,消息,标题,颜色,消失延时,时间看到这些字段我们大致也可以猜到信息提示窗口有哪些功能了吧(其实是可以扩展的),这里需要重点关注下Show方法(后面源码分析再表述)

B、NoticeUI和NoticeMessageUI都是MonoBehavior的子类,也就是说他们都是组件,分析其字段有具有ScrollRect和Text说明他们都是需要和UI进行绑定的。这里特变关注下VS使用双箭头表示组合关联,所以NoticeUI组合关联NoticeMessageUI,而继承了IPoolableObject接口顾名思义它具有入对象池的能力,也就是可以加入对象池,我们也看到了NoticeUI有一个InventoryPool,我们大概可以猜到NoticeUI中List和InevntoryPool猥琐的关系。
####调用流程分析
调用的流程其实可以画一个流程图,这里只是简单的描述一下

  • 1、InventoryNoticeMessage.Show()
  • 2、 全局NoticeUI.AddMessage()
  • 3、InventoryPool池对象NoticeMessageUI.SetMessage()
  • 4、NoticMessageUI通过setAictive(true)进行显示
  • 5、NoticeUI.Update,通过循环调用NoticMessageUI的showTime.deltaTime做控制隐藏

1、InventoryNoticeMessage.Show()
这里写图片描述
通过以上代码我们看的出来其实notice也是一个全局的UI,所以才可以通过单例来访问,应该是有固定区域的。

2、 全局NoticeUI.AddMessage()
这里写图片描述
NoticeUI中的AddMessage就比较复杂了,主要要处理几个事情A、事件触发;B、滚动处理;C、对象池获取NoticeMessageUI并激活显示D、List和InventoryPool好基友的处理(对象池的回收及引用数组的移除)

3、InventoryPool池对象NoticeMessageUI.SetMessage()
这里写图片描述
SetMessage() 就像它的方法名一样好像什么也没有做的样子,只是设置了一些简单字段的内容以及显示时间,实际的显示激活却是在4对象池获取的时候置位的。

4、NoticMessageUI通过setAictive(true)进行显示
这里写图片描述
对象池的使用和回收是通过池对象的activeSelf属性来确定的,这个开关有一箭双雕的意思,既通过它来控制对象池的使用和回收,又用于控制UI对象的演示与否。

5、NoticeUI.Update,通过循环调用NoticMessageUI的showTime.deltaTime控制隐藏
这里写图片描述
通过显示时间来控制信息的隐藏
这里写图片描述
隐藏函数使用了动画效果,由于动画是有显示时间的,所以通过一个字段isHiding做为状态判断。

####核心源码
NoticeUI

using UnityEngine;
using UnityEngine.EventSystems;
using System;
using System.Collections;
using System.Collections.Generic;
using Devdog.InventorySystem.Models;
using Devdog.InventorySystem.UI.Models;
using UnityEngine.UI;

namespace Devdog.InventorySystem
{
    /// <summary>
    /// How long a message should last.
    /// Parse to int to get time in seconds.
    /// </summary>
    public enum NoticeDuration
    {
        Short = 2,
        Medium = 4,
        Long = 6,
        ExtraLong = 8
    }


    [AddComponentMenu("InventorySystem/Windows/Notice")]
    public partial class NoticeUI : MonoBehaviour
    {
        #region Events

        /// <summary>
        /// Note that it also fired when message == null or empty, even though the system won't process the message.
        /// This is because someone might want to implement their own system and just use the event as a link to connect the 2 systems.
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        /// <param name="duration"></param>
        /// <param name="parameters"></param>
        public delegate void NewMessage(InventoryNoticeMessage message, params System.Object[] parameters);
        public event NewMessage OnNewMessage;

        #endregion

        [Header("General")]
        public NoticeMessageUI noticeRowPrefab;

        [InventoryRequired]
        public RectTransform container;

        public ScrollRect scrollRect;
        public AudioClip onNewMessageAudioClip;


        /// <summary>
        /// When more messages come in the last items will be removed.
        /// </summary>
        [Header("Messages")]
        public int maxMessages = 50;

        /// <summary>
        /// Remove the item after the show time has passed, if false, the item will continue to exist.
        /// </summary>
        public bool destroyAfterShowTime = true;
    
        /// <summary>
        /// All show times are multiplied by this value, if you want to increase all times, use this value.
        /// </summary>
        public float showTimeFactor = 1.0f;
    
        [NonSerialized]
        protected List<NoticeMessageUI> messages = new List<NoticeMessageUI>(8);
        private InventoryPool<NoticeMessageUI> pool;


        public virtual void Awake()
        {
            pool = new InventoryPool<NoticeMessageUI>(noticeRowPrefab, maxMessages);
        }

        public virtual void Update()
        {
            if (destroyAfterShowTime == false)
                return;

            foreach (var message in messages)
            {
                message.showTime -= Time.deltaTime;
                if (message.showTime < 0.0f)
                {
                    message.Hide();
                }
            }
        }

        public virtual void AddMessage(string message, NoticeDuration duration = NoticeDuration.Medium)
        {
            AddMessage(message, duration);
        }

        public virtual void AddMessage(string message, NoticeDuration duration, params System.Object[] parameters)
        {
            AddMessage(string.Empty, message, duration, parameters);
        }

        public virtual void AddMessage(string title, string message, NoticeDuration duration, params System.Object[] parameters)
        {
            AddMessage(new InventoryNoticeMessage(title, message, duration));
        }

        public virtual void AddMessage(InventoryNoticeMessage message)
        {
            // Fire even if we do the nullcheck, just incase other people want to use their own implementation.
            if (OnNewMessage != null)
                OnNewMessage(message, message.parameters);

            if (string.IsNullOrEmpty(message.message))
                return;

            bool scrollbarAtBottom = false;
            if (scrollRect != null && scrollRect.verticalScrollbar != null && scrollRect.verticalScrollbar.value < 0.05f)
                scrollbarAtBottom = true;

            // Incase we don't actually want to display anything and just port the data to some other class through events.
            if (noticeRowPrefab != null)
            {
                var item = pool.Get();
                //var item = GameObject.Instantiate<NoticeMessageUI>(noticeRowPrefab);
                item.transform.SetParent(container);
                item.transform.SetSiblingIndex(0); // Move to the top of the list
                item.SetMessage(message);

                if (onNewMessageAudioClip != null)
                    InventoryUIUtility.AudioPlayOneShot(onNewMessageAudioClip);

                messages.Add(item);
            }
        
            if (messages.Count > maxMessages)
            {
                StartCoroutine(DestroyAfter(messages[0], messages[0].hideAnimation.length));
                messages[0].Hide();
                messages.RemoveAt(0);
            }

            if (scrollbarAtBottom)
                scrollRect.verticalNormalizedPosition = 0.0f;
        }

        protected virtual IEnumerator DestroyAfter(NoticeMessageUI item, float time)
        {
            yield return new WaitForSeconds(time);
            pool.Destroy(item);
        }
    }
}

NoticeMessageUI

using System;
using System.Collections;
using System.Collections.Generic;
using Devdog.InventorySystem.Models;
using UnityEngine;
using UnityEngine.UI;

namespace Devdog.InventorySystem.UI.Models
{
    /// <summary>
    /// A single message inside the message displayer
    /// </summary>
    [RequireComponent(typeof(Animator))]
    public partial class NoticeMessageUI : MonoBehaviour, IPoolableObject
    {
        public UnityEngine.UI.Text title;
        public UnityEngine.UI.Text message;
        public UnityEngine.UI.Text time;

        public AnimationClip showAnimation;
        public AnimationClip hideAnimation;

        [HideInInspector]
        public float showTime = 4.0f;

        public DateTime dateTime { get; private set; }

        [NonSerialized]
        protected Animator animator;
        [NonSerialized]
        protected bool isHiding = false; // In the process of hiding

        public virtual void Awake()
        {
            animator = GetComponent<Animator>();

            if (showAnimation != null)
                animator.Play(showAnimation.name);
        }

        public virtual void SetMessage(InventoryNoticeMessage message)
        {
            this.showTime = (int)message.duration;
            this.dateTime = message.time;

            if (string.IsNullOrEmpty(message.title) == false)
            {
                if (this.title != null)
                {
                    this.title.text = string.Format(message.title, message.parameters);
                    this.title.color = message.color;
                }
            }
            else
                title.gameObject.SetActive(false);


            this.message.text = string.Format(message.message, message.parameters);
            this.message.color = message.color;

            if (this.time != null)
            {
                this.time.text = dateTime.ToShortTimeString();
                this.time.color = message.color;
            }
        }

        public virtual void Hide()
        {
            // Already hiding
            if (isHiding)
                return;

            isHiding = true;

            if (hideAnimation != null)
                animator.Play(hideAnimation.name);
        }

        public void Reset()
        {
            isHiding = false;
        }
    }
}

InventoryNoticeMessage

using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;

namespace Devdog.InventorySystem.Models
{
    [System.Serializable]
    public partial class InventoryNoticeMessage : InventoryMessage
    {
        public DateTime time;

        public Color color = Color.white;
        public NoticeDuration duration = NoticeDuration.Medium;


        /// <summary>
        /// Required for PlayMaker...
        /// </summary>
        public InventoryNoticeMessage()
        { }

        public InventoryNoticeMessage(string title, string message, NoticeDuration duration, params System.Object[] parameters)
            : this(title, message, duration, Color.white, DateTime.Now, parameters)
        { }

        public InventoryNoticeMessage(string title, string message, NoticeDuration duration, Color color, params System.Object[] parameters)
            : this(title, message, duration, color, DateTime.Now, parameters)
        { }

        public InventoryNoticeMessage(string title, string message, NoticeDuration duration, Color color, DateTime time, params System.Object[] parameters)
        {
            this.title = title;
            this.message = message;
            this.color = color;
            this.time = time;
            this.parameters = parameters;
        }

        public override void Show(params System.Object[] param)
        {
            base.Show(param);

            this.time = DateTime.Now;

            if (InventoryManager.instance.notice != null)
                InventoryManager.instance.notice.AddMessage(this);
        }
    }
}