2015-10-23 15:51:55 a117653909 阅读数 4207
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4645 人正在学习 去看看 张刚

我觉得要真正地理解四元数是把Unity3D里四元数函数都上手测试一下其功能。
四元数在Unity3D中的作用就是拿来表示旋转。

AngleAxis
创建一个旋转,绕着某个轴旋转,返回结果是一个四元数。
跟ToAngleAxis实现的是相反的功能。

Angle
返回两个旋转值(四元数)之间的角度,返回值是float类型的角度值。
(不知道这个值算出来后有什么用)

Dot
点乘,我也不太理解其意义。
参见

eulerAngles
返回表示旋转的欧拉角度(Vector3 即3个值)
(如果调用的是某个物体,则表示该物体当前位置是从原始位置怎么旋转过来的,
其实就是Inspector里的Rotation的3个值)

Euler
感觉跟eulerAngles是相反的操作(经测试,确实是)
输入的是欧拉角的3个值,返回的是一个旋转(四元数)

FromToRotation
参数是两个Direction(Vector3),返回一个Quaternion
我觉得主要是用在要把A物体的朝向转成跟B物体的朝向一样时所需要的旋转值(Quaternion)
然后再调用Transform.Rotate就可以把A旋转成跟B一样了。

identity
可以理解为单位四元数

Inverse
就是一个旋转的反旋转,其实就是把刚才的那个旋转转回去。

Lerp
插值,可以理解为可以把一个物体的朝向转向跟另一个物体一样,跟时间配合可以慢慢地旋转。

LookRotation
可以让A物体一直朝向B物体。
参照下面这篇文章的做法,导入ThirdPersonController
http://blog.csdn.net/lijing_hi/article/details/7272089

Quaternion
构造函数,参数是4个float值

RotateTowards
跟Slerp类似

SetFromToRotation
跟FromToRotation功能一样,只是调用方式不一样。

SetLookRotation
跟LookRotation功能一样,只是调用方式不一样。

Slerp
旋转一个角度从from到to。

this[int index]
像数组那样调用x, y, z, w,感觉还不如直接用.x来调用。
可能是为了循环方便。

ToAngleAxis
把一个rotation 转成 angle-axis 形式。跟AngleAxis实现的是相反的功能。
用引用的方式传入 angle和 axis 。
这跟transform.localEulerAngles很类似。
输入:transform.localEulerAngles=(0,0,0);
输出:angle=0, axis=(1,0,0);
输入:transform.localEulerAngles=(0,90,0);
输出:angle=90,axis=(0,1,0);
输入:transform.localEulerAngles=(270,0,0);
输出:angle=90,axis=(-1,0,0)

ToString
格式化成一个字符串

参考:http://blog.csdn.net/yustarg/article/details/8623687

2019-05-11 18:11:54 qq484402076 阅读数 2044
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4645 人正在学习 去看看 张刚

点击下载出处:Unity3D 2018.2破解版

Unity3D 2018破解版是一款世界领先的3D游戏开发引擎软件,通过本软件用户可以自由创造三维视频游戏、建筑三维立体图、实时三维动画等专业三维内容,Unity3D 2018通过引入外部已建模的三维模型,通过用户自定义脚本处理,实现三维立体动态呈现,因而可以创造出连续的动态的三维立体内容。Unity3D 2018对比同类虚幻4引擎更有普遍性,入门难度较低,开发周期更短,并且Unity3D本身还具有asset store商城,需要的素材可以从商城里下载,这使得用户在开发制作时,可以进一步节约开发的时间成本,有效提高开发效率。另外,作为个人开发用户,只是兴趣爱好的话,Unity3D 2018开发商对年收入10w美元以下的作品不会收取额外的费用,非常人性化。

安装说明及破解教程

1.我们使用压缩包内UnitySetup64-2018.2.8f1.exe这个程序来开始安装

2.选择unity 主程序进行安装

3.安装完成后,不急着打开,我们要把crack文件夹下面的Unity.exe复制到安装目录下

4.另外一个Unity_lic.ulf这个是破解补丁需要复制到C:\ProgramData\Unity\,如果在C:驱动器上没有ProgramData文件夹,则启用隐藏文件的显示。如果C:\ ProgramData文件夹中没有Unity文件夹,请创建它。复制之后我们打开软件,发现需要注册,注册很方便,注册之后激活一下,就可以自由使用软件了。

软件功能

一、Scriptable Render Pipeline可编脚本渲染管线
Scriptable Render Pipeline可编脚本渲染管线是一种由C#脚本控制的,在Unity中配置和实行渲染的方式。与Unity传统的渲染方式相比,这是一个巨大的进步,完成了渲染控制从硬编码到完全项目驱动式的转变。
目前Unity提供了大量的渲染配置,例如:正向渲染和延迟渲染。这些管线是固定无法更改的,存在许多的缺点: 
1、无法修复或绕过渲染问题。
2、因为要可能需要应对所有用例,管线太过庞大复杂。
3、由于过时性与复杂性,很难添加最先进的效果。
为了解决这些问题,我们在Unity 2017中在试验性地开发SRP API。现在该功能已经进入Unity 2018.1 beta版本中。我们相信SRP不仅可以解决这些问题,对于开发者来说,非常简单易用。通过SRP,我们不再需要提供一种单一的渲染管线,试图为所有人完成所有事情。SRP API开放了Unity的渲染接口,允许使用C#脚本进行灵活控制。开发者可以根据项目的具体情况编写自定义渲染器。
想象一下,通过组合模块的方式设计一个自定义渲染管线。每个模块都是一个定义良好,易于测试、改进和维护的函数。这就是Scriptable Render Pipeline的工作原理。这不仅使开发者更易使用Unity,也使我们可以向引擎的工具盒中添加新模块,从而使用户能够快速获得对最新渲染功能的访问。
在Unity 2018.1中,我们已为用户提供了二个渲染管线。它们不仅可以直接使用,也可以作为开发者构建自定义渲染管线的参考。
1、Lightweight Rendering Pipeline轻量级渲染管线: 专为无需计算着色器的移动设备和平台设计的渲染管线。
2、High Definition Rendering Pipeline高清渲染管线:现代的高逼真度渲染器,用于支持Shader Model 5.0(DX11及以上)平台的游戏开发。
二、Lightweight Rendering Pipeline轻量级渲染管线
设计LT RP是为了速度和更好地扩展到低端硬件。它是一个基于正向的渲染器,能很好地从移动扩展到VR和PC。
Lightweight Pipeline轻量级渲染管线是一种在Unity 2018.1中可用的Scriptable Render Pipeline。它采取的是一种按每个对象进行光照剔除的单pass正向渲染,其优势是会在单次pass中完成所有光照的着色。与之相比,Unity的vanilla正向渲染会为每个像素光照进行一次额外的pass,使用LT管线会产生更少的绘制调用,而代价是轻微的着色器复杂度增加。
轻量级渲染管线管线最多支持每对象8个光源,并仅支持Unity内置渲染特性的一个子集。
三、HD Rendering Pipeline高清渲染管线
HD Rendering Pipeline高清晰渲染管线是一种在Unity 2018.1中可用的Scriptable Render Pipeline。HDRP是一种现代渲染管线,设计时同时考虑了PBR、线性光照、HDR光照。它使用一种可配置混合平铺(Tile) / 集群延迟(Cluster deferred)/正向光照(Forward lighting)架构构建。
HDRP在Unity内置功能上增加了一些特性,改进了光照、反射探头和标准材质的选项。它提供了诸如Anisotropy、Subsurface scattering、Clear Coating这样的高级材质,以及对高级光照的支持,例如Area lights区域光。
四、Post-processing Stack v2后期处理特效包
Post-processing Stack后期处理特效包已做改进,以提供更好的效果质量,以及自动化体积混合功能。它强大的重写栈和弹性框架,使开发者可以编写和分发自定义效果。它与轻量级渲染管线、高清晰渲染管线以及内置渲染管线兼容。
五、Unity Shader Graph着色器视图
以往能在Unity中创作着色器的都是具有编程能力的人。在Unity 2018中,我们正在改变这一现状!
开发者可以使用Shader Graph着色器视图可视化地构建着色器。无需手工编写代码,而是在图形网络中创建和连接节点。图形框架会对这些更改做出实时反馈,它足够简单,新用户也能即刻上手着色器制作。
Shader Graph着色器视图系统可以和LT和HD渲染管线一起使用,也可以扩展与任何其它自定义的渲染管线一起使用,它拥有一个开放架构,可编写自定义节点。

新功能特色

一、新渲染架构:Scriptable Render Pipeline
作为Unity 2018.1版本的一部分,我们将引入新的实时渲染架构选项Scriptable Render Pipeline ,简称SRP。我们将在每个新版本中进行迭代添加新功能和持续改进。SRP使开发者和技术美术可以充分利用现代硬件和GPU的性能,而无需研究数百万行的C++引擎代码。SRP是一个可扩展的且强大的,通过C#代码和材质着色器轻松定制渲染管线的选项。
我们将提供现成的模板,这些模板可以利用SRP,从轻量级(Lightweight)和高清晰(High-Definition)的管线开始,针对不同场景做了优化。轻量级的目标场景是通用跨平台场景,而高清晰针对的则是一个具有更宏大图形目标的场景,即高端PC、主机这样的高性能平台。
SRP目前还处于实验阶段,你当然还可以继续使用内置的渲染管线以及它不同的配置选项。
二、着色器可视化编程工具Shader Graph
为配合SRP而设计的着色器可视化编程工具Shader Graph,帮助开发者、美术人员等进行可视化的着色器构建。不必手工编写代码,只要在一个图形网络中创建并连接节点,就可以设计和调试着色器,并且每一步都可预览。
在Unity 2018.1 Beta的下一个版本中,我们将把这些元素整合到一个用户友好的工作流中,以创建和使用项目。但是如果你已迫不及待地希望尝试,我们制作了一个使用了SRP的简单示例,它基于轻量级管线构建,并且还包括了着色器图形工具。你可以下载示例项目,并使用Unity 2018. 1 beta版打开,开始新功能探索!
三、C# Job System
在Unity 2018.1 beta的版本周期中,我们还将实现C# Job System和实验性的实体组件系统Entity Component System可以使用。这将使编写安全的多线程代码和提高性能变得更加容易。
四、系统需求更改
1、Unity 2018.1 beta中移除编辑器中针对Substance Designer材质导入的内置支持,但是你仍可以使用由Allegorithmic提供的外部导入器,在你的项目中导入和使用Substance Designer材质。
2、移除了对Wii U的支持。
3、移除了独立播放器构建中对Windows XP的支持。Windows Vista现在是Windows独立播放器所支持的最老操作系统。
4、我们还放弃了对MonoDevelop-Unity的支持,这意味着现在Visual Studio是MacOS和Windows系统上的推荐和支持的C#编辑器。
5、因为Visual Studio for Mac需要MacOS 10.11,所以Unity编辑器对MacOS系统的需求也已变为MacOS 10.11。
五、逐步移除旧粒子系统
从Unity 2018.1开始,我们将逐步移除旧粒子系统。我们的目标是在Unity 2018.2中完全移除旧粒子系统。它在Unity 3.5中,被一个新的系统Shuriken所替换,并从Unity 5.4开始被完全弃用。我们的分析显示,其使用量已几乎为零,这促使我们决定移除旧粒子系统。
如果这对你产生了影响,可以将你的旧粒子系统移植到Shuriken或使用我们的自动升级脚本,尝试自动转换 。
你可以在《Unity 2017.3 版本粒子系统的改进》找到一些关于Unity 2017.3中最新粒子系统改进的示例。
六、未来展望
正如任何beta版项目一样,你将能提前访问到仍在开发中的功能。但那也意味着你将体验到的Unity版本,其稳定性要比最终版本差。要参与测试过程很简单。只要访问我们的beta测试区,阅读指南并下载安装程序,即可获得Unity 2018.1 beta版。
我们也鼓励你注册下面的Beta版测试者电子邮件列表。注册后你将能收到新版本的可用通知,以及如何成为有效测试者的提示。Unity 2018.1 beta版本对所有Unity用户免费提供,包括个人版用户。在发行说明区,你能找到一个完整的列表,其中列出了当前发布版本中所有的新特性、改进以及bug修复。

更新日志

Unity 2018.2.9f1
发行说明
修复
(1074081) - 2D:修复了某些AMD Radeon卡的六边形单元布局中网格组件的渲染。
(1066715) - 2D:修复了重新打开项目并加载资源后进入播放模式时MonoStringNew崩溃的问题。
(921228) - AI:添加了有关在静态场景NavMesh中创建或加载超过65535个自动生成的OffMeshLinks的警告。
(944759) - AI:删除了表达式“verts.size()> 0”的断言。
(1053433) - 动画:修复了中断的转换清除控制器参数值。
(1061326(1025425)) - 资产导入:修复了scriptedImporter切换到其他脚本导入程序的错误:预先导入的资产将继续尝试使用旧的导入程序。
(1074400) - 编辑器:在启用了金属API验证的macOS Mojave上运行时修复了Metal Editor性能回归。
(1016861) - 编辑器:修复了不支持AVX指令的CPU的启动崩溃问题。(Linux版)。
(1025713) - 图形:修复了Android上的OpenGL ES 3.1 + AEP检测。
(1074400) - 图形:在启用了金属API验证的macOS Mojave上运行时固定金属编辑器性能回归。
(1074413) - 图形:初始支持使用MTLHeap,修复了在macOS Mojave上运行时的细分性能回归。
(1064723) - IL2CPP:修复了一个问题,它允许调试器堆栈帧数据与调试器线程不同步,并在调试IL2CPP播放器时导致崩溃。
(1070548) - IL2CPP:当固定和未固定指针类型都用作局部变量且启用了调试器时,在代码转换期间防止出错。
(无) - IL2CPP:修复了IL2CPP中未处理的异常。在执行initobj指令时添加对指针类型的支持。
(1023109) - iOS:修复了在iOS上使用故事板启动屏幕时发布时的崩溃问题。
(无) - OSX:修复构建系统diabling新输入系统。
(1071429) - PS4:固定地形树在PSVR上的广告牌。
(1049901) - PS4:修复了着色器编译器散列的错误哈希生成。
(1066638) - SceneManager:修复了用户可以将DontDestroyOnLoadScene设置为活动状态的可能性。
(无) - UI:修复了删除驱动对象/值时不会删除的RectTransform DrivenProperty字段。
(无) - 通用Windows平台:修复了使用Visual Studio 15.8部署UWP应用程序时出现“SplashScreen缺失”错误。
(1073029(1071331)) - 通用Windows平台:修复了使用Visual Studio 15.8部署UWP应用程序时出现“SplashScreen缺失”错误。
(1057185) - Web:UnityWebRequest:修复了没有Location头的300重定向处理。
(1076485(988420)) - XR:修复360立体声捕捉,其中在调用ConvertToEquriect API后,在立体声征场地图中交换左眼和右眼图像。
变更:2207421190e9
Unity 2018.2.6f1
修复
(1070250(1064071)) - 2D:修复了一个问题,即在命令行上使用带有-no-graphics的batchmode构建精灵图集会导致点采样精灵。
(1064876(1063235)) - 资产导入:修复了导入包含立体相机的文件时FBX模型导入崩溃的问题。
(1072565(1045074)) - 编辑器:修复了预设在材料上的拖放材料未正确刷新材料检查器和材料图标的问题。
(1072564) - 编辑器:修复了只要管理器列表发生变化,PresetManagerEditor就无法正确刷新可用预设列表。
(1066405) - IL2CPP:使用新脚本运行时防止了委托取消订阅中的内存泄漏。
(1068657) - IL2CPP:当iOS嵌入到iOS上的另一个应用程序中时,阻止il2cpp :: os :: Image :: Initialize崩溃。
(1045881(1027704))(1030311) - iOS:修复了使用仅深度摄像头和使用LWSRP时的崩溃问题。
(1042973) - UI:修复了ShaderGraph标签上的FieldMouseDragger。
(1047330) - XR:Windows MR现在可以正确报告其输入,不再对控制台进行垃圾邮件。
(无) - XR:XR.InputTracking现在仅报告移动设备上连接控制器的XRNodeStates。
修订版:c591d9a97a0b
Unity 2018.2.1f1
现在更新
统一新功能?立即获取!
发行说明
修复
(1027063) - 2D:固定标签按钮不切换到精灵编辑器窗口中的其他输入字段。
(1054125(1045785)) - Android:修复了启动时遇到的18,000或更多资产的android崩溃问题。
(1045436) - Android:修复了在PlayerSettings中未选中“使用32位显示缓冲区”时崩溃的问题。
(1056241) - 动画:修正即使设置为零也会影响父子约束源权重。
(947055) - 资产导入:ScriptedImporter - 修复了在检查器中应该只读的属性的显示。
(1059266(1035312)) - 编辑器:修复了在后期绑定后sprite没有从Sprite Atlas正确加载到场景中的问题。
(947055) - 编辑器:修复了Inspector中不是只读的ScriptedImporter字段。
(1058290(1037161)) - 编辑器:修复VSCode打开多个文件,其中包含项目名称中的空格。
(1028797) - 编辑器:修复脚本编译在脚本同步导入过程中随机触发的问题。可能导致错误的编译错误发送到Editor.log。
(1041276(948616)) - 编辑:修复了一个问题,即Menu.SetChecked不会更新上下文菜单弹出窗口中的项目。
(1040404(1018539)) - 编辑器:修复了当粒子碰撞事件被摧毁时由粒子碰撞事件引起的转换错误。
(1058347(1046782)) - Facebook:修正了Facebook平台支持未被编辑检测到。
(1046782) - Facebook:修正了Facebook平台支持未被编辑检测到。
(无) - GI:固定,以便区域灯具有切换而不是下拉。
(1057921) - 图形:固定Metal / Vulkan / OpenGL着色器codegen问题,使用MOVC指令翻译特殊情况。
(1020262) - IL2CPP:修复了在某些情况下未正确保留的call-by-reference方法参数的问题。
(1037601) - IL2CPP:改进了反射调用的线程同步性能。
(1045847) - IL2CPP:修复了当项目包含从C#代码编译的.winmd文件时IL2CPP构建失败的问题。
(1044485) - IL2CPP:修复了使用stdcall调用约定从代理调用本机函数的问题,即使明确标记为cdecl也是如此。在Windows x86(和UWP x86)上出现问题,导致使用SSLStream的代码中罕见的崩溃。
(1042026) - 脚本:修复了在编辑器中重新编译脚本后在OnApplicationQuit中调用DestroyImmediate(gameObject)时崩溃的问题。修复了退出播放模式后有时会发生的ZenInject崩溃问题。
(1053937) - 时间线:修复了在编辑器先前暂停后进入播放模式时PlayableDirector无法报告正确状态的问题。
(1043171) - 通用Windows平台:IL2CPP现在可以成功地在advapi32.dll中P / Invoke进入kernel32.dll!GetNativeSystemInfo和事件跟踪API。
(1049132(1041274)) - Web:修复了两次发送相同UnityWebRequest时崩溃的问题。
修订版:1a9968d9f99c
1.x更新
发行说明
修复
(980197) - 编辑:测试运行者现在可以在iOS / tvOS上启动测试。
(1006601) - 许可证:解决了长时间闲置编辑器第一次未能激活许可证的问题。
(1010774(1006311)) - 资产导入:修复ImportAsset崩溃。
(980970) - 资产导入:在启用“生成光照贴图UV”设置的情况下重新导入某个模型时修复崩溃。
(1016639) - 2D:修复了不删除将Sprite放入Hierarchy窗口中创建的GameObject。
(1021969(1015836)) - 2D:修正Sprite模式设置为Multiple时出现的Sprite Pivot设置,但当它设置为Single时不会显示。
(1021484) - 用户界面:修正了设置位置,然后设置父母失去适当位置的问题。
(1010047) - XR:固定带有线性色彩空间和单通道渲染的黑暗VR视图。
(1024560(1014022)) - iOS:在iOS UnityWebRequest后端中已禁用内置缓存以与其他平台保持一致。
(1024558(1011741)) - iOS:修正中止时UnityWebRequest未完成。
(1026717) - 着色器:修正了几个自定义着色器包含路径角的情况,使系统更强大,可防止随机脚本错误。
(无) - 图形:改进计算着色器调试标签支持。
(1014724) - 资产导入:与旧版Unity相比,固定照明差异。
(1011566(1011514)) - 资产导入:升级到FBX SDK 2018.1.1,在所有版本上动态链接到libfbxsdk。
(952966) - 动画:固定评估多输出可播放。允许修复时间表。
(1007989) - 脚本 - 脚本升级:修复了使用脚本运行时版本4.x时加载循环依赖资产的问题。
(1031063(967406)) - 动画 - 动画录制:固定录制“动画制作者”控制器在控制台中生成80个错误。
(1030295) - 多人游戏:固定主机在发送大包后被破坏\删除。
(1029909) - 脚本:固定内存快照分析器。
(1018162) - 脚本升级:修复了调用Process.Start()打开文件夹时崩溃的问题。
(1010809) - 图形:修正了一些背面剔除角落案例(案例1010809)。
(965024) - 服务:修复了启用了性能报告的可能崩溃。
(984292) - Android:使用Postprocessing Stack和GLES2图形API修复Adreno GPU上的黑屏。
(972927) - Apple TV:删除导致App Store验证失败的2个应用商店图标切片。
(1024859) - iOS:修复了使用开发证书的手动签名。
(1011604) - iOS:修复了在应用退出期间usbmuxd代理仍在运行时崩溃的问题。
(无) - 图形:金属:固定镶嵌着色器,使用未能加载的实例。
(1026722) - Xbox One:Unity项目现在为Xbox One搭建.net 4.6兼容脚本和Roslyn编译器(案例1026722)。
(1003917) - 音频:修复了不推荐使用的WebAudio设置器。
(1003912) - 音频:添加了Chrome音频自动播放策略解决方法。
(无) - 图形:修正使用无类型D3D11 32位纹理格式时的错误。
(1029439) - 2D:修复了无效纹理与平铺颜色只渲染,而不是显示上次使用的雪碧纹理。
(1028457) - 2D:TilemapRenderer现在可以在设置SRP时进行渲染。
(1024422) - 2D:如果瓦片贴图与当前场景视图摄像头平行,并且在5度的边界内,则禁用固定的贴图贴图。
修订版:1a308f4ebef1

2017-11-14 14:31:41 lijianfex 阅读数 756
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4645 人正在学习 去看看 张刚

Unity3D——何谓热更新,为何热更新,如何热更新

首先来赞叹一下中文,何谓为何如何,写完才发现这三个词是如此的有规律。

为何赞叹中文?因为这是一篇针对新手程序员的文字,是一节语文课。

然后来做一下说文解字,也就是

何谓热更新

热更新,每个程序员一听就明白,但是它语出何处,究竟表达了什么含义,到底代表了什么,对技术有什么要求,对经验相对较少的程序员来说可能就有一层神秘面纱了。

热更新,是对hot update 或者 hot fix的翻译,计算机术语,表示在不停机的前提下对系统进行更改。

hot 就是热,机器运行会发烫,hot就是不停机的意思。

热更新,是个很形象的词,机器烫的时候更新,开着更新。

比如Windows 不重启的前提下安装补丁

比如Http服务器在不重启的前提下换掉一个文件

那么对于Unity3D来说,何谓热更新?

额……这个真相实在是不想讲出来,因为很多时候,这个词都用错了。

Unity3D是一个客户端工具,用户是否重启客户端,根本是我们不关心的问题。

很多时候我们用着热更新这个词汇,却不需要真的热更新。

只有少部分游戏,游戏资源在玩的过程中边玩边下,不重启的前提下变更了资源。

我们不需要用户不重启客户端就能实现资源代码的更新,我们需要的是用户重启客户端能实现资源代码的更新。

让我们暂时放过这个我们的需求连词汇都用错了这个基本事实,来总结一下何谓Unity3D热更新

Unity3D热更新就是指:用户重启客户端就能实现客户端资源代码更新的需求或者功能。

为何热更新

热更新,能够缩短用户取得新版客户端的流程,改善用户体验。

没有热更新:

  • pc用户:

下载客户端->等待下载->安装客户端->等待安装->启动->等待加载->玩

  • 手机用户:

商城下载APP->等待下载->等待安装->启动->等待加载->玩

有了热更新

  • pc用户:

启动->等待热更新->等待加载->玩

  • 有独立loader的pc用户:

启动loader->等待热更新->启动游戏->等待加载->玩

  • 手机用户:

启动->等待热更新->等待加载->玩

通过对比就可以看出,有没有热更新对于用户体验的影响还是挺大的,主要就是省去用户自行更新客户端的步骤。

为了方便用户、留住用户、进而从留住的用户身上赚到钱,热更新如今已经成为了大部分游戏的标配功能。

如果你的游戏不标配这个功能,那么竞争力就会少一些,无论是主动还是被动,无论是方便用户还是被标配,你都必须面对热更新这个课题,虽然这个词用错了。

如何热更新

热更新是为了让用户获得资源和代码的变更,这里的代码不是指真的代码,用户不要代码,他要的是变化的业务逻辑。实现变更的具体过程是首先查并更新本地资源和业务逻辑,如需下载则下载。然后启动时资源均从本地资源创建,业务逻辑从本地执行。

  • Unity3D提供了一种机制AssetBundle,可以满足所有资源的比对下载加载,但是assetbundle每平台分别打包对于多平台项目而言比较麻烦,是一个明显的短板,而且assetbundle不能脱离unityeditor产生,也是一个麻烦,项目大了话,多人合作,把所有资源都放入assetbundle明显降低效率。

  • 对于代码,Unity3D是不提供变更机制的。但是Unity3D执行核心是Mono,也就是dotnet,dotnet有一种符号反射机制,可以直接加载一个dll,然后反射出其中的类型进行操作。符号和反射的主要问题是有些平台不能使用,比如ios wp8。另外dotnet有一种emit机制,可以运行时调用编译器对代码进行编译,他的问题也是平台不支持。

以上两点是Unity3D免费赠送给你的帮助

如果不能满足你的需求,你就需要自己搞定三个模块:

资源下载模块

当assetbundle不能满足需要时,我们需要自己建立检查更新需要则下载的机制,也就是资源下载模块。

这个资源下载模块应该有一个版本生成工具,我们将一组文件生成一个一个版本待下载。

有一个Unity3D用的下载模块,下载模块会首先检查服务器上的版本信息,和本地信息做比对,需要的文件则下载。

资源加载模块

然后需要建立自己的从下载保存在本地的文件中加载出资源的机制,也就是资源加载模块。

资源加载模块负责从下载的文件中加载出资源。

如果你希望游戏带有一份初始资源文件,这里有两种思路

一种是资源加载模块直接提供从包内文件和下载文件两种加载路径

一种是游戏第一次启动时,将包内文件全部copy到下载文件

脚本模块

当符号反射不能满足需求时,业务逻辑更新就只有套用脚本语言这一条路,也就是脚本模块。

虽然dotnet世界里有很多脚本可以用 ironRuby ironPython,可是在unity这个特定环境下全部不可用。

你可以使用的一个选择是lua,这个由魔兽世界采用作为界面脚本,从而红遍整个游戏行业,十年经久不衰的脚本。

unity有了很多lua的绑定库,也有了unilua这样的pure c#移植实现。

你还有一个选择是C#Light/Evil,他是C#语法的,pure c#实现的一门新生脚本语言,就是为了Unity3D逻辑热更新而生。

2013-09-03 17:53:40 wyz365889 阅读数 6388
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4645 人正在学习 去看看 张刚

由于项目不得不用到json来解析服务器端传来的数据,于是不得不选择一种在unity3d上面可用的json.开始根据网上推荐LitJson,于是下载下来源码,导入项目;

经过测试可以用;但是移植到ipad时,就各种问题了,上网查问题也没结果,就发现有人说JsonMapper is not compatible with IOS,太坑爹不兼容

于是又根据网友推荐的MiniJson,功能虽然没有litjson强大,不能够解析嵌套数组,但是可以满足自己现在的需求。最终经过两天的挣扎,测试litjson无法可用,选择了MiniJson. (测试期间还接触了jsonfx,但是导入项目时出错,就不折腾了)

MiniJson下载:

https://gist.github.com/darktable/1411710

MiniJson测试代码:

using MiniJson;

void Start () 
	{
		
	string jsonString = "{ \"array\": [1.44,22,33]" +
                        "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
                        "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
                        "\"unicode\": \"\\u3041 Men\\u00fa sesi\\u00f3n\", " +
                        "\"int\": 65536, " +
                        "\"float\": 3.1415926, " +
                        "\"bool\": true, " +
                        "\"null\": null }";
		
	//Dictionary<string, object> dict = MiniJSON.LC_MiniJson.Deserialize(jsonString) as Dictionary.<string. Object>;
	Dictionary<string, object> dict = MiniJSON.LC_MiniJson.Deserialize(jsonString) as Dictionary<string, object>;
    Debug.Log("deserialized: " + dict.GetType());
		


		//double[][] arrayList  = MiniJSON.LC_MiniJson.Deserialize((dict["array"] as List<object>)[0].ToString()) as double[][];
		
		List<object> lst = (List<object>) dict["array"];
		
	Debug.Log("dict['array'][0]: "+ lst[2]);
	
    
    Debug.Log("dict['string']: " + dict["string"].ToString());
    Debug.Log("dict['float']: " + dict["float"]); // floats come out as doubles
    Debug.Log("dict['int']: " + dict["int"]); // ints come out as longs
    Debug.Log("dict['unicode']: " + dict["unicode"].ToString());
		
	Dictionary<string, object> dict2 = (dict["object"]) as Dictionary<string, object>;
		
	
    string str = MiniJSON.LC_MiniJson.Serialize(dict2);

    Debug.Log("serialized: " + str);
	}


/*
 * Copyright (c) 2013 Calvin Rien
 *
 * Based on the JSON parser by Patrick van Bergen
 * http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
 *
 * Simplified it so that it doesn't throw exceptions
 * and can be used in Unity iPhone with maximum code stripping.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace MiniJSON {
    // Example usage:
    //
    //  using UnityEngine;
    //  using System.Collections;
    //  using System.Collections.Generic;
    //  using MiniJSON;
    //
    //  public class MiniJSONTest : MonoBehaviour {
    //      void Start () {
    //          var jsonString = "{ \"array\": [1.44,2,3], " +
    //                          "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
    //                          "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
    //                          "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
    //                          "\"int\": 65536, " +
    //                          "\"float\": 3.1415926, " +
    //                          "\"bool\": true, " +
    //                          "\"null\": null }";
    //
    //          var dict = Json.Deserialize(jsonString) as Dictionary<string,object>;
    //
    //          Debug.Log("deserialized: " + dict.GetType());
    //          Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]);
    //          Debug.Log("dict['string']: " + (string) dict["string"]);
    //          Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles
    //          Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs
    //          Debug.Log("dict['unicode']: " + (string) dict["unicode"]);
    //
    //          var str = Json.Serialize(dict);
    //
    //          Debug.Log("serialized: " + str);
    //      }
    //  }

    /// <summary>
    /// This class encodes and decodes JSON strings.
    /// Spec. details, see http://www.json.org/
    ///
    /// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary.
    /// All numbers are parsed to doubles.
    /// </summary>
    public static class Json {
        /// <summary>
        /// Parses the string json into a value
        /// </summary>
        /// <param name="json">A JSON string.</param>
        /// <returns>An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false</returns>
        public static object Deserialize(string json) {
            // save the string for debug information
            if (json == null) {
                return null;
            }

            return Parser.Parse(json);
        }

        sealed class Parser : IDisposable {
            const string WORD_BREAK = "{}[],:\"";

            public static bool IsWordBreak(char c) {
                return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1;
            }

            enum TOKEN {
                NONE,
                CURLY_OPEN,
                CURLY_CLOSE,
                SQUARED_OPEN,
                SQUARED_CLOSE,
                COLON,
                COMMA,
                STRING,
                NUMBER,
                TRUE,
                FALSE,
                NULL
            };

            StringReader json;

            Parser(string jsonString) {
                json = new StringReader(jsonString);
            }

            public static object Parse(string jsonString) {
                using (var instance = new Parser(jsonString)) {
                    return instance.ParseValue();
                }
            }

            public void Dispose() {
                json.Dispose();
                json = null;
            }

            Dictionary<string, object> ParseObject() {
                Dictionary<string, object> table = new Dictionary<string, object>();

                // ditch opening brace
                json.Read();

                // {
                while (true) {
                    switch (NextToken) {
                    case TOKEN.NONE:
                        return null;
                    case TOKEN.COMMA:
                        continue;
                    case TOKEN.CURLY_CLOSE:
                        return table;
                    default:
                        // name
                        string name = ParseString();
                        if (name == null) {
                            return null;
                        }

                        // :
                        if (NextToken != TOKEN.COLON) {
                            return null;
                        }
                        // ditch the colon
                        json.Read();

                        // value
                        table[name] = ParseValue();
                        break;
                    }
                }
            }

            List<object> ParseArray() {
                List<object> array = new List<object>();

                // ditch opening bracket
                json.Read();

                // [
                var parsing = true;
                while (parsing) {
                    TOKEN nextToken = NextToken;

                    switch (nextToken) {
                    case TOKEN.NONE:
                        return null;
                    case TOKEN.COMMA:
                        continue;
                    case TOKEN.SQUARED_CLOSE:
                        parsing = false;
                        break;
                    default:
                        object value = ParseByToken(nextToken);

                        array.Add(value);
                        break;
                    }
                }

                return array;
            }

            object ParseValue() {
                TOKEN nextToken = NextToken;
                return ParseByToken(nextToken);
            }

            object ParseByToken(TOKEN token) {
                switch (token) {
                case TOKEN.STRING:
                    return ParseString();
                case TOKEN.NUMBER:
                    return ParseNumber();
                case TOKEN.CURLY_OPEN:
                    return ParseObject();
                case TOKEN.SQUARED_OPEN:
                    return ParseArray();
                case TOKEN.TRUE:
                    return true;
                case TOKEN.FALSE:
                    return false;
                case TOKEN.NULL:
                    return null;
                default:
                    return null;
                }
            }

            string ParseString() {
                StringBuilder s = new StringBuilder();
                char c;

                // ditch opening quote
                json.Read();

                bool parsing = true;
                while (parsing) {

                    if (json.Peek() == -1) {
                        parsing = false;
                        break;
                    }

                    c = NextChar;
                    switch (c) {
                    case '"':
                        parsing = false;
                        break;
                    case '\\':
                        if (json.Peek() == -1) {
                            parsing = false;
                            break;
                        }

                        c = NextChar;
                        switch (c) {
                        case '"':
                        case '\\':
                        case '/':
                            s.Append(c);
                            break;
                        case 'b':
                            s.Append('\b');
                            break;
                        case 'f':
                            s.Append('\f');
                            break;
                        case 'n':
                            s.Append('\n');
                            break;
                        case 'r':
                            s.Append('\r');
                            break;
                        case 't':
                            s.Append('\t');
                            break;
                        case 'u':
                            var hex = new char[4];

                            for (int i=0; i< 4; i++) {
                                hex[i] = NextChar;
                            }

                            s.Append((char) Convert.ToInt32(new string(hex), 16));
                            break;
                        }
                        break;
                    default:
                        s.Append(c);
                        break;
                    }
                }

                return s.ToString();
            }

            object ParseNumber() {
                string number = NextWord;

                if (number.IndexOf('.') == -1) {
                    long parsedInt;
                    Int64.TryParse(number, out parsedInt);
                    return parsedInt;
                }

                double parsedDouble;
                Double.TryParse(number, out parsedDouble);
                return parsedDouble;
            }

            void EatWhitespace() {
                while (Char.IsWhiteSpace(PeekChar)) {
                    json.Read();

                    if (json.Peek() == -1) {
                        break;
                    }
                }
            }

            char PeekChar {
                get {
                    return Convert.ToChar(json.Peek());
                }
            }

            char NextChar {
                get {
                    return Convert.ToChar(json.Read());
                }
            }

            string NextWord {
                get {
                    StringBuilder word = new StringBuilder();

                    while (!IsWordBreak(PeekChar)) {
                        word.Append(NextChar);

                        if (json.Peek() == -1) {
                            break;
                        }
                    }

                    return word.ToString();
                }
            }

            TOKEN NextToken {
                get {
                    EatWhitespace();

                    if (json.Peek() == -1) {
                        return TOKEN.NONE;
                    }

                    switch (PeekChar) {
                    case '{':
                        return TOKEN.CURLY_OPEN;
                    case '}':
                        json.Read();
                        return TOKEN.CURLY_CLOSE;
                    case '[':
                        return TOKEN.SQUARED_OPEN;
                    case ']':
                        json.Read();
                        return TOKEN.SQUARED_CLOSE;
                    case ',':
                        json.Read();
                        return TOKEN.COMMA;
                    case '"':
                        return TOKEN.STRING;
                    case ':':
                        return TOKEN.COLON;
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                    case '-':
                        return TOKEN.NUMBER;
                    }

                    switch (NextWord) {
                    case "false":
                        return TOKEN.FALSE;
                    case "true":
                        return TOKEN.TRUE;
                    case "null":
                        return TOKEN.NULL;
                    }

                    return TOKEN.NONE;
                }
            }
        }

        /// <summary>
        /// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
        /// </summary>
        /// <param name="json">A Dictionary<string, object> / List<object></param>
        /// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
        public static string Serialize(object obj) {
            return Serializer.Serialize(obj);
        }

        sealed class Serializer {
            StringBuilder builder;

            Serializer() {
                builder = new StringBuilder();
            }

            public static string Serialize(object obj) {
                var instance = new Serializer();

                instance.SerializeValue(obj);

                return instance.builder.ToString();
            }

            void SerializeValue(object value) {
                IList asList;
                IDictionary asDict;
                string asStr;

                if (value == null) {
                    builder.Append("null");
                } else if ((asStr = value as string) != null) {
                    SerializeString(asStr);
                } else if (value is bool) {
                    builder.Append((bool) value ? "true" : "false");
                } else if ((asList = value as IList) != null) {
                    SerializeArray(asList);
                } else if ((asDict = value as IDictionary) != null) {
                    SerializeObject(asDict);
                } else if (value is char) {
                    SerializeString(new string((char) value, 1));
                } else {
                    SerializeOther(value);
                }
            }

            void SerializeObject(IDictionary obj) {
                bool first = true;

                builder.Append('{');

                foreach (object e in obj.Keys) {
                    if (!first) {
                        builder.Append(',');
                    }

                    SerializeString(e.ToString());
                    builder.Append(':');

                    SerializeValue(obj[e]);

                    first = false;
                }

                builder.Append('}');
            }

            void SerializeArray(IList anArray) {
                builder.Append('[');

                bool first = true;

                foreach (object obj in anArray) {
                    if (!first) {
                        builder.Append(',');
                    }

                    SerializeValue(obj);

                    first = false;
                }

                builder.Append(']');
            }

            void SerializeString(string str) {
                builder.Append('\"');

                char[] charArray = str.ToCharArray();
                foreach (var c in charArray) {
                    switch (c) {
                    case '"':
                        builder.Append("\\\"");
                        break;
                    case '\\':
                        builder.Append("\\\\");
                        break;
                    case '\b':
                        builder.Append("\\b");
                        break;
                    case '\f':
                        builder.Append("\\f");
                        break;
                    case '\n':
                        builder.Append("\\n");
                        break;
                    case '\r':
                        builder.Append("\\r");
                        break;
                    case '\t':
                        builder.Append("\\t");
                        break;
                    default:
                        int codepoint = Convert.ToInt32(c);
                        if ((codepoint >= 32) && (codepoint <= 126)) {
                            builder.Append(c);
                        } else {
                            builder.Append("\\u");
                            builder.Append(codepoint.ToString("x4"));
                        }
                        break;
                    }
                }

                builder.Append('\"');
            }

            void SerializeOther(object value) {
                // NOTE: decimals lose precision during serialization.
                // They always have, I'm just letting you know.
                // Previously floats and doubles lost precision too.
                if (value is float) {
                    builder.Append(((float) value).ToString("R"));
                } else if (value is int
                    || value is uint
                    || value is long
                    || value is sbyte
                    || value is byte
                    || value is short
                    || value is ushort
                    || value is ulong) {
                    builder.Append(value);
                } else if (value is double
                    || value is decimal) {
                    builder.Append(Convert.ToDouble(value).ToString("R"));
                } else {
                    SerializeString(value.ToString());
                }
            }
        }
    }
}


2014-10-17 17:43:48 book_longssl 阅读数 710
  • Unity3D入门到精通-(3)Unity资源管理精讲

    本次系列课程的目标是让Unity3D初学者掌握Unity3d的资源管理技术进行了全面介绍,特别对AssetBundle资源如何进行更新,以及加载(依赖资源加载)进行了系统的介绍。 适合对象:Unity初学开发者,Unity中级开发者,网络程序开发者,所有对游戏开发有兴趣的人员。 学习条件:有一定的Unity3D基础,了解C#的基本开发知识。

    4645 人正在学习 去看看 张刚



初学者经常把Awake和Start混淆。


简单说明一下,Awake在MonoBehavior创建后就立刻调用,Start将在MonoBehavior创建后在该帧Update之前,在该Monobehavior.enabled == true的情况下执行。




void Awake (){
}     
//初始化函数,在游戏开始时系统自动调用。一般用来创建变量之类的东西。

void Start(){
}
//初始化函数,在所有Awake函数运行完之后(一般是这样,但不一定),在所有Update函数前系统自动条用。一般用来给变量赋值。
我们通常书写的脚本,并不会定义[ExecuteInEditMode]这个Attribute,所以Awake和Start都只有在Runtime中才会执行


例1:


public class Test : MonoBehaviour {
    void Awake () {
        Debug.Log("Awake");
        enabled = false;
    }

    void Start () {
        Debug.Log("Start");
    }
}
以上代码,在Awake中我们调用了enabled = false; 禁止了这个MonoBehavior的update。由于Start, Update, PostUpdate等属于runtime行为的一部分,这段代码将使Start不会被调用到。


在游戏过程中,若有另外一组代码有如下调用:




Test test = go.GetComponent<Test>();
test.enabled = true;
这个时候,若该MonoBehavior之前并没有触发过Start函数,将会在这段代码执行后触发。




例2:


player.cs


private Transform handAnchor = null;
void Awake () { handAnchor = transform.Find("hand_anchor"); }
// void Start () { handAnchor = transform.Find("hand_anchor"); }
void GetWeapon ( GameObject go ) {
    if ( handAnchor == null ) {
        Debug.LogError("handAnchor is null");
        return;
    }
    go.transform.parent = handAnchor;
}


other.cs


GameObject go = new GameObject("player");
player pl = go.AddComponent<player>(); // Awake invoke right after this!
pl.GetWeapon(weaponGO);
...
以上代码中,我们在player Awake的时候去为handAnchor赋值。如果我们将这步操作放在Start里,那么在other.cs中,当执行GetWeapon的时候就会出现handAnchor是null reference.


总结:我们尽量将其他Object的reference设置等事情放在Awake处理。然后将这些reference的Object的赋值设置放在Start()中来完成。
当MonoBehavior有定义[ExecuteInEditMode]时


当我们为MonoBehavior定义了[ExecuteInEditMode]后,我们还需要关心Awake和Start在编辑器中的执行状况。


当该MonoBehavior在编辑器中被赋于给GameObject的时候,Awake, Start 将被执行。
当Play按钮被按下游戏开始以后,Awake, Start 将被执行。
当Play按钮停止后,Awake, Start将再次被执行。
当在编辑器中打开包含有该MonoBehavior的场景的时候,Awake, Start将被执行。


值得注意的是,不要用这种方式来设定一些临时变量的存储(private, protected)。因为一旦我们触发Unity3D的代码编译,这些变量所存储的内容将被清为默认值。


下面再来看看Unity圣典中的解释。


Awake()


当一个脚本实例被载入时Awake被调用。


Awake用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次.Awake在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如 


GameObject.FindWithTag 这样的函数搜索它们。每个游戏物体上的Awke以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息。Awake总是在Start之前被调


用。它不能用来执行协同程序。
Start()


Start仅在Update函数第一次被调用前调用。Start在behaviour的生命周期中只被调用一次。它和Awake的不同是Start只在脚本实例被启用时调用。
你可以按需调整延迟初始化代码。Awake总是在Start之前执行。这允许你协调初始化顺序。


真Unity3d_AI四选一

阅读数 4529

没有更多推荐了,返回首页