关于unity:Unity编写Shader内置各种矩阵和方法介绍

嗨,各位小伙伴们,我是你们的好敌人咕噜铁蛋!明天,咱们要来聊一聊对于Unity中编写Shader时内置的各种矩阵和办法。作为Unity开发者,把握Shader编写是十分重要的一项技能,而理解内置的矩阵和办法将帮忙咱们更好地了解和利用Shader的弱小性能。什么是Shader?首先,让咱们简略理解一下Shader是什么。在Unity中,Shader是一种用来形容渲染物体外观和特效的程序。通过编写Shader代码,咱们能够管制物体的色彩、光照、透明度等成果,从而实现各种炫酷的视觉效果。内置矩阵和办法的作用在编写Shader时,咱们能够应用Unity内置的一些矩阵和办法来实现各种成果。这些内置的矩阵和办法包含但不限于模型矩阵、视图矩阵、投影矩阵以及罕用的数学函数等。理解它们的作用和用法,将使咱们可能更高效地编写出合乎预期成果的Shader。模型矩阵(Model Matrix)模型矩阵是用来形容物体的地位、旋转和缩放等变换的矩阵。在Shader中,咱们能够应用unity_ObjectToWorld矩阵来获取模型矩阵,它将物体空间的坐标转换到世界空间中。视图矩阵(View Matrix)和投影矩阵(Projection Matrix)视图矩阵和投影矩阵别离用来形容摄像机的地位和朝向,以及透视投影的成果。在Shader中,咱们能够应用UNITY_MATRIX_V来获取视图矩阵,应用UNITY_MATRIX_P来获取投影矩阵。罕用数学函数除了矩阵之外,Unity还提供了丰盛的数学函数供咱们在Shader中应用,比方常见的三角函数、指数函数、取整函数等。这些函数能够帮忙咱们进行各种简单的计算,实现更加壮丽的视觉效果。其余罕用办法除了矩阵和数学函数之外,Unity还内置了许多罕用的办法,比方光照计算方法、纹理采样办法、颜色混合办法等。这些办法为咱们提供了丰盛的性能,帮忙咱们轻松实现各种渲染成果。如何利用内置矩阵和办法编写Shader当咱们了解了这些内置的矩阵和办法后,咱们就能够开始编写本人的Shader了。在编写Shader时,咱们能够利用这些矩阵和办法进行各种计算和操作,实现咱们想要的成果。比方,咱们能够应用视图矩阵和投影矩阵将物体的坐标从世界空间转换到裁剪空间,再利用数学函数对顶点地位进行变换,最终实现各种炫酷的渲染成果。总的来说,Unity内置的矩阵和办法为咱们编写Shader提供了弱小的反对。通过充沛了解和利用这些内置的性能,咱们能够更好地实现各种炫酷的视觉效果,晋升游戏的品质和表现力。心愿本文对你有所帮忙,如果有任何问题或倡议,欢送在评论区留言,咱们一起学习提高!感激大家的浏览,咱们下期再见!

March 1, 2024 · 1 min · jiezi

关于unity:游戏中的体积流体技术

随同着Games 103的推出,十分欣慰地看到越来越多的学生群体和沉闷在前沿的业内人士开始器重“基于物理的计算机动画”技术在游戏开发中的利用,开发者也不再局限于通过序列帧或者Flowmap的形式去模仿流体景象,而是基于实在的流体力学还原其静止法则。另一方面体积渲染,无论是云,还是雾,做为游戏美术添彩的重要一笔,亦或是彰显技术实力的重要载体,也越来越高频地呈现在大家视线中。计划优良,成果上乘,运行高效,编辑器敌对的体积解决方案是任何一款古代游戏引擎中十分重要的组成部分。当然,对于游戏开发者而言,咱们时刻面临着“可交互速率运行”的考验。这间接导致了简直每一个落地的体积渲染或者流体模仿计划都有其必须做出的割舍与就义,也都有其善于应答的情景和无意躲避的难题。然而,尽管曾经有一些剖析“体积渲染”或“流体模仿”的文章,但大多唯此一篇,不足针对不同计划的纵向深刻与横向比照;或受限于特定引擎平台,不足底层通用性实践的零碎阐述。事实是,截止到SIGGRAPH 2022,咱们仍旧没有找到一套适应于任意场景需要的体积云雾计划【仅限游戏畛域】。而实时背景下将“体积渲染”与“流体模仿”联合起来,仍旧是一个十分小众的话题。至于体积的焚烧过程与爆炸模仿依然是影视“一家合唱”。但无论如何,实时、体积、流体三个词融在一起都是令人无比神往的,因为很多时候并不是我的项目须要推动了技术提高,而是技术提高带来了新的玩法与新的艺术表现形式。 鉴于此,笔者决定保护一个专栏用于系统性的剖析以后与潜在的“体积流体”计划,它们中的一些惊艳于SIGGRAPH与GDC,并在游戏开发史上留下了浓墨重彩的一笔,另外一些尽管小众,但切入点与思考方向十分有价值。如果读者之前有接触过这些工作,就会意识到这是一个波及数据的生成,存储与转换,算法的设计与优化,视觉表白与内容治理,以及引擎和工具链开发的宏大内容,当咱们站在产品项目管理周期的视角去思考,会发现大家最常常探讨的3D噪声纹理与Ray marching其实只是整个零碎的一小部分。除了传统的伎俩,随同着机器学习MLP对数据、信号的解构与重建的钻研,利用神经网络压缩数据,减速拜访与碰撞检测的尝试也逐步受到了工程界与学术界的器重,这仿佛为解决一些传统窘境提供了可能。除了已被落地证实的持重计划,这些潜在的钻研价值也会被纳入探讨范畴。 在流体计划层面,咱们可能更着重探讨欧拉视角,或以Stable Fluid为代表的半拉格朗日办法,以及MLS-MPM等混合办法。这全然是因为咱们须要面对的是云、雾、烟、火焰这类的介质 ,而非液体。更为简单的多相流问题,仿佛在游戏中利用的可能性不大,但如果有工夫,追加这部分的钻研也是十分让人兴奋的。一旦咱们将视角放开到影视畛域,对物理法则的还原将变得更加刻薄,虚拟世界也更加实在! 作者Angelou.lv:技术美术 当初国内某头部游戏厂商做TA,主攻图形渲染与物理模仿。 目录1|实在云景象的钻研 2|《Horizon Zero Dawn》的体积云景实现 3|《西部禁域》中的体积云渲染 4|Unreal中的体积云景实现剖析 本篇转载自《游戏中的体积流体技术》的第1节。 鉴于平台编辑限度,以下色彩信息无奈体现,欢送有趣味的小伙伴致原文浏览。 文中会对一些名词进行色彩标注:绿色是计划中比拟重要的专有术语,蓝色是维基百科上能够检索到的术语,橙色示意目录。 注:蓝色文字且加下划线示意加了超链接。 一、云族谱1.1 云属 依照国内气象组织的分类[1],云一共有10个属:云的10个属 每属均有本人的简称和察看特色,这10个云属依照高度散布如下: 云层散布(全称) 云层散布(简称) 上图内容大抵是云的品种由低到高别离为【低云族】“积云(Cumulus),积雨云(Cumulonimbus),层云(Stratus), 层积云(Stratocumulus)”;【中云族】“高积云(Altocumulus),高层云(Altostratus),雨层云(Nimostrartus)”;【高云族】“卷云(Cirrus),卷积云(Cirrocumulus),卷层云(Cirrostratus)”。 大多数云都仅存在于它们的层面内,但通常界线比拟含糊。比方高层云通常属于中间层,但它的顶部往往可延长至更高的层面,而雨层云尽管位于中层云最高层但也可延长至另外两个层面,积云和积雨云通常在低层,但积雨云的垂直范畴又很大。 云层的大略散布范畴在600~8000米,而渲染上影响云的视觉体现次要有“形态,构造,灰度,透光水平”这四个次要因素。 每个层面的近似高度&每个层面中呈现的属 1.2 云种 依据云的形态和其内部结构,大多数的云属可被细分为云种,如毛状云(Ci),钩状云(Ci),密云(Ci)都是卷云,但形态又有不同: 又如堡状云(Ac),絮状云(Ac),层状云(Ac)都属于高积云: 再如扁平云(Cu),中云(Cu),浓云(Cu)都属于积云: 1.3 云类 除了依照形态与内部结构分,也能够依照云的另外两种重要属性——灰度与透光水平去分,被称为不同的云类: 另外除了“形态,构造,灰度,透光水平”这四个次要特色,云还有附加特色与附属性,前者是指附着在云上或者局部主体合并的特色,后者则侧重于随同云主体呈现在左近的小云团,它们被称为从属云。云是在一直变动的,母云会演化出不同的衍生云,随着主体逐步变动,母云本来的特色可能齐全隐没不见,此时的云被称为“转化云”。 除了天然演变,人类流动也会影响上空的云变换,比方核电站冷却塔,大坝上空,飞机航线上,这些非凡因素产生的云被别称为“非凡”: 二、低云族2.1 积云(Cu) 积云是间隔地表最近的,因而也是密度最大的,依照第一局部云的四个次要个性的维度去剖析:积云具备最清晰的轮廓,上局部相似花椰菜或山丘,底部则绝对较平,这些云被阳光照耀局部多为红色,底部绝对较暗,这能够用Beer-Lambert定律解释。典型的状态如下: 图为:浓积云正在蒙特勒前面的瑞士阿尔卑斯山脉和瑞士日内瓦湖沿岸倒退 当然这是最典型的,这种厚重的积云往往呈现在沿海城市,因为更容易有水蒸气升腾,它们被细分为一个云种——浓积云(Cu con)。对于浓积云来说顶部通常以”塔“的模式垂直倒退,塔太高被风吹过期会瓦解,从而造成丰盛的衍生云,浓积云会产生诸多模式的降水:降雨,降雪,极其天气下还会产生冰雹。 图为:西班牙巴塞罗那Vallvidrera 但积云不肯定会产生降水,在内陆地区,水蒸发量会少很多,这个时候它们的密度绝对较低,垂直范畴变窄,导致看起来绝对扁平,这是积云属的一个非凡云种——淡积云或扁平云: 图为:淡积云 随着含水量逐步升高,积云变得越来越重,越来越厚。逐渐变为中积云(Cu med):中积云也具备一系列清晰的轮廓,云底绝对较暗,云层只体现出适当的垂直倒退,顶部个别有“小突起” 或者“云芽”,也没有降水。 图为:德国爱尔福特地区上空的中积云 但中积云有肯定水平会倒退为浓积云。另外还有一种积云种叫“碎积云(Cu fra)”:云底同样是程度的,变动十分迅速,垂直范畴很小,外观扁平: 图为:蒙古赛音山达的碎积云 2.2 积雨云(Cb) 积雨云一个十分典型的特色是:有相当大的垂直范畴,出现高塔或平地状,且其上部至多有一部分通常是润滑平缓的,而在下部通常则较暗且毛糙,整体通常为“砧状”,具备显著的纤维状构造。 图为:西班牙昆卡的积雨云 ...

September 8, 2023 · 1 min · jiezi

关于unity:DOTS实战技巧总结

【USparkle专栏】如果你深怀绝技,爱“搞点钻研”,乐于分享也博采众长,咱们期待你的退出,让智慧的火花碰撞交错,让常识的传递生生不息! 随着技术的倒退,客户端方向技术也日趋考究高性能,毕竟大世界高自由度玩法正在迫近,在这一趋势下,Unity也是催生出了DOTS这一高性能技术计划,这一解决方案考究的是高并行和缓存敌对。以后的DOTS还处于正式版前夕的1.0版本,尽管有很多有余,然而其用法和开发思维曾经根本确定,将来的正式版本除了反对更多Unity个性,开发思维预计变动不会太大。 一、System间的数据传递依照ECS的代码框架,代码逻辑只能写到System外面。理论开发过程中,有很多情景下是须要A零碎去告诉B零碎做某件事的。 在以往的Unity程序开发中,代码的自由度十分高,要实现A零碎告诉B零碎做某事的方法很多。最简略的方法就是通过回调函数来实现,也能够通过观察者模式实现一个音讯零碎等这样的模式比拟优雅地去实现。 然而DOTS的限度比拟多。首先,是用不了回调函数,因为Burst编译不反对Delegate。其次,观察者模式也不适用于ECS框架,因为ECS框架的逻辑都是面向数据,将数据切成组,一批一批调用解决的。换句话说就是观察者模式是触发式的调用,ECS框架的逻辑是轮训式的调用。 有一种思路是在ISystem里定义一个NativeList成员,用来接管内部传递给本ISystem的音讯数据,而后在OnUpdate函数里将音讯数据一个一个取出来解决。但会遇到以下几个问题。 问题一,若这个NativeList定义为ISystem的成员,其余ISystem在本人的OnUpdate函数里是拜访不到本人这个ISystem的对象的,只能拜访到其对应的SystemHandle。那么是不是能够把NaitveList定义为动态变量呢?这将会引出问题二和问题三。 当然,也能够调用EntityManager.AddComponent(SystemHandle system, ComponentType componentType) 给零碎加组件,而后其余ISystem拜访这个零碎的组件来达到消息传递的目标,然而这种做法首先只能传递独自一个组件的数据,数据质变大就不实用了;其次这种做法不属于本文的技巧,这是Unity Entities 1.0官网的规范做法,有足够的文档去形容如何操作,这里不再赘述。问题二,ISystem的OnUpdate函数只能拜访readonly润饰的动态容器变量。如果不必readonly润饰NativeList,并且在OnUpdate里还去拜访他的话,就会失去报错信息。 Burst error BC1042: The managed class type Unity.Collections.NativeList1<XXX>* is not supported. Loading from a non-readonly static field XXXSystem.xxx` is not supported问题三,如果用readonly润饰动态的NativeList,就不得不在定义变量时初始化变量,那么你将会失去如下报错信息。 (0,0): Burst error BC1091: External and internal calls are not allowed inside static constructors: Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle.Create_Injected(ref Unity.Collections.LowLevel.Unsafe.AtomicSafetyHandle ret)因而,应用NativeList的这种思路是行不通的。上面介绍一些我的项目中摸索进去的可行技巧。 1.1 将数据包装成实体传递当初换种思路,先创立一个Entity,并将要传递的信息组织成IComponentData绑到Entity上,造成一个个音讯实体,其余ISystem通过去遍历这些音讯实体实现数据在实体间传递的性能。 这种做法不仅实用于ISystem和ISystem之间的数据传递,甚至实用于MonoBehaviour和ISystem、以及ISystem和SystemBase之间的数据传递。 上面是一个具体例子,定义了一个MonoBehaviour用来绑定到UGUI的按钮上,点击按钮就会调用OnClick函数。 public struct ClickComponent : IComponentData{ public int id;}public class UITest : MonoBehaviour{ public void OnClick() { World world = World.DefaultGameObjectInjectionWorld; EntityManager dstManager = world.EntityManager; // 每次点击按钮都创立一个Entity Entity e = dstManager.CreateEntity(); dstManager.AddComponentData(e, new ClickComponent() { id = 1 }); }}代码第14到第18行,就是把要传递的音讯(id = 1)包装为一个Entity给到默认世界。 ...

August 16, 2023 · 7 min · jiezi

关于unity:定制高性能GPU粒子系统

【USparkle专栏】如果你深怀绝技,爱“搞点钻研”,乐于分享也博采众长,咱们期待你的退出,让智慧的火花碰撞交错,让常识的传递生生不息! 一、技术设计背景Unity引擎自带的粒子系统始终是CPU端计算的,这里是指粒子系统以下三大步骤都是在CPU计算。 粒子系统的次要3个开销大的步骤: 每个发射器每帧创立新粒子实例每个粒子实例每帧更新粒子地位、色彩等状态每个发射器的绘制提交与发射器之间渲染排序起初硬件的倒退GPU晋升的更快,而理论我的项目中经常也是CPU瓶颈居多。所以有了基于ComputeShader与GPUInstance技术的GPU粒子系统。比方Unreal Engine有CPU和GPU 2套,较新版Unity也有VFX。然而抉择本人写一套次要是这几个考量。 ComputeShader与GPUInstance联合的技术曾经开发过很屡次最高收益的性能了,比方海量植被渲染、大世界的GPUTerrain、RealtimeVirtualTexture等,所以算比拟有把握。现有我的项目曾经上线,心愿现有美术资源不做人工批改,就能实现与引擎的粒子系统性能统一、算法统一、逻辑架构统一并实现一键批量转换,所以本人按Unity的算法写到ComputeShader更适合些。通用的GPU粒子更合乎大量发射器产生大量粒子的模式,而理论游戏很少用到这种模式。不管角色技能还是FPS的设计,子弹碰撞成果、弹孔成果等等,全都是发射器数量多,但每个反射器创立的粒子数很少,所以须要用本人定制优化的非凡排序进步性能。根底性能的面板数据 二、单个简单粒子模式这种模式尽管游戏内不太罕用,然而性能晋升最大,也是开发最简略直观的。而且GitHub曾经有Demo,我就不反复写这种模式的代码了。如果感觉我这里说的不够具体,没有根底代码局部有点晕的同学能够下载这份很短但残缺的源码。https://github.com/Robert-K/gpu-particles 具体的做法分3个步骤: 在C#脚本中,每帧对这个发射器计算这一帧须要创立的粒子数(依据粒子系统上每秒多少个和 Burst参数),而后须要创立多少个Dispatch、多少个线程数,因为这种模式发射器数量很少,粒子数很大,比方全地图烟雾、全图落叶等。所以CPU计算发射数的工作量非常少,没必要让GPU计算。把这种粒子系统看成粒子数量是固定的,比方N,这N就是粒子系统里粒子下限参数。创立长度为N的StructuredBuffer,寄存Particle实例信息的Struct。因为每个实例生命完结程序不固定,所以须要一个可用粒子池的AppendBuffer来记录Particle数组里哪些Index粒子可被拿来复用。每帧对所有粒子实例更新,每个ComputeShader线程解决一个粒子实例。所以不论以后多少个粒子在渲染都是按N来做的。这种粒子个别都是循环N,根本就是要渲染的全副,只有设置正当,其实并不会节约不可见粒子的空循环,比再用Buffer治理无效粒子,渲染时再跳转反而性能更好。局部要害代码: Buff内粒子实例数据 粒子数据与可用粒子对象池索引变量 这里须要留神:dead与alive其实对于C#那边同一份Buffer数据。只是在创立粒子的Kernel里生产,在Init与Update的Kernel里Append,因为死亡或初始化都要把粒子设置为可用,就是把Index还给Buffer。 创立粒子是耗费可用的粒子Index 更新时,如果生命到期就把粒子的Index还给可用Buffer 渲染的时候,数量逻辑一样按粒子系统的设置maxCount作为InstanceCount。其中不可见的粒子用col=pInst.alive*pInst.color,实现暗藏。这种模式绝大部分时候绘制的粒子数量就靠近maxCount,所以根本都是alive=true的,很少空计算。 以下是测试后果渲染20w个粒子,这种性能晋升是微小的。Unity的CPU计划107帧 VS GPU实现计划1661帧。 色彩不同是因为,Demo的作者在对色彩随生命变动的突变图转图形时,没思考用线性空间导致的,不影响性能比照。 单个简单粒子CPU/GPU计划帧数比照右边是抓帧证实渲染的粒子数量一样 三、多发射器的简略粒子这个模式才是我真正为我的项目开发的模式,也是更能写出性能大收益的模式,老老实实的写很容易负优化。这是因为GPU中的半透明与CPU中的半透明对象很难一起高性能排序,通用引擎为了通用与相对正确,据我粗略理解,这个问题是无解的(高性能的解),前面会讲如何定制优化,先看性能比照。 独自200个子弹碰撞特效,每个有6个发射器,所以一共1200粒子反射器,但来回切换激活 同时只显示50%左右(前面按每帧600个粒子更新来算)。Unity CPU版是373FPS,本计划是2461FPS。如果用上个计划的那个GitHub Demo之间做这种,会发现只有100多帧,负优化。所以我没有拿那个源码用,而是本人从新设计了一套合乎具体我的项目的计划。 很多发射器实例的模式下性能比照:Unity CPU粒子(上)vs 本计划GPU粒子(下) 这是因为单个简单粒子模式是每个粒子发射器都创立一个含有粒子数据的Buff,每帧通过Dispatch ComputeShader更新这些粒子,也就是说,这样须要600次Dispatch,性能天然就差了。 所以第一步改良就是申请一个专用的大Buff来寄存以后激活的所有发射器的粒子数据。对于这种数据组织个别有2种模式:一种是间接寻址,一种是每个粒子发射器定长数组占用,而后通过Offset获取本人在Buffer内的数据。 这里采纳第二种,每种发射器最多同时存在32个粒子实例,这样能够满足大部分战斗中重复呈现的大量及时性特效。然而咱们下面说Particles是依据粒子创立死亡保护的对象池,数据是无序的。过后是同一个粒子发射器,一次DrawIndirect,所以不须要在意程序。但当初这个数据里有不同的发射器创立的粒子,渲染时也须要拜访不同的Index来获取对应数据。所以须要一个RWStructuredBuffer<uint> particlesIndexer;来记录每个发射器,蕴含的粒子在Particles数组中的Index。每个发射器占32位元素,同样渲染的时候,须要用另一个RWStructuredBuffer<uint> emitterCounter;,这个变量就是用在 DrawMeshInstancedIndirect(Mesh mesh, int submeshIndex, Material material, Bounds bounds, ComputeBuffer bufferWithArgs, int argsOffset); 这个API里的bufferWithArgs,配合前面argsOffset就能实现每个发射器不同的偏移了。 更新函数中,是这样把以后帧须要渲染的活着的粒子写入这2个Buffer的。 这样尽管每帧对粒子的Update在一次Dispatch后就执行完了,但渲染的时候,每个发射器独自执行DrawCall还是会性能很差。从Nsight工具能够看到十分恐怖的切换Shader次数,工夫很快是因为我是3080显卡,在一般显卡中这个性能是不具备事实可用性的。 每个粒子发射器一次DrawCall的GPU切换状况 四、半透明排序与合批渲染这是整个技术的关键所在也是最大的矛盾点,目前的DrawIndirect API每次调用都只能传一个AABB,引擎会依据这个AABB核心参加场景里其余对象进行排序,所以一次DrawIndirect绘制的所有粒子领有同一个程序,要么全副在某对象前,要么全副在某对象后渲染。当初每个粒子发射器独自一个DrawCall的状况下排序失常了(和Unity自带CPU粒子一样,逐发射器排序失常,不思考多个发射器之间逐粒子排序),但性能不行。 如果所有同材质发射器合并成一个DrawCall,那么排序又会不失常,因为它们两头呈现场景的半透明对象无奈交叉到这个DrawCall里。这也是为什么Unity的GPUInstance文章都是不拿半透明做例子,因为Opaque的排序不正确不影响画面成果,有Depth保障最终程序。通明材质是没有写Depth的,除非用了深度剥离技术。但这说远了,个别不会这样做的,所以如何合批是重点。 先看下Unity自身是如何合批粒子的,通过简略测试就能发现,如果ab是雷同的粒子发射器的不同实例,c是不同的粒子反射器,ab间隔凑近,而c在ab前或在ab后,那么只有2个DrawCall;如果c在ab两头就会有3个DrawCall。所以引擎是排序后才把相邻的又雷同的反射器合批渲染。但咱们渲染数据是在GPU,如果让CPU排序后要合批,则须要搬运Buffer内数据后合并到一起,很简单且要改引擎。如果在GPU内排序更不可能,GPU内只能粒子本人排序,无奈与场景上对象排序,这些对象都在CPU。所以通用引擎很难解决这个问题。 但做定制开发就轻松多了。首先察看下这些我的项目中的特效,同一种特效总是呈现在世界空间地位相机的中央,比方一个人开枪的特效总是在他枪口左近,而子弹的碰撞特效又总是在后方某个地位,不同的玩家是不同的,所以只有用玩家ID+粒子发射器Prefab品种做Key 来分组,Key雷同的一次性渲染就能够了。但这个性能很高,须要就义精确度,比方同一个人在玻璃后开几枪,再跑玻璃后面开几枪,那么先创立出的玻璃后的粒子也会一起渲染到玻璃下面。然而这问题不大,因为这些特效都是0.5秒之内就隐没的,不会长期停留在跑动和下次开枪时,但墙上的弹孔是个特例他们会停留30秒,所以这个计划不好。 另一个更好的办法是依据世界空间把1立方米内的雷同粒子发射器Prefab的所有粒子做一次Draw,因为地位很凑近所以它们按同一个地位参加排序根本是正确的,比较简单的是用long类型把这些信息计算到一起且不反复。假如这里场景范畴是正负5000米,全副合批发射器用这个治理 Dictionary<long,ParticleEmitterBatch> activeEmitterTypes;。 依据地位与发射器类型计算合批渲染的编号 分组发射器数据结构 最初介绍该计划的次要数据。因为改用这种合批,这里有和下面批改的中央。 按类型与空间合批渲染的更新形式 CreatingEmitter:发射器创立粒子时要传一份发射器数据让粒子初始化时能够晓得如何初始化,比方这个粒子life要从发射器的lifeMin与lifeMax之间随机取一个。Emitters:所有发射器类型数据,因为更新每个粒子时,怎么更新是来自这个数据比方 色彩随生命变动,是把开始色彩和最初色彩记录到发射器的,如果反复的记录到每个粒子那么很节约空间。Particles:所有粒子,外面有激活的有不激活的,渲染哪些是ParticlesIndexer的值来这里取。ParticlesIndexer:每种发射器记录占MAX_COUNT_PER_EMITTERKIND(我用2048)个元素,记录本人创立的粒子在Particles数组中的实在地位。EmitterCounter:用在DrawIndirect的粒子数量设置, Graphics.DrawMeshInstancedIndirect(quadMesh, 0, item.material, item.aabb, emitterCounter, (5 item.emitterBatchID) 4,item.mpb);freePool_a与freePool_c是同一份粒子索引可用池,在不同阶段散布做生产与增加,保护粒子实例的复用。该计划的次要数据 ...

July 5, 2023 · 1 min · jiezi

关于unity:Unity3D场景可见性

举荐:将NSDT场景编辑器退出你的3D工具链3D工具集:NSDT简石数字孪生场景可见性Unity 的场景可见性控件可用于在 Scene 视图中疾速暗藏和显示游戏对象,而无需更改它们在游戏中的可见性。 这可用于解决难以查看和抉择特定游戏对象的大型或简单场景。抉择的游戏对象以蓝色突出显示 更改场景可见性设置会在 Scene 视图中暗藏选定的游戏对象 应用可见性选项比停用游戏对象更平安,因为可见性选项仅影响 Scene 视图。 这意味着不会从渲染的场景中意外删除游戏对象或触发光照、遮挡和其余零碎的不必要烘焙作业。 Unity 将场景可见性设置保留到我的项目的 Library 文件夹中名为 SceneVisibilityState.asset 的文件中。 场景会主动从该文件读取可见性设置,并在可见性设置每次更改时进行自动更新。 这样,相干设置就可从一个会话继续到下一个会话。 因为 Unity 的源代码管制设置通常会疏忽 Library 文件夹,因而更改可见性设置应该不会造成源代码管制方面的抵触。 能够在层级视图中设置特定场景项的可见性,然而如果禁用场景范畴内的可见性设置,则标记为暗藏的项可能仍会显示在 Scene 视图中。 要更改此设置,能够在工具栏中切换场景可见性。场景可见性控件与场景拾取控件十分类似。 为游戏对象及其子对象设置场景可见性能够从 Hierarchy 窗口管制各个游戏对象在场景中的可见性。每个游戏对象都有场景可见性图标/开关 要切换场景可见性,请执行以下操作: 单击 Hierarchy 窗口中游戏对象的可见性图标,或者按 H,在暗藏和显示游戏对象及其子项之间进行切换。切换对象及其子项的可见性会影响所有子对象(从“指标”对象始终到层级视图的底部)。按住 Alt 并单击 Hierarchy 窗口中游戏对象的可见性图标,在仅暗藏和显示这个游戏对象之间进行切换。提醒:还能够单击场景可见性图标在暗藏和显示场景中标记为暗藏的项之间进行切换。 因为能够切换整个分支或单个游戏对象的可见性,因而最初游戏对象变为可见状态,但子项或父项为暗藏状态。 为了帮忙跟踪产生的状况,可见性图标会扭转以批示每个游戏对象的状态。一个游戏对象可见,然而其某些子项被暗藏。B游戏对象被暗藏,然而其某些子项可见。C游戏对象及其子项可见。 仅当鼠标悬停在游戏对象上时,才会显示此图标。D游戏对象及其子项被暗藏。 在 Hierarchy 窗口中进行的场景可见性更改是长久无效的。 每当在 Scene 视图中敞开和再次开启场景可见性、敞开而后从新关上场景或进行其余操作时,Unity 都会从新利用这些更改。 关上和敞开场景可见性场景视图视图选项叠加工具栏中的场景可见性开关显示或暗藏场景中的游戏对象。单击它以关上和敞开场景可见性。视图选项叠加 工具条中的场景可见性图标 敞开场景可见性实质上会使 Hierarchy 窗口中设置的场景可见性设置放弃静默状态,但不会删除或更改这些设置。 所有暗藏的游戏对象都是临时可见。 从新开启场景可见性将会从新利用在 Hierarchy 窗口中设置的可见性设置。 隔离选定的游戏对象Isolation 视图会临时笼罩场景可见性设置,以便仅显示所选的游戏对象,而其余所有对象被暗藏。Isolation 视图会笼罩场景可见性设置,因而只有所选对象及其子项 (A) 可见。单击 Exit 按钮 (B) 会复原以前的场景可见性设置。 要进入 Isolation 视图,请执行以下操作: 按下 Shift + H。这样可隔离所有选定的游戏对象及其子项。隔离暗藏的游戏对象会使这些游戏对象可见,直到退出 Isolation 视图。在 Isolation 视图中时,能够持续更改场景可见性设置,然而所做的任何更改都会在退出时失落。要退出 Isolation 视图,请执行以下操作: ...

July 4, 2023 · 1 min · jiezi

关于unity:图形引擎实战Unity-Shader变体管理流程

一、什么是Shader变体治理想要答复这个问题,要看看什么是Shader变体。 1. 变体咱们用ShaderLab编写Unity中的Shader,当咱们须要让Shader同时满足多个需要,例如,这个是否反对暗影,此时就须要加Keyword(关键字),例如在代码中#pragma multi_compile SHADOW_ON SHADOW_OFF,对逻辑上有差别的中央用#ifdef SHADOW_ON或#if defined(SHADOW_ON)辨别,#if defined()的益处是能够有多个条件,用与、或逻辑运算连接起来: Light mainLight = GetMainLight();float shadowAtten = 1;#ifdef SHADOW_ON shadowAtten = CalculateShadow(shadowCoord);#endiffloat3 color = albedo * max(0, dot(mainLight.direction, normalWS)) * shadowAtten;而后对须要的材质进行material.EnableKeyword("SHADOW_ON")和material.DisableKeyword("SHADOW_ON")开关关键字,或者用Shader.EnableKeyword("SHADOW_ON")对全场景蕴含这一keyword的物体进行设置。 上述情况是开关的设置,还有设置配置的状况。例如,我心愿高配光照计算用PBR基于物理的光照计算形式,而低配用Blinn-Phong,其余计算例如暗影、雾效完全一致,也能够将光照计算用变体的形式分隔。 如果是Shader编写的老手,可能有两个问题: 1. 我不能间接传递个变量到Shader里,用if实时判断吗?答:不能够,简略来说,因为GPU程序须要高度并行,很多状况下,Shader中的分支判断须要将if else两个分支都计算一遍,如果你的两个需要都有不短的代码,这样的开销太大且不合理。 2. 我不能够间接将Shader复制一份进去改吗?答:不是很好,例如你当初复制一份Shader进去,还须要对应脚本去找到须要替换的Shader而后替换。更重要的是,当你的Shader同时蕴含很多须要切换的成果:暗影、雾效、光照计算、附加光源、溶解、反射等等,总不能有一个需要就Shader*2是吧。 #pragma multi_compile FOG_OFF FOG_ON#pragma multi_compile ADDLIGHT_OFF ADDLIGHT_ON#pragma multi_compile REFLECT_OFF REFLECT_ON//something keyword ...这种写法属于比拟死亡的写法,别在意,前面天然会说出各种写法中不好的中央并提出回避倡议。而对于以后材质,就会利用上述的关键字进行排列组合,例如一个“不心愿承受暗影,心愿有雾,须要附加光源,不带反射”,失去的Keyword组合就是:SHADOW_OFF FOG_ON ADDLIGHT_ON REFLECT_OFF,这个Keyword组合就是一个变体。对于下面这个例子,能够失去2的4次方16个变体。 咱们晓得了什么是变体,再来答复为什么要变体治理。 能够发现上述例子中,每多一条都会乘2,实际上一列Keyword申明能够不止两个,申明三个、甚至更多也是可能的。 不管怎么说,随着#pragma multi_compile的减少,变体数量会指数增长。这样会带来什么问题呢? 这时候须要理解下Shader到底是什么。 2. ShaderShaderLab其实不是很底层的货色,它封装了图形API的Shader,以及一堆渲染命令。对于图形API,Shader是GPU的程序,不同API上传Shader略有区别,例如OpenGL: GLuint vertex_shader;GLchar * vertex_shader_source[];//glsl源码//创立并将源码传递给GPUvertex_shader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertex_shader, 1, vertex_shader_source, NULL);//编译glCompileShader(vertex_shader);//绑定glAttachShader(program, vertex_shader);DX12/Vulkan的编译形式有很多,能够提前编译成二进制/两头语言的DXBC/SPIR-V,也能够用HLSL/GLSL实时生成DXBC/SPIR-V传递给GPU,例如DX12应用D3DCompileFromFile实时编译HLSL到DXBC: ComPtr<ID3DBlob> byteCode = nullptr;//二进制DXBCD3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE, entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, nullptr);对于当初的咱们来说次要关注前两个参数,第一个是读取的文件名,第二个是D3D_SHADER_MACRO的数组: ...

May 24, 2023 · 3 min · jiezi

关于unity:FEGAMEUnity入门笔记脚本

官网文档 unity.cn/中文文档本地门路 Editor/Data/Documentation/en/Manual/index.html设置脚本默认打开方式:  Edit/Preferences/External Tools/ Exteral Script Editor Helloworld增加一个脚本 右键 Create/C# Script文件名必须标准, 文件名即类名 如SimpleLogic双击在vs中关上脚本,查看类名与文件名是否统一在vs中编辑代码 增加一行打印输出void Start(){ Debug.Log("Hello World")}其中Debug是Unity API中的一个工具类Ctrl+S 保留代码,敞开vs编译 保留会主动编译挂载脚本 点Add Component,选Scripts/Simple Logic或者,间接将脚本拖到Inspector窗口的最下方运行游戏 点play按钮,运行帧Frame 一个游戏帧FrameRate 帧率|刷新率FPS Frames Per Second 每秒更新多少帧Update() 帧更新此办法会被游戏引擎定时调用,以更新游戏的状态 帧更新Time.time 游戏工夫Time.deltaTime 间隔上次更新的时间差显然,帧率是不固定的,Unity会尽量较快地更新Unity不反对固定的帧率,但能够设定一个近似帧率 Application.targetFameRate=60其中,批示Unity尽量以FPS=60的帧率更新游戏 物体根本属性this 以后脚本组件this.gameObject 以后物体this.gameObjecet.name 以后物体的名字this.gameObject.transform 以后物体下的transform组件为了简化书写 也可写作this.transform 成果雷同 GameObject obj = this.gameObject;string name = obj.name;Transform tr = obj.transform;API中的大部分类型,来自于UnityEngineusing UnityEngine; 基类 MonoBehaviour游戏物体 GameObject变换组件 Transform三维向量 Vector3 坐标物体的坐标 transform.position 世界坐标transform.localPosition 本地坐标个别用localPosition 与Inspector窗口中的统一 Vector3 pos = tr.position;Vector3 pos1 = tr.localPosition;tr.localPosition = new Vector3(1.2f, 1.2f, 1.2f);挪动float speed = 3;float distance=speed * Time.deltaTime;Vector3 pos=this.transform.localPosotion;pos.x+=distance;this.transform.localPosotion=pos;相对运动个别应用trasform.Translate(dx,dy,dz,space)实现相对运动 ...

April 16, 2023 · 2 min · jiezi

关于unity:FEGAMEUnity入门笔记操作

来自教程 https://www.bilibili.com/video/BV1TZ4y1o76s 环境配置下载 装置Unity Hub激活集体许可证从Hub下载 2021.3.16f1 装置 Editor 创立我的项目Projects > New project >3D>Create project菜单 GameObject | 3D Object | Cube 增加立方体 窗口布局 复原布局调整字体大小 场景创立我的项目时,默认创立了一个场景:SampleScene Project窗口 Assets>Scenes>Sample SceneHierarchy窗口 Sample Scene默认场景中,只有一个 主摄像机 和 平行光 游戏物体GameObject,即游戏汇总任何角色、道具、修建等 在 Hierarchy窗口 右键 / 3D Object / Cube 增加一个立方体 几个操作: 选中一个物体,以橙色轮廓显示右键 / rename ,起名右键 / Delete ,删除物体在Inspector窗口,察看物体坐标3D视图导航器 Gizmo 示意世界坐标的方向 导航器操作 按Shift ,点击两头的小方块,复原方向点Y轴,顶视图点X轴,右视图点Z轴,前视图栅格 Grid 示意xz坐标立体天空盒 Skybox 示意游戏世界的背景 视图操作旋转视图 alt + 左键缩放视图 鼠标滚轮alt+右键,精密缩放平移视图 鼠标中键世界坐标系 3D游戏世界坐标系(左手系)物体的坐标 Inspector窗口 position x y z ...

April 16, 2023 · 2 min · jiezi

关于unity:Unity客户端开发工程师的进阶之路

UWA技能成长零碎是UWA依据学员的职业倒退指标,提供技能学习的举荐门路,再将所需学习内容按难易等多维度,设计分成多个学习阶段,能够循序渐进地进行学习。 每个阶段学员实现学习工作后岂但能够取得技能的晋升,还将取得UWA社区相应的积分处分(积分可兑换礼品和优惠券哦)。 进入技能成长体系,指标抉择高级客户端开发工程师(Unity)即可开始学习,初、中级的学习门路咱们也会尽快上线,以供更多的开发者们学习成长。 高级客户端开发工程师(Unity)指标共设置了7个学习工作,适宜有肯定Unity开发教训,想要进一步学习Unity优化技能的开发者,实现学习后,能够把握游戏性能瓶颈定位的办法和常见的CPU、GPU、内存相干的性能优化办法。 Mission 1:定位性能瓶颈 学会应用常见的性能排查工具可能定位性能瓶颈来自CPU、GPU还是内存难度:1颗星预计学习时长:3小时 目录:第一章:性能规范1.1 耗时推荐值1.2 内存推荐值1.3 渲染模块推荐值 第二章:性能排查工具2.1 Unity Profiler2.2 Unity FrameDebugger2.3 Mali Offline Compiler2.4 XCode FrameDebugger2.5 GOT Online 戳此进入>>https://community.uwa4d.com/objective Mission 2:内存占用 把握联合我的项目加载模块耗时和内存占用的理论状况制订正当策略的能力把握各种次要资源内存的基础知识和常见优化伎俩理解Mono堆内存的常见问题和排查办法难度:1颗星预计学习时长:3小时 目录:第一章:策略导致的内存问题1.1 资源冗余1.2 代码生成的资源1.3 加载和缓存策略 第二章:Gfx内存2.1 纹理资源2.2 网格资源2.3 Shader资源 第三章:Reserved Unity3.1 Render Texture资源3.2 动画资源3.3 音频资源3.4 字体资源3.5 粒子系统资源 第四章:托管堆内存4.1 Mono堆内存具体调配 戳此进入>>https://community.uwa4d.com/objective Mission 3:动画模块 理解Unity的两种动画零碎可能定位动画耗时的次要瓶颈把握Mecanim动画常见的优化计划难度:1颗星预计学习时长:3小时 目录:第一章:Mecanim动画1.1 Active Animator数量1.2 Optimize Game Objects1.3 Apply Root Motion1.4 Compute Skinning1.5 Animator.Initialize 第二章:Legacy动画2.1 Animation.Sample 戳此进入>>https://community.uwa4d.com/objective Mission 4:物理模块 把握Contacts、物理更新次数、Rigidbody、 Collider等物理相干的基本概念理解物理更新次数的影响因素和限度办法把握常见的代替物理模仿的优化计划把握如何应用Profiler剖析和确认Contacts的数量难度:1颗星预计学习时长:3小时 目录:第一章:物理模块耗时1.1 Collision的产生1.2 Trigger的代替计划1.3 Physics Layer的设置1.4 物理更新次数1.5 Auto Simulation1.6 RaycastCommand ...

March 31, 2023 · 1 min · jiezi

关于unity:Unity开发实战经验分享

本课程次要记录了笔者我的项目初期碰到的并值得一说的理论业务问题,涵盖了比拟多客户端框架设计的内容,以及无效晋升开发者编程体验的内容。  次要包含以下内容以及一些其余配套的小工具。 Luban配表工具的应用介绍ECS设计下的加载治理设计我的项目资源规范化设计本地化组件C# Task使用指南设计技能零碎Unity Android多渠道治理 作者L:杭州某游戏公司客户端主程从事游戏行业六年无余,目前次要在公司负责通用底层框架设计,及一款手游我的项目的客户端主程 目录1| Luban Excel配表工具应用举荐及总结2| 基于ECS设计下的加载治理3| 对资源的规范化咱们能做什么4| 如何设计本地化组件5| C# Task指南6| 如何设计技能零碎7| Unity Andorid多渠道治理附录1| 一些对于代码积攒的记录附录2| 如何设计角色属性组件附录3| CliToolkit工具附录4| ET Entity Tree 工具附录5| 内网Package治理 本篇转载自《Unity开发实战经验分享》的第1节。  简直每个游戏的制作过程中都少不了和配置打交道的需要,有的是用Unity自带的ScriptObject进行存储,或者更多的是应用Excel等表格工具,二次导出配置文件等。  每种计划见仁见智,按照不同的应用场景各有优劣。 一般来说数据的输出都是由策动来实现的,而大部分策动十分偏向于应用Excel作为日常配置应用的工具,尤其是在须要批量拉表的场景下,其余的计划在这个场景下与Excel简直没有任何可比性。 相干链接Luban 仓库地址Luban 官网示例Luban 官网文档Luban 简略示例Luban 简略文档Luban Unity GUI 工具 为什么应用Luban 如果我的项目中应用Excel作为配表的载体,大部分都会抉择应用相干的导表工具,可能是我的项目本人开发,也可能是应用一些现成的工具,比方Git上tabtoy、excel2json等相干工具。  然而下面这些,包含大部分本人开发的导表工具,或多或少都会存在一些致命的限度,或者不够通用,亦或者不够灵便等等的问题。  一个具备普适性的配表工具须要兼容的场景十分多,各种语言、自定义生成模板、数据反倒、数据有效性验证等等。 而Luban是目前惟一一个市面上有资格成为行业配表规范的工具,将来也很难会有同类产品能够超过。 外围性能介绍 数据有效性验证 这里的数据有效性不是指bool的格子填了一个int。 有效性验证这个问题之前让我十分头大,经常出现策动跑过来找你说:“我这里程序有 bug,你检查一下”,而后花了半天工夫查到了因为配表中某一行的id或者要害数据填写不非法。  一次两次还能够,次数多了不免心态不好,尤其当人员产生变动,新来的人无奈齐全了解每个值的意义,就很容易放飞自我,最终就是事变,而这些问题是能够从源头无效切断的。  如果你我的项目中配表的内容存在某个中央须要相干人员记住应该怎么配,而没有相干主动校验,那么这里假以时日肯定会出问题。 工具反对的校验器如下:当然,一个游戏的开发如果须要残缺校验所有配置的合法性,上述这些校验器是无奈齐全满足的,或者说这些简单场景不应该由Luban来解决,所以针对这些场景,在官网示例中有一个CfgValidator来解决这种问题。 生成模板一个合格的配表工具该当兼容自定义生成代码性能,而Luban这里应用的是scriban的计划。  当你有代码定制需要时,99%的场景不须要改代码生成工具的源码即可实现定制需要。 在我整顿的Luban 简略文档这个文档外面蕴含了模板具体生成的介绍,能够减速你了解如何自定义模板。在Luban现有的设计下,简直能够兼容任何场景(包含老我的项目的迁徙)。  简单类型的填写在这个仓库中,Luban 简略示例的多态示例中能够很直观地看到继承相干的简单类型该当如何在Excel中开展和填写。 配表反对继承在很多游戏开发场景中十分十分受用,比方游戏中的道具、配备和英雄类的定义肯定存在共用数据结构和特化的数据结构等,如果没有继承这里的代码会十分难看,而且配表填写的内容也会成为劫难。 Luban反对任意简单数据结构的嵌套,只有在代码中能定义进去,Luban就能解析,然而在理论应用中,并不举荐应用非常复杂的数据结构,这样会给策动带来额定的填表累赘,以及局部场景下的代码生成的额定工作量。  数据及定义过滤一些定义只须要在客户端应用,或者只会在服务端应用,须要在生成时进行动静剔除,当然这个性能,个别的配表工具也都反对,但Luban额定思考到了一些场景,比方这一条数据须要长期正文或者仅在测试环境下应用。 这里的 test就十分有用,咱们会独自配置一套test数据,用于游戏中的外围逻辑验证,及测试用例的辅助配表,而这些数据不会呈现在正式环境下。 数据反倒有时候会有这种场景,我的项目一些配置须要在Unity等游戏引擎中实现,可能是一些技能的配置等,这个时候就可能须要反倒数据。  如果是一些老的我的项目须要迁徙,也是一个十分适合的场景。 本地化只有是配表工具,就肯定绕不开本地化这件事,Luban同样也提供了本地化的解决方案。  这里值得一提的是,Luban会将所有未退出翻译表的key独自输入到指定的文件中,不便查看。 其余Luban反对的序列化格局、语言和场景十分多,这里不一一介绍了,仅对我目前应用中碰到的外围性能进行介绍。 流程举荐 上面提到的内容在Luban 简略示例仓库中都能够找到相干代码。  导出脚本抉择集体比拟举荐实用sh作为我的项目通用导表工具,Windows须要配置sh文件默认应用git bash作为打开方式即可。 这里次要思考的是平台兼容性问题,比方开发环境中可能有人应用的是Mac也可能是Windows,然而在部署时大部分都是Linux服务。如果每个平台独自保护一份脚本,加上不同环境和应用场景,这里对应的文件数量就比拟离谱了。 test、dev、release倡议我的项目依照这种形式来划调配表: test 仅在测试用例、Editor 下应用dev 仅在开发环境下应用release 仅在正式环境下应用 auto_validation首先,咱们并不心愿策动推送一个曾经被主动流程检测出谬误的提交,此时须要对Git进行Hook,外围就是提交时本地查看一遍,如果有谬误,禁止本次commit,将这种低级谬误扼杀在源头。  watch个别可能会有这种须要对配置热重载的性能,应用watch,配合本人我的项目中的热重载就能够做到这边保留Excel,Unity不须要重开游戏就能够间接加载到新配置。 以上就是《Unity开发实战经验分享》的第1节,此篇文章比拟适宜从事游戏行业的开发人员、对Unity开发感兴趣的同学以及心愿晋升底层设计能力,解决理论业务痛点的读者。  读完全篇后你会深刻了解如何实现理论业务需要,晋升底层设计能力以及局部文章的配套Demo。    

March 17, 2023 · 1 min · jiezi

关于unity:Unity实现水墨山水画风格

本文介绍了一些3D渲染技术的组合思路,并用来对中国传统绘画中的宋代水墨山水画格调进行模拟实现。文章思路同样能够用于油画、水彩、素描以及漫画等其余美术格调方向的渲染。 作者Shadow,独立游戏开发者,作品《无极道人》。 目录1|我的项目起因2|山水画简介3|宋代水墨山水画简介4|在Unity引擎中进行实现5|其余注意事项 本篇转载自《Unity实现水墨山水画格调》的第4.1节。 4|在Unity引擎中进行实现4.1 渲染应用的相干技术阐明这里给出相干技术的中英文名称和简介不便查找。 有些技术会有多个不同的中文翻译名称。 Non-Photorealistic Rendering(NPR) 非真实感绘制 本我的项目的美术格调属于NPR的一种。 非真实感绘制(NPR)是计算及图形学的一类,次要模仿艺术式的绘制格调,也用于法阵新的绘制格调。和传统的谋求真实感的计算机图形学不同,NPR受到油画、素描、技术图纸和动画卡通的影响。—— Wikipedia Borderlands(无主之地)一款NPR美术格调十分闻名的游戏 (图片起源网络) Vertex Extrusion Outlines 顶点外扩描边 顶点外扩描边为游戏渲染中罕用的几种实现描边(Outlines)的办法之一。这里次要是用沿法线方向对顶点进行外扩。想要失去比拟好的成果,模型的顶点数量最好不要太低,同时整个模型须要全副软边(Maya里称为软边,3ds max里称为同一润滑组,在不同软件里可能有不同叫法)。 如果描边成果不好,能够尝试减少边缘的顶点数量并查看是否都是软边。如果遇到肯定要硬边的状况,则能够应用沿顶点色方向扩张,行将顶点扩张的方向记录在顶点色里。这可能须要额定的制作流程关系和相干工具。立方体的布线模式别离为硬边、软边、加辅助线和倒角 Rim Light 边缘光 边缘光是一个常见的游戏渲染成果,依据摄像机视角方向和物体法线的夹角来计算一个值以进行照亮、描边等后续操作。 MatCap (Material Capture) MatCap是Material Capture的缩写,是一种用作图像纹理的图像,能够在3D应用程序中伪造包含照明和反射在内的整个材质。容许通过简略地预渲染一个3D球体对象的2D图片来创立外表材质和照明环境,而后在Shader中将原始3D球形法线映射到渲染外表的法线,从而将预渲染的球形图像的光影和材质利用于指标外表,并使其看起来像其原始渲染环境中心愿外表呈现的样子。  2D MatCap素材纹理 Lambert 兰伯特光照模型 兰伯特是渲染中常见的一个光照模型。在这个我的项目中次要是用来和其余技术一起组合出纹理的渲染区域。 Tri-planar projection 三方向纹理投影 Tri-planar projection是将多个立体纹理组合在一起并混合以笼罩整个3D网格的纹理投影,能够创立无缝的乐音和纹理贴图。通常应用模型的法线方向作为投影和混合根据。能够是世界坐标也能够是部分坐标。 Tilemap 四方间断贴图 四方间断贴图也称瓦片贴图,特点是相邻贴图上下边和左右边的纹理是间断的没有接缝。通常用来绘制大片雷同区域。 图片起源网络 Noise map 乐音纹理 Noise map一种用于在计算机生成的外表上产生有天然外观的纹理的技术,用于游戏电影等视觉效果。罕用的有Perlin Noise等生成算法。Noise map用来让计算机图形渲染时可能更好地体现视觉效果中天然景象的复杂性。 3D Noise map 3D乐音纹理 3D Noise map为三维空间下的乐音纹理,通常应用模型的顶点作为3D uv应用,不受模型自身uv的影响。 以上就是《Unity实现水墨山水画格调》的第4.1节,此篇文章比拟适宜在非真实感绘制(NPR)方向的研究者。 读完全篇后你会理解3D渲染技术的组合思路和在Unity中如何实现水墨山水画格调。

February 17, 2023 · 1 min · jiezi

关于unity:URP管线全解析

将来,URP管线将取代内置渲染管线,成为Unity中的默认渲染管线。Unity历经几年的专一开发,URP技术现已非常牢靠,能够投入生产。 本教程介绍了内置渲染管线到URP管线的迁徙,应用具体案例联合具体代码,详细分析如何在具体URP管线的我的项目中做渲染,从光照到暗影(通过批改源码来反对多光源暗影),再到后处理。渲染案例剖析之后又深刻性能优化局部具体介绍SRP Batcher、GPU Instancing如何在具体我的项目中做优化以及它们是如何给我的项目带来性能晋升的。 可编程渲染管线概念探索SRP Batcher(失效,生效条件以及性能优化)URP 和 内置渲染管线的光照URP管线下的成果实现(应用Renderer Feature)URP管线下的GPU Instancing(失效,生效条件以及和SRP Batcher的关系)作者于洋,Unity技术专家、引擎组组长。曾就任于人人网、Kabam、竞技世界。从事游戏开发十余年,经验了从Flash到Unity的游戏开发过程,长期从事游戏渲染和性能优化相干工作,对PBR、云、雾、地形、URP管线等有深入研究,曾参加过《Legacy of Zeus》、《荒岛求生》、《mythwar puzzle》等游戏的渲染和性能优化工作,乐于分享渲染和优化的相干技术。 目录1|可编程渲染管线简介2|如何将Built-in管线降级为URP管线3|探索SRP Batcher4|URP和内置渲染管线的光照5|URP管线下的成果实现6|URP管线下应用GPU Instancing(附demo下载) 本篇转载自《URP管线全解析》的第1节。 1|可编程渲染管线简介1.1 SRP是什么?Unity的渲染管线能够分为Built-in内置渲染管线和SRP可编程渲染管线,其中SRP可编程渲染管线又可分为HDRP和URP,如下图所示(图片来自网络): SRP全称为Scriptable Render Pipeline(即可编程渲染管线/脚本化渲染管线),是Unity提供的新渲染零碎,是能够在Unity通过C#脚本调用一系列API配置和执行渲染命令的形式来实现的渲染流程,SRP将这些命令传递给Unity底层图形体系结构,而后再将指令发送给图形API。 简略来说,就是咱们能够用SRP的API来创立自定义的渲染管线,用来调整渲染流程或批改或减少性能。 它次要把渲染管线拆分成二层:一层是比拟底层的渲染API层,像OpenGL,D3D等相干的都封装起来;另一层是渲染管线下层,下层代码应用C#来编写。在C#这层不须要关注底层在不同平台上渲染API的差异,也不须要关注具体如何做一个DrawCall。 1.2 HDRP是什么?它的全称为High Definition Render Pipeline(高清晰度渲染管线),是Unity官网基于SRP提供的模板,更多是针对高端设施,如游戏主机和高端台式机,同时更关注于真实感图形和渲染。 该管线仅与以下平台兼容: Windows和Windows Store,带有DirectX 11或DirectX 12和Shader Model 5.0古代游戏机(Sony PS4和Microsoft Xbox One)应用金属图形的MacOS(最低版本10.13)具备Vulkan的Linux和Windows平台1.3 URP是什么?它的全称为Universal Render Pipeline(通用渲染管线),也是Unity官网基于SRP提供的模板,它的前身是LWRP(Lightweight RP即轻量级渲染管线),在2019.3版本开始改名为URP,它涵盖了范畴宽泛的不同平台,是针对跨平台开发而构建的,性能比内置管线要好,另外能够进行自定义、实现不同格调的渲染,通用渲染管线将来将成为在Unity中进行渲染的根底 。 平台范畴:能够在Unity以后反对的任何平台上应用。 以上根本梳理了一些基本概念。 1.4 为什么诞生SRP?内置渲染管线的缺点定制性差:Unity的内置渲染管线,渲染管线全副写在引擎的源码里。大家基本上不能改变,当然大部分开发者是不会去改源码,所以过来的管线对开发者来说,很难进行定制。想要有非凡订制的Unity引擎,须要高额跟Unity购买服务,Unity的工作人员会依照客户的需要去批改管线,为客户量身定做一款引擎。 钻研过Unity源代码的同学应该会晓得,其代码极其臃肿,成果效率无奈做到最佳:内置渲染管线在一个渲染管线外面反对所有的平台,包含十分高端的PC平台,也包含十分低端的平台,很老的手机也要反对,所以代码越来越臃肿,很难做到使效率和成果都做到最佳。 为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。决定在C++端保留一个十分小的渲染内核,让C#端能够通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面能够保障C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就能够在理论我的项目中调整。(摘自网络)以上就是《URP管线全解析》的第1节,此篇文章比拟适宜从事游戏开发的Unity客户端开发人员、心愿晋升渲染和性能优化能力的人以及对可编程渲染管线感兴趣的读者。 读完全篇后你会深刻理解URP管线的具体案例实操以及性能优化相干剖析;并把握如何将内置管线晋升为可编程渲染管线;如何在URP管线中做光照以及通过批改源码来反对多光源暗影及如何应用SRP Batcher以及 GPU Instancing等技能。

January 20, 2023 · 1 min · jiezi

关于unity:Unity干货教程如何实现Unity和Android原生互相调用

Unity是一个跨平台开发工具,公布到挪动平台也是大部分Unity开发者的必备技能。而因为Unity跨平台的个性,总会遇到在挪动平台的技术细节反对不够,或者须要在调用其余原生插件的状况。这里咱们说一下如何在Android Studio中创立一个可供Unity调用的aar插件,以实现Unity和Android原生相互调用的目标。 开发工具Android Studio,Unity 实现步骤关上AndroidStudio,创立一个新的工程,抉择NoActivity 2.抉择存储门路。填写我的项目名,我的项目名无所谓,后续用不到;填写包名,包名后续须要在援用的时候用到。 3.创立好之后抉择Android视图,在app上点击右键 new—Module,创立一个新的Module,抉择Android Library。这个就是后续用来导出的库。 4.进入刚创立的Library,在下图的文件夹下右键,创立JavaClass,这个java类就是后续在Unity中援用的类。 5.接下来是一个Demo场景:在Unity中调用Android,弹出Tosat。并且回调Unity的指定办法: ![图片](https://images.ctfassets.net/...) 导出AAR:选中刚刚创立好的Library,抉择Build— Make Module,将这个library打包为aar7.导入Unity:将刚刚打包出的AAR文件放入Plugins-- Android文件夹中 8.Unity调用示例: 小结 Unity和Android代码相互调用是一种十分常见的开发方式,在补救Unity性能的同时,也给原生SDK的接入提供了更多的可能性。较新版本的Unity曾经能够反对间接导入java脚本文件而无需打包aar插件。 3DCAT实时云渲染为挪动利用提供新的可能随着3D可交互内容的逐渐演进,客户端体积变得越来越宏大,对挪动设施的硬件性能要求也越来越高,这些都成为了用户的痛点,并为新用户减少了极大的妨碍。 云渲染的呈现解决了这个痛点,开发者能够将宏大的3D资源寄存于云端,用户只须要极少的老本便可疾速体验到所需3D内容,为企业和用户清理了重重妨碍。运行在3DCAT实时渲染云的利用内容可灵便嵌入任意平台,反对海量用户平安拜访,主动负载平衡和伸缩扩容。 如有需要,欢送随时与咱们分割! 本文《【Unity干货教程】如何实现Unity和Android原生相互调用?》内容由3DCAT实时云渲染解决方案提供商整顿公布,如需转载,请注明出处及链接:https://www.3dcat.live/share/...

January 13, 2023 · 1 min · jiezi

关于unity:Unity渲染一文看懂Unity通用渲染管线URP介绍

一、Unity通用渲染管线(URP)Unity 的渲染管线蕴含内置渲染管线、SRP、URP和HDRP。自从Unity2019.3开始,Unity将轻量级渲染管线批改为了通用渲染管线,这是一种疾速、可扩大的渲染管线,反对所有的挪动设施,实用于 2D、3D、虚拟现实 (VR) 和加强事实 (AR) 我的项目。 二、Unity URP渲染管线的3大劣势1. 光照处理 URP是单Pass前向渲染管线,而内置管线是多Pass,可选前向渲染管线和提早渲染管线。所谓的前向渲染,就是在渲染物体受点光光照的时候,别离对每个点光对该物体产生的影响进行计算,最初将所有光的渲染后果相加失去最终物体的色彩。内置渲染管线是多pass的,简略来讲,当场景中有多个光源时,会产生多个pass来渲染光照,在挪动端如果有多个光源的话,将会产生微小的性能耗费。而URP应用单个pass,这个物体收到的所有光照都只会产生一个pass,所有的光源解决都能够在一个DrawCall中实现,对于性能的耗费就小得多。 2. SRP Bacher 批处理是性能优化的一大法宝,传统内置渲染管线中的批处理有着诸多限度。特地是动静批处理,这在SRP Batcher 中失去了彻底优化。SRP Batcher 是一个底层渲染优化机制,可通过许多应用同一着色器变体的材质来放慢场景中的 CPU 渲染速度。即便是不同的材质球,只有是用一个着色器变体的物体都能够批处理到一起。 3. 可扩展性 利用通用渲染管线提供的接口,技术美术能够轻松实现以往很难实现的成果,例如下图的陆地,就是通过URP来实现的。像这样的例子还有很多,大家能够在Unity资源商店搜寻相干资源。 三、Unity如何降级到URP渲染管线1. 装置URP组件 Window->Package Manger 下载并导入Universal RP 2. 创立URP PipelineAsset(ForwardRender)文件 3.指定我的项目应用URP渲染管线,关上Editor->Project Settting窗口 4. 降级材质零碎 四、3DCAT实时云渲染为实时渲染提供坚实基础实时渲染和云渲染是渲染技术演进的必然方向。3DCAT实时云渲染将基于游戏开发引擎(Unity、UE4等)制作的实时渲染内容进行云端计算渲染,并通过网络及串流技术,实时推送到终端。满足宽广用户随时随地跨终端、可交互、超高清、沉迷式的拜访需要。 运行在3DCAT实时渲染云的利用内容,无需下载安装,可灵便嵌入任意平台,反对海量用户平安拜访,主动负载平衡和伸缩扩容。3DCAT实时云渲染将为数字化内容的倒退提供坚实基础,将来的数字化内容将以云渲染+便携终端为根底,3DCAT实时云渲染也将为离线渲染向实时渲染的转换提供强劲能源。 点击注册,收费体验实时云渲染!https://app.3dcat.live/register 本文《【Unity渲染】一文看懂!Unity通用渲染管线URP介绍》内容由3DCAT实时云渲染解决方案提供商整顿公布,如需转载,请注明出处及链接:https://www.3dcat.live/share/...

January 13, 2023 · 1 min · jiezi

关于unity:架构中实时引擎与离线渲染的主要优势

对于任何从事修建和AEC行业的人来说,评估应用像Unity这样的实时引擎与传统离线渲染引擎相比的所有劣势都是必不可少的。 思考应用Unity的修建或室内设计行业的可视化,以及通过应用实时引擎进行可视化,它通常与虚拟现实体验相关联,而同时当咱们议论静止图像和电影视频时,咱们通常认为引擎是V射线或电晕渲染。 尽管这可能是修建可视化行业以及修建和室内设计工作室的一种做法,但这并不意味着它总是最好的办法。 与离线渲染引擎相比,Unity有哪些劣势?第一个是渲染工夫 以脱机渲染引擎映像为例,在一台电脑上渲染最多可能须要 8 小时。很显著,作为Unity的实时引擎,图像渲染速度十分快。 如果单个图像的几个小时甚至一天不是大问题,请尝试思考以全高清渲染真切的3分钟视频所需的工夫。 思考到每秒至多30帧,这意味着一个3分钟的视频由5400张图像组成。当初只需10分钟作为图像参考,咱们将有大概37天的工夫在一台电脑上实现视频渲染。 尽管这在方面看起来很显著,但重要的是要记住,实时办法能够带来微小的劣势,更高的分辨率,更短的渲染工夫,也实用于更“经典”的内容,如静止图像和视频,这些内容依然是当今的规范。 这就是为什么许多修建工作室,甚至渲染公司,都防止创立高分辨率的真切视频。然而,这不仅仅是工夫问题,也是金钱问题。 思考到没有人能够花一个月的工夫渲染视频,这意味着必须应用渲染农场或相似的硬件配置,尽管它缩小了计算工夫,但大大增加了老本。 尽管通过应用离线渲染(如 V-Ray、Corona 或 Octane)进行修建可视化的经典办法须要为客户须要的每个内容付费,但咱们的办法容许通过应用 Unity 以每秒 60 张图像的速度渲染超高清甚至5K 的视频,十分晦涩。此外,即便是静止渲染的老本也简直为零。 如果须要更改某些内容会怎么?在实时批改材质时,挪动或更换家具的速度十分快,并且能够疾速取得新的图像和视频,而无需破费大金额来渲染渲染在渲染农场中批改的内容。 总结了与Unity等实时引擎的渲染速度相干的主题,以实现真切的内容,让咱们持续探讨与离线渲染引擎相比应用实时引擎的另一个重要劣势。 现在,创立减少潜在客户参与度的内容至关重要。当然,也能够应用离线渲染引擎进行某种交互,例如创立360 Pano旅行,但毛病有几个,例如,您必须渲染须要数小时的微小图像,因而,用户可能挪动的动态点数量是无限的。 相同,应用Unity,能够增加任何类型的交互,并在客户和我的项目之间有更多的参加。这必定是一张可能的获胜卡。 在这里能够看到Unity外部可能的几种交互,例如尝试不同的地板资料,切换不同的日光,关上/敞开电视和灯光以及拜访BIM信息。 为何实时云渲染可能改革实时渲染体验?相比于传统的离线渲染和实时渲染,都极大依赖本地的终端算力,然而大家都晓得,物理定律决定了超强算力无奈在十分轻的终端上实现,所以这时候云渲染呈现了 由瑞云科技推出的3DCAT实时渲染云基于云计算理念,将XR利用部署在云端运行,云端资源进行图形数据的实时计算和输入,并把运行后果用“流(Streaming)”的形式推送到终端进行出现,终端用户可随时随地交互式拜访各种XR利用,无论何时何地,均可突破时空枷锁,畅享元宇宙的世界。 3DCAT把十分重的图形计算放到云端,云端服务器能够配置较高端的GPU设施,通过云端服务器进行计算,进行实时编码,编码后将其“流化”,通过网络,把计算结果实时推送至终端(例如手机、PC、平板和XR设施)。 用户在终端做出的任何交互动作(比方手指滑动屏幕、PC上点击了一下鼠标等)的信号也是通过网络实时地传输到云端服务器,云端服务器接管到信号再通过云端计算后将计算结果回传到终端,这个过程在网络好的状况下能够在100毫秒以内实现。 「重云端轻终端」的云渲染将更多的图形算力、存储等需要放到云端,终端对于图形算力、存储硬件配置要求大幅升高,为终端交互硬件轻量化、便携化提供了根底SaaS级反对,同时也是对终端硬件制作老本的优化,推动元宇宙生产级硬件的疾速浸透,进一步减速元宇宙的遍及。 本文《架构中实时引擎与离线渲染的次要劣势》内容由__3DCAT实时渲染解决方案提供商__整顿公布,如需转载,请注明出处及链接:https://www.3dcat.live/share/...

December 30, 2022 · 1 min · jiezi

关于unity:高质量泛光Bloom从理论到实战

泛光(Bloom)是古代电子游戏中常见的后处理特效,通过图像处理算法将画面中高亮的像素向外“扩张”造成光晕以减少画面的真实感,可能活泼地表白太阳、霓虹灯等光源的亮度。Bloom的好坏可能极大地改善游戏的表现力。 泛光特效的原理并不简单,提取图像高亮的局部做含糊再叠加回原图。在互联网上有很多对于泛光算法原理的介绍文章或者教程,我这里就不唠叨了。 为什么写这篇文章只管网上有十分多的材料,然而要想制作出高品质的泛光成果却没那么容易。应用最根底的办法做出的成果可能是下图这样的,显然这个后果间隔在显示器上制作闪闪发光的小太阳还很边远: 第一次写泛光特效的时候还是在高二,过后在玩Minecraft,磕磕绊绊地抄着代码却只是在游戏里实现了一个比上图还要不堪的成果。我想在游戏外面复现文章一开始那张图的成果,却没有进一步的材料能够参考,这令我非常丧气。迫于做题家的压力也没有钻研上来,最初不了了之。 网上的教程大多数都在介绍完基础理论就戛然而止,鲜有更加深刻的探讨与实际。为了补救童年时的遗憾,萌发了写这篇文章的想法。 什么是高品质泛光对于优良的泛光特效来说,我认为须要满足以下几个特点: 发光物边缘向外 “扩张” 的足够大发光物核心足够亮(甚至超过1.0而被Clamp成红色)该亮的中央(灯芯、火把)要亮,不该亮的中央(红色墙壁、皮肤)不亮上面是一组比拟有代表性的(我认为)高质量泛光成果截图: 与之对应的,放一组(我认为)成果比拟个别的泛光。如果该亮的中央不够亮,不该亮的中央亮了,那么很容易产生场景的 “含糊” 感: 上面这张图则是发光处的核心和向外扩散出的轮廓都很亮,此外下图中红色土地也在发光,画面显得很脏: 下图则是发光物泛光的扩散范畴不够大,画面的表现力不够强: 高质量的泛光成果能够用一张图清晰地总结。简略来说就是两头亮的批爆,然而越往外亮度降落越快。这有点相似正态分布曲线。下图是UE4给出的现实泛光亮度曲线: 为何要应用 HDR 纹理HDR纹理容许像素的亮度超过255,这可能很好地示意事实世界的亮度。只管最终输入到屏幕上会被Clamp,但最重要的是在对HDR纹理做滤波的时候,超亮的像素能够被无效地扩散到四周区域。 滤波的实质是对Kernal笼罩的范畴内所有像素按某种权重做加权均匀。打个比方,我和马云的财产均匀一下,我也是富哥了。不同的Filter有不同的Weight,然而只有高亮像素的值足够大,它总可能辐射到周边的像素。 上面是一组比照图,应用了大尺寸(radius=100,sigma=30)的高斯含糊进行解决。HDR源纹理输入像素为纯白,值缩放大小由Emissive intensity管制: 其中Emissive intensity = 1.0时对应一般的LDR纹理。因为Kernal的尺寸足够大,1.0的像素值很快被摊派洁净。如果像素足够亮,那么即便处于Kernal边缘也可能积攒可观的亮度。像素越亮,它能扩散的间隔就越远。这意味着单个高亮像素也能扩散出很大的范畴: 此外,HDR纹理可能帮忙咱们疾速辨别须要进行含糊的高亮像素。这可能让美术更加灵便地依据真实世界的参数调整材质。 疾速的大范畴含糊要想光晕扩的足够大,第一件事件就是扩充含糊的范畴。一种非常简单的思路就是加大滤波盒的尺寸,应用一个微小的Kernal对纹理进行含糊。然而性能上必定是吃不消,单Pass的纹理采样次数是N^2而双Pass是N+N。 此外还有一个问题,在解决高分辨率纹理时你须要等比减少滤波盒的尺寸,能力造成等同大小的含糊。比方在1000x1000分辨率下用250像素的Kernal,含糊的后果占1/4屏幕,当分辨率减少到2000x2000的时候,要应用500像素的Kernal能力达到同样的成果。 回到含糊的问题,含糊滤波的实质是查问Kernal范畴内的所有像素并加权均匀,即范畴查问问题。在计算机图形学中实现疾速范畴查问,通常会请到老朋友Mipmap出场。Mipmap将图像大小顺次折半造成金字塔,Mip[i]中的单个像素代表了Mip[i-1]中的2x2像素块均值,也代表Mip[i-2]中的4x4像素块均值: 通过查问高Level的Mipmap能够在常数工夫内查问大范畴的源纹理。在(w/4,h/4)的贴图上做3x3滤波,近似于在(w,h)的贴图上做12x12的滤波。为此须要创立size逐级递加的纹理,并应用downSampler着色器将Mip[i-1]下采样到Mip [i],以Unity为例,在OnRenderImage中一个最简略的下采样Mip串实现: 在downSample着色器中间接输入源纹理的色彩。留神源纹理须要启用双线性滤波,这样硬件会帮忙咱们计算上一级Mip中2x2像素块的均值: 在足够高的Mip等级下,含糊的范畴的确增大了。然而含糊的后果不够好,这是因为双线性滤波实质上是个2x2的Box Filter,方形的Pattern很重大: 为了取得更加圆滑的含糊咱们须要选用更高级的Blur Kernel,高斯含糊是一个不错的抉择。一个5x5,标准差为1的高斯含糊就足够好了。这里我抉择手动计算高斯滤波盒的权重,通常来说应用预计算的2D数组会放慢计算速度: 自此咱们通过多次下采样造成Mip链以实现大范畴的圆形含糊成果: 描述核心高亮区域应用下采样生成大范畴的含糊仅仅是第一步,间接将最高层级Mip叠加到图像上尽管可能产生足够大的光晕扩散,然而发光物的核心区域不够亮堂。此外,发光物和泛光之间没有适度而是间接跳变,从高亮区域跳到低亮度区域显得十分不天然: 不论应用何种滤波器,实质上都是在做加权均匀。只有一均匀,就有人拖后腿!每次含糊都会升高源图像的亮度,并将这些亮度摊派到四周的纹理。边缘的跳变来自于高层级Mip和原图之间亮度差距过大: 为了实现发光物和最高层Mip之间的过渡,咱们须要叠加所有的Mip层级到原图上。因为 Mip[i]是基于Mip[i-1]进行计算的,相邻层级之间绝对间断则不会产生跳变: 较低的Mip层级含糊范畴小且亮度高,次要负责发光物核心的高亮,较高的Mip层级含糊范畴大且亮度低,次要负责发光物边缘的泛光。叠加所有的Mipmap就能同时达到高质量泛光的两个要求,即够亮与够大: 是不是有感觉了? 解决方块图样因为咱们间接从Mipmap链中采样到全分辨率,很难免会呈现方块状的Pattern,因为最高级别的Mip分辨率小到个位数: 能够通过含糊滤波来解决方块图样。值得注意的是不能间接对小分辨率的高阶Mip进行滤波,因为分辨率太小,不管怎么滤波,上采样到Full Resolution的时候都会有方块。除非滤波产生在高分辨率纹理。 然而高分辨率纹理上一大块区域都对应低分辨率Mip上的同一个Texel,如果Kernal不够大那么做Filter的时候查问的值都是同一个Texel,这意味着在高分辨率纹理上要应用超大的滤波盒能力打消这些方块。下图很好的阐明了这一点: 问题又回到了如何应用便宜的小尺寸滤波盒实现大范畴含糊的问题。和下采样时相似,采样逐级递进的形式对低分辨率的Mip链进行上采样。将Mip[i]上采样到Mip[i-1],再和Mip[i-1]自身叠加失去新的Mip[i-1],这种策略在《使命号召 11的GDC分享》中被提出: 进行这个操作须要额定创立一组RenderTexture,上面是下采样Mip链(RT_BloomDown)和上采样Mip链(RT_BloomUp)之间的数据倒腾关系,以964x460分辨率和N=7次为例: 对应的C#代码也比较简单,只是须要留神纹理之间尺寸、下标的关系。这里RT_BloomUp仅有N-1个纹理,记得在Frame Debugger中确保尺寸关系的正确: upSample的着色器也比较简单,同样用的5x5的高斯含糊解决curr_mip,对于prev_mip 能够小滤一下也能够间接采样。通过测试最好对两者都进行滤波,可能失去更加平滑的成果。最初叠加两者作为本级Mip的处理结果: 当初方块图样有了显著的改善: 和闪动抗衡如果相熟PBR流程的话,不难想到Specular的BRDF在Roughness十分小、NdotL靠近1.0的时候,会输入极大的数值,尤其是当光源的强度足够高时。即高光局部十分亮,如果应用了法线贴图等高频法线信息,会导致画面闪动的很厉害:https://www.youku.com/video/X... 对此COD的计划是在Mip0到Mip1,即第一次下采样时,退出额定的权重来试图抹平因法线贴图碰巧NdotL很靠近1.0而引起单个超高亮像素。这个做法叫做Karis Average: 须要一个独自的firstDownSample着色器来进行第一次下采样。高斯含糊版本对应的代码如下,如果应用的是自定义的Kernal可能须要做一些调整: 这个办法因为对亮度做了束缚,会损失肯定的Bloom范畴和亮度,然而失去更加稳固的高光:https://www.youku.com/video/X... 更好的滤波盒在高低采样都应用5x5的高斯滤波盒显得有些侈靡。采样纹理是十分低廉的操作,GPU须要通过数百个时钟周期能力实现。间接应用2x2的Box尽管足够疾速,但会有很显著的Pattern。 在COD的分享中应用了更为玲珑的滤波盒,下采样时依照2x2一组进行采样。采样共5组,并依照肯定的权重加权。这个滤波盒在高斯含糊和2x2的Box之间进行了平衡,既保证了效率又保障了品质: 而在上采样的Filter中,他们更是应用了更为简略的3x3 Tent Filter,值得注意的是他们应用了一个Radius来管制滤波的范畴,这有点相似于深度学习中的 “带洞卷积” 滤波器。这也是为何游戏有些中央会有显著的格子感的起因: ...

December 1, 2022 · 1 min · jiezi

关于unity:URP管线修改落地实战

URP作为最近越来越多的新我的项目开始应用的管线,到很多老我的项目开始进行降级,很多人会遇到各种问题,比方为什么降级到了URP,性能并没有变得更好,甚至在低端机上还降落了?GrabTexture机制没有了,怎么做扭曲成果?想用线性空间但UI色彩不对怎么办?URP为什么值得降级?等等。 文章会从我的项目制作的角度,从根底性能批改动手,到我的项目层面一些刚需批改,来教会大家如何批改URP管线,从而达到本人想要的目标和成果。文章适宜能看懂代码的技术美术,源码之下,了无机密! 阐明:本课程次要对URP管线在我的项目中的理论利用落地进行解说和实现(附带代码可无效运行在Unity 2021.3.4f1版本上,URP版本为12.1.7,但每个我的项目须要自行了解整合应用)。 作者苏晟晖是资深客户端程序,我的项目渲染负责人。2006年毕业,曾工作于SEGA,KONAMI,制作过实况足球,也做过端游MOBA,参加过MLBB和MLA。 目录1|前言2|身边实用的工具3|外围代码的简略了解4|实战-批改Camera的附加数据(示例中有具体实现代码)5|实战-线性空间与伽马空间共存(示例中有具体实现代码)6|实战-以Volume的形式增加后处理(示例中有具体实现代码)7|不额定减少Camera实现扭曲成果(代码中不附带)8|分辨率拆散的做法和优劣(FSR)(代码中不附带) 本篇转载自《URP管线批改落地实战》的第1-3节。 1|前言本课程次要对URP管线在我的项目中的理论利用落地进行解说和实现(附带代码可无效运行在Unity 2021.3.4f1版本上,URP版本为12.1.7,但每个我的项目须要自行了解整合应用)。 URP作为最近越来越多的新我的项目开始应用的管线,到很多老我的项目开始进行降级,让很多人遇到了各种问题,比方为什么降级到了URP,性能并没有变的更好,甚至在低端机上还降落了?GrabTexture没有了,怎么做扭曲成果?想用线性空间但UI色彩不对怎么办?URP为什么值得降级?等等。 就以上问题,兴许你能够从上面的内容取得答案: 实用的工具外围代码的简略了解裁减UniversalAdditionalCameraData线性空间和伽马空间共存(UI的Atlas,文字等)以Volume的形式增加后处理不额定减少Camera实现扭曲成果(Grab)分辨率拆散的做法和优劣(FSR) 将来有新的值得记录的内容也会持续更新。 2|身边实用的工具工欲善其事,必先利其器。咱们想要用好URP,或者说改好URP,上面两个工具,咱们肯定要把握好。 1. Frame DebuggerUnity自从提供了Frame Debugger之后,管线调整以及问题追述都比之前不便和明确很多。 咱们通过上图(右边)能够看到,场景里有2个Camera,他们别离用了什么管线配置,以及绘制的程序和内容,并且在点选单项后,能够看到绘制的具体内容和数据(左边)。 右边的每一项,在UniversalRenderer.cs里都是一个Pass,倡议学习的时候先在代码里把每个Pass对应的内容都先相熟,这样有利于之后对管线进行批改(援用某人的一句话:源码之下无机密,看源码这事,不能偷懒)。 由此可见,咱们能够通过Frame Debugger来确认咱们批改代码后冀望的渲染程序,渲染内容,渲染后果是否正确,所以,在咱们批改了管线的代码后,肯定要在这里确认无误。 2. XCode从项目组角度来说,咱们应用XCode来进行调试通常是在我的项目中后期,对于很多半路被砍掉的我的项目来说,兴许基本没有机会应用XCode就曾经被砍了,但这里我想倡议一下,有条件的团队能够早一些应用XCode来编译我的项目,iOS的环境更有利于确认管线的性能和正确性。 咱们用XCode在手机上运行游戏时,在Options页面,将GPU Frame Capture设置为Metal,同时勾选下方Profile GPU Trace after capture。 而后在运行时,将右边的按钮切换到小瓶子按钮上,就能够看到App的相干性能数据。 咱们点击下方M这个按键时,就会截取以后Frame的所有信息,咱们能够通过截帧的信息,看到咱们管线的渲染流程,以及每个Pass的耗费。 通过这些内容,咱们能够很不便地晓得哪些Pass的耗费太大,须要优化,哪些管线做法设计的不合理,须要优化。 更多具体内容可自行应用XCode钻研。 3|外围代码的简略了解源码之下无机密,肯定肯定肯定(重要的事说三遍)要花工夫看一遍源码,如果全副源码看不了,起码看一下各个Pass和总管文件UniversalRenderer的实现。 这里对外围的代码做一下简略的介绍,不便之后的内容了解: 1. 管线代码文件:UniversalRenderPipeline.cs protected override void Render(ScriptableRenderContext renderContext, List<Camera> cameras)这个函数外面,会执行每个Camera的UniversalRenderer内的Pass,如此一来,咱们就能够在每个Camera进行绘制之前,先进行一个小判断,从而来扭转Camera外部绘制的流程。 2. 外围向渲染代码:UniversalRenderer.cs这个代码里定义了所有管线流程中会应用的Pass。 咱们须要创立额定的RT,也能够在CreateCameraRenderTarget函数里创立,相似下图: 3. 管线更换!=性能降级有遇到不少敌人把Built-in管线降级到URP,而后发现低配性能升高了,其实应用URP不意味着降级了就算实现了,咱们还须要对管线进行定制优化。 这里为了之后的内容便于了解,先简略解释一些点。 一些须要了解的点:a. 因为咱们在Editor下,除了GameView以外,还有SceneView,其中GameView走的是失常的Camera渲染流程(也就是说,理论有几个Camera,就会有什么样对应的管线解决),而SceneView用的是独立Camera,所以咱们在批改管线的时候,须要思考同步解决SceneView内的渲染成果,代码里能够用cameraData.isSceneViewCamera的值来做判断,辨别代码失效的渲染窗口,也能够防止批改管线带来编辑窗口和运行窗口成果的不对立(但在同时反对伽马空间和线性空间之后,SceneView确实有个取舍的成果不统一的问题)。 b. 咱们批改管线,须要思考到Editor没有Play的状况,以及Play的状况,因为还有半路创立Camera的状况(比方UI的Camera是用逻辑创立的,而非带在启动Scene内),这些都会影响到团队内其他人在关上我的项目的状况下看到的画面。 c. 一旦开始批改管线,倡议从整体上进行思考,尽量不要只思考繁多性能在管线中的实现,而疏忽了管线自身的承前启后的性能。 以上就是《URP管线批改落地实战》的第1-3节,此篇文章比拟适宜从事Unity开发的技术美术人员;心愿晋升批改URP管线能力,零碎了解URP管线落地办法的以及对Unity的URP渲染感兴趣的同学的读者。 读完全篇后你会对URP管线有更深的了解,并理解管线批改落地的办法,课程还提供了配套工程和大部分课程内的实现代码,有助于咱们自行钻研。

November 26, 2022 · 1 min · jiezi

关于unity:Unity版本使用情况统计更新至2022年10月

本期UWA公布的内容是第11期Unity版本应用统计,周期为2022年5月至2022年10月,数据来源于UWA网站(www.uwa4d.com)提测的我的项目。心愿给Unity开发者一个行业参考,理解近半年来哪些Unity版本的应用概率更高。 2022年5月 - 2022年10月版本散布 以近半年的数据统计来看,如图1所示,2020.3的版本在开发团队中的使用率较高,达到41.05%;其次别离为2019.4、2021.3、2018.4和2017.4;相较于2021年11月至2022年4月期间的数据统计,2018.4的版本使用率明显降低。 近半年Unity版本应用占比和趋势 从近半年的应用趋势来看,如图2所示,2019.4和2020.3是Unity开发者较为青眼的版本,也是近半年来使用率最高的两个版本。 上面,咱们再对每个大版本做具体的剖析。 近半年2020.3版本应用散布 2020.3系列中,版本普及率顺次为2020.3.21(10.42%)、2020.3.30(8.66%)、2020.3.35(7.29%)、2020.3.25(6.71%)、2020.3.2(6.57%)、2020.3.29(6.32%)和2020.3.32(5.87%)。 近半年2019.4版本应用散布 2019.4系列中,版本普及率顺次为2019.4.36(18.49%)、2019.4.37(12.94%)、2019.4.32(10.99%)、2019.4.40(8.65%)、2019.4.34(7.1%)、2019.4.15(6.69%)和2019.4.30(5.29%)。同时,如果是在2019版本上继续开发,UWA倡议降级版本至2019.4 LTS。 近半年2021.3版本应用散布 2021.3系列中,版本普及率顺次为2021.3.5(48.41%)、2021.3.4(12.28%)、2021.3.9(10.31%)、2021.3.6(8.51%)、2021.3.8(6.72%)、2021.3.1(5.96%)和2021.3.3(3.65%)。 更多Unity版本相干的问题能够拜访UWA问答(answer.uwa4d.com)查问。

November 9, 2022 · 1 min · jiezi

关于unity:分享一次查找GfxDriver内存暴涨的经历

前言网上有很多无关内存的优良文章(比方《Unity游戏内存散布概览》),看完后收益颇多,总感觉对内存(比方PSS的散布)曾经一目了然。直到最近遇到游戏中播放奥义导致GfxDriver内存暴涨500MB左右的问题,才发现之前的“一目了然”到真正解决问题之间,还有一段路要走。这段路,就是实践到实际过程中的方法论,而这方法论,或多或少是有迹可寻的。因而借这个机会,尝试去总结一下,同时分享给大家,欢送探讨。 “分享一次查找GfxDriver内存暴涨的经验”这个题目,其实是取自UWA上的一篇分享。正所谓“幸福”(GfxDriver内存暴涨)是相似的,但各有各的“可怜”(暴涨起因不尽相同)。好了,废话不多说,让咱们进入正题。 问题形容某个角色进入战斗后,只有开释奥义,PSS霎时暴涨靠近500MB,如下图所示:PSS间接从1228MB涨到1724.19MB,并且霎时达到峰值后又会回落一部分,直到维持在一个高位。 从上图中咱们能够看出两个问题: PSS霎时暴涨PSS达到峰值后又会回落一部分问题定位1. 初步定位1.1 放大范畴通过以上的问题形容,首先通过简略的测试放大问题范畴:1)是否跟设施兼容性无关2)是否跟后效无关3)PSS暴涨的大头局部在哪里 对于第1条和第2条,自测或请QA帮忙可能很快定位:这是个通用问题且跟后效无关;对于第3条,我的办法是应用以下三个工具进行组合判断:1)GamePerf(或者UWA、PerfDog、UPR等都能够)2)ADB shell dumpsys meminfo3)Unity Profiler 上面具体讲解一下: 从GamePerf报告剖析初步断定是显存局部增长过快,如下图所示: 与此同时,应用dumpsys meminfo查看播放奥义前后两次PSS的快照,这样可能大体定位问题所在: 上图是奥义播放前的快照,下图是奥义播放后的快照,通过比照发现涨幅都集中在GL mtrack。 再联合真机在Unity Profiler上的后果,显示GfxDriver从127MB涨到0.56GB: 1.2 小结通过以上三个工具组合,咱们能够大抵定位PSS的增长大头在“显存”上。但咱们晓得,手机上是没有独显的,SoC中GPU和CPU共用一块LPDDR物理内存,因而我在显存上加上了引号。而以上三个工具别离引出了无关“显存”的三个概念,前面咱们会深刻理解一下以下三个概念:GamePerf——memGraphicsPSS——GL mtrackUnity Profiler——GfxDriver 2. 单元测试既然已定位到,那么接下来就能够通过单元测试来进一步定位问题所在了。在这个阶段,就要引入新的工具——System Profiler。在测试之前,先简略介绍一下这个工具。 2.1 工具抉择——System ProfilerSystem Profiler是华为提供给开发者的一款用于Android平台应用程序的性能数据实时采样工具。通过性能数据的实时动态变化与利用的动静场景相结合做关联剖析,帮忙开发者疾速定位应用程序的性能问题。它能够采集的数据有: CPU性能数据指标:CPU负载、CPU各核使用率、CPU各核频率和CPU性能计数器。GPU性能数据指标:GPU频率、GPU负载和GPU性能计数器。Memory性能数据指标:零碎Memory应用状况、利用APP过程Memory应用状况和GPU Memory应用状况。Graphics性能数据指标:帧耗时FrameTime、实时帧率FPS、卡顿Jank和重大卡顿Big jank。其余性能数据指标:设施CPU温度、GPU温度、电池温度、网络数据流量速率、Disk数据读写速率和用户自定义性能数据事件。为什么会选用这个工具呢,次要是从以下几个方面思考的: 通过以上的初步定位,内存暴涨问题跟机型无关须要看到PSS的实时变动 dumpsys meminfo无奈满足实时这个需要Unity Profiler的数据又比拟局限,无奈纵观全局Android Profiler尽管可能看到PSS的实时变动,但跟dumpsys相比,Android Profiler没有System和Private Other项,然而多了一个Others项,须要通过一些换算能力跟dumpsys进去的PSS匹配最好可能看到除了PSS之外其它的一些性能指标,不便对问题做进一步排查定位2.2 单个特效播放测试2.2.1 测试数据 2.2.2 剖析 System Profiler中Graphics的含意跟APP Summary中的Graphics一样,播放前后的差距为16.7MB当初只粗略算一下特效的其中一个Shader,一个顶点占用为:4(4+3+2+4+4)= 68Byte,数量为:101681,由此算出占用大小大略为:68 101681 / 1024 / 1024 = 6.59MB。 struct VertexInput { float4 vertex : POSITION; float3 normal : NORMAL; float2 texcoord0 : TEXCOORD0; float4 texcoord1 : TEXCOORD1; float4 vertexColor : COLOR; };2.3 12个特效霎时播放测试先阐明一下,通过后期的状况摸底,游戏中奥义的播放会霎时播放12个雷同特效。因而,单元测试还须要模仿测试一下游戏中的真实情况,看看霎时播放12个雷同特效的话成果如何。 ...

September 29, 2022 · 1 min · jiezi

关于unity:MobTech-短信验证-Unity插件

下载unitypackage从Mob的github地址下载ShareSDK.unitypackage:Git地址,如下图所示 下载实现后失去一个SMSSDK.unitypackage结尾的文件,如下图: 导入unitypackage双击导入到unity我的项目中并勾选,如下图: Demo.cs是demo的示例页面,可通用 Android AlertDialog.cs,DialogOnClickListener.cs,MemoryCString.cs:安卓dialogSMSSDKUnityBridge.jar:桥接层baseProjectTemplate.gradle,launcherTemplate.gradle:gradle配置 iOS (全副勾选) 按图选好之后,点击import导入 挂接SMSSDK脚本在unity界面左侧抉择要应用的场景(例如Main Camera),点击Add Component 按钮增加SMSSDK.cs(SMSSDK外围文件),或者间接拖进去。如果要测试成果,也能够增加Demo.cs(SMSSDK示例UI),如下图所示: 批改编译环境Platform设置,在unity中抉择菜单栏-File-Build Settings,会关上Build Settings操作框,依据本人所编译的环境抉择,如果是iOS的请抉择iOS环境,如果是Android请抉择Android环境,而后点击Switch Platform切换环境,如下图: Android端编译配置批改Unity设置生成Gradle文件放弃Build Settings操作框中左侧Platform列表中Android项的选中状态,点击Player Settings,会关上Project Settings操作框,如下图: 左侧切换到Player标签,而后在右侧抉择Publishing Settings卡片,勾选Custom Launcher Gradle Template和Custom Base Gradle Template及Custom Gradle Properties Template,选项,如下图: 该操作会在"工程\Assets\Plugins\Android目录下"生成baseProjectTemplate.gradle、launcherTemplate.gradle、gradleTemplate.gradle文件。 批改Gradle文件批改baseProjectTemplate.gradle文件关上baseProjectTemplate.gradle文件后有两处须要操作: 1.在classpath 'com.android.tools.build:gradle'后增加mob的classpath classpath "com.mob.sdk:MobSDK:2018.0319.1724"2.增加Mob本人的maven仓库 maven { url "https://mvn.mob.com/android/"}最终baseProjectTemplate.gradle文件的参考后果为: 批改launcherTemplate.gradle文件关上launcherTemplate.gradle,在文件的最下方退出以下代码: apply plugin: 'com.mob.sdk'MobSDK {appKey "替换为mob官网申请的appkey"appSecret "替换为mob官网申请的appkey对应的appSecret"SMSSDK {}}在gradleTemplate.gradle文件中增加 MobSDK.spEdition=FP增加混同配置混同设置:SMSSDK曾经做了混同解决,再次混同会导致不可预期的谬误,请在您的混同脚本中增加如下的配置,跳过对SMSSDK的混同操作: -keep class com.mob.**{*;}-keep class cn.smssdk.**{*;}-dontwarn com.mob.**iOS端编译配置批改初始化SDK的Appkey和AppSecret 接口调用参考Demo.cs文件的应用形式,先引入命名空间 using cn.SMSSDK.Unitypublic SMSSDK smssdksmssdk = gameObject.GetComponet();如下图: ...

September 22, 2022 · 1 min · jiezi

关于unity:游戏中动态分辨率从原理到应用

序随着以后越来越多的手游向“3A”聚拢,手机上的各种性能优化也在致力地为“3A”保驾护航,巴不得要把芯片上每一个晶体管的性能都开掘进去。然而,当一台“高分低能”的手机摆在你背后的时候,是不是总是有一种“欲哭无泪”的无力感——既要放弃高帧率又要保障画面质量。成年人从来不做选择题,在两个都要的状况下,降分辨率往往是起效最快的方法。 说到调整设施的分辨率,Screen.SetResolution这个办法大家必定是很相熟了,然而这种调整是全局的,是硬件级别的调整,无奈做到3D和UI渲染指标的离开调整。当然,随着SRP管线的推出,咱们曾经能够实现3D相机和UI相机分辨率的离开调整,并且UWA上已有相干文章的介绍了(见参考9)。 明天这篇文章要探讨的是,Unity和Unreal都提供的动静分辨率的计划,它能够动静缩放单个渲染指标,以缩小GPU上的工作量。 说到3D和UI的离开渲染,聪慧的小伙伴必定想到了一种计划:3D渲染到一张RT上,最初把3D的RT Blit到最终的RT上。那么这种计划跟Unity提出的动静分辨率计划有何不同的中央吗?还是说只是新瓶装旧酒? 接下来,我就跟大家一起摸索一下动静分辨率(以Unity为主)的原理以及它的利用场景。 传统的3D和UI拆散计划 如上图所示,基本原理是渲染场景的时候调整视口大小(Viewport),将渲染束缚到屏幕外Render Target的一部分,而后再把场景的Render Target上的内容Blit到最终的RT上。例如,渲染指标的大小可能为(1920,1080),但视口的原点可能为(0,0),大小为 (1280,720)。 这种实现形式可能会有如下几个问题: Blit的性能损耗,这个操作必定不能是实时的,个别也就是在游戏初始化后或者在进入某个场景前设置一次,是一个低频操作,无奈做到真正的“实时”调整。可能受限于渲染管线 如果是默认渲染管线的话,最初这个Blit的操作机会就要选好,因为游戏中个别会有后处理阶段,咱们要利用好这个阶段顺便把Blit也做了。这个能够利用CommandBuffer向相机的不同渲染阶段插入视口批改和后处理操作。如果是SRP渲染管线的话(Unity 2018当前的版本),咱们就能有本人解决Blit的机会了,当然这个操作也不能是个高频操作。应用流程参考Unity官网文档,咱们先来看一下动静分辨率的应用流程。 首先咱们要确认一点:动静分辨率启用的前提是GPU Bound了。所以要通过实时获取每帧GPU的运行工夫来决定: 是否是GPU压力过大导致游戏掉帧渲染指标的缩放系数再依据缩放系数对渲染指标进行动静缩放。在这个过程中须要保障批改渲染指标分辨率的时候不重新分配GPU显存,否则就跟Screen.SetResolution一样了(会导致画面闪动)。 在须要动静缩放的相机上勾选,如图所示: 在PlayerSettings中勾选上“Enable Frame Timing Stats”: 通过两个接口FrameTimingManager.CaptureFrameTimings()和FrameTimingManager.GetLatestTimings获取CPUTime和GPUTime后自行判断缩放系数最初调用ScalableBufferManager.ResizeBuffers(m_widthScale, m_heightScale)设置缩放平台反对Unity官网文档上是这么写的: 可能跟了解程度有关系,看到以上的阐明,我就犯迷糊了:OpenGLES不反对动静分辨率,内置渲染管线、URP等兼容,那么如果是URP下的OpenGLES平台呢?反对还是不反对? 不论如何,先把纳闷放一边,咱们来探索一下动静分辨率的实现原理。 原理探索咱们顺着官网文档上的应用流程,摸入Unity源码外部,看看为什么对OpenGLES如此一视同仁。因为波及到源码局部,这里就间接说论断了。 缩放RT是跟平台相干的,OpenGLES无奈创立缩放RT,起因咱们前面再讲动静分辨率的原理为Vulkan的内存混叠(Memory Aliasing)性能Memory AliasingMemory Aliasing能够翻译成内存混叠或内存别名,参考[1]是Vulkan针对此概念的阐明。 古代图形 API(如DirectX 12或Vulkan)能够让用户定义内存地位,将调配的GPU资源放入手动创立的堆中。它容许咱们创立纹理和缓冲区,它们的内存局部甚至能够齐全重叠。这也是为什么OpenGLES不反对动静分辨率的起因,因为OpenGLES没有凋谢更底层的API让咱们能够实现更高效的内存治理。 以游戏中典型的一帧为例:光栅化一些几何体,执行着色,而后运行一堆后处理。这里的每个阶段的输入都将写入纹理或缓冲区,稍后在一帧中被其余阶段应用。然而,某个阶段产生的资源可能只被多数其余阶段应用,比方在后处理中:Bloom产生的输入,只会被下一阶段的Tone mapping(色调映射)应用,并且在帧中的其余任何中央都不须要。咱们能够看到,资源的无效生命周期可能很短,但很可能是事后调配的,并且在整个帧中都占用了它的内存。 解决内存频繁调配开释的办法就是对象池,Unity的RenderTexture.GetTemporary就是在外部保护了一个RenderTexture的对象池。然而这种办法只实用于后处理阶段,因为不同格局、大小的资源不能复用,后处理通常是全屏的Pass,读取、写入的Texture通常都有雷同的属性,一些简略的后处理只须要两个RT重复交替应用就能实现(这个我会在稍后的URP章节中重点解读一下) 。 对象池实质上是一种更下层的Memory Aliasing,开发者不须要关注内存治理;但古代图形API(DX12和Vulkan)提供了内存治理的接口,能够实现底层的Memory Aliasing。Memory Aliasing指的是不同变量指向同一地址,即在同一片内存区域中同时寄存多个资源,如果有很多大型资源在工夫上不会重叠,就能够在雷同的内存调配这些资源。相比对象池,Memory Aliasing能够进一步升高内存占用,因为在底层都是一堆字节,所以就不须要思考资源的类型、格局、大小等。具体的示意如下图所示: 小结从以上的剖析咱们大略理解到了Unity实现动静分辨率的原理:利用Vulkan提供的内存治理接口,实现底层对内存高效地复用。这样咱们在游戏中就能够高效实时地调整分辨率,根本没有性能损耗。 URP实现思考到URP的前身LWRP还有项目组在用,上面先简略看一下LWRP。 LWRP简略点说就是通过从新创立相机的渲染指标来实现的。Setup时会先进入函数RequiresIntermediateColorTexture判断是否要创立新的RT,外面就有个变量isScaledRender,如果须要缩放,则进入创立RT的Pass: m_CreateLightweightRenderTexturesPasspublic void Setup(ScriptableRenderer renderer, ref RenderingData renderingData){ ... bool requiresRenderToTexture = ScriptableRenderer.RequiresIntermediateColorTexture(ref renderingData.cameraData, baseDescriptor); RenderTargetHandle colorHandle = RenderTargetHandle.CameraTarget; RenderTargetHandle depthHandle = RenderTargetHandle.CameraTarget; if (requiresRenderToTexture) { colorHandle = ColorAttachment; depthHandle = DepthAttachment; var sampleCount = (SampleCount)renderingData.cameraData.msaaSamples; m_CreateLightweightRenderTexturesPass.Setup(baseDescriptor, colorHandle, depthHandle, sampleCount); renderer.EnqueuePass(m_CreateLightweightRenderTexturesPass); } ...}public static bool RequiresIntermediateColorTexture(ref CameraData cameraData, RenderTextureDescriptor baseDescriptor){ if (cameraData.isOffscreenRender) return false; bool isScaledRender = !Mathf.Approximately(cameraData.renderScale, 1.0f); bool isTargetTexture2DArray = baseDescriptor.dimension == TextureDimension.Tex2DArray; bool noAutoResolveMsaa = cameraData.msaaSamples > 1 && !SystemInfo.supportsMultisampleAutoResolve; return noAutoResolveMsaa || cameraData.isSceneViewCamera || isScaledRender || cameraData.isHdrEnabled || cameraData.postProcessEnabled || cameraData.requiresOpaqueTexture || isTargetTexture2DArray || !cameraData.isDefaultViewport;}URP从Unity 2019.3.0a这个版本开始,LWRP开始正式降级为URP。URP次要分为两个文件夹:一个是独自提取进去跟HDRP共用的根底外围库core,另一个就是URP本人用的universal。 ...

September 1, 2022 · 1 min · jiezi

关于unity:雪地脚印-体积云

【博物纳新】专栏是UWA旨在为开发者举荐新鲜、易用、乏味的开源我的项目,帮忙大家在我的项目研发之余发现世界上的热门我的项目、前沿技术或者令人惊叹的视觉效果,并摸索将其利用到本人我的项目的可行性。很多时候,咱们并不知道本人想要什么,直到某一天咱们遇到了它。 明天举荐的两个我的项目来自UWA开源库: 1)雪地足迹2)体积云 01 雪地足迹一、概览该我的项目实现的性能是在雪地上踩出足迹,只反对PC平台。 成果如下: 二、实现原理1. 足迹踩在何处?咱们能够看到dynamic_texture这个父物体下有一个摄像机(临时叫它副摄像机),并且在运行状态下,每一个实时踩出的足迹都一一对应着框中一个个实例化出的default_sprite预制体,如下图:图1:dynamic_texture构造 在dynamic_texture附带的DistanceMapPainter中,最次要的工作就是把足迹的根本信息以色彩的模式附给预制体的色彩信息,如下图:图2:色彩蕴含的信息(from DistanceMapPainter.cs) 那这些预制Sprite的意义是什么呢?不只是传递以色彩为表现形式的地位信息,也在通过a通道传递足迹的“寿命”信息。图3:色彩的a通道(from sprite_fading.cs) 最重要的是,还会保留深度信息:图4:Sprite中的深度信息(from DistanceMapPainter.shader) 2. 足迹信息如何传递?副摄像机在这里会专门“监督”这些实时生成的预制体,而后把它们渲染在PainterRT上(如下图):图5:副摄像机的渲染指标 那这张“PainterRT”记录着什么呢?直观上来讲,它记录着如图1中花花绿绿的方块图,上文也提到过了这些色彩蕴含的是各个足迹的地位等信息,但它们重叠怎么办呢? 请留神咱们方才提到Sprite预制体中的Shader会保留深度信息,而后看PainterRT的信息:图6:PainterRT根本信息 它是关上深度缓存的,所以这张RT上保留的是通过了深度检测的点的色彩信息。至于哪些点能通过深度检测呢?依照图4对于深度的写法,那就是处于每个Sprite核心地位最“白”的中央,也就是最靠近摄像机的中央,也就是最初能通过深度检测的中央,那最初这张图的核心处必定能渲染到RT上。图7:把深度作为色彩输入之后的PainterRT 为了直观地看到深度图的成果,咱们临时把深度作为色彩输入(看完之后记得改回来哦~),能够看到单个的Sprite是一个红色的“山丘”,越凑近核心越白,而咱们看到的这些红色其实曾经是通过了深度测试的了,可见每个山丘的核心是肯定能通过深度测试的,而其余边缘处,即便记录了足迹信息,然而足迹也很可能笼罩不到那儿(当然,这得看你想把足迹画多大了)。 那么会存在两张图齐全重合而使得信息读取不到的状况吗?从上图可知只有不是两张图完完全全重合,就不会相互影响,如果真的是两个山丘齐全重合,那么确实会失落其中一个脚印。这就是为什么操控着角色在一个中央重复彷徨,总有几个足迹画不进去的缘故。 而这张RenderTexture将会作为高空Shader的输出,这样就把地位、角度等信息通过一张图传给了高空Shader。 3. 曲面细分着色器咱们当初来看最重要的高空Shader——SnowGroundShader。 关上并浏览这个Shader,能够看到这个Shader中应用了细分曲面。图8:曲面细分着色器和几何着色器的标记 对于曲面细分的教程并不多,能够参考《Unity Shader:曲面细分着色器》。 咱们次要关注细分计算着色器,因为大部分重点在这一部分。图9:细分计算着色器中获取足迹信息局部 其中_DistanceFiled就是上文中副摄像机渲染的图,centerPos和angle别离读取了其中保留的地位和角度信息。 至于高空的凹凸起伏,就是在顶点的纵向上加上高度,而高度就是从高度图读取的,如下图:图10:细分计算着色器中顶点的起伏计算 顶点只有变换了,法线就得重构,这里切线空间的三个向量会依据高度图从新计算,存入o.tspace中:图11:细分计算着色器中法线重构局部 4. 最初的成果最初的成果在片元着色器中。 首先视觉效果上,上文的o.tspace中保留的因高度扭转而重构的法线会与雪地和足迹的法线之和做一个点乘操作,如下图:图12:片元着色器中法线合并局部 之后的光照,就是用了Unity封装的LightingStandard_Deferred(该我的项目用了提早渲染),这里就不赘述了。 总之,整个流程就是确定地位和角度、依据高度图调整顶点高度、重构法线。对于曲面细分的局部,细分的越精密,成果就越粗劣。 5. Tips下图是Tessellation hull constant shader,其性能是用来输入细分因子(Tessellation factor)。细分因子用于在Tessellation阶段通知硬件如何对Patch进行细分。 咱们能够看到这里的factor不是写死的,旨在让须要细分的中央细分,也就是只细分足迹四周的中央,不失为一个优化性能的好方法。图13:细分因子的实时计算局部 图14:细分因子的实时计算成果 02 体积云一、概览体渲染是从三维标量数据生成图像的渲染技术。通过对天然景象的测量或数值仿真,体渲染能够绘制出真切的成果。在视觉艺术和计算机游戏畛域中,体渲染常常用于模仿云、雾和火等天然景象。 该篇的CloudSkybox我的项目是Unity默认程序天空盒着色器的扩大,它就是应用了体渲染技术绘制云。 二、原理概述该我的项目次要由两局部组成:基于大气散射的天空和基于光线步进的体积云。 在描述天空时,通过简略地模仿大气散射过程的算法,咱们能够渲染出一个带有瑞利和米氏散射景象的天空;而在描述云时,则采纳了噪声采样和光线步进联合的传统办法,来渲染出云层的体积感。 三、具体实现1. 大气散射该项目标次要逻辑都在CloudSkybox.shader和ProceduralSky.cginc中,先看一下顶点着色器: 图1:顶点着色器 顶点着色器重点在vert_sky函数,该函数的实现在ProceduralSky.cginc中。它的作用在描述高空、天空和太阳的色彩,用的办法来自于Nvidia上一篇很经典的文章:https://developer.nvidia.com/... 对于大气散射原理的解说有很多,这里不再细说,只讲一些标志性的代码段: 图2:scale函数 该函数近似于查找表,计算出来的是某个方向的射线上光强的衰减。该办法尽管简化了计算,但前提条件是要求均匀大气密度为2.5%。这是一种比拟简化的办法,当初大都用预计算和查找表的办法代替该办法。图3:衰减公式实现 这里对应的公式次要是衰减和光学密度公式: 其中scale查表查到的是以某个角度发射的射线门路上的光学密度,而(kInvWavelength * kKr4PI + kKm4PI)对应的是上式的散射系数,所以119行的attenuate就是衰减函数的实现。 最终要满足的是上述方程,而121行的depth和scaledLength别离对应上式的密度函数R,M(h)和微元ds。至于相位函数F()和散射系数是在之后的输入时别离乘上的:图4:散射公式的欠缺 这里的很多变量是申明在结尾处的一些分母的计算,旨在缩小除法的运算次数。 上述公式图来源于以下知乎文章: 《[Rendering] 基于物理的大气渲染》 《【译】【大气散射】[Elek09] 实时渲染参数化的、有屡次散射的行星大气》 ...

August 31, 2022 · 1 min · jiezi

关于unity:Unity-撤销还原

源码的类图classDiagram direction LR RecordContainer --> IRecord class IRecord{ <<interface>> +Unod() +Redo() +OnRemove() +OnException(Exception ex) } class RecordContainer{ +Record(IRecord record) +Undo() +Redo() +Clear() }Demo的类图classDiagram IRecord <|-- DelRecord IRecord <|-- SpawnRecord RecordContainer --> IRecord class IRecord{ <<interface>> } class DelRecord class SpawnRecord class RecordContainer{ }github地址:https://github.com/pangdudu72...

August 19, 2022 · 1 min · jiezi

关于unity:你所需要了解的几种纹理压缩格式原理

本文基于材料收集,概括了几种纹理压缩格局的根本思维,心愿对于学习有所帮忙。 为什么咱们须要纹理压缩格局?例如R5G6B5、A4R4G4B4、A1R5G5B5、R8G8B8或A8R8G8B8等未经压缩的图片格式,是可能被GPU间接读取的原生纹理格局。但在低端硬件设施或者说挪动平台下,有两个问题须要解决。 一个是内存,例如A8R8G8B8格局中一个像素占4字节,如果是512x512分辨率内存就占用512x512x4 B=1048576 B=1 MB,这种内存耗费在低端设施上根本无法承受。 另一个重要的是数据传输时的带宽,带宽是发热的首恶,在渲染3D场景时,会有大量的贴图被传输到GPU,若不限度,总线带宽很快就会成为瓶颈,手机秒变暖手宝,重大的还会影响渲染性能。 因而咱们须要一种内存占用既小又能被GPU读取的格局——压缩纹理格局。纹理压缩对应的算法是以某种模式的固定速率有损向量量化(Lossy Vector Quantization)将固定大小的像素块编码进固定大小的字节块中。 有损示意对于渲染来说,有损压缩是能够承受的,个别抉择压缩格局时须要在纹理品质和文件大小上寻求一个均衡。 固定速率压缩指的是什么呢?因为GPU须要可能高效的随机拜访一个像素,这意味着对任意像素,解码速度不该有太大的变动。因而,咱们常见的贴图压缩算法都是有损压缩。相同的例如zip则是一种可变速率压缩。 向量量化(Vector Quantization,VQ)是一种量化技术,将一组大量的点(向量)分成具备近似雷同数量的最靠近它们的点的组。每个组用它的质心点示意,因而存在数据误差,实用于有损压缩。放到纹理压缩中来了解,就例如将4x4块像素的色彩以2个基色来示意。 编码和解码速度:一般来说编码速度慢没关系,因为通常纹理压缩只须要在游戏打包时进行一次,对于用户运行时体验齐全没有影响。但解码速度必须足够快,而且基本上不能影响到渲染性能。 压缩比:通常以比特率或每像素的均匀比特数(bits per pixel,bpp)示意,常见的为2~8bpp。个别RGB原生纹理的像素指24位,4bpp示意每像素占4位,所以也能够认为4bpp示意压缩比为6:1。 顺便一提,在Unity中,任何图片文件格式都存在一个导入过程,导入后的文件格式都是Texture2D,在Texture2D的导入设置选项中须要针对不同平台设置纹理压缩格局。 为什么咱们不应用png、jpg这类常见的压缩格局?只管像jpg、png的压缩率很高,但并不适宜纹理,次要问题是不反对像素的随机拜访,这对GPU相当不敌对,GPU渲染时只应用须要的纹理局部,咱们总不可能为了拜访某个像素去解码整张纹理吧?不晓得程序,也不确定相邻的三角形是否在纹理上采样也相邻,很难有优化。这类格局更适宜下载传输以及缩小磁盘空间而应用。 常见纹理压缩格局ETCETC(Ericsson Texture Compression)最后为挪动设施开发,现在它是安卓的规范压缩计划,ETC1在OpenGL和OpenGL ES中都有反对。 其原理简略来说,是将4x4的像素块编码为2x4或4x2像素的两个块的办法,每个块指定一个基色,每个像素的色彩通过一个编码为绝对于这些基色偏移的灰度值确定。 具体来说,ETC1每4x4像素块编码为64位的字节数据,每一个像素块又分为两个2x4子块(由一个“flip”位管制程度或竖直划分),每个子块蕴含一个3位的润饰表索引(modifier table index)和一个根本色彩值,这两个色彩值要么是2*R4G4B4要么是R5G5B5+R3G3B3(由一个“ diff”位管制是哪一种)。 3位的润饰表索引对应于8种润饰值: 这样一个子块由1个根本色彩值和4个润饰值能够确定出4种新的色彩值: color0 = base_color + RGB(modifier0, modifier0, modifier0)color1 = base_color + RGB(modifier1, modifier1, modifier1)color2 = base_color + RGB(modifier2, modifier2, modifier2)color3 = base_color + RGB(modifier3, modifier3, modifier3)最终的色彩就是从这4个色彩值中选出。其原理就是另外32位数据中蕴含16个2位选择器数据,每个像素的色彩都依据2位选择器的值从这4种中选出。 ETC1编码方式直观图如下: Unity的几种ETC纹理压缩格局: RGB ETC1 4 bit:4 bits/pixel,对RGB压缩比6:1,不反对Alpha,绝大部分安卓设施都反对。 RGB ETC2 4 bit:4 bits/pixel,对RGB压缩比6:1。不反对Alpha,ETC2兼容ETC1,压缩品质可能更高,但对于色度变动大的块误差也更大,须要在OpenGL ES 3.0和OpenGL 4.3以上版本。 ...

August 19, 2022 · 2 min · jiezi

关于unity:主流游戏相机实现-之Cinemachine-概述

《支流游戏相机实现》通过Unity 3D引擎和Cinemachine组件来实现支流游戏的相机设置,切换和治理的概念,习惯,基本原理,教训参数等。实现内容以动作游戏相机为主,但不限于动作游戏,也能够用于横版搏斗、FPS、RPG等类型,须要自行调整和钻研。 课程属于根底和进阶课程,适宜对镜头管制感兴趣的读者。 《支流游戏相机实现》目录1|Cinemachine 概述2|《战双》相机合成和实现3|《Sifu/徒弟 》战斗相机局部合成与实现4|多指标相机实现5|《对马岛之魂》战斗相机局部实现6|相机碰撞和场景交互解决7|Demo工程 本文节选自UWA学堂的《支流游戏相机实现》第一节《Cinemachine 概述》。 1 根本组件组成Cinemachine外围组件包含Brain和Virtual Camera(虚构相机),Brain负责相机的切换,虚构相机负责拍摄。 Virtual Camera虚构相机次要由六个模块组成,别离是:Lens:包含调整FOV等参数;Body:负责解决相机和跟踪目标之间的绝对地位的这样一个关系;Aim:负责解决焦点和跟踪目标在镜头中的绝对地位;Noise:模仿手持相机的晃动;后处理模块:让每个镜头有不同的后处理成果;Extnesions可扩大模块:包含碰撞解决等性能。 Body属性提供了下列算法来挪动虚构相机: Do Nothing:不挪动虚构相机。Framing Transposer:在屏幕空间,放弃相机和追随指标的绝对地位,能够设置缓动。Hard Lock to Target:虚构相机和追随指标应用雷同地位。Orbital Transposer:相机和追随指标的绝对地位是可变的,还能接管用户的输出。常见于玩家管制的相机。Tracked Dolly:相机沿着事后设置的轨道挪动。Transposer:相机和追随指标的绝对地位固定,能够设置缓动。Aim属性提供了下列算法来旋转相机对准Look At的指标: Composer:将指标放弃在相机镜头内,能够设置多种束缚Group Composer:将多个指标放弃在相机镜头内Do Nothing:不旋转相机POV:依据用户的输出旋转相机Same As Follow Target:将相机的旋转和追随指标的旋转同步Hard Look At:将Look At指标固定在镜头核心的地位。 组织构造:1.1 Brain和Virtual Camera执行过程 CinemachineBrain每帧通过VirtualCamera计算实在相机的地位,并同步到实在相机上。 真正的数据计算又是通过VirtualCamera上的流水线来计算的。 这是一个简化的流程阐明,实在计算还有相机切换时的混合、流水线之外的Extension、和CinemachineCore对Cinemachine的全局治理等。 1.2 CinemachineBrain具体调用流程次要能够分为两个工夫节点和三件事。 工夫节点 FixedUpdate之后LateUpdate三件事 保护虚构相机的状态,永远在LateUpdate。通过虚构相机计算State,依据UpdateMethod的设置,在FixedUpdate之后或LateUpdate。将虚构相机的State同步到实在相机上,依据BlendUpdateMethod的设置,在FixedUpdate之后或LateUpdate。 VirtualCamera中State计算流程 VirtualCamera中 Pipeline Stage流水线阶段 UpdateComponentPipeline(); //if (m_ComponentOwner != null && m_ComponentOwner.gameObject != null){ // 排序 the pipeline list.Sort((c1, c2) => (int)c1.Stage - (int)c2.Stage); m_ComponentPipeline = list.ToArray();}// 预处理 camStatefor (int i = 0; i < m_ComponentPipeline.Length; ++i) m_ComponentPipeline[i].PrePipelineMutateCameraState(ref state, deltaTime);// 按body到final程序执行CametaState,有2个例外CinemachineComponentBase postAimBody = null;for (var stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage){ ... if (stage == CinemachineCore.Stage.Body && c.BodyAppliesAfterAim) { postAimBody = c; continue; // do the body stage of the pipeline after Aim } c.MutateCameraState(ref state, deltaTime);}InvokePostPipelineStageCallback(this, stage, ref state, deltaTime);if (stage == CinemachineCore.Stage.Aim){ if (c == null) state.BlendHint |= CameraState.BlendHintValue.IgnoreLookAtTarget; // If we have saved a Body for after Aim, do it now if (postAimBody != null) { postAimBody.MutateCameraState(ref state, deltaTime); InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Body, ref state, deltaTime); }}适宜读者1、从事第三人称3D游戏研发的策动、程序、美术、分镜师以及独立制作人2、心愿晋升游戏体验设计和实现能力,零碎了解业务与技术如何联合的开发者3、具备肯定Unity引擎相机根底的同学4、对镜头管制感兴趣的同学5、器重3C和晋升相机体现品质的群体 ...

July 21, 2022 · 1 min · jiezi

关于unity:Unity移动端游戏性能优化简谱之-前言

《Unity挪动端游戏性能优化简谱》是从Unity挪动端游戏优化的一些根底探讨登程,例举和剖析了近几年基于Unity开发的挪动端游戏我的项目中最为常见的局部性能问题,并展现了如何应用UWA的性能检测工具确定和解决这些问题。内容包含了性能优化的根本逻辑、UWA性能检测工具和常见性能问题,心愿能提供给Unity开发者更多高效的研发办法和实战经验。 明天向大家介绍第一局部,共五大节。残缺内容请返回UWA学堂查看。 玩家眼中的游戏性能开发者关注的游戏性能如何确定问题如何解决问题如何继续监控性能前言1. 玩家眼中的游戏性能任何一个玩家在玩耍游戏过程中都会碰到相似的疑难:为什么我玩一段时间就会闪退?为什么我的游戏这么卡?为什么我的手机这么烫?为什么手机电量下得这么快? 对于玩家而言,无关手里这款游戏自身的美术是否精良、玩法是否乏味,当上述这些疑难呈现时,其游戏体验会自然而然地变差,并不可避免地导致黏性降落。这是每个游戏开发者都不想看到的后果。 而实情是,对于咱们明天着重探讨的挪动端游戏市场而言,国内市面上各类硬件机型性能跨度很大;如果我的项目要反对海内市场,则往往我的项目所要承当的性能压力更大。那么,如何让持有不同硬件设施的玩家,尤其是通过中低端设施接触到咱们游戏我的项目的玩家也能顺利流畅地体验游戏内容,就是咱们的次要指标之一。 2. 开发者关注的游戏性能在游戏我的项目的开发过程中,一个十分常见的景象是:对于一个规模较小或教训不够丰盛的开发团队,在新我的项目的中后期往往把全副精力投入到实现性能中去,而疏忽了性能优化的工作;到了游戏中后期甚至邻近上线的时候进行测试,才发现我的项目充斥着大量重大的性能问题,影响到了游戏的失常体验,但却不晓得从何改起,工夫十分困顿。足以见得,性能问题是一款游戏我的项目重要的生命线,须要开发者建立良好的优化意识和优化常识体系,能力让我的项目少走弯路。 和玩家视角不同的是,像闪退、卡顿、发热、耗电这种感官层面的性能问题,UWA认为在开发者的视角中应该将其次要分为内存、CPU和GPU三个大类的问题。本文的次要内容旨在围绕这三个大类的常见典型问题进行例举和探讨,从而造成一份挪动端游戏性能优化概述。 那么,既然曾经发现了若干性能问题,如何排查确定问题和解决问题就成了最要害的两个步骤。 3. 如何确定问题发现了性能问题后的第一大步骤就是确定问题。 举例而言,咱们的游戏我的项目运行工夫一长就会闪退,是因为咱们的缓存策略做的不好或者存在冗余景象,导致内存一直回升造成的闪退?还是因为咱们的美术资源内存管制的不好,几个较大的资源就轻易把整体内存给撑大了? 又或者咱们发现一个游戏场景卡顿重大、帧率很低,是因为这里的同屏渲染面片数和DrawCall数都太高导致渲染模块压力大?还是因为此处的UI过于简单且变动频繁造成的更新耗时? 还或者咱们感到手里的手机烫的惊人,是不是因为以后场景GPU压力过大?咱们的带宽、Shader复杂度、Overdraw、后处理等是不是管制的不够好?咱们在以后机型上做的分级策略并不适合? 以上只是泛滥千奇百怪层出不穷的性能问题中的寥寥几点。后文还将具体探讨一些比拟常见的问题。总之,确定问题的工作自身也是一项挑战。 3.1 计算机语言/图形学/游戏引擎常识常识和教训无疑是一个开发者在优化我的项目性能时最贵重的武器,一直地学习和更新也是每一个程序员武装起本人的过程。欠缺而丰满的常识天然不仅仅有利于确定问题,同样有助于解决问题。放在这里说是因为它是咱们有能力着手优化工作的重要前提。 除了查看各类工具源码和文档、跟踪工具更新内容、翻阅大牛撰写的书籍、浏览各类技术论坛和博客等等以外,也能够在UWA的技术社区中找到很多优质内容。 UWA 学堂:继续更新各类优质课程,内容从某些开发工具的入门和应用到某个引擎模块的机制原理详解;从最新的挪动端游戏技术实际到精品我的项目案例解说等等。还邀请了泛滥具备多年从业教训的业界大佬和驰名游戏公司合作伙伴撰稿,涵盖面广、品质颇高。更有每年侑虎科技UWA主办的UWA DAY游戏开发者大会的优良内容。 UWA问答:帮忙开发者找到更好的答案。是游戏开发者们日常沟通交流技术问题的良好平台。在这里你对游戏制作方方面面的任何问题,都可能失去探讨或解答。通过最间接的技术交换来少走弯路、取得通过实际测验的开发教训,让确定和解决问题的过程变得简略许多。 UWA开源库:帮忙开发者发现更好的解决方案。不断更新和整合了各种起源的开源我的项目,翻译结束、分类清晰、图文并茂,为开发者提供了更加不便地搜寻发现和获取开源资源的渠道。 UWA Blog:蕴含UWA精选的各类技术文章、揭示学堂优质内容上新、整合问答优质问题、介绍UWA产品和其更新等各类UWA社区动向。 3.2 多种多样的辅助工具工欲善其事必先利其器。应用工具取得间接准确的性能数据,能更加直观地反映出性能瓶颈,从而达到事倍功半的成果。 随着业界的倒退和成熟,各大引擎、IDE和硬件厂家都推出并更新着本人的性能剖析辅助工具,也有不少开发者或团队喜爱依据本身我的项目的状况开发本人的性能监控插件。 以引擎而言,Unity Profiler是很多开发者都会用且在用的工具,能够记录并查看CPU/渲染/内存等各模块的实时耗费参数。而Unreal 4中也已提供了相似的性能。 以IDE而言,Windows、MAC OS/iOS、Andriod平台都别离有实用的工具,如Visual Studio的性能探查器、XCode的Instruments、Android Studio的Profiler等。 以硬件而言,SnapDragon Profiler、XCode Metal Frame Capture、Mali Graphics Debugger等品种就更多了,不一一赘述。 总而言之,依据开发者的应用熟练度、不同硬件平台的兼容性、工具性能的覆盖面等等因素,开发者在筛选适合的性能优化辅助工具时很容易挑花眼。 3.3 UWA工具那么有没有一款简略易用、兼容性强、性能全面的性能优化辅助工具呢? 当然有啦—— UWA GOT Online工具能够通过UWA提供的SDK疾速集成到测试项目中,在真机上实现测试流程后,在极短的工夫内实现数据的上传和解析,从而主动生成一系列可视化的图表。与此同时,还基于UWA丰盛的优化教训和宏大的数据库进行评分,依据各个性能模块提供针对性的剖析倡议和性能参数的变化趋势。 不仅如此,UWA还提供了GPM、GOT、真人真机测试服务、在线AssetBundle检测、本地资源检测、GPU专项报告和深度优化等工具或服务,从不同维度满足开发者的优化需要。 4. 如何解决问题性能的第二大步骤就是解决问题。在有了常识教训和上述工具的加持下,咱们的我的项目越是简单、越是来到前期,就越是会面临一条长长的优化问题清单。在无限工夫里正当地布局并缩短这条清单,就是咱们面临的下一项挑战。 4.1 优化工作优先级在进行具体的优化工作前,正当的布局往往能节俭大量总体工夫。 布局工作的第一点就是要抓住问题的主要矛盾。在一条长长的优化清单中,把每一项条目按程序齐全优化结束往往是不事实的。此时就须要重新整理这份清单,依照优先级从新进行排序,为优化性价比更高的问题投入更多。 对于优先级的判断,UWA认为次要参考两点:一是重要性,如果一个问题对以后场景的性能压力影响远大于其余因素、显然是主要矛盾,如果不予修改很难维持失常的游戏体验,那么就应该投入更多的人力工夫集中优先解决;二是易操作性,如果一个问题造成的性能压力相对来说并不大,但只是消耗极少功夫的举手之劳,比方只是在引擎设置中敞开一个开关,就应该优先把它改掉。 4.2 性能和成果的衡量第二点则是一种取舍,针对资源和渲染等局部的优化往往会影响到游戏的体现成果。为此,程序员往往须要和策动、美工共事增强沟通,在两者之间找到一个平衡点。这种均衡是一种动态平衡,须要结合实际状况操作。比方,一款吃鸡游戏画面变动频繁、且须要晦涩的战斗操作,那么对体现的要求能够适当低一些;而一款养成类游戏把精美的角色作为卖点,其模型和动画就须要十分粗劣,而就义一部分性能。哪怕在同一款游戏中也能够在过场动画和角色展现界面中重视体现,而在战斗场景中关注性能,须要具体问题具体分析。 总的来说,游戏我的项目的优化最终目标是可能在玩家硬件上顺利运行,而为此放弃很大一部分体现成果,也是大多数开发团队的理论抉择。 4.3 分级策略和制订规范第三点,在每一类机型上都达成上述的均衡就是咱们的分级策略。咱们大能够在高端机乃至旗舰机上开启后处理、应用高分辨率高模;但在中低端机型上则应用另外一套规范。 制订分级策略同样须要咱们依据我的项目类型调整。更细节精确的分级还应参考我的项目发行指标市场的硬件机型和同类游戏的分档教训,具体能够参考文章《基于挪动设施算力的UWA性能规范零碎上线》。 另外,开发团队还最好指定一套详尽正当的规范来治理束缚整个开发流程。最常见的就是指定美术资源规范,从而极大水平防止内存问题的失控。Unity还提供了回调函数在资源导入时就进行归一化设置,具体也能够在UWA学堂中找到相干课程《自动化标准Unity资源的实际》。 4.4 UWA倡议布局结束后就来到了具体的优化工作。这个工作简短且须要急躁,而UWA很乐意在这个过程中持续提供帮忙: 你当然能够在UWA社区中寻找办法,或者是在UWA工具的自动化报告中找到解决方案。你还能够分割UWA工作人员,从而提供真人真机测试报告1对1解说服务或者是更进一步地深度优化服务。UWA的引擎技术团队将对游戏我的项目进行更加具体的诊断和倡议。 5. 如何继续监控性能在迈出了性能优化的前两步后,很多开发团队都想要或者走出第三步,也就是继续监控性能的这一步。为此,一些团队安顿专门的引擎优化人员、尝试开发本人的DevOps工具。咱们认为这是一种游戏研发的“工业化”趋势的体现。 ...

June 16, 2022 · 1 min · jiezi

关于unity:Unity-C-反射性能优化

原文地址:https://segmentfault.com/a/11... 本篇内容次要是讲如何优化获取或设置对象字段和属性的反射性能。 先上论断:1.反射性能很差,比间接调用慢成千盈百倍。2.反射性能优化的思路是绕开反射。3.反射性能优化形式有:字典缓存(Dictionary Cache)、委托(Delegate.CreateDelegate)、Expression.Lamda、IL.Emit、指针(IntPtr) 等各种形式。4.性能排名:间接调用 > (IL.Emit == Expression) > 委托 > 指针 > 反射5.通用性排名: 反射 > (委托 == 指针) > (IL.Emit == Expression) > 间接调用6.综合排名:(委托 == 指针) > (IL.Emit == Expression) > 反射 > 间接调用 IL.Emit本是性能最优计划,但惋惜IL2CPP模式不反对JIT 所有动静生成代码的性能都不好用 故IL.Emit无奈应用、Expression.Lamda也是同样的起因不可用。退而求其次选用性能和通用性都不错的委托,因为委托只能优化办法无奈用于字段,所以又加上了指针用于操作字段。 性能测试:以设置对象的属性Name为测试条件(obj.Name = "A")SetValue办法都调整为通用的SetValue(object target, object value)状态。除了反射,所有调用办法都创立委托后再调用,省略查找委托缓存的步骤。在Release模式下,循环屡次调用,查看耗时。如果间接调用耗时约为:1ms,其余形式耗时约为: 间接调用IL.EmitExpression委托(Delegate)反射(MethodInfo)1ms1.3ms1.3ms2.1ms91.7ms指针只操作字段, 不参加该项测试 如果测试条件改为每次获取或设置值都须要通过Dictionary从缓存中查找字段所对应的委托办法后调用,那么工夫将大大增加,大部分工夫都节约在了字典查找上,但这也更贴近咱们失常应用的状况。 间接调用=IL.EmitExpression委托(Delegate)反射(MethodInfo)1ms8.8ms8.8ms9.7ms192.5ms设置string字段测试: 间接调用=IL.EmitExpression指针反射(MethodInfo)1ms8ms8ms9ms122ms委托只操作属性, 不参加该项测试 从测试后果能够看出间接调用 > IL.Emit(Expression与IL统一) > 委托(指针与委托互补)> 反射 基本上只有能绕开反射,性能都会有显著的晋升。 IL.Emit长处:性能十分高。毛病:不反对IL2CPP。 IL.Emit通用工具类: 用法: var getName = ILUtil.CreateGetValue(type, "Name");var setName = ILUtil.CreateSetValue(type, "Name");setName(obj, "A");string name = (string)getName(obj);代码: ...

June 13, 2022 · 10 min · jiezi

关于unity:技术分析探讨大世界游戏的制作流程及技术前期流程篇

本文旨在从全面的角度去剖析制作一款大世界游戏所须要具备的质素。所以从初期的设计和策动过程、美术设计环节、美术制作环节、渲染技术和自动化等方方面面都会在本文中进行一个对立的议论。 在过来的十年里,凋谢世界游戏失去了迅速的倒退和成长,从晚期最胜利的作品《GTA4》到当初的《荒野大镖客2》,咱们曾经看到游戏场景的规模和成果都失去极大的晋升。 那么,一个宏大的游戏世界是如何被发明进去的呢? 以前制作一个凋谢宏大的场景,靠的是艺术家们手工一步一步制作进去的,他们要去思考整个地图的布局、高空的构造和渲染以及高空上的修建物体是如何出现的,玩家的流动范畴内看到的所有景观元素,从远到近所有的细节都须要艺术家们牢牢把控好。典型的代表如:《GTA4》。这显然是一个宏大的工作。 起初的《身败名裂》采纳关卡组合拼接的办法,事后制作多组场景部件,再依据特定的规定拼接组合出大世界。但显然,其毛病是重复性高。 再到当初的《刺客信条》系列、《漫威蜘蛛侠》、《荒野大镖客2》等,程序化/自动化的制作技术越发成熟。 凋谢世界的制作是一个极其简单的领域,波及到的技术面十分宽泛。没有一套零碎且迷信的流程是很难撑持整个凋谢过程的。 侥幸的是,过来10年,以育碧为代表的一批厂商致力于钻研大世界场景的制作技术,程序化的制作流程逐步欠缺,开发速度取得极大晋升。 育碧在凋谢世界方面的技术水平始终是解决世界领先地位,旗下《刺客信条》、《孤岛惊魂》、《幽灵口头》等系列作品一直地推动着凋谢世界制作技术的办法。 接下来咱们将围绕这些作品展开讨论。 育碧最新作《刺客信条:奥德赛》是系列的集大成者。以下内容摘自其主创团队对于该我的项目制作过程的教训分享。 设计阶段一、前提信息收集 信息的收集,通过大量的实地勘察、拍摄、电影、文学作品等收集重要的构建游戏世界的重要元素。确定视觉格调和支柱。不是常见的希腊世界中灰蒙蒙的、棕色的、低饱和、单色的、平坦的。而是一个具备生态系统多样性的世界,充斥着各种茂密生物群落、蓝天碧水、人文景观等元素,这是咱们想要出现给玩家的希腊。二、确立艺术领导准则:确保发明出最多样化的世界 生机多彩的:玩家将会体验到一个被群山和陆地盘绕的雄伟世界,展现出系列品牌中从未见过的雄伟风光。充斥比照和动静。雄伟史诗感。“比照”是整个游戏世界形成的重要艺术领导准则,玩家能在玩耍中感触到这种变动,到处充斥着比照:天然与人、海洋和陆地、感性和神话、漂亮和和平、斯巴达和雅典。 三、“图纸”制作阶段 钻研学习大量的常识,整合所有信息,制作图纸Paper Map。 初步划分地区并记录相干信息:政治、文化特点、经济、军事实力、海拔、生态地貌和生物群落等,制作一系列的图纸。 团队其余环节会依据图纸Paper Map来开展相干工作,如:故事世界观、文案、玩法设计等。能够看到世界地图的迭代过程,上方最左边的格子是最终游戏的地图 细化地图,标记海拔、生态地貌、生物群落等 世界区域以蜂窝式的形态划分,相似于真实世界国家、城市边界划分。每个区域有本人独立的信息。 此外,随着进度推动,图纸会进一步细分到城镇的布局,用简略的线框划分出城市每个区域局部的重点内容,赋予其相应的性能。 典型的例子如城市的布局:卫城、富人区、贫民窟、文化区、妓院、农耕区等。 四、依据“图纸”的指引,地形艺术家开始雕刻天然地貌,构建这个世界的雏形。 地形是咱们所有所有的前提,在确定地形之后能力布局城市和路线。地形艺术家能够发展工作,利用Houdini、World Machine等软件导入和雕刻整体地形,而后在引擎中进一步将地形细化成形。 值得注意的是,这一步骤中地形艺术家的次要责任是让图纸的构想疾速成型,以便为各个环节(美术和策动)及时提供正向的反馈,以验证设计的合理性。这时候还不是美术制作团队染指量产资源的时候。 地形的制作流程将会稍后重点讲述。 五、美术设计和制作环节开始大范畴染指 地形开始成形后,美术设计环节的人员开始依据图纸上布局好的生物群域、气象类型和海拔高度等等因素来划分生物群落和地表物质组成。 为了确保游戏世界的视觉多样性,尽量保障两块相邻区域的次要生态系统类型要有具体的对比性。上图展现了7大生物群域的划分以及海拔维度的散布状况。(其中一个为水生物群域) 通过大量的照片钻研和学习,建设这样相似于海拔带的生物群落散布示意图,来提供程度和垂直两个维度的群落散布。失去各种气象、生态环境下的群落散布和地质构造。 以原画设计为领导,美术制作环节的人员开始工作,包含模型、材质等。材质设计师以及TA开始构建地表、天然材质 场景制作环节,如有条件最好细分一下工作:植被、岩石、修建等。业余的人做最业余的事 六、灰盒测试阶段 在地形艺术家的致力下,地形开始成形,并且具备根本的地表材质和植被。 关卡设计师(策动)开始进行灰盒测试阶段,用简略的Box堆砌出城市布局,以疾速验证地形与城市、路线的布局、关卡设计是否正当,期间对地形进行调整。 进行此阶段后,每两周一次Review进行审查进度。 七、美术量产阶段 确定好构造布局后,接下来开始细化,进入传统的制作流程:原画概念->模贴制作->场景编辑。 下一篇《大世界制作技术及流程》会从TA、场景制作角度为大家介绍大世界地形的制作流程、渲染技术等等内容。 这是侑虎科技第1140篇文章,感激作者Kerry供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:793972859) 作者主页:https://www.zhihu.com/people/... 再次感激Kerry的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ群:793972859)

June 9, 2022 · 1 min · jiezi

关于unity:DOTS的组成部分

本系列文章应用Unity最新版本Unity 2019.3.0f6,大家能够感觉到Unity 2019.3公布得十分慢,其实有个重要起因就是他们引擎外部也在全面转DOTS,DOTS能够给Unity引擎带来微小的性能晋升。2020年我置信DOTS会在世界范畴内全面开花结果,各大游戏公司也会转向DOTS开发。 DOTS的全称是Data-Oriented Tech Stack (DOTS),翻译过去就是多线程式数据导向型技术堆栈(DOTS)。原理就是充分利用CPU多核处理器,让游戏解决的速度更快,更高效。 Unity官网分享了一个来自瑞典的游戏工作室的教程,他们工作室的一个我的项目须要海量的僵尸角色在屏幕中,配角能够发射大量的弹道和僵尸交互,我的项目有了性能优化所以不得不采纳了DOTS计划。 如下图所示,是对他们的采访,可见他们应用DOTS的计划带来了微小的效率优化。 这里再插播一句,2019年12月,在北京UUG上我也分享过一期DOTS的视频。 可能大家对效率高没有什么概念,咱们从他们提供的一组数据中来看看DOTS到底能晋升多大的效率,如下图所示,场景中一共搁置2000个游戏对象进行同步坐标操作。 传统游戏对象形式须要9毫秒。应用ECS只须要1毫秒,性能晋升了9倍。ECS+Job System只须要0.2毫秒,性能晋升了45倍。ECS+Job System + Burst Compiler 场中搁置2W个游戏对象,仅须要0.04毫秒,性能晋升了2250倍。 在正式进入学习之前,首先咱们看一张图,如下图所示看看神秘的ECS写法和传统MonoBehaviour有什么区别。其实就是把本来MonoBehaviour中数据局部和逻辑局部分拆在IComponentData和ComponentSystem中实现。 通过这个简略例子我置信大家曾经对学习ECS充斥了信念,那咱们就正式开始啦! 文章简介DOTS全称是Data-Oriented Tech Stack,翻译过去就是多线程式数据导向型技术堆栈(DOTS),它由工作零碎(Job System)、实体组件零碎(ECS)、Burst Compiler编译器三局部组成。DOTS保障雷同类型组件在内存中都是顺序排列,极大水平减少缓存的命中率,此外配合工作零碎(Job System)让开发者无需头疼多线程同时拜访数据须要手动加解锁的麻烦,最终加持Burst Compiler让性能飞起来。文本以最新的Unity 2019.3版本,学习DOTS的原理、ECS零碎原理、Burst Compiler原理和JobSystem原理,带你把握DOTS让游戏性能晋升2000倍的机密。 适宜读者对DOTS感兴趣的开发人员想要疾速入门及把握DOTS的技术人员心愿将现有游戏转成DOTS的开发人员 你将取得DOTS设计的核心思想Unity 2019.3新版本DOTS的重大更新ECS零碎原理、Burst Compiler原理、JobSystem原理介绍 具体课程可返回《DOTS深度钻研之原理剖析篇》查看。 1.1 DOTS的组成部分通过后面的数据咱们能够发现ECS + Job System + Burst Compiler的性能几乎逆天,这三个货色加在一起就是DOTS的终极性能体现,DOTS次要由这三局部组成: 工作零碎(Job System),可用于高效运行多线程代码。实体组件零碎(ECS),用于默认编写高性能代码。Burst Compiler编译器,用于生成高度优化本地代码,提供单指令多数据(SIMD)。另外,Unity的H5技术计划Tiny也是齐全基于DOTS的。DOTS的应用非常灵活,齐全能够只用ECS或者ECS + Job System 或者ECS + Job System + Burst Compiler,灵便的益处就是移植性,当初有很多产品都曾经开发了一半,如果一上来就全副切到DOTS意味着全都要推倒重来,这是不事实的。 这些开发了一半的产品就能够思考选择性应用DOTS,比方游戏中有一些大量计算带来的耗时操作,咱们能够应用Job System把它放入多线程中。如果场景须要有些大量的模型元素,比方航行射击类游戏的子弹,为了不影响原有框架的构造,能够让子弹应用ECS + Job System + Burst Compiler,别的还是采纳原始游戏对象的形式,这样将大大减少移植的工夫。 ...

May 27, 2022 · 1 min · jiezi

关于unity:打造爆款游戏互动体验拍乐云Unity实时语音了解一下

玩家之间的实时语音互动是互联游戏的必备性能,拍乐云近日推出 Unity 实时语音解决方案,帮忙游戏厂商和开发者在接入游戏引擎的同时疾速实现跨平台游戏中的社交互动模块,带给玩家更沉迷式的互动体验,享受美妙的游戏时光。Unity 作为一个弱小的跨平台开发引擎,反对手机、PC 等多个终端,为开发者提供丰盛的交互式 2D、3D、VR 和 AR 体验的工具,是支流游戏引擎。基于此,拍乐云也心愿为寰球 Unity 开发者提供实时语音 SDK,晋升开发效率、节俭技术老本,进而促成玩家活跃度、留存率和游戏黏性。 PART 01 拍乐云Unity实时语音个性在目前风行的基于 Unity 引擎的爆款游戏中,如《王者光荣》、《炉石传说》、《永劫无间》等,实时语音都是其外围模块,用于战队沟通攻守策略、玩家间互动聊天,可能影响战局体现及玩家的游戏体验。但现阶段游戏场景中的实时语音性能还存在一些体验问题,比方遇到弱网场景产生的提早和丢包,声音的卡顿、回声和噪声的烦扰等。那么,拍乐云的Unity实时语音有哪些个性呢? 高清音质,盘绕立体声高清音质能给玩家互动带来更佳的体验,拍乐云反对 48kHz 音频采样率,实现 Full Band 超高音质,并反对全链路双声道,实现 360° 盘绕立体声成果,让游戏玩家的声音具备方位感和距离感。领有业界一流的 3A 算法,智能适应各类环境,全面打消回声、打消乐音,在嘈杂环境下实现音频的自动增益,保障优异的单讲和双讲体现,为游戏玩家提供极致的实时语音体验。 超低时延,寰球笼罩拍乐云自建的 Pano Backbone 实时传输减速网络,采纳自建DC+多云+POP节点的混合计划,笼罩寰球 200+ 国家及地区,失常网络下端到端最低时延能够达到 68ms,保障游戏玩家在开释技能、沟通策略的实时性。同时,Pano Backbone 能够做到实时探测链路品质,自适应调整组网与路由策略,客户端就近接入,高效解决最初一公里问题,实现专线级别的稳固品质。 高可用,高并发Pano Backbone 在架构设计上领有高可用、高并发等个性,保障99.95%可用率。多数据中心接入,多链路备份,多集群灾备,实现故障无缝切换,用户无感知。高并发架构设计,领有年服务数亿分钟级教训,动静负载监控,实现自动化无缝扩容。同时,单频道能够反对百万人在线、万人连麦,轻松应答爆款游戏流量激增。 拥塞管制,稳固晦涩拍乐云自研的弱网反抗算法,领有前向纠错(FEC)、丢包重传(ARQ)、丢包暗藏(PLC)等三大抗弱网保障超强抗丢包能力。在简单的互联网环境下,通过自适应学习模型,精准预测网络可用宽带,调整音频码率,防止网络呈现拥塞,网络抖动时仍然可能保障音频的晦涩。 丰盛乏味的音效在高清音质的根底之上,拍乐云还提供美声和变声音效。通过扭转声音频率,实现12种声音丑化和扭转音色成果,也反对将用户的声音朝着特定的方向进行调整以达到变声成果,如大叔、小姐姐、萝莉、惊悚音、困兽音等,通过变声蛊惑对手,享受游戏中的趣味。此外,还反对播放背景音效,叠加气氛音,比方鼓掌、起哄、难堪等,沉闷游戏气氛。 无感切换分组互动在同一个频道内开启多个分组,在主会场和分组之间做到毫秒级无感切换,让在线群聊互动的模式更灵便、更多元。比方两个战队在互不烦扰的环境下各自商议战术,主播能够随时退出战队分组,直播端观众能够任意抉择战队分组或主播主会场抉择收听,既有利于游戏氛围的衬托,又能让观众更有沉迷感。 PART 02 三步实现Unity实时语音性能1.创立Pano账号在 Pano控制台注册账号,并创立本人的测试项目(https://developer.pano.video/...),获取到 App ID和长期 Token。 2.下载Pano Unity Audio SDK在Pano官网下载 Pano Unity SDK(https://developer.pano.video/...) 3.运行Pano Unity Demo把下载的sdk解压,而后:把SDK中 libs/Android/ 下的内容,复制到我的项目的 Assets/PanoRtcEngine/Plugins/Android/ 文件夹下 把SDK中 libs/iOS/ 下的内容,复制到我的项目的 Assets/PanoRtcEngine/Plugins/iOS/ 文件夹下 把SDK中 libs/macOS/ 下的内容,复制到我的项目的 Assets/PanoRtcEngine/Plugins/macOS/ 文件夹下 ...

February 15, 2022 · 1 min · jiezi

关于unity:Unity中Compute-Shader的基础介绍与使用

前言Compute Shader是现在比拟风行的一种技术,例如之前的《天刀手游》,还有最近大火的《永劫无间》,在分享技术的时候都有提到它。 Unity官网对Compute Shader的介绍如下:https://docs.unity3d.com/Manu... Compute Shader和其余Shader一样是运行在GPU上的,然而它是独立于渲染管线之外的。咱们能够利用它实现大量且并行的GPGPU算法,用来减速咱们的游戏。 在Unity中,咱们在Project中右键,即可创立出一个Compute Shader文件: 生成的文件属于一种Asset文件,并且都是以 .compute作为文件后缀的。 咱们来看下外面的默认内容: #pragma kernel CSMainRWTexture2D<float4> Result;[numthreads(8,8,1)]void CSMain (uint3 id : SV_DispatchThreadID){ Result[id.xy] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);}本文的次要目标就是让和我一样的萌新可能看懂这区区几行代码的含意,学好了根底才可能看更牛的代码。 语言Unity应用的是DirectX 11的HLSL语言,会被主动编译到所对应的平台。 kernel而后咱们来看看第一行: #pragma kernel CSMainCSMain其实就是一个函数,在代码前面能够看到,而kernel是内核的意思,这一行即把一个名为CSMain的函数申明为内核,或者称之为核函数。这个核函数就是最终会在GPU中被执行。 一个Compute Shader中至多要有一个kernel才可能被唤起。申明办法即为: #pragma kernel functionName 咱们也可用它在一个Compute Shader里申明多个内核,此外咱们还能够再该指令前面定义一些预处理的宏命令,如下: #pragma kernel KernelOne SOME_DEFINE DEFINE_WITH_VALUE=1337 #pragma kernel KernelTwo OTHER_DEFINE 咱们不能把正文写在该命令前面,而应该换行写正文,例如上面写法会造成编译的报错: #pragma kernel functionName // 一些正文 RWTexture2D接着咱们再来看看第二行: RWTexture2D<float4> Result;看着像是申明了一个和纹理无关的变量,具体来看一下这些关键字的含意。 RWTexture2D中,RW其实是Read和Write的意思,Texture2D就是二维纹理,因而它的意思就是一个能够被Compute Shader读写的二维纹理。 如果咱们只想读不想写,那么能够应用Texture2D的类型。 咱们晓得纹理是由一个个像素组成的,每个像素都有它的下标,因而咱们就能够通过像素的下标来拜访它们,例如:Result[uint2(0,0)]。 同样的每个像素会有它的一个对应值,也就是咱们要读取或者要写入的值。这个值的类型就被写在了<>当中,通常对应的是一个RGBA的值,因而是float4类型。通常状况下,咱们会在Compute Shader中解决好纹理,而后在FragmentShader中来对解决后的纹理进行采样。 ...

October 18, 2021 · 5 min · jiezi

关于unity:学习unity做自己的游戏

unity简介是什么Unity是由Unity Technologies研发的跨平台2D/3D游戏引擎,可用于开发Windows、MacOS及Linux平台的单机游戏,PlayStation、Xbox、Wii、任天堂3DS和Switch 等游戏主机平台的视频游戏,以及iOS、Android等挪动设施的游戏。 Unity所反对的游戏平台还延长到了基于WebGL技术的HTML5网页平台,以及tvOS、Oculus Rift、ARKit等新一代多媒体平台。除能够用于研发电子游戏之外,Unity还宽泛用作修建可视化、实时三维动画等类型互动内容的综合型创作工具。 次要个性可视化编程界面多平台开发和部署主动资源导入游戏成果好,性能卓越多人网络联机性能由第三方包提供为什么抉择unity市场占有率较高的游戏开发引擎有 unity 和 ue4 ,那样为什么咱们要抉择 unity? 从老本,效率,研发几个角度探讨这个问题: unity 开发同样规模的产品,须要的金钱老本是不到UE一半,极其状况下甚至不到2成。unity开发效率更高,java,C++,C#程序都能很快上手开发unity。unity的跨平台以及商店能帮忙小团队填很多坑,而且unity有大量的材料能够查问,对于大部分人来说程序 = Google + 英语,而不是仅是算法加数据结构。unity能做什么应用领域手游,像王者光荣网游端游VR游戏虚构仿真汽车修建电影动漫能找什么工作游戏方向:分为传统游戏,如手机游戏。以及VR/AR游戏。行业利用方向:VR/AR利用,如医疗,影视,机械等行业。虚构仿真:修建可视化,产品设计交互,室内设计可视化等。unity学习路线unity 学习次要是两大块,C# 语言和unity引擎,能够按上面的路线学习 跟着视频学做游戏,不要钻语言,先让本人取得成就感。复盘,反推成果的实现形式,钻研他人是如何实现的,加深你对 unity 的了解。模拟,从模拟性能到模拟整个游戏,找你玩过的或者当下热门的游戏,模拟它,这期间你会遇到各种各样的问题,也是晋升最快的阶段。享受成绩,这一阶段你曾经能从容驾驭 Unity 和代码,能够自在地把脑海中的想法实现进去,开始享受发明的高兴。我想,做技术的乐趣莫过于此。file完! 您的点赞、评论、关注是对小编最大的激励 O(∩_∩)O 我是极客猿小兵,公众号【极客猿】,记录独立开发者学习成长,一起走向财产自在。

September 13, 2021 · 1 min · jiezi

关于unity:Unity-全流程开发热门游戏BallSort助力迈入游戏高薪领域

download:Unity 全流程开发热门游戏BallSort,助力迈入游戏高薪畛域func TestQuickSort3(t *testing.T) { values := []int{5} QuickSort(values) if values[0] != 5 { t.Error("QuickSort() failed. Got", values, "Expected 5") } } bubble_test.go // bubble_test.go package bubblesort import "testing" func TestBubbleSort1(t *testing.T) { values := []int{5, 4, 3, 2, 1} BubbleSort(values) if values[0] != 1 || values[1] != 2 || values[2] != 3 || values[3] != 4 || values[4] !=5 { t.Error("BubbleSort() failed. Got", values, "Expected 1 2 3 4 5") } } ...

September 11, 2021 · 1 min · jiezi

关于unity:技术盘点Unity-SDK-开发中有哪些大坑

Untiy作为游戏引擎和内容开发平台,吸引了泛滥游戏开发者,基于其开发的游戏更是不胜其数。具体请参见1。 环信作为当先的即时通讯云服务商,在游戏行业也进行了继续的摸索和研发投入。在产品公布的晚期(2015年)就推出了Unity SDK,帮忙游戏开发者疾速实现游戏场景下诸如世界频道,游戏公会、组队群聊,1对1私聊等性能,平安稳固的服务也为游戏玩家带来了极佳的实时沟通体验。 2021年第二季度,环信IM Unity SDK进行了重构改版,环信IM Unity SDK 2.0正式公布,次要改良包含如下: 1、迭代更新,更加实用的API接口 2、IM+Push加强性能的补全 3、C#语言层面引入了版本7.0 – 9.0之后的一些新语法改良 4、特地的,减少了PC端Unity Editor环境下编译调试反对,大大晋升了开发效率 在过来的一段时间里,笔者也参加了相应的研发工作。在整个过程中,为了解决各种问题,不仅要到处翻阅材料,还要尝试各种办法和参数组合。其间也经验了各种程序解体甚至零碎解体,诡异的程序体现一次次让开发人员大刀阔斧,到处碰壁,当真像深夜里行走在迷宫之中,手里还拿着一个待破解的魔方。“此路不通,请绕行!”,是在一次次的尝试后无奈的感慨和难舍的放弃。而一旦问题最初失去圆满解决,又宛如飞入云端,以上帝视角鸟瞰一片片迷宫,所有又显得那么天经地义,简约琐细但又丝丝入扣,这样的苦尽甘来也算是做程序员能享受到的微小喜悦和满足。 不敢独享,特记录下一些心得供大家参考,也欢送.NET平台资深玩家批评指正。以下,Enjoy! 开发概览:非托管插件开发(Native/Unmanaged Plugin)Unity是基于Microsoft .Net Framework开发的游戏引擎2,它采纳了开源的.NET Platform,并依赖此框架来实现跨硬件设施和运行时(操作系统)的指标,也是所谓的”Write once, run anywhere”。在语言方面,Unity抉择C#作为次要的脚本编程语言,尽管.NET平台自身反对的语言有很多种。 进一步,Unity反对Mono和ILC2PP两种脚本框架(Scripting Backends)。特地的,Unity Editor采纳的是Mono脚本框架。 个别的,游戏类库开发者能够抉择间接用C#语言开发,指标类库能够实现基于.NET Framework根底性能之上的高级性能,这类插件称之为Managed Plugin(托管插件)。因为环信IM外围SDK曾经基于C++开发,因而咱们抉择另一种Native Plugin(本地插件)的形式,正是它把咱们引向了迷宫之旅。两种类型的Plugin介绍,参见3。 可怜的是,Unity网站上对于Native Plugin的相干介绍少只又少,想要理解它的具体细节还要去参考Microsoft MSDN文档。作为中规中矩的文档介绍,微软的文档是合格的,然而,当你真正上手编程时就会发现,这些远远不够:上面记录的一些坑点就很难在相应的文档中失去间接的提醒;而要通过Google大法,联合其余程序员留下的蛛丝马迹,再加上本人一直的调试来最终确认。 在微软文档上下文中,Unity Native Plugin有个另外的名字:Unmanaged Plugin,即非托管插件。简略来讲,Managed Plugin生存在.NET Framework的运行时环境(相似于Java的JVM),而Unmanaged Plugin则生存在这个运行时环境之外,也即和运行时环境是兄弟的关系。如果你本来的类库实现满足微软的COM(Component Object Model)标准,那天然最好是应用COM Interop4的互操作形式;而环信IM SDK自身是纯C++实现,因而采纳了Platform Invoke5(简称P/Invoke)形式,本文剩下的内容均是基于P/Invoke。 下图则概要形容了Managed和Unmanaged区域代码之间相互操作的形式: 更具体的,为了实现对于Unmanaged DLL function的调用,只须要简略的4步6: 1、确认DLL类库中须要被操作的函数; 2、创立一个C#类来关联被操作的这些函数(给函数穿上一个马甲,以便集中管理和重复调用); 3、应用DllImport标记在受管侧(C#)定义函数原型; 4、在受管侧随便调用相干非托管区域函数。 上图中,Standard marshalling service即负责将数据在两个区域进行封装/解封装传送(marshall/unmarshall),它次要定义了数据在两个不同内存区域进行拷贝(Copy)和援用(Reference)的规定7,而迷宫中的坑次要是和这些具体规定无关。 坑王驾到之封送(Marshall/Unmarshall)中的那些坑坑一:sizeof(bool) = ?绝大多数的根本类型属于Blittable Types8:如System.Byte, System.Single等。System.Boolean尽管不属于Blittable types,然而Standard Marshalling Service默认将其转换为1,2,4字节的内存存储,当其值为true时,其对应的值为1。如果你想当然的间接将System.Boolean映射到Unmanaged侧的bool类型而不做特地解决的话,你并肯定会了解碰到编译或者运行时谬误,然而如果你严格的测试每个字段是,会诧异的发现这些bool值跟你设想的不尽相同:有时正确,有时谬误。 ...

August 25, 2021 · 2 min · jiezi

关于unity:Unity-全流程开发热门游戏BallSort助力迈入游戏高薪领域

Unity 全流程开发热门游戏BallSort超清原画 残缺无密 下载地址:网盘下载 游戏市场再次掀起新的浪潮,人才紧缺,薪资迷人。Unity作为当下爆火的游戏开发引擎,非常适合对游戏开发感兴趣的人员入行!但Unity易上手难精通,本课程将带大家在我的项目实战中疾速把握游戏开发全流程,及Unity核心技术,最终领有一款属于本人的线上游戏作品。 适宜人群游戏开发爱好者独立游戏开发者 技术储备要求有C#开发根底相熟面向对象编程思维 技术参数操作系统: windows 10 / Unity版本: 2020.2.1f1c1以上版本IDE版本: Android studio 4.2.1 / JetBrain Rider 2019.3.4章节目录:第1章 课程介绍与学习指南 2 节 | 10分钟本章作为课程内容引入,次要介绍课程所波及的技术及实战我的项目,课程的学习办法以及课程内容具体安排,心愿大家都能通过这门课程,学有所成,学有所归。 收起列表图文:1-1 课前必读((不看会错过一个亿))视频:1-2 Unity 全流程开发热门游戏BallSort-导学 (09:10)第2章 BallSort游戏工程搭建 4 节 | 53分钟本章次要解说Unity装置和我的项目工程的搭建、我的项目目录构造构建,把握Unity 2D和3D我的项目的差别,解说Unity内置的资源Resources和Assetbundle。 收起列表视频:2-1 工程搭建 (12:24)视频:2-2 创立Unity工程 (15:12)视频:2-3 资源导入(一) (12:14)视频:2-4 资源导入(二) (12:25)第3章 游戏需要剖析及设计 2 节 | 15分钟本章次要通过展现demo,介绍如何应用性能拆解法对指标需要进行剖析,通过分层设计,展现如何将一个我的项目通过层层拆解,合成为多个小的易实现的单元,进步代码的可读性和可维护性,展现最终的架构设计后果,为编码做好筹备。 ... 收起列表视频:3-1 BallSort游戏之需要剖析 (08:37)视频:3-2 性能拆解 (05:40)第4章 游戏场景搭建 5 节 | 73分钟解说手机屏幕适配常识,蕴含Canvas、Canvas Scaler、Rect Transform,让学员相熟游戏UI如何适配不同尺寸的手机屏幕;解说UGUI、Prefab预制体的用法,并率领学员搭建游戏页面(Home、Main、Win页面),创立小球、小球容器等Prefab预制体对象; 通过本章节的学习,能够让学员们疾速相熟游戏UI的搭建过程、把握UGUI组件的常... 收起列表视频:4-1 UI创立和适配 (17:01)视频:4-2 大厅界面 (21:15)视频:4-3 主界面,结算界面和菜单界面的制作 (12:40)视频:4-4 小球制作 (09:20)视频:4-5 瓶子和动画的制作 (12:29)第5章 游戏数据管理 4 节 | 47分钟本章次要介绍如何通过数据驱动来治理游戏内的数据,在Unity中JSON的用法,率领学员们实现JSON数据结构的定义、JSON转换工具的封装(JsonUtility),JSON是游戏数据管理中十分重要的一个技术点,把握JSON根底用法、JSON模型转换形式,对学员们技能晋升至关重要。... ...

August 4, 2021 · 1 min · jiezi

关于unity:Unity-全流程开发热门游戏BallSort助力迈入游戏高薪领域

download:Unity 全流程开发热门游戏BallSort,助力迈入游戏高薪畛域办法实现:123456789public static string ConvertXML(XmlDocument InputXMLDocument, string XSLTFilePath , XsltArgumentList XSLTArgs) { System.IO.StringWriter sw = new System.IO.StringWriter(); XslCompiledTransform xslTrans = new XslCompiledTransform(); xslTrans.Load(XSLTFilePath); xslTrans.Transform(InputXMLDocument.CreateNavigator(), XSLTArgs, sw); return sw.ToString(); }示例xml文件如下: 123456789101112<?xml version="1.0" encoding="utf-8" ?><sexystars> <category name="Bollywood"> <sexystar name="Antra mali" /><sexystar name="Deepika Padukone" /><sexystar name="Mandira Bedi" /></category> <category name="Hollywood"> <sexystar name="Jennifer Lopez" /><sexystar name="Jessica Alba" /></category></sexystars>示例xslt文件如下: 12345678910111213141516171819202122<?xml version="1.0" encoding="UTF-8" ?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output omit-xml-declaration="yes"/> <xsl:template match="sexystars"> <div> <xsl:apply-templates select="category" /></div></xsl:template> <xsl:template match="category"> <h2> <xsl:value-of select="@name"/></h2><ul> <xsl:apply-templates select="sexystar" /></ul></xsl:template> <xsl:template match="sexystar"> <li> <xsl:value-of select="@name"/></li></xsl:template></xsl:stylesheet>调用的c#代码: 123XmlDocument xDoc=new XmlDocument(); xDoc.Load(@"C:\sample.xml"); string returnhtml = ConvertXML(xDoc, @"C:\sample.xslt", new XsltArgumentList());将输出如下html: ...

August 3, 2021 · 1 min · jiezi

关于unity:Unity-全流程开发热门游戏BallSort

download:Unity 全流程开发热门游戏BallSort!/usr/bin/env python-- coding: utf-8 --import osimport subprocessimport shutil def main(): sdk_path = os.getenv('ANDROID_HOME')ndk_path = os.getenv('NDK_HOME')standalone_path = os.getenv('NDK_STANDALONE')directory = build_directory(sdk_path, args.output.filestem_str , native_shared_libs)# // Copy the additional native libs into the libs directory.for name, path in native_shared_libs.items(): shutil.copy( path, os.path.join( directory, "libs/", "armeabi/",name)# compile android_native_app_glue.ccmd = os.path.join( standalone_path , "bin/" , "arm-linux-androideabi-gcc ")arg1 = os.path.join( ndk_path, "sources/" , "android/" ,"native_app_glue/", "android_native_app_glue.c ")arg2 = " -c "arg3 = " -o "arg4 = directory + "android_native_app_glue.o"os.system(cmd + arg1 + arg2 + arg3 + arg4)"""calling gcc to link a shared object"""cmd = os.path.join(standalone_path , "bin/", "arm-linux-androideabi-gcc ")arg1 = passthrougharg2 = os.path.join( directory , "android_native_app_glue.o")arg3 = " -o " + os.path.join( directory ,"libs", "armeabi", "libmain.so")arg4 = " -shared"arg5 = " -Wl,-E"os.system(cmd + arg1 + arg2 + arg3 + arg4 + arg5 )"""call ant debug"""ant_command = "ant debug"os.system(ant_command )#copy apk file to required destshutil.copy( os.path.join( directory, "bin/", "rust-android-debug.apk"), output)def find_native_libs(args: &Args) -> HashMap<String, Path> { ...

August 3, 2021 · 2 min · jiezi

关于unity:Unity-全流程开发热门游戏BallSort助力迈入游戏高薪领域

download:Unity 全流程开发热门游戏BallSortpackage whu.extract.pubtime.core; import java.util.ArrayList;import java.util.Calendar;import java.util.Collections;import java.util.List;import java.util.regex.Matcher;import java.util.regex.Pattern; import whu.utils.TimeUtil; /** Created On 2014年3月13日 下午2:49:05@description 获取网页的公布工夫 */public class FetchPubTime { /** 示意url中间断的8位日期,例如http://www.baidu.com/20140311/2356.html */private static String url_reg_whole= "([-|/|_]{1}20\\d{6})";/** 示意 用-或者/隔开的日期,有年月日的,例如 http://www.baidu.com/2014-3-11/2356.html */private static String url_reg_sep_ymd = "([-|/|_]{1}20\\d{2}[-|/|_]{1}\\d{1,2}[-|/|_]{1}\\d{1,2})";/** 示意 用-或者/隔开的日期,只有年和月份的,例如 http://www.baidu.com/2014-3/2356.html */private static String url_reg_sep_ym = "([-|/|_]{1}20\\d{2}[-|/|_]{1}\\d{1,2})";private static Calendar current = Calendar.getInstance();/** 格局正确的工夫正则表达式*/private static String rightTimeReg = "^((\\d{2}(([02468][048])|([13579][26]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])))))|(\\d{2}(([02468][1235679])|([13579][01345789]))[\\-\\/\\s]?((((0?[13578])|(1[02]))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\\-\\/\\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\\-\\/\\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\\s(((0?[0-9])|([1-2][0-3]))\\:([0-5]?[0-9])((\\s)|(\\:([0-5]?[0-9])))))?$"; /** * @param url * @param urlContent * @return */public static String getPubTimeVarious(String url,String urlContent) { String pubTime = getPubTimeFromUrl(url); //链接外面没有,匹配文本中的 if(pubTime == null) { if(urlContent!=null&&!urlContent.trim().equals("")) return extractPageDate(urlContent); } return pubTime;} /**从url外面抽取出公布工夫,返回YYYY-MM-DD HH:mm:ss格局的字符串 * @param url * @return */public static String getPubTimeFromUrl(String url){ Pattern p_whole = Pattern.compile(url_reg_whole); Matcher m_whole = p_whole.matcher(url); if(m_whole.find(0)&&m_whole.groupCount()>0) { String time = m_whole.group(0); time = time.substring(1,time.length()); //每一步都不可能超出以后工夫 if(current.compareTo(TimeUtil.strToCalendar(time, "yyyyMMdd"))>=0) { return time.substring(0,4)+"-"+time.substring(4,6)+"-"+ time.substring(6,8)+" "+"00:00:00"; } } p_whole = null; m_whole = null; Pattern p_sep = Pattern.compile(url_reg_sep_ymd); Matcher m_sep = p_sep.matcher(url); if(m_sep.find(0)&&m_sep.groupCount()>0) { String time = m_sep.group(0); time = time.substring(1,time.length()); String[] seg = time.split("[-|/|_]{1}"); Calendar theTime = Calendar.getInstance(); theTime.set(Calendar.YEAR,Integer.parseInt(seg[0])); theTime.set(Calendar.MONTH, Integer.parseInt(seg[1])); theTime.set(Calendar.DAY_OF_MONTH, Integer.parseInt(seg[2])); if(current.compareTo(theTime)>=0) { return seg[0]+"-"+seg[1]+"-"+seg[2]+" "+"00:00:00"; } } p_sep = null; m_sep = null; Pattern p_sep_ym = Pattern.compile(url_reg_sep_ym); Matcher m_sep_ym = p_sep_ym.matcher(url); if(m_sep_ym.find(0)&&m_sep_ym.groupCount()>0) { String time = m_sep_ym.group(0); time = time.substring(1,time.length()); Calendar theTime = Calendar.getInstance(); String[] seg = time.split("[-|/|_]{1}"); theTime.set(Calendar.YEAR,Integer.parseInt(seg[0])); theTime.set(Calendar.MONTH, Integer.parseInt(seg[1])); theTime.set(Calendar.DAY_OF_MONTH, 1); if(current.compareTo(theTime)>=0) { return seg[0]+"-"+seg[1]+"-"+"01"+" "+"00:00:00"; } } return null;}

August 3, 2021 · 2 min · jiezi

关于unity:unity设置游戏中的怪物机械地左右移动

在游戏中咱们常看到怪物机械地来回挪动,这里提供一种实现办法。(1)首先将图片拖到Hierarchy窗口,主动创立一个gameObject,命名为monster(1).(2)为monster(1)绑定c#脚本。脚本内容如下:次要留神增加的两个Transform对象pos_left,pos_right,这两个对象是场景中的两个固定gameObject,通过pos_left.position.x和pos_right.position.x获取它们的x坐标来设置怪物左右挪动的边界。怪物超过左边界(怪物地位横坐标小于pos_left.position.x)则怪物变为面向右(faceLeftfalse)同时给怪物一个朝右的速度(rb.velocity = new Vector2(speed,rb.velocity.y)),怪物超过右边界(怪物地位横坐标大于pos_right.position.x)则怪物变为面向左(faceLefttrue)同时给怪物一个朝左的速度(rb.velocity = new Vector2(-speed,rb.velocity.y))。如上图就是这两个固定的pos_left,pos_right(hierarchy窗口中的物体名字为fixed(3),fixed(4)),因为它们的作用只是限度怪物挪动的范畴,所以它们图片很小很难被看见。(3)将怪物绑定的c#脚本的两个tansform对象设定为fixed(3),fixed(4)这样就能够实现物体的主动左右挪动。

July 1, 2021 · 1 min · jiezi

关于unity:Unity学习笔记FPS游戏制作1角色的移动旋转与推进上升

一,什么是FPS游戏第一人称射击类游戏,FPS(First-person shooting game), 严格来说第一人称射击游戏属于ACT类游戏的一个分支,但和RTS类游戏一样,因为其在世界上的迅速风靡,使之倒退成了一个独自的类型。FPS(First-person Shooting game)第一人称视角射击游戏顾名思义就是以玩家的主观视角来进行射击游戏。玩家们不再像别的游戏一样操纵屏幕中的虚构人物来进行游戏,而是身临其境的体验游戏带来的视觉冲击,这就大大加强了游戏的主动性和真实感。晚期第一人称类游戏所带给玩家的个别都是的屏幕光线的刺激,简略快捷的游戏节奏。随着游戏硬件的逐步完善,以及各种游戏的一直联合。第一人称射击类游戏提供了更加丰盛的剧情以及精美的画面和活泼的音效。 二,性能实现思路与过程(1)新建角色新建我的项目,载入或者新建角色模型与枪械模型,另外,因为我的项目视角是第一人称,所以倡议将枪械设为摄像头的子物体,并为了当前的对战性能的开发,同样倡议将实现之后的角色模型汇合以预制体的形式保留,实现之后的效果图如下(2)实现挪动性能的思路要实现键盘输入管制挪动性能,首先必须对键盘的输出进行实时获取,获取的办法,是在脚本的Update()中进行获取(PS:Update(): 当游戏正在运行,同时脚本是可用的,这个办法会在每帧刷新时调用),获取用户的输出,失去用户挪动矢量的模(挪动矢量的模即挪动间隔)。 /**获取用户挪动矢量的模的代码如下**/float _xMov = Input.GetAxisRaw("Horizontal");//获取程度方向输出float _zMov = Input.GetAxisRaw("Vertical");//获取垂直方向输出再乘以用户挪动矢量的规范矢量(PS:transform.right,transform.forward实质为规范矢量,且依据矢量的概念,一个非零矢量除以它的模,可得所需规范矢量。从而反推挪动矢量的规范矢量乘以挪动矢量的模可得出挪动矢量),获取挪动矢量,思考到玩家的挪动受垂直与程度两个挪动矢量的影响,玩家静止的繁难模型如下图依据矢量三角形法令可知,玩家挪动矢量等于程度矢量+垂直矢量(法令简略图如下) /**程度矢量,垂直矢量与玩家挪动矢量的计算代码如下**/Vector3 _moveHoruzontal = transform.right * _xMov;//程度挪动矢量=程度方向规范矢量*程度方向挪动矢量的模(键盘程度方向输出)Vector3 _moveVertical = transform.forward * _zMov;//垂直挪动矢量=垂直方向规范矢量*垂直方向挪动矢量的模(键盘垂直方向输出)Vector3 _velocity = (_moveHoruzontal + _moveVertical).normalized * speed;//将玩家挪动矢量归一化为规范矢量,在乘以而后将玩家挪动矢量归一化(normalized)为规范矢量,在将归一化的规范矢量乘以速度矢量的模,获取玩家速度矢量,最初将速度矢量乘以工夫加上以后玩家刚体的地位,得出玩家最初应该挪动到的地位。 (3)实现旋转性能的思路同理在脚本的Update()中进行获取用户的鼠标输出,将获取的鼠标X,Y轴挪动角度乘以用户视线旋转速度,失去摄像头与玩家刚体旋转角度 (4)实现推动回升性能的思路同理在脚本的Update()中进行获取用户的键盘输入,当监听到用户输出空格的时候,让角色刚体像正上方挪动。 (5)具体代码PlayerMoter.cs(代码如下) using System.Collections;using System.Collections.Generic;using UnityEngine;public class PlayerMoter : MonoBehaviour{ [SerializeField] private Camera cam;//摄像头 private Vector3 velocity = Vector3.zero; private Vector3 rotation = Vector3.zero; private float cameraRotationX = 0f; private float currentCamerRotationX = 0f; private Vector3 thrusterForce = Vector3.zero; [SerializeField] private float cameraRotationLimit = 85f; private Rigidbody rb; void Start() { rb = GetComponent<Rigidbody>();//获取刚体 } public void Move(Vector3 _velocity)//挪动 { velocity = _velocity; } public void Rotate(Vector3 _rotation)//旋转 { rotation = _rotation; } // Update is called once per frame void Update() { } private void FixedUpdate() { PerformMovement(); PeformRotation(); } public void RotarCamera(float _cameraRotation)//摄像头旋转 { cameraRotationX = _cameraRotation; } public void ApplyThruster(Vector3 _thrusterForce)//利用推进力 { thrusterForce = _thrusterForce; } void PerformMovement()//挪动 { if(velocity!=Vector3.zero) { rb.MovePosition(rb.position + velocity * Time.fixedDeltaTime);// } if(thrusterForce!=Vector3.zero) { rb.AddForce(thrusterForce * Time.fixedDeltaTime,ForceMode.Acceleration); } } void PeformRotation()//旋转 { rb.MoveRotation(rb.rotation * Quaternion.Euler(rotation)); if(cam!=null) { currentCamerRotationX -= cameraRotationX; currentCamerRotationX = Mathf.Clamp(currentCamerRotationX,-cameraRotationLimit,cameraRotationLimit); cam.transform.localEulerAngles = new Vector3(currentCamerRotationX, 0f, 0f); } }}PlayerControl.cs(代码如下) ...

June 25, 2021 · 3 min · jiezi

关于unity:Unity2D简单游戏飞机大战开发一

1.第一步:首先再unity的Assets下新建三个文件夹,别离为materials ,scripts ,textures。而后在textures文件夹中拖入三个图片,别离是飞机,敌机以及子弹。(能够去2D资源网等中央下载)2.第二步:为了让游戏运行时有更好的体验,能够设置窗口大小以及分辨率等等,设置分辨率的步骤为抉择Edit | Project Setting | Player,而后找到Resolution栏中设置Default Screen Width为800,Default Screen Height为600,之后抉择窗口下面的Game局部,点击Standalone,便能够抉择设置的800*600作为窗口大小。3.第三步:为场景增加游戏对象,将飞机以及敌机拖入到屏幕中,在主摄像机的范畴中,接下来为飞机以及敌机增加碰撞(Collider)主键,步骤为点击飞机,在unity屏幕的右不便能够看到查看器(Inspector)那一栏,而后抉择下方的Component | Physics | Box Collider(这里的盒碰撞器有2D,3D,没什么大差异,我用的时2D)。在两个都增加完碰撞器后,发现能够设置在X,Y,Z方向上的大小,这个本人设置一个范畴。4.第四步:让飞机动起来,首先咱们要在之前创立的Scripts文件夹上面新建一个C#脚本文件,将其命名为 PlayerController ,双击这个脚本文件,我这里时在VS中进行编辑,如果没有的话倡议下一个VS,关联Unity,网上有很多这样的教程,代码如下,其中有些正文可能不是很精确,请包涵。 using System.Collections;using System.Collections.Generic;using UnityEngine;public class PlayerController : MonoBehaviour{ //飞船每秒挪动的单元的个数 public float Speed; //保障飞船在上下左右挪动 public Vector3 MinMaxX = Vector3.zero; // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { transform.position = new Vector3( Mathf.Clamp( //Mathf.Clamp限度x挪动范畴,Input.GetAxis("Horizontal")获取键盘左右挪动 transform.position.x + Input.GetAxis("Horizontal") * Speed * Time.deltaTime, MinMaxX.x, MinMaxX.y), //transform.position.y, Mathf.Clamp( //Mathf.Clamp限度y挪动范畴,Input.GetAxis("Vertical")获取键盘高低挪动 transform.position.y + Input.GetAxis("Vertical") * Speed * Time.deltaTime, MinMaxX.z, 5), transform.position.z ); }}5.第五步:代码写好后保留,而后将这段脚本文件拖到屏幕左边的飞机的查看器(Inspector)那一栏中,便会发现此时多了一个PlayerController(Scripts)组件,其中能够调节Speed参数,也能够调X,Y,Z,这里的X,Y,Z,代表的是飞机的静止边界的设定。而后启动程序,便能够通过键盘上下左右进行管制。以上便是第一大部分所作,我接下来还会写第二局部,直到实现,两头会记录一些我遇到的问题,以及解决的方法。 ...

June 21, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器锁定状态Unity随手记

明天实现的内容:新增锁定输出为输出模块增加锁定键和锁定信号,更新信号。 --- IPlayerInput public bool lockOn; //锁定信号 --- JoystickInput public MyButton buttonLockOn = new MyButton(); //锁定键 // Update is called once per frame void Update() { // 更新按键 buttonLockOn.Tick(Input.GetButton(btnRS)); // 锁定信号 lockOn = buttonLockOn.onPressed;锁定和解锁的代码逻辑和摄像机代码逻辑首先,要锁定目标,先要确定要锁定的指标是什么。咱们应用Physics.OverlapBox来失去指定盒子区域内的碰撞体。锁定须要始终将摄像机对准目标。所以咱们在CameraController中增加新办法LockOn_or_Unlock用来解决锁定解锁逻辑,而后在PlayerController中调用该办法。 // 摄像机锁定/解除锁定 public void LockOn_or_Unlock() { // 尝试去锁定一个 Vector3 tmp_modelCenter = modelGO.transform.position + Vector3.up; //取得模型的核心 Vector3 tmp_boxCenter = tmp_modelCenter + modelGO.transform.forward * 5.0f; //失去OverlapBox的核心 Collider[] cols = Physics.OverlapBox(tmp_boxCenter, //通过OverlapBox尝试获取范畴内Enemy标签的碰撞体 new Vector3(1.0f, 1.0f, 5.0f), modelGO.transform.rotation, LayerMask.GetMask("Enemy")); if (cols.Length != 0 && lockTarget == null) { //如果失去碰撞体并且没有锁定目标 将第一个赋值给lockTarget lockTarget = cols[0].gameObject; lockonIcon.enabled = true; // 设置LockonIcon的图片地位 lockonIcon.rectTransform.position = Camera.main.WorldToScreenPoint(lockTarget.transform.position); isLockon = true; } else { // 如果没有检测到任何货色或者曾经锁定了指标 将lockTarget设置为null lockTarget = null; lockonIcon.enabled = false; isLockon = false; } }LockOn_or_Unlock会应用OverlapBox查看给定范畴内是否有碰撞体并且是否曾经锁定目标,如果找到了碰撞体,将数组中的第一个给lockTarget,如果没找到或曾经锁定了指标则设置为null。如果摄像机没有锁定,则在FixedUpdate中将playerController按输出设置旋转。如果锁定了,则摄像机要计算模型和锁定目标的方向向量,把这个方向向量交给摄像机,在咱们的架构中,间接用来设置PlayerController的forward就行,记得要将该向量的y轴设置为0。最初,咱们会在锁定时让摄像机始终看向指标的“脚底”来让视角更贴近黑魂。 ...

June 17, 2021 · 3 min · jiezi

关于unity:黑魂复刻游戏的按键长按功能Unity随手记

明天实现的内容:按键长按长按是判断咱们按下一个按键的持续时间是否大于一个固定值,持续时间大于该值阐明以后咱们正在长按,在黑魂游戏中,玩家的翻滚/后跳和冲刺都是一个按键,区别在于长按该键是冲刺,短按就是翻滚/后跳。咱们仍然会用到计时器来实现按钮长按。在按下按键后,咱们会开启delaying计时器,在delaying计时器执行期间按键的isDelaying信号会被设置为true。所以,按住按钮并且isDelaying为false表明按键为长按状态,松开按键并且isDelaying为true表明按键为短按。下列代码为最新的MyButton。 using System.Collections;using System.Collections.Generic;using UnityEngine;// 自制按钮类public class MyButton{ public float extendingDuration = 0.2f; //extending的工夫长度 public float delayingDuration = 0.2f; //delaying的工夫长度 public bool isPressing = false; //正在按压 public bool onPressed = false; //刚刚按下 public bool onRelease = false; //刚刚被开释 public bool isDoubleClick = false; //双击 public bool isLongPress = false; //长按 public bool isShortPress = false; //长按 public bool isExtending = false; //开释按键后的一段时间内为true 用于双击判断 public bool isDelaying = false; //刚刚按下后的一段时间内为true 用于长按判断 private bool currentState = false; //以后状态 private bool lastState = false; //上一帧的状态 private MyTimer extendingTimer = new MyTimer(); //extending计时器 private MyTimer delayingTimer = new MyTimer(); //delaying计时器 // 更新MyButton public void Tick(bool _input) { extendingTimer.Tick(Time.deltaTime); delayingTimer.Tick(Time.deltaTime); onPressed = false; //OnPressd只有一种状况下是true 所以每次都先设置为false onRelease = false; //与OnPressed同理 currentState = _input; //按键的以后状态永远和对应物理按键的状态统一 isPressing = currentState; //是否被按下 按下就是true if (currentState != lastState) //判断按键信号是否扭转 扭转阐明刚刚按下按键或刚刚松开按键 { if (currentState == true) //刚刚按下按键 { onPressed = true; StartTimer(delayingTimer, delayingDuration); } else //刚刚松开按键 { onRelease = true; StartTimer(extendingTimer, extendingDuration); } } lastState = currentState; //完结时将上一帧状态更新为以后帧状态 isExtending = (extendingTimer.state == MyTimer.TIMER_STATE.RUN) ? true : false; //设置extending isDelaying = (delayingTimer.state == MyTimer.TIMER_STATE.RUN) ? true : false; //设置delaying isLongPress = (isPressing && !isDelaying) ? true : false; //长按判断 isShortPress = (isDelaying && onRelease) ? true : false; //短按判断 isDoubleClick = (isExtending && isPressing) ? true : false; //双击判断 } private void StartTimer(MyTimer _timer, float _extDuration) { _timer.duration = _extDuration; _timer.Go(); }}有了新的长按/短按信号,咱们就能够间接应用。 ...

June 17, 2021 · 2 min · jiezi

关于unity:黑魂复刻游戏的计时器类及按键双击功能Unity随手记

明天实现的内容:计时器类计时器类,预计是个游戏都用得上。咱们将专门实现一个计时器类,马上就会在实现按钮长按判断和双击判断中用上。过后,设置计时器的duration,执行Go办法,计时器状态将在elapsedTime >= duration时从RUN变为FINISHED,计时器代码如下: using System.Collections;using System.Collections.Generic;using UnityEngine;public class MyTimer{ // 计时器的状态 public enum TIMER_STATE { IDLE, RUN, FINISHED } // 以后计时器的状态 public TIMER_STATE state; // duration秒之后计时器进行执行 public float duration = 1.0f; // 计时器执行的工夫 public float elapsedTime; // 计时器更新办法 public void Tick(float _deltaTimer) { switch (state) { case TIMER_STATE.IDLE: break; case TIMER_STATE.RUN: elapsedTime += _deltaTimer; if (elapsedTime >= duration) state = TIMER_STATE.FINISHED; break; case TIMER_STATE.FINISHED: break; default: break; } } // 计时器开始执行 public void Go() { elapsedTime = 0; state = TIMER_STATE.RUN; }}应用计时器类实现按键双击判断给MyButton类新增信号IsExtending,当刚刚松开按键时,咱们会开启计时器,在计时器执行期间,IsExtending会被设置为true。那么双击的逻辑就很分明了,只有在IsExtending为true时按下了按键,就判断为双击。上面的代码展现了增加了计时器用于设置Extending的MyButton类。要实现双击的代码还没有写出,能够在MyButton类中增加一个新的信号,或者在须要双击判断的中央查看以后isExtending和isPressing是否都为true。 ...

June 17, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的自制按钮类Unity随手记

明天实现的内容:自制按钮类明天咱们将实现咱们本人的按钮类。按钮类的具体性能是提供按压,刚刚按下,刚刚松开的信号,也就是对按钮进行再封装。对于刚刚按下和刚刚松开信号,我晓得Unity提供了GetButtonDown和GetButtonUp,但我感觉能够学习一下老师的思路,假如未来须要开发新的零碎,就能够通过这个办法实现本人的GetButtonDown和GetButtonUp。代码如下: using System.Collections;using System.Collections.Generic;using UnityEngine;// 自制按钮类public class MyButton{ public bool isPressing = false; //正在按压 public bool OnPressed = false; //刚刚按下 public bool OnRelease = false; //刚刚被开释 public bool currentState = false; //以后状态 public bool lastState = false; //上一帧的状态 // 更新MyButton public void Tick(bool _input) { OnPressed = false; //OnPressd只有一种状况下是true 所以每次都先设置为false OnRelease = false; //与OnPressed同理 currentState = _input; //按键的以后状态永远和对应物理按键的状态统一 isPressing = currentState; //是否被按下 按下就是true if (currentState != lastState) //判断是否刚刚按下按键或刚刚松开按键 { if (currentState == true) OnPressed = true; else OnRelease = true; } lastState = currentState; //完结时将上一帧状态更新为以后帧状态 }}逻辑简介咱们的Button类须要在Input模块的脚本中的Update里始终通过Tick失去GetButton的状态从而进行更新。 ...

June 16, 2021 · 2 min · jiezi

关于unity:黑魂复刻游戏的控制器盾牌防御的IK方案以及动画状态方案Unity随手记

明天实现的内容:增加shieldHandle在人物模型适合的地位增加shieldHandle作为盾牌的地位,如图所示。进攻的IK计划因为本来的Idle动画在装上盾牌当前看上去就像举着盾牌,这里咱们能够通过OnAnimatorIK办法调整手臂地位(旋转)将盾牌放下来。在应用OnAnimatorIK之前,咱们须要在动画层设置中关上IK Pass,只有关上IK Pass,OnAnimatorIK才会被零碎调用。IK Pass(IK 通道?)能够当成动画机会不会真的去计算IK的开关,咱们对骨骼地位的批改也要通过IK Pass才行。接下来,在模型游戏对象上挂载脚本,代码如下: using System.Collections;using System.Collections.Generic;using UnityEngine;// 用于调整模型骨骼左手臂的地位public class LeftArmIK : MonoBehaviour{ // 存储要调整的量 public Vector3 a; private Animator m_anim; private void Awake() { m_anim = GetComponent<Animator>(); } private void OnAnimatorIK(int layerIndex) { // 获取要操作的具体骨骼的Transform 在这里是左手小臂 Transform leftLowerArm = m_anim.GetBoneTransform(HumanBodyBones.LeftLowerArm); // 将要调整的量加上去 加到local坐标 leftLowerArm.localEulerAngles += a; // 通过IK Pass设置骨骼旋转 m_anim.SetBoneLocalRotation(HumanBodyBones.LeftLowerArm, Quaternion.Euler(leftLowerArm.localEulerAngles)); }}咱们将a调整到适合值,就能实现将手臂骨骼放下来。站着不动时看着还不错,游戏角色跑起来看着就很生硬了。然而这样做当前,咱们甚至能够利用代码将手臂IK调整值清零来实现举盾,这也算是一种计划了。当然咱们还是要用Avator Mask和现成的动画来实现。 进攻的动画状态计划假如咱们没有应用下面的IK计划,而是增加新的动画进去。咱们将退出新的动画层Defense,设置Weigh和Avatar Mask,增加idle和defense_oneHand动画节点,其中idle是放下盾的动画而defense_oneHand是举起盾的动画,增加新的Bool参数defense作为转换条件。Avator Mask设置如图,让Defense层只影响左手,咱们的我的项目目前还没有辨别配备左右手。进攻状态代码这个就很简略了,仍旧是依据是否输出来设置一个defense信号,defense信号是按压信号。而后咱们能够通过defense信号进一步设置动画机参数defense。动画状态计划间接SetBool就行了。 // 触发defense anim.SetBool("defense", current_pi.defense);不论应用何种计划,设置动画机参数defense都很有用,上面是我的IK计划的代码。当动画机参数defense为true时将手臂旋转调整量设置为举盾时的量(在这里Vector3.zero刚刚好),为false设置为放下时的量。采纳Lerp做个突变也行。 private void OnAnimatorIK(int layerIndex) { // 设置手臂的旋转最终目标 tmp_target = m_anim.GetBool("defense") ? Vector3.zero : a; // 突变tmp_currentEulerAngle tmp_currentEulerAngle = Vector3.Lerp(tmp_currentEulerAngle, tmp_target, 0.3f); // 获取要操作的具体骨骼的Transform 在这里是左手小臂 Transform leftLowerArm = m_anim.GetBoneTransform(HumanBodyBones.LeftLowerArm); // 将要调整的量加上去 加到local坐标 leftLowerArm.localEulerAngles += tmp_currentEulerAngle; // 通过IK Pass设置骨骼旋转 m_anim.SetBoneLocalRotation(HumanBodyBones.LeftLowerArm, Quaternion.Euler(leftLowerArm.localEulerAngles)); }

June 16, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的摄像机抖动问题Unity随手记

明天实现的内容:问题产生的起因在咱们的代码中,摄像机位移应用了SmoothDamp,然而旋转却是间接欧拉角赋值,这导致了摄影机地位还没有调整完,旋转就曾经到位了。设想一下这个过程,摄像机的眼帘核心在旋转时会偏移角色颈椎地位,这就产生了抖动。 // 摄像机游戏对象的挪动和旋转 cameraGO.transform.eulerAngles = this.transform.eulerAngles;通过齐全固定摄像机每帧的指向解决问题通过应用LookAt,在每一帧将摄像机指向到cameraHandle的transform,咱们就能解决问题,这是最简略的方法。 // 摄像机游戏对象的挪动和旋转 cameraGO.transform.LookAt(cameraHandle.transform);可能呈现的使用RootMotion动画晃动问题当咱们使用上述解决方案当前,可能会呈现使用了RootMotion的动画播放时呈现晃动的问题,这个问题可能来自两处。1.Animator的动画更新速率问题,咱们的动画机是能够抉择何种更新模式的,normal意味着和Update一起更新,依据状况咱们抉择Animator Physics。2.该游戏动画Root Motion的信号问题,Root Motion可能会有杂波,咱们能够用一些伎俩平滑掉。以下计划将上一帧的信号与以后帧的信号相加再除以2。 // 更新m_deltaPos为动画机的Root Motion 之所以用累加是因为物理帧和动画帧不一样 在物理帧的最初会将m_deltaPos清零 // 加上上一帧的再除以2.0是为了平滑 m_deltaPos += (m_deltaPos + (Vector3)_deltaPos) / 2.0f;

June 16, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器镜像动画实现另一只手的攻击Unity随手记

明天实现的内容:镜像动画黑魂游戏里,不会限度你用那只手拿武器,而咱们的我的项目中,目前还只有右手攻打,接下来咱们将通过镜像动画实现左手攻打。选中动画节点,能够看到检视窗口中有一个Mirror选项,勾选Mirror咱们的动画就会镜像反转。咱们心愿能通过脚本来管制它。如果咱们不勾选Mirror,而是勾选它前面的Parameter,咱们就能够应用动画机参数(要是bool类型的)来管制它了,咱们能够很轻松的在代码中管制动画机参数(anim.SetBool)。!新增一个bool型参数mirror来专门管制要镜像翻转的动画节点,在这里是咱们的三个攻打动画。咱们只须要管制mirror参数,就能够管制是用右手攻打还是用左手。

June 15, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器播放攻击动画时的位移Unity随手记

明天实现的内容:通过代码使用动画自带的Root Motion在这篇博客之前,攻打时是不会呈现位移的,因为咱们没有使用动画自带的Root Motion。为了更好的动画成果,咱们接下来将会使用到Root Motion。上图中,红色的标识批示了动画的Root Motion量(具体我也不是太懂,反正就是个量),当咱们使用Root Motion时,零碎会将Root Motion量套用到网络游戏对象的transform上,留神是Animator所在的游戏对象,咱们的我的项目中,Animator被放到控制器次级的模型对象上,所以挪动的只是模型。要应用Root Motion,咱们须要设置Animator组件下的Apply Root Motion为true,然而如果咱们间接就这么做了,那些自身带有Root Motion然而咱们并不想要使用的动画也会被用上,比如说挪动动画,而且因为咱们的模型只是控制器对象的子对象,使用动画的Root Motion会导致模型脱离父对象的原点。为了解决这些问题,咱们还是须要用脚本来使用Root Motion。上面的图片展现的就是我刚刚提到的问题。咱们的代码将应用OnAnimatorMove办法(MonoBehaviour.OnAnimatorMove),该办法会在动画机算完Root Motion值之后的每一帧调用(然而还是在IK计算之前)。在这里咱们甚至能够对动画机算完之后的Root Motion做批改。为什么是算完之后?因为在算之前批改等于没改。这次咱们不会去批改Root Motion,咱们将利用OnAnimatorMove的零碎调用机会,首先失去动画机计算的deltaPosition,就是动画机计算出的模型的Root Motion的位移(Root Motion不仅仅是位移还有旋转deltaRotation,然而这次咱们只有位移就行),持续施展传统艺能,在OnAnimatorMove中将deltaPosition,通过发送消息传递给PlayerController,由PlayerController中的OnUpdateRootMotion依据deltaPosition去挪动Rigidbody。毕竟Rigidbody在同一级,位移相干的所有操作还是要交给PlayerController。这样一来,咱们就通过脚本使用了攻打动画的Root Motion。 public class RootMotionController : MonoBehaviour{ private Animator anim; private void Awake() { anim = GetComponent<Animator>(); } void OnAnimatorMove() { SendMessageUpwards("OnUpdateRootMotion", (object)anim.deltaPosition); }}能够发现当咱们用了OnAnimatorMove当前,Apply Root Motion变成了Handled by Script。上面是PlayerController中的OnUpdateRootMotion具体是如何使用Root Motion的。留神,咱们是在每一物理帧中才进行Rigidbody的地位批改,所以在每一个动画帧中要做的事件是累加Root Motion的位移量。 // Root Motion的位移量 用于脚本使用Root Motion private Vector3 m_deltaPos; // 解决刚体的操作 private void FixedUpdate() { // 使用Root Motion 要放到批改rb.velocity以前进行 rb.position += m_deltaPos; // ... // 清零以后物理帧累积的m_deltaPos m_deltaPos = Vector3.zero; } // 通过脚本使用动画的Root Motion // 通过RootMotionController脚本中的OnAnimatorMove调用 public void OnUpdateRootMotion(object _deltaPos) { // 以后处于attack_oneHand_C动画才会使用Root Motion位移 if (CheckState("attack_oneHand_C", "Attack")) { // 更新m_deltaPos为动画机的Root Motion 之所以用累加是因为物理帧和动画帧不一样 在物理帧的最初会将m_deltaPos清零 // 依据我那点可怜的C#基础知识 这一步会导致拆箱 OnAnimatorMove中的那一步会导致装箱 损耗计算资源 m_deltaPos += (Vector3)_deltaPos; } }最初的要害一步,咱们要辨认以后处在什么动画状态,只有在攻打时if (CheckState("attack_oneHand_C", "Attack"))才更新Root Motion,这样做完当前,就躲避了间接Apply Root Motion带来的问题。 ...

June 15, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器连续攻击及优化Unity随手记

明天实现的内容:新增攻打状态B配置好咱们的新动画,将其退出动画机。首先咱们要将Rig中的如图所示的参数调整好了。接下来,设置Animation页下的烘培,烘培旋转和Y轴,旋转依据Original,Y轴依据Feet。要实现间断攻打,攻打B要连贯到攻打A之后,在做攻打A动画时如果再次按下攻打键,就会从攻打A转换到攻打B,咱们的转换条件是attack。攻打A转换到攻打B要设置为优先转换。最初,批改这个玩意(这叫什么来着?),咱们不须要攻打A前面的收刀动作和攻打B后面的起刀动作,这样做就能“剪”掉这些不须要的动画了,也就是灰色局部。你也能够间接调整攻打B动画的起始地位。新增攻打状态C和攻打B一样,配置好动画,放到动画机中就行,转换条件和之前的也是一样的。连击的优化在咱们之前的设计中,你能够通过疾速连按来实现连击,咱们心愿网络游戏玩家打出连击要更难一点,玩家只能在攻打快要完结的那一段时间内再按出攻打键能力连击,而不是随随便便就能打出连击。在接下来咱们要实现通过代码来重设Attack信号。确切来说,咱们将通过代码,在动画播放完挥砍动作时,将之前输出的attack信号给Reset掉,咱们将要通过动画事件(Animation Events)来实现以上性能。动画事件能够在动画资源下增加,如图所示。咱们给须要做上述操作的动画资源都增加了这个叫ResetTrigger的事件,咱们并没有在事件名称中具体指明reset哪个trigger,起因是咱们想要复用这个事件,咱们能够在String参数中指明咱们要reset的trigger的名字。要让动画事件得以接管,咱们须要在Animator组件的同一个游戏物体上挂载一个脚本来实现具体代码。代码如下: public class AnimTriggerController : MonoBehaviour{ // 动画组件 private Animator anim; private void Awake() { anim = GetComponent<Animator>(); } // 动画事件 用于在执行时将triggerName代表的信号重置 public void ResetTrigger(string triggerName) { anim.ResetTrigger(triggerName); }}老师在这里提到了在AnimTriggerController中处理事件的两种计划,一种是就在AnimTriggerController中拿到咱们想要的动画组件对象,而后处理事件,另一种是AnimTriggerController不间接处理事件,而是将事件再SendMessage到PlayerController中进行解决。在这里咱们采纳第一种,因为曾经有太多的SendMessage了。

June 11, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器攻击的基础实现Unity随手记

明天实现的内容:攻打动画层为了退出新的攻打动画,咱们要设计一个攻打动画层。新的动画层一是为了使老动画层不至于臃肿,而是为了配置Avatar Mask。配置Avatar Mask新建一个Avatar Mask,我晓得这里是多个Avatar Mask。咱们须要的是一个全身都被抉择的Mask。将Mask放到动画层设置外面。接下来要做的就是,在攻打时调整动画层权重。这要在代码中做到。在进入代码之前,给idle增加StateMachineBehaviour脚本FSMClearSignals。咱们须要革除的是attack信号来避免累积。攻打信号输出和翻滚一样攻打属于一次性触发信号。因为应用键盘,所以咱们采纳MouseButtonDown(0)。 // 一次性信号 public bool attack; //攻打信号 // Update is called once per frame void Update() { // ... // 攻打信号 attack = Input.GetMouseButtonDown(0); attack = Input.GetKeyDown(keyAttack); }攻打动画的根本代码逻辑因为有了attack层,咱们的动画机控制代码简单了一点。咱们须要应用代码批改动画层的权重。 // 进入Attack层的动画节点attack_oneHand_A时执行的办法 // 通过PlayerController动画机中的attack_oneHand_A节点上挂载的FSMOnEnter调用 public void OnAttack_oneHandAEnter() { // 敞开输出模块 pi.inputEnabled = false; // 批改攻打动画层权重为1 m_anim.SetLayerWeight(m_anim.GetLayerIndex("Attack"), 1.0f); } // 在Attack层的动画节点attack_oneHand_A更新时执行的办法 // 通过PlayerController动画机中的attack_oneHand_A节点上挂载的FSMOnEnter调用 public void OnAttack_oneHandAUpdate() { // 计算攻打时的冲量 m_thrustVec = model.transform.forward * m_anim.GetFloat("attackOneHandAVelocity") * m_planarVec.magnitude * 4f; } // 进入Attack层的动画节点idle时执行的办法 // 通过PlayerController动画机中的idle节点上挂载的FSMOnEnter调用 public void OnAttackIdleEnter() { // 关上输出模块 pi.inputEnabled = true; // 批改攻打动画层权重为0 m_anim.SetLayerWeight(m_anim.GetLayerIndex("Attack"), 0); }办法仍旧是相熟的配方,通过FSMOnEnter的SendMessage来通知PlayerController以后进入攻打动画了,进而从新设置层权重。m_anim.GetLayerIndex来让程序帮你找到LayerIndex。接下来,攻打时咱们的角色不能再按玩家的输出任意挪动,否则会很奇怪(很显著了)。所以在攻打时要输出模块敞开,不必锁定立体挪动(m_lockPlanar)。如果角色在攻打时自身处于挪动状态,咱们就须要体现出惯性,h5游戏角色须要向前冲一点,要实现这个成果,能够应用和jab相似的计划,也就是在攻打动画中退出一个曲线示意冲量的速度大小,只不过这次还要和m_planarVec.magnitude相乘,能力和角色的速度相干,失去的就是看起来不错的惯性成果。至于为什么挪动时攻打会在敞开输出模块之后还能失去惯性成果,是因为咱们的输出模块不是间接将输出值拿来就用,而是应用了Slerp。 ...

June 11, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的碰撞摩擦问题Unity随手记

前言本篇博客旨在解决黑魂like游戏开发时遇到的一个问题,当咱们的角色跳起来撞墙时,会卡在半空中。而且因为在地面时咱们是没用方法操作的,所以基本上就卡死了。 明天实现的内容:角色跳起来时的卡墙问题形容这个问题的形容如前言所述,问题产生的起因次要是因为在咱们的角色控制器代码会在跳起来时锁死m_planarVec,而这时m_planarVec是有大小的,所以在FixedUpdate中会始终给刚体的速度赋值(m_rb.velocity = new Vector3(m_planarVec.x, m_rb.velocity.y, m_planarVec.z) + m_thrustVec;),这原本是为了维持跳跃时的惯性成果,却导致角色撞在墙上时依旧会始终具备一个向前的速度,依照Unity的物理引擎算法,这会产生一个与墙面的摩擦力,所以导致角色掉不下来卡死在墙上。所以这是一个摩擦力的问题。要解决,咱们能够更换墙面的物理材质,或者在检测到撞墙时扭转m_planarVec从而扭转速度。 批改物理材质计划先创立一个新的物理材质。对于Unity物理材质的几个参数,别离是动摩擦系数,(最大)静摩擦系数,弹性,形容该物理材质与物体产生摩擦时依照什么算法计算理论摩擦系数(两个物体的最大值、最小值、平均值、相乘的值)、以及形容该物理材质与物体产生弹性时依照什么算法计算理论弹性系数(最大值、最小值、平均值、相乘)。在这里,咱们将动静摩擦都设为最小,将Combine设置为依照最小值计算。接下来,关上Project Settings中的Physics,咱们能够将咱们本人的物理材质设置到Default Material项,这样我的项目中所有未指明物理材质的游戏对象会被默认设置为Default Material项上的材质。在这里咱们将一个失常摩擦力的材质放在Default Material项,因为咱们还是须要摩擦力来爬坡,否则站到坡上时角色会本人滑下来。最初,为了解决BUG,咱们将在脚本中动静批改游戏对象的物理材质,计划还将是老一套,在动画状态Ground上增加FSMOnExit,增加音讯OnGroundExit,之后在OnGroundEnter中将材质设置为失常摩擦力的,在OnGroundExit中将材质设置为零摩擦力的。脚本如下: [Header("===== 物理材质 =====")] // 最大摩擦力材质 public PhysicMaterial friction_normal; // 零摩擦力材质 public PhysicMaterial friction_zero; // 角色胶囊体碰撞器 private CapsuleCollider m_capCol; // ------------------- 生命周期办法 ---------------------- // Awake适宜用来做GetComponent void Awake() { // ... m_capCol = GetComponent<CapsuleCollider>(); } // ------------------- 音讯解决 ---------------------- public void OnGroundEnter() // OnGround应该了解为:行走/跑时 不肯定是指在高空时 { // ... // 在行走时设置角色的物理材质为最大摩擦力材质 m_capCol.material = friction_normal; } // 来到Base层的动画节点ground时执行的办法 // 通过PlayerController动画机中的ground节点上挂载的FSMOnExit调用 public void OnGroundExit() { // 没有行走时设置角色的物理材质为最大摩擦力材质 m_capCol.material = friction_zero; }该计划能很好的解决问题 ...

June 11, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的摄像机控制Unity随手记

明天实现的内容:鼠标输出在没有引入手柄操作前,将由鼠标来管制摄像机旋转,首先咱们要获取到鼠标输出。 public class PlayerInput : MonoBehaviour{ // 摄像机管制轴 public string cameraAxisX; public string cameraAxisY; // 摄像机管制信号 public float cameraUp; public float cameraRight; // ... void Update() { // 摄像机信号 cameraUp = Input.GetAxis(cameraAxisY); cameraRight = Input.GetAxis(cameraAxisX); // ... }}摄像机方案设计老师的计划为,将摄像机挂载到游戏对象下作为子物体,同时在摄像机的上一级增加一个新对象CameraHandle作为摄像机的父物体。如果咱们要旋转,就要旋转整个玩家的游戏对象,高低旋转只须要旋转CameraHandle就行了。这是新增脚本CameraController ,挂载到Camera上。 using System.Collections;using System.Collections.Generic;using UnityEngine;public class CameraController : MonoBehaviour{ // 灵敏度 public float horizontalSensitivity; public float verticalSensitivity; // 输出模块 public PlayerInput pi; // PlayerController游戏对象 private GameObject playerHandle; // CameraHandle游戏对象 private GameObject cameraHandle; // 用于存储CameraHandle的欧拉角X值 private float temp_eulerX; void Awake() { cameraHandle = transform.parent.gameObject; playerHandle = cameraHandle.transform.parent.gameObject; pi = playerHandle.GetComponent<PlayerInput>(); } // Update is called once per frame void Update() { // 左右旋转时旋转PlayerHandle playerHandle.transform.Rotate(Vector3.up, pi.cameraRight * horizontalSensitivity * Time.deltaTime); // 高低旋转时旋转CameraHandle temp_eulerX -= pi.cameraUp * verticalSensitivity * Time.deltaTime; // 限度俯仰角 temp_eulerX = Mathf.Clamp(temp_eulerX, -40, 30); // 赋值localEulerAngles cameraHandle.transform.localEulerAngles = new Vector3(temp_eulerX , 0, 0); }}旋转摄像机时的角色模型管制当摄像机旋转时,角色模型不要跟着旋转。方法很简略,将模型在摄像机旋转前的欧拉角保留下来,在摄像机旋转后再将之前保留的欧拉角赋值回去就行了。所以这两个操作的地位千万别弄错。 ...

June 10, 2021 · 2 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器后跳奔跑翻滚跳跃的再重新设计Unity随手记

明天实现的内容:黑魂游戏中后跳、翻滚、跳跃的键位设计在黑魂游戏中,后跳和翻滚是由一个键实现的,在站立不动时,按下(哪怕你是按住)该键会后跳。挪动时单击该键会翻滚,按住该键时挪动会奔跑。奔跑时按下跳跃键能力跳跃。最大的问题就是要辨别同一个按键的单击和按住,咱们将采纳的计划为通过计算按下按键的工夫来判断到底是按住了还是单击。而在键盘上,跳跃和后跳翻滚奔跑竟然都是一个键(默认space),通过奔跑时疾速再按下该键来实现跳跃,而手柄上连按两下该键是翻滚。 单击和按住的代码实现 // 按键按压计时器 private float pressTimer; // 单击的间隔时间 小于等于这个工夫是单击 大于这个工夫是长按(Long Press) private float clickIntervalTime = 0.2f; // 长按还是单击 决定是冲刺还是翻滚/后跃 private void RollOrRun(string _key) { if (Input.GetKeyDown(_key)) { timer = 0; } if (Input.GetKeyUp(_key) && timer <= clickLimitTime) { jab_roll = true; } else { jab_roll = false; } if (Input.GetKey(_key)) { timer += Time.deltaTime; if (timer > clickLimitTime) run = true; } else { run = false; } }使用 ...

June 10, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器后跳及动画控制的重新设计Unity随手记

明天实现的内容:动画管制的从新设计咱们将彻底勾销跳跃信号,改为应用与速度相干的forward参数来判断以后应该转换到哪个动画。当初jump曾经不再须要,能够删除了,也就不须要在代码中触发了,同时也就不必Clear Signal了。后跳动画的退出在黑魂网络游戏中,在原地站住不动时按下翻滚键,玩家会做出后跳的动作。咱们退出这个新的动作。当forward小于0.1时,按下roll键会进行后跳。当初,ground的转换优先级也要调整一下了,首先应该还是fall,而后因为咱们是判断forward小于某个值,所以其次是jab,再其次是roll,最初才是jump。后跳冲量和翻滚一样,增加一个OnJabEnter,通过FSMOnEnter脚本来调用,OnJabEnter要应用两个参数,一个示意后跳的间隔,一个示意后跳产生的高度。其实要不要产生高度取决于动画看着行不行。 // 进入动画节点jab时执行的办法 通过PlayerController动画机中的jab节点上挂载的FSMOnEnter调用 public void OnJabEnter() { // 敞开输出并且锁定立体挪动 DisableInput_LockPlanar(); // 使用后跳冲量 m_planarVec = -model.transform.forward * jabVelocity; // 使用跳跃冲量 m_jumpThrustVec.y = jabHeight; }以上办法是我本人的,上面是老师的方法,说白了就是应用StateMachineBehaviour脚本上的OnStateUpdate来更新地位。 public class FSMOnUpdate : StateMachineBehaviour{ public string[] onUpateMessages; // OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { foreach (var msg in onUpateMessages) { animator.SendMessageUpwards(msg); } }}当然,OnStateUpdate其实仍旧只是发送音讯,理论的地位更新还是要交给PlayerController中的新办法OnJabUpdate。 // 进入动画节点jab时执行的办法 通过PlayerController动画机中的jab节点上挂载的FSMOnUpdate调用 public void OnJabUpdate() { m_jumpThrustVec = -model.transform.forward * jabThrust; }为了使后跳的位移显得更天然,咱们将应用曲线来管制每次的位移量。为jab动画增加曲线,调整好曲线的款式,同时增加新的float型动画参数jabVelocity,肯定和曲线同名。这样做了当前,参数jabVelocity就齐全受曲线的管制了。咱们要做的就是用jabVelocity去设置jabThrust。 ...

June 10, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器翻滚及跳跃的重新设计Unity随手记

明天实现的内容:翻滚和跳跃的从新设计依照黑魂游戏的设计,跳跃其实只有在跑起来时能力起跳,并且随同翻滚动作,也就是说咱们之前设计的被动向前跳跃的性能在黑魂游戏里是没有的,咱们的设计要改。当初的设计是将跳跃键替换为翻滚键,让翻滚取代跳跃,并且只有跑起来时按下翻滚键能力跳起来,并且在落地时会进行翻滚。 翻滚动画及触发逻辑这是一个前滚翻的动画,须要将动画的Y轴位移也bake into pose。将动画间接退出到动画机。翻滚会产生在玩家被动按下翻滚键时进行,并且间接转换回ground。转换条件是当玩家按下翻滚键时(原来是跳跃键),触发动画机参数roll。roll的触发通知动画机进行转换。因为ground能够转换到多个其它动画状态,所以优先级就显得很重要了。配合roll和jump的相干代码,优先级如下。roll和jump的相干代码如下,首先是roll的时候须要和之前的jump一样将输出敞开,立体挪动锁死。 // 进入动画节点roll时执行的办法 通过PlayerController动画机中的roll节点上挂载的FSMOnEnter调用 public void OnRollEnter() { // 敞开输出并且锁定立体挪动 DisableInput_LockPlanar(); }最初是roll的触发代码。 // 翻滚动画 if(pi.roll) m_anim.SetTrigger("roll");翻滚的向前冲量既然翻滚时锁死了立体挪动,咱们就须要一个翻滚的冲量来让动画显得不那么奇怪(位移和动画不匹配)。目前咱们的计划是锁定后调整立体挪动量为归一化后的立体挪动向量乘上翻滚冲量,这样会使得走和跑时的翻滚位移齐全一样。其实更好的方法是依据不同速度制作不同的翻滚动画(走路时和跑时),这样能使得咱们能够针对走和跑的翻滚设置不同的速度。 // 翻滚冲量 用于使翻滚时的位移显得更正当 public float rollThrust= 2.0f; // 进入动画节点roll时执行的办法 通过PlayerController动画机中的roll节点上挂载的FSMOnEnter调用 public void OnRollEnter() { // 敞开输出并且锁定立体挪动 DisableInput_LockPlanar(); // 翻滚冲量 m_planarVec = m_planarVec.normalized * rollThrust; }游戏角色跳跃并向前翻滚之前说过,当初只有在奔跑时按下翻滚键能力跳跃。跳跃仍旧采纳jump参数来触发。留神转换优先级。触发代码如下。 // 当跑起来时按下翻滚键 能力进入跳跃 if(pi.run && pi.roll) m_anim.SetTrigger("jump");

June 9, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器降落落地检测Unity随手记

明天实现的内容:退出起飞动画起飞产生在跳跃之后,所以网络游戏角色起飞动画要紧接着跳跃动画播放,动画机如下。这两条Transition都has exit time,并且jump到fall的优先级高于jump到ground,因为目前为止这两个转换都没有条件。在图示地位调整优先级。 落地检测的实现及相干动画使用要实现落地检测,须要应用Physics.Overlap里的货色,应用Physics.Overlap能够找到某个形态范畴内有重合的所有碰撞体。比方咱们要用到的Physics.OverlapCapsule就是在一个给定的胶囊体内失去所有与胶囊体有重合的特定layer的碰撞体,Overlap只能失去有哪些碰撞体重合,而不晓得其它信息(先后,远近等等)。咱们新建落地检测器脚本OnGroundSensor,挂载到PlayerController游戏对象的子物体sensor下,sensor对象将专门因为各种检测器的挂载。OnGroundSensor代码如下。通过SendMessage来通知PlayerController以后是否正在高空。 using System.Collections;using System.Collections.Generic;using UnityEngine;// 落地检测器public class OnGroundSensor : MonoBehaviour{ // 用于援用PlayerController游戏对象上的CapsuleCollider public CapsuleCollider capCol; // 用于绘制胶囊检测器的两个点 private Vector3 point1; private Vector3 point2; // 用于绘制胶囊检测器的半径 private float radius; void Awake() { radius = capCol.radius; } void FixedUpdate() { // 更新检测器的地位 point1 = transform.position + transform.up * radius; point2 = transform.position + transform.up * (capCol.height - radius); // 检测高空碰撞 Collider[] outputCols = Physics.OverlapCapsule(point1, point2, radius, LayerMask.GetMask("Ground")); // 判断是否撞到高空 并向父物体发送对应音讯 if(outputCols.Length != 0) { // 在高空 SendMessageUpwards("IsGround"); } else { // 不在高空 SendMessageUpwards("IsNotGround"); } }}接下来要做的就是,在PlayerController中实现音讯接管。 ...

June 9, 2021 · 1 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器跳跃Unity随手记

明天实现的内容:跳跃信号要实现跳跃,首先要实现跳跃的输出。跳跃信号是按下的当场触发的一次性触发管制(Trigger Once Signal)。 public class PlayerInput : MonoBehaviour{ ... // 一次性信号 public bool jump; //跳跃信号 public bool lastJump; //记录上一次的jump信号 用于和以后jump信号做比对 了解为是否正在跳跃 ... // Update is called once per frame void Update() { ... // 获取跳跃键输出 bool newJump = Input.GetKey(keyJump); // 这样只有当按下跳跃键时jump会被设置为true 等同于GetKeyDown if (newJump != lastJump && newJump) { jump = true; } else { jump = false; } lastJump = newJump; }}以上代码如果图便宜能够间接采纳GetKeyDown来省略if判断。if判断的性能就是让jump只有在按下跳跃键的时候(不是按住,也不是松开)才为true。 跳跃动画的利用 动画机配置很简略,新增trigger参数jump来作为跳跃条件。动画触发目前也很简略。 // 跳跃动画 if(pi.jump) anim.SetTrigger("jump");跳跃时锁死Input为了让跳跃时不能再管制角色旋转,咱们须要将Input在跳跃时锁死。好在之前咱们就曾经实现了输出模块的软开关(详见:魂类游戏的玩家输出模块)。当初的问题就是何时开关输出模块,咱们将应用动画状态机StateMachineBehaviour脚本来实现管制。对于StateMachineBehaviour脚本能够往下翻到 值得注意的 局部,外面有讲到。通过下图展现的方法增加一个StateMachineBehaviour脚本。以下是FSMOnEnter 的代码,作用是通过重载OnStateEnter,在动画状态机执行到挂载该脚本的动画状态时向父物体及本身的所有MonoBehavior发送音讯调用名叫msg的办法。 ...

June 9, 2021 · 3 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器基础移动动画实现及优化Unity随手记

明天实现的内容:动画机设计理念要我说,动画机真的是陈词滥调的货色了。做了好几次了,目前也就是将角色的高空动画做进去。设计理念就是应用混合树ground将高空挪动动画对立治理起来。动画机的使用及模型旋转要使用动画机,须要将动画机和输出模块串接起来。所以咱们须要新的脚本,没错,PlayerController。同样的,棘手解决角色的旋转性能,旋转的思路为间接批改模型的forward。 using System.Collections;using System.Collections.Generic;using UnityEngine;public class PlayerController : MonoBehaviour{ // 玩家的人物模型 用来获取模型身上的动画机以及其它 public GameObject model; // 输出模块 public PlayerInput pi; // 动画机 private Animator anim; // Awake适宜用来做GetComponent void Awake() { anim = model.GetComponent<Animator>(); pi = GetComponent<PlayerInput>(); } // Update is called once per frame void Update() { // 将输出转换为速度 赋值给动画机相干参数 anim.SetFloat("forward", pi.dirMag); // 只在有速度时可能旋转 避免原地旋转 if(pi.dirMag > 0.1f) model.transform.forward = pi.dirVec; }}在咱们的计划中,旋转的只是模型,PlayerController所在的父物体节点不会旋转咱们能够将旋转和输出的模长计算放到PlayerInput中进行,能让咱们的代码看起来更丑陋。 // 玩家的输出模长量 用于当成向前量大小作为动画管制 public float dirMag; // 玩家的方向 用于旋转模型 public Vector3 dirVec; // Update is called once per frame void Update() { // 计算输出模长 dirMag = Mathf.Sqrt((dirUp * dirUp) + (dirRight * dirRight)); // 计算玩家的方向 dirVec = dirRight * transform.right + dirUp * transform.forward; // ... }网络游戏玩家角色的位移对于挪动,这次咱们将采纳Rigidbody计划。通过间接批改刚体的地位来挪动。 ...

June 8, 2021 · 2 min · jiezi

关于unity:黑魂复刻游戏的玩家控制器锁定状态1Unity随手记

明天实现的内容:新增锁定输出为输出模块增加锁定键和锁定信号,更新信号。 --- IPlayerInput public bool lockOn; //锁定信号 --- JoystickInput public MyButton buttonLockOn = new MyButton(); //锁定键 // Update is called once per frame void Update() { // 更新按键 buttonLockOn.Tick(Input.GetButton(btnRS)); // 锁定信号 lockOn = buttonLockOn.onPressed;锁定和解锁的代码逻辑和摄像机代码逻辑首先,要锁定目标,先要确定要锁定的指标是什么。咱们应用Physics.OverlapBox来失去指定盒子区域内的碰撞体。锁定须要始终将摄像机对准目标。所以咱们在CameraController中增加新办法LockOn_or_Unlock用来解决锁定解锁逻辑,而后在PlayerController中调用该办法。 // 摄像机锁定/解除锁定 public void LockOn_or_Unlock() { // 尝试去锁定一个 Vector3 tmp_modelCenter = modelGO.transform.position + Vector3.up; //取得模型的核心 Vector3 tmp_boxCenter = tmp_modelCenter + modelGO.transform.forward * 5.0f; //失去OverlapBox的核心 Collider[] cols = Physics.OverlapBox(tmp_boxCenter, //通过OverlapBox尝试获取范畴内Enemy标签的碰撞体 new Vector3(1.0f, 1.0f, 5.0f), modelGO.transform.rotation, LayerMask.GetMask("Enemy")); if (cols.Length == 0) { // 如果没有检测到任何货色 将lockTarget设置为null lockTarget = null; } else { //如果失去碰撞体 将第一个赋值给lockTarget lockTarget = cols[0].gameObject; } }LockOn_or_Unlock会应用OverlapBox查看给定范畴内是否有碰撞体,如果找到了碰撞体,将数组中的第一个给lockTarget,如果没找到则设置为null。如果摄像机没有锁定,则在FixedUpdate中将playerController按输出设置旋转。如果锁定了,则摄像机要计算模型和锁定目标的方向向量,把这个方向向量交给摄像机,在咱们的架构中,间接用来设置PlayerController的forward就行,记得要将该向量的y轴设置为0。 ...

June 8, 2021 · 2 min · jiezi

关于unity:Unity小游戏一Unity-JigsawPuzzle拼图游戏

1、前言: 简略的Unity网络游戏,切割图片,生成随机区块,拖拽替换地位。 此游戏代码只贴了一小部分。2、素材筹备 简略做了下,所以没有用太多素材,只筹备了两种字体(毛笔、楷书),筹备了两张按钮图片,一张拼图素材图(数码宝贝)。 3、导入素材开始口头4、繁难UI框架 写了一个极简的UI框架: (1)基类 只做了显示暗藏办法: public class BasePanel : MonoBehaviour{ /// <summary> /// 显示面板 /// </summary> public void Show() { gameObject.SetActive(true); } /// <summary> /// 暗藏面板 /// </summary> public void Hide() { gameObject.SetActive(false); }}(2)UIManger 治理类,做了单例,显示和暗藏面板的办法 public class UIManger : MonoBehaviour{ public static UIManger Instance; public List<BasePanel> panels = new List<BasePanel>(); private void Awake() { Instance = this; panels.AddRange(GetComponentsInChildren<BasePanel>()); } private void Start() { for (int i = 1; i < panels.Count; i++) { panels[i].Hide(); } } public T ShowPanel<T>() where T : BasePanel { T panel = panels.Find(p => p is T) as T; panel.Show(); return panel; } public T HidePanel<T>() where T : BasePanel { T panel = panels.Find(p => p is T) as T; panel.Hide(); return panel; }}5、首页 此处有一个页面跳转,和难度抉择,代码不在赘述 6、游戏界面 ...

June 2, 2021 · 2 min · jiezi

关于unity:Unity-游戏黑暗之光笔记第六章-状态系统的实现

Unity 游戏光明之光笔记第六章 状态零碎的实现1.UI设计2.管制显示挂载Status脚本 public static Status _instance; private TweenPosition tween; private bool isShow = false; private void Awake() { _instance = this; tween = this.GetComponent<TweenPosition>(); } public void TransformState() { if(isShow==false) { tween.PlayForward(); isShow = true; } else { tween.PlayReverse(); isShow = false; } }3.管制属性的变动增加脚本管制 public static Status _instance; private TweenPosition tween; private bool isShow = false; private UILabel attackLabel; private UILabel defLabel; private UILabel speedLabel; private UILabel pointRemainLabel; private UILabel summaryLabel; private GameObject attackButtonGo; private GameObject defButtonGo; private GameObject speedButtonGo; private PlayerStatus ps; void Awake() { _instance = this; tween = this.GetComponent<TweenPosition>(); attackLabel = transform.Find("attack").GetComponent<UILabel>(); defLabel = transform.Find("def").GetComponent<UILabel>(); speedLabel = transform.Find("speed").GetComponent<UILabel>(); pointRemainLabel = transform.Find("point_remain").GetComponent<UILabel>(); summaryLabel = transform.Find("summary").GetComponent<UILabel>(); attackButtonGo = transform.Find("attack_plusbutton").gameObject; defButtonGo = transform.Find("def_plusbutton").gameObject; speedButtonGo = transform.Find("speed_plusbutton").gameObject; ps = GameObject.FindGameObjectWithTag(Tags.player).GetComponent<PlayerStatus>(); } public void TransformState() { if (isShow == false) { UpdateShow(); tween.gameObject.SetActive(true); tween.PlayForward(); isShow = true; } else { tween.PlayReverse(); isShow = false; } } void UpdateShow() {// 更新显示 依据ps playerstatus的属性值,去更新显示 attackLabel.text = ps.attack + " + " + ps.attack_plus; defLabel.text = ps.def + " + " + ps.def_plus; speedLabel.text = ps.speed + " + " + ps.speed_plus; pointRemainLabel.text = ps.point_remain.ToString(); summaryLabel.text = "挫伤:" + (ps.attack + ps.attack_plus) + " " + "进攻:" + (ps.def + ps.def_plus) + " " + "速度:" + (ps.speed + ps.speed_plus); if (ps.point_remain > 0) { attackButtonGo.SetActive(true); defButtonGo.SetActive(true); speedButtonGo.SetActive(true); } else { attackButtonGo.SetActive(false); defButtonGo.SetActive(false); speedButtonGo.SetActive(false); }在transformStatus办法中留神加上 tween.gameObject.SetActive(true);才会使得动画正确播放 ...

May 25, 2021 · 2 min · jiezi

关于unity:Unity-游戏黑暗之光笔记第五章-背包系统的实现

Unity 游戏光明之光笔记第五章 背包零碎的实1.开发性能按钮留神Anchors的地位,能够让图标在窗口变动时放弃绝对地位不会变动2.性能按钮的事件的监听给FunctionBar增加脚本别离给按钮增加点击办法监听事件 public void OnStatusButtonClick() { }public void OnBagButtonClick() { }public void OnEquipButtonClick() { }public void OnSkillButtonClick() { }public void OnSettingButtonClick() { }3.创立物品信息的管理系统应用Text文件存储信息增加icon给Atals图集创立用来治理治理信息的TXT 文档 OjbectsInfoList //对应的信息名称//ID,游戏名称,icon名称,类型,hp,mp,出售价,购买价1001,小瓶血药,icon-potion1,Drug,50,0,50,601002,大瓶血药,icon-potion2,Drug,100,0,70,1001003,蓝药,icon-potion3,Drug,0,100,60,80创立ObjectInfo脚本治理信息 //应用单例模式 void Awake() { _instance = this; ReadInfo(); } //定义各个物品的性能 public enum ObjectType { Drug, Equip, Mat}//示意物品的一条信息public class ObjectInfo { public int id; //id public string name;//名称 public string icon_name;//这个名称是存储在图集中的名称 icon名称 public ObjectType type;//类型(药品drug) public int hp; //加血量值 public int mp;//加魔法值 public int price_sell;//出售价 public int price_buy;//购买}在程序中读取文本,将物品信息读取到内存 ...

May 25, 2021 · 3 min · jiezi

关于unity:Unity-游戏黑暗之光笔记第四章-任务系统的实现

Unity 手游光明之光笔记第四章 工作零碎的实现留神要点在把鼠标放在工作面板上点击时,为了让角色不挪动,在PlayerDir脚本的Update办法中的第一个if断定语句中增加一句,因为NGUI版本的起因应用上面一行代码 if (Input.GetMouseButtonDown(0)&& UICamera.isOverUI==false)NPCBar脚本 public TweenPosition questTween; public UILabel desLabel; public GameObject acceptBtnGo; public GameObject okBtnGo; public GameObject cancelBtnGo; public bool isInTask = false;//示意是否在工作中 public int killCount = 0;//示意工作进度,曾经杀死了几只小野狼 private PlayerStatus status; void Start() { //查找办法,获取状态 status = GameObject.FindGameObjectWithTag(Tags.player).GetComponent<PlayerStatus>(); } void OnMouseOver() {//当鼠标位于这个collider之上的时候,会在每一帧调用这个办法 if (Input.GetMouseButtonDown(0)) {//当点击了老爷爷 if (isInTask) { ShowTaskProgress(); } else { ShowTaskDes(); } ShowQuest(); } } void ShowQuest() { questTween.gameObject.SetActive(true); questTween.PlayForward(); } void HideQuest() { questTween.PlayReverse(); // questTween.gameObject.SetActive(false); } void ShowTaskDes() { desLabel.text = "工作:\n杀死了10只狼\n\n处分:\n1000金币"; okBtnGo.SetActive(false); acceptBtnGo.SetActive(true); cancelBtnGo.SetActive(true); } void ShowTaskProgress() { desLabel.text = "工作:\n你曾经杀死了" + killCount + "\\10只狼\n\n处分:\n1000金币"; okBtnGo.SetActive(true); acceptBtnGo.SetActive(false); cancelBtnGo.SetActive(false); } //工作零碎 工作对话框上的按钮点击工夫的解决 public void OnCloseButtonClick() { HideQuest(); } public void OnAcceptButtonClick() { ShowTaskProgress(); isInTask = true;//示意在工作中 } public void OnOkButtonClick() { if (killCount >= 10) {//实现工作 //取得金币 status.GetCoint(1000); //数量归零 killCount = 0; ShowTaskDes(); } else { //没有实现工作 HideQuest(); } } public void OnCancelButtonClick() { HideQuest(); }鼠标指针治理创立GameSetting游戏物体加载CursorManager组件并且制订好指针图片 ...

May 24, 2021 · 1 min · jiezi

关于unity:Unity-游戏黑暗之光笔记第三章-创建游戏运行场景及角色控制

Unity 游戏光明之光笔记第三章 创立游戏运行场景及角色管制 创立场景导入资源等标签治理应用标签来进行断定,应用const缩小代码的出错率 //高空标签 public const string ground = "Ground";//const 表明这个是一个共有的不可变的变量 //玩家标签 public const string player = "Player";鼠标管制人物挪动鼠标点击高空特效,在角色上挂载脚本脚本 public GameObject effect_click_prefab; void Update() { //检测鼠标的按下 //将鼠标的坐标转换为射线该 //射线与高空产生碰撞返回产生碰撞的点,而后让角色转向该点,开始挪动。当挪动到肯定范畴时进行挪动。 if (Input.GetMouseButtonDown(0)) { Ray ray=Camera.main.ScreenPointToRay(Input.mousePosition); //保留碰撞信息 RaycastHit hitInfo; bool isCollider = Physics.Raycast(ray, out hitInfo); if(isCollider&& hitInfo.collider.tag ==Tags.ground) { //实例化点击的成果 showClickEffect(hitInfo.point); } } void showClickEffect(Vector3 hitPoint) { hitPoint = new Vector3(hitPoint.x, hitPoint.y+0.1f,hitPoint.z); GameObject.Instantiate(effect_click_prefab, hitPoint, Quaternion.identity); } }管制配角的朝向(鼠标点击高空的时候转向)减少两个对象 private Vector3 targetPosition = Vector3.zero;private bool isMoving = false;//示意鼠标是否按下在update办法的 if(isCollider&& hitInfo.collider.tag ==Tags.ground)中减少 ...

May 24, 2021 · 3 min · jiezi

关于unity:Unity-游戏黑暗之光笔记第二章-创建角色场景

Unity 游戏光明之光笔记第二章 创立角色场景 开始角色创立场景,导入模型和UI资源:创立UI 具体参考上一章的放出的NGUI的网址 设计Idle状态的两个角色Prefabs管制所有的角色的创立和显示:创立一个空物体更名为characterCreation,增加管制抉择角色的脚本,留神地位与你所放模型的地位统一把制作好的gameobject挂载到,Next和Prev按钮的On click Notify上,并制订对应的Method public GameObject[] characterPrefabs; //取得角色模型 public UIInput nameInput;//用来失去输出的文本 private GameObject[] characterGameObjects; private int selectedIndex = 0; //抉择的索引 private int length;//所有可供选择的角色的个数 void Start () { //取得角色模型的个数 length = characterPrefabs.Length; characterGameObjects = new GameObject[length]; //用for循环遍历角色的数组,实例化具体模型 for (int i = 0; i < length; i++) { characterGameObjects[i] = GameObject.Instantiate(characterPrefabs[i], transform.position, transform.rotation) as GameObject; } UpdateCharacterShow(); } void UpdateCharacterShow() {//更新所有角色的显示 characterGameObjects[selectedIndex].SetActive(true); for (int i = 0; i < length; i++) { if (i != selectedIndex) { characterGameObjects[i].SetActive(false);//把为抉择的角色设置为暗藏 } } } public void OnNextButtonClick() {//当咱们点击了下一个按钮 selectedIndex++; selectedIndex %= length; UpdateCharacterShow(); } public void OnPrevButtonClick() {//当咱们点击了上一个按钮 selectedIndex--; if (selectedIndex == -1) { selectedIndex = length - 1; } UpdateCharacterShow(); } public void OnOkButtonClick() { PlayerPrefs.SetInt("SelectedCharacterIndex", selectedIndex);//存储抉择的角色 PlayerPrefs.SetString("name", nameInput.value);//存储输出的名字 //加载下一个场景 }如果多个角色模型抉择,在Size中削减数字并指定相应模型 ...

May 20, 2021 · 1 min · jiezi

关于unity:Unity-游戏黑暗之光笔记第一章-完善场景

Unity 游戏光明之光笔记第一章 欠缺场景 导入资地形、地貌资源,新建场景,导入地形、地貌prefab设置相机与视线匹配选中主摄像机,点选菜单栏中GameObject > Align With View增加灯光Direction light > Intensity 光照强度 增加鼠标指针File > Build Settings > Player Settings增加水面 实现镜头迟缓拉近的成果public float speed = 10; //设置速度private float endZ = -20; //设置完结坐标void Start () { void Update () { //判断坐标小于完结坐标 if (transform.position.z < endZ) { //还没有达到目标地位,须要挪动 transform.Translate( Vector3.forward*speed*Time.deltaTime); } }增加雾Window > Rendering > Lighting Settings > other Settings应用UGUI和红色背景给场景增加渐显成果增加UI >image 给image增加Canvas Group 组件增加一个管制渐显的脚本,在主摄像头中增加办法,其余的动画成果相似这样制作 press的动画成果能够应用timeline制作 UIFadeTest.Instance.UIShow(); public float fadeSpeed = 10; //速度 private CanvasGroup canvasGroup; private float alpha = 1.0f; private static UIFadeTest instance; //设置单例 public static UIFadeTest Instance { get { return instance; } } void Start() { instance = this; canvasGroup = this.gameObject.GetComponent<CanvasGroup>(); } // Update is called once per frame void Update() { if (alpha != canvasGroup.alpha) { canvasGroup.alpha = Mathf.Lerp(canvasGroup.alpha, alpha, fadeSpeed * Time.deltaTime); if (Mathf.Abs(canvasGroup.alpha - alpha) < 0.01f) { canvasGroup.alpha = alpha; } } } public void UIShow() { alpha = 0; canvasGroup.blocksRaycasts = false; }}增加鼠标点击事件在点击Press后显示按钮,先让newgame和loadgame按钮不激活 private bool isAnyKeyDown = false;//示意是否有任何按键按下 private GameObject buttonContainer; void Start() { buttonContainer = this.transform.parent.Find("buttonContainer").gameObject; } // Update is called once per frame void Update() { //断定没有按键按下 if (isAnyKeyDown == false) { //如果按下显示案件 if (Input.anyKey) { { ShowButton(); } } } //开始和加载按钮显示办法 void ShowButton() { buttonContainer.SetActive(true); this.gameObject.SetActive(false); isAnyKeyDown = true; } }newgame和loadgame按钮鼠标事件1 游戏数据的保留,和场景之间游戏数据的传递应用 PlayerPrefs2 场景的分类2.1 开始场景2.2 角色抉择界面2.3 游戏玩家打怪的界面,就是游戏理论的运行场景 ...

May 19, 2021 · 2 min · jiezi

关于unity:Unity中的SetParent和parent

一:前言两者都能够去设置一个对象为另一个对象的子物体SetParent办法中有两个参数,第二个参数示意是否应用世界坐标,默认为true 例如上面这段代码,如果SetParent的第二个参数为true,child的地位将会是(-1,-1,-1) using UnityEngine; public class Test : MonoBehaviour{ void Start() { GameObject parent = new GameObject("parent"); parent.transform.position = Vector3.one; GameObject child = new GameObject("child"); child.transform.SetParent(parent.transform); }}二:总结——如果父物体的地位、旋转、缩放不是默认值,则须要应用SetParent设置父物体并设置第二个参数为false——在UI中,SetParent比.parent的效率高很多,这是RectTransform导致的,所以倡议在UI中设置父子关系应用SetParent并应用第二个参数为false 图片起源:http://www.walajiao.com/ 游戏加盟

April 20, 2021 · 1 min · jiezi

关于unity:七Unity-生成几种常用模型meshSphereShape球

1、取得顶点的数据汇合 protected override Vector3[] GetVertices() { var curIndex = 0; var arrayLen = (_latitude + 1) * (_longitude + 1); var vertices = new Vector3[arrayLen]; for (var lat = 0; lat <= _latitude; lat++) { //经线角度的取值范畴是0~180度 var latRad = lat * 1.0f / _latitude * Mathf.PI; var latCos = Mathf.Cos(latRad); var latSin = Mathf.Sin(latRad); for (var lon = 0; lon <= _longitude; lon++) { //纬线角度的取值范畴是0~360度 var lonRad = lon * 1.0f / _longitude * Mathf.PI * 2;//每一圈的最初一个点和第一个点重合(设置UV的坐标) var lonCos = Mathf.Cos(lonRad); var lonSin = Mathf.Sin(lonRad); vertices[curIndex++] = new Vector3(latSin * lonCos, latCos, latSin * lonSin) * _radius - _vertexOffset; } } return vertices; }2、取得法线方向的数据汇合 ...

March 21, 2021 · 2 min · jiezi

关于unity:六Unity-生成几种常用模型meshFrustumShape圆锥台

1、取得顶点的数据汇合 protected override Vector3[] GetVertices() { var bottomPoints = new Vector3[_circularSideCount]; var topPoints = new Vector3[_circularSideCount]; for (var i = 0; i < _circularSideCount; i++) { var rad = i * 1.0f / _circularSideCount * Mathf.PI * 2; var bottomCos = Mathf.Cos(rad) * _bottomRadius; var bottomSin = Mathf.Sin(rad) * _bottomRadius; var topCos = Mathf.Cos(rad) * _topRadius; var topSin = Mathf.Sin(rad) * _topRadius; bottomPoints[i] = new Vector3(bottomCos, -_height * 0.5f, bottomSin) - _vertexOffset; topPoints[i] = new Vector3(topCos, _height * 0.5f, topSin) - _vertexOffset; } var curIndex = 0; var arrayLen = (_circularSideCount + 1) * 2 + (_circularSideCount + 1) * 2; var vertices = new Vector3[arrayLen]; //底面 vertices[curIndex++] = new Vector3(0, -_height * 0.5f, 0) - _vertexOffset; for (var i = 0; i < _circularSideCount; i++) { vertices[curIndex++] = bottomPoints[i]; } //顶面 vertices[curIndex++] = new Vector3(0, _height * 0.5f, 0) - _vertexOffset; for (var i = 0; i < _circularSideCount; i++) { vertices[curIndex++] = topPoints[i]; } //侧面 for (var i = 0; i < _circularSideCount; i++) { vertices[curIndex++] = bottomPoints[i]; vertices[curIndex++] = topPoints[i]; } vertices[curIndex++] = bottomPoints[0]; vertices[curIndex] = topPoints[0]; return vertices; }2、取得法线方向的数据汇合 ...

March 14, 2021 · 3 min · jiezi

关于unity:五Unity-生成几种常用模型meshConeShape圆锥体

1、取得顶点的数据汇合 protected override Vector3[] GetVertices() { var points = new Vector3[_circularSideCount];//保留底面圆形的点汇合 for (int i = 0; i < _circularSideCount; i++) { var rad = i * 1.0f / _circularSideCount * Mathf.PI * 2; var cos = Mathf.Cos(rad) * _radius; var sin = Mathf.Sin(rad) * _radius; points[i] = new Vector3(cos, -_height * 0.5f, sin) - _verticeOffset; } int curIndex = 0;//以后索引 int arrayLen = (_circularSideCount + 1) * 3;//顶点数组的长度 var vertices = new Vector3[arrayLen]; //底面 vertices[curIndex++] = new Vector3(0, -_height * 0.5f, 0) - _verticeOffset; for (int i = 0; i < _circularSideCount; i++) { vertices[curIndex++] = points[i]; } //侧面 var topPoint = new Vector3(0, _height * 0.5f, 0) - _verticeOffset;//顶点的地位 for (int i = 0; i < _circularSideCount; i++) { vertices[curIndex++] = points[i]; vertices[curIndex++] = topPoint; } vertices[curIndex] = points[0]; vertices[curIndex++] = topPoint; return vertices; }2、取得法线方向的数据汇合 ...

March 3, 2021 · 3 min · jiezi

关于unity:四Unity-生成几种常用模型meshCylinderShape圆柱体

圆柱体的顶点有顶面、底面和侧面三局部。1、取得顶点的数据汇合 protected override Vector3[] GetVertices() { int curIndex = 0;//以后索引 int arrayLen = (_circularSideCount + 1) * 2 + _circularSideCount * 2 + 2;//数组的长度 var vertices = new Vector3[arrayLen]; //底面 vertices[curIndex++] = new Vector3(0, -_height * 0.5f, 0) - _verticeOffset; ;//圆心 for (int i = 0; i < _circularSideCount; i++) { var rad = i * 1.0f / _circularSideCount * Mathf.PI * 2;//弧度 var cos = Mathf.Cos(rad) * _radius; var sin = Mathf.Sin(rad) * _radius; vertices[curIndex++] = new Vector3(cos, -_height * 0.5f, sin) - _verticeOffset; } //顶面 vertices[curIndex++] = new Vector3(0, _height * 0.5f, 0) - _verticeOffset; ;//圆心 for (int i = 0; i < _circularSideCount; i++) { var rad = i * 1.0f / _circularSideCount * Mathf.PI * 2;//弧度 var cos = Mathf.Cos(rad) * _radius; var sin = Mathf.Sin(rad) * _radius; vertices[curIndex++] = new Vector3(cos, _height * 0.5f, sin) - _verticeOffset; } //侧面 int sideStartIndex = curIndex; for (int i = 0; i < _circularSideCount; i++) { var rad = i * 1.0f / _circularSideCount * Mathf.PI * 2;//弧度 var cos = Mathf.Cos(rad) * _radius; var sin = Mathf.Sin(rad) * _radius; vertices[curIndex++] = new Vector3(cos, -_height * 0.5f, sin) - _verticeOffset; vertices[curIndex++] = new Vector3(cos, _height * 0.5f, sin) - _verticeOffset; } vertices[curIndex++] = vertices[sideStartIndex]; vertices[curIndex] = vertices[sideStartIndex + 1]; return vertices; }2、取得法线方向的数据汇合 ...

March 2, 2021 · 4 min · jiezi

关于unity:三Unity-生成几种常用模型meshBoxShape

生成方块盒子的mesh,由底面的四个顶点和顶面的四个顶点组成,底面的Mesh是朝下的,所以底面的四个顶点以逆时针的形式排列,顶面的四个顶点还是以顺时针顺序排列。1、取得顶点的数据汇合 protected override Vector3[] GetVertices() { var p0 = new Vector3(-_xSize * 0.5f, -_ySize * 0.5f, -_zSize * 0.5f) - _verticeOffset;//左下 var p1 = new Vector3(_xSize * 0.5f, -_ySize * 0.5f, -_zSize * 0.5f) - _verticeOffset;//右下 var p2 = new Vector3(_xSize * 0.5f, -_ySize * 0.5f, _zSize * 0.5f) - _verticeOffset;//右上 var p3 = new Vector3(-_xSize * 0.5f, -_ySize * 0.5f, _zSize * 0.5f) - _verticeOffset;//左上 var p4 = new Vector3(-_xSize * 0.5f, _ySize * 0.5f, -_zSize * 0.5f) - _verticeOffset;//左下 var p5 = new Vector3(-_xSize * 0.5f, _ySize * 0.5f, _zSize * 0.5f) - _verticeOffset;//左上 var p6 = new Vector3(_xSize * 0.5f, _ySize * 0.5f, _zSize * 0.5f) - _verticeOffset;//右上 var p7 = new Vector3(_xSize * 0.5f, _ySize * 0.5f, -_zSize * 0.5f) - _verticeOffset;//右下 return new Vector3[] { //下 p1,p2,p3,p0, //上 p4,p5,p6,p7, //左 p3,p5,p4,p0, //右 p1,p7,p6,p2, //前 p2,p6,p5,p3, //后 p0,p4,p7,p1 }; }2、取得三角面顶点的索引 ...

February 28, 2021 · 5 min · jiezi

关于unity:二Unity-生成几种常用模型meshPlaneShape

面板Mesh次要由四个点组成,这里生成的是双面Plane,所以须要四个点的坐标。顶面顶点的程序如图,以p0点和p2点为对角线切割成两个三角面代码如下 public class PlaneShape : Shape { private static readonly string _planeMeshName = "Plane Mesh";//mesh的名称 #region private members private float _xSize, _zSize; #endregion #region ctor public PlaneShape(float xSize,float zSize,MeshPivot meshPivot = MeshPivot.CENTER) : base(meshPivot, _planeMeshName) { _xSize = xSize; _zSize = zSize; _verticeOffset = GetVertexOffset(); } #endregion #region override functions /// <summary> /// 依据mesh中心点的地位,取得顶点地位的偏移量 /// </summary> /// <returns></returns> protected override Vector3 GetVertexOffset() { Vector3 offset; switch (_meshPivot) { case MeshPivot.CENTER: case MeshPivot.TOP: case MeshPivot.BOTTOM: offset = Vector3.zero; break; case MeshPivot.LEFT: offset = new Vector3(-_xSize, 0, 0) * 0.5f; break; case MeshPivot.RIGHT: offset = new Vector3(_xSize, 0, 0) * 0.5f; break; case MeshPivot.FRONT: offset = new Vector3(0, 0, _zSize) * 0.5f; break; case MeshPivot.BACK: offset = new Vector3(0, 0, -_zSize) * 0.5f; break; default: offset = Vector3.zero; break; } return offset; } /// <summary> /// 取得顶点的数据汇合 /// </summary> /// <returns></returns> protected override Vector3[] GetVertices() { var p0 = new Vector3(-_xSize, 0, -_zSize) * 0.5f - _verticeOffset;//左下 var p1 = new Vector3(-_xSize, 0, _zSize) * 0.5f - _verticeOffset;//左上 var p2 = new Vector3(_xSize, 0, _zSize) * 0.5f - _verticeOffset;//右上 var p3 = new Vector3(_xSize, 0, -_zSize) * 0.5f - _verticeOffset;//右下 return new Vector3[] { p0,p1,p2,p3, p3,p2,p1,p0 }; } /// <summary> /// 取得法线方向的数据汇合 /// </summary> /// <returns></returns> protected override Vector3[] GetNormals() { return new Vector3[] { Vector3.up,Vector3.up,Vector3.up,Vector3.up, Vector3.down,Vector3.down,Vector3.down,Vector3.down }; } /// <summary> /// 取得三角面顶点的索引 /// </summary> /// <returns></returns> protected override int[] GetTriangles() { return new int[] { 0,1,2,2,3,0, 4,5,6,6,7,4 }; } /// <summary> /// 取得UV坐标的数据汇合 /// </summary> /// <returns></returns> protected override Vector2[] GetUVs() { return new Vector2[] { new Vector2(0,0),new Vector2(0,1),new Vector2(1,1),new Vector2(1,0), new Vector2(0,0),new Vector2(0,1),new Vector2(1,1),new Vector2(1,0), }; } #endregion }

February 28, 2021 · 2 min · jiezi

关于unity:一Unity-生成几种常用模型mesh基类

Unity中mesh次要通过Vertexs(顶点)、Normals(法线)、Triangles(三角面)、UVs(uv坐标)这四个局部数据生成。一、Mesh的轴心地位的类型通过这个类型判断生成坐标的偏移量 public enum MeshPivot { /// <summary> /// 核心 /// </summary> CENTER, /// <summary> /// 上 /// </summary> TOP, /// <summary> /// 下 /// </summary> BOTTOM, /// <summary> /// 左 /// </summary> LEFT, /// <summary> /// 右 /// </summary> RIGHT, /// <summary> /// 前 /// </summary> FRONT, /// <summary> /// 后 /// </summary> BACK, }二、形态父类设置成抽象类,须要必须要继承这个类并实现相干形象办法 public abstract class Shape { #region protected members protected MeshPivot _meshPivot = MeshPivot.CENTER;//轴心的地位 protected string _meshName; protected Vector3 _verticeOffset;//顶点的偏移量 protected Vector3[] _vertices;//顶点数据 protected Vector3[] _normals;//法线数据 protected int[] _triangles;//三角面顶点的索引数据 protected Vector2[] _uvs;//uv坐标数据 protected Mesh _shapeMesh;//保留生成的mesh #endregion #region public properties /// <summary> /// 轴心的地位 /// </summary> public MeshPivot MeshPivot { get { return _meshPivot; } } /// <summary> /// 顶点数据 /// </summary> public Vector3[] Vertices { get { if (_vertices == null) _vertices = GetVertices(); return _vertices; } } /// <summary> /// 法线数据 /// </summary> public Vector3[] Normals { get { if (_normals == null) _normals = GetNormals(); return _normals; } } /// <summary> /// 三角面顶点的索引数据 /// </summary> public int[] Triangles { get { if (_triangles == null) _triangles = GetTriangles(); return _triangles; } } /// <summary> /// uv坐标数据 /// </summary> public Vector2[] UVs { get { if (_uvs == null) _uvs = GetUVs(); return _uvs; } } /// <summary> /// 生成的mesh数据 /// </summary> public Mesh ShapeMesh { get { if (_shapeMesh == null) _shapeMesh = GenerateMesh(); return _shapeMesh; } } #endregion #region ctor public Shape(MeshPivot meshPivot, string meshName) { _meshPivot = meshPivot; _meshName = meshName; } #endregion #region protected functions /// <summary> /// 生成mesh /// </summary> /// <returns></returns> protected Mesh GenerateMesh() { var mesh = new Mesh(); mesh.name = _meshName; _vertices = GetVertices(); _normals = GetNormals(); _triangles = GetTriangles(); _uvs = GetUVs(); mesh.vertices = _vertices; mesh.normals = _normals; mesh.triangles = _triangles; mesh.uv = _uvs; mesh.RecalculateTangents(); mesh.RecalculateBounds(); mesh.Optimize(); return mesh; } #endregion #region abstract /// <summary> /// 依据mesh中心点的地位,取得顶点地位的偏移量 /// </summary> /// <returns></returns> protected abstract Vector3 GetVertexOffset(); /// <summary> /// 取得顶点的数据汇合 /// </summary> /// <returns></returns> protected abstract Vector3[] GetVertices(); /// <summary> /// 取得法线方向的数据汇合 /// </summary> /// <returns></returns> protected abstract Vector3[] GetNormals(); /// <summary> /// 取得三角面顶点的索引 /// </summary> /// <returns></returns> protected abstract int[] GetTriangles(); /// <summary> /// 取得UV坐标的数据汇合 /// </summary> /// <returns></returns> protected abstract Vector2[] GetUVs(); #endregion #region protected static functions /// <summary> /// 取得圆心切割的边数(依照固定长度) /// </summary> /// <param name="radius">半径</param> /// <param name="arcLen">弧度的长度</param> /// <returns></returns> protected static int GetCircularSideCount(float radius, float arcLen = 0.1f) { const int minSideCount = 3; const int maxSideCount = 3000; int sideCount = Mathf.RoundToInt(Mathf.PI * 2 * radius / arcLen); sideCount = Mathf.Min(Mathf.Max(minSideCount, sideCount), maxSideCount); Debug.Log("sideCount:" + sideCount); return sideCount; } #endregion }

February 28, 2021 · 2 min · jiezi

关于unity:添加EventTrigger事件监听方法

/// <summary> /// 增加EventTrigger事件监听办法 /// </summary> /// <param name="trans"></param> /// <param name="type"></param> /// <param name="action"></param> public static void AddEventTriggerHandler(this Transform trans, EventTriggerType type,UnityAction<BaseEventData> action) { EventTrigger eventTrigger = null; if (!trans.TryGetComponent<EventTrigger>(out eventTrigger)) { eventTrigger = trans.gameObject.AddComponent<EventTrigger>(); } if (eventTrigger.triggers == null) { eventTrigger.triggers = new List<EventTrigger.Entry>(); } var entry = eventTrigger.triggers.FirstOrDefault(p => p.eventID == type); if (entry == null) { entry = new EventTrigger.Entry { eventID = type, }; entry.callback.AddListener(action); eventTrigger.triggers.Add(entry); } else { entry.callback.RemoveAllListeners(); entry.callback.AddListener(action); } } /// <summary> /// 增加带自定义参数的EventTrigger事件监听办法 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="trans"></param> /// <param name="type"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddEventTriggerHandler<T>(this Transform trans, EventTriggerType type, UnityAction<BaseEventData, T> action, T t) { trans.AddEventTriggerHandler(type,p => { action(p,t); }); }

December 22, 2020 · 1 min · jiezi

关于unity:UGUI注册组件回调函数

用工具类让Button、Toggle等组件更好的增加不带自定义参数和带自定义参数的UnityAction回调。/// <summary> /// 增加按钮点击事件 /// </summary> /// <param name="btn"></param> /// <param name="action"></param> public static void AddOnClickHandler(this Button btn, UnityAction action) { if (btn != null) { btn.onClick.RemoveAllListeners(); btn.onClick.AddListener(action); } } /// <summary> /// 增加带参数的按钮点击事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="btn"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddOnClickHandler<T>(this Button btn, UnityAction<T> action, T t) { if (btn != null) { btn.onClick.RemoveAllListeners(); btn.onClick.AddListener(() => { action(t); }); } } /// <summary> /// 增加Toggle值批改事件 /// </summary> /// <param name="tog"></param> /// <param name="action"></param> public static void AddOnValueChangedHandler(this Toggle tog, UnityAction<bool> action) { if (tog != null) { tog.onValueChanged.RemoveAllListeners(); tog.onValueChanged.AddListener(action); } } /// <summary> /// 增加带参数的Toggle值批改事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="tog"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddOnValueChangedHandler<T>(this Toggle tog, UnityAction<bool, T> action, T t) { if (tog != null) { tog.onValueChanged.RemoveAllListeners(); tog.onValueChanged.AddListener(p => { action(p, t); }); } } /// <summary> /// 增加Slider值批改事件 /// </summary> /// <param name="slider"></param> /// <param name="action"></param> public static void AddOnValueChangedHandler(this Slider slider,UnityAction<float> action) { if (slider != null) { slider.onValueChanged.RemoveAllListeners(); slider.onValueChanged.AddListener(action); } } /// <summary> /// 增加带参数的Slider值批改事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="slider"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddOnValueChangedHandler<T>(this Slider slider, UnityAction<float, T> action, T t) { if (slider != null) { slider.onValueChanged.RemoveAllListeners(); slider.onValueChanged.AddListener(p => { action(p, t); }); } } /// <summary> /// 增加dropdown值批改事件 /// </summary> /// <param name="dropdown"></param> /// <param name="action"></param> public static void AddOnValueChangedHandler(this Dropdown dropdown, UnityAction<int> action) { if (dropdown != null) { dropdown.onValueChanged.RemoveAllListeners(); dropdown.onValueChanged.AddListener(action); } } /// <summary> /// 增加带参数的dropdown值批改事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dropdown"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddOnValueChangedHandler<T>(this Dropdown dropdown, UnityAction<int, T> action, T t) { if (dropdown != null) { dropdown.onValueChanged.RemoveAllListeners(); dropdown.onValueChanged.AddListener(p => { action(p, t); }); } } /// <summary> /// 增加InputField值批改事件 /// </summary> /// <param name="input"></param> /// <param name="action"></param> public static void AddOnValueChangedHandler(this InputField input, UnityAction<string> action) { if (input != null) { input.onValueChanged.RemoveAllListeners(); input.onValueChanged.AddListener(action); } } /// <summary> /// 增加带参数的InputField值的批改事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="input"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddOnValueChangedHandler<T>(this InputField input, UnityAction<string, T> action, T t) { if (input != null) { input.onValueChanged.RemoveAllListeners(); input.onValueChanged.AddListener(p => { action(p, t); }); } } /// <summary> /// 增加InputField编辑实现事件 /// </summary> /// <param name="input"></param> /// <param name="action"></param> public static void AddOnEndEditHandler(this InputField input, UnityAction<string> action) { if (input != null) { input.onEndEdit.RemoveAllListeners(); input.onEndEdit.AddListener(action); } } /// <summary> /// 增加带参数的InputField编辑实现事件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="input"></param> /// <param name="action"></param> /// <param name="t"></param> public static void AddOnEndEditHandler<T>(this InputField input, UnityAction<string, T> action, T t) { if (input != null) { input.onEndEdit.RemoveAllListeners(); input.onEndEdit.AddListener(p => { action(p, t); }); } }

December 22, 2020 · 3 min · jiezi

关于unity:利用反射机制给View面板脚本里的组件字段赋值

当UI脚本上须要赋值的组件太多的时候,利用反射机制主动给参数赋值就显得很不便。只须要参数名称和UI节点的名称相一致(能够定义本人的匹配规定) /// <summary> /// 给字段赋值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="root"></param> /// <param name="tObj"></param> public static void SetFieldValue<T>(this Transform root,T tObj) { var fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic)?.Where(p => p.FieldType.IsSubclassOf(typeof(Component))).ToList(); RecursiveSetValue(root, fields, tObj); } /// <summary> /// 递归函数给类里的字段赋值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="root"></param> /// <param name="fieldInfos"></param> /// <param name="tObj"></param> private static void RecursiveSetValue<T>(Transform root,List<FieldInfo> fieldInfos,T tObj) { if (fieldInfos == null || fieldInfos.Count == 0) return; for (int i = 0; i < root.childCount; i++) { var child = root.GetChild(i); RecursiveSetValue(child, fieldInfos,tObj); for (int j = fieldInfos.Count - 1; j >= 0; j--) { var fieldInfo = fieldInfos[j]; if (fieldInfo.FieldType.IsSubclassOf(typeof(Component)) && fieldInfo.Name.IsMatch(child.name)) { var component = child.GetComponent(fieldInfo.FieldType); fieldInfo.SetValue(tObj, component); fieldInfos.RemoveAt(j); } } } } /// <summary> /// 判断字段的名称和节点的名称是否匹配 /// </summary> /// <param name="name"></param> /// <param name="transName"></param> /// <returns></returns> private static bool IsMatch(this string name,string transName) { var temp = name.StartsWith("_") ? name.Substring(1, name.Length - 1) : name; return temp.ToLower().Equals(transName.ToLower()); }

December 22, 2020 · 1 min · jiezi

关于unity:UGUI-自定义摇杆

自定义一个虚构摇杆,具体须要是:1、未点击时核心的小圆点暗藏 2、点击通明背景范畴内时操作栏挪动到点击地位 3、拖拽小圆点输入拖拽方向 4、松开时操作栏复位,小圆点暗藏一、UI制作1、创立一个Joystick的背景通明的Image,作为点击的范畴2、创立Joystick下创立一个子物体TouchBg,作为操作栏3、创立TouchBg下创立一个子物体TouchPoint 二、创立脚本Joystick附加在Joystick物体上,继承ScrollRect,将TouchPoint赋值给Content参数,将TouchBg赋值给ViewPort参数 三、编写Joystick脚本 /******************************************************************** 文件:Joystick.cs 作者:Panngdudu 日期:2020/12/19 形容: 虚构摇杆性能*********************************************************************/using System;using UnityEngine;using UnityEngine.UI;using UnityEngine.EventSystems;public class Joystick : ScrollRect, IPointerDownHandler, IPointerUpHandler, IDragHandler{ public GameObject touchBgGo; public GameObject touchPointGo; //事件触发的回调 public Action<Vector2> onDragCallback;//拖拽事件的回调,参数1:方向 protected float radius = 100; protected override void Start() { radius = touchPointGo.GetComponent<RectTransform>().sizeDelta.x * 0.7f; } public void OnPointerDown(PointerEventData eventData) { touchPointGo.SetActive(true); touchBgGo.transform.position = eventData.position; } public void OnPointerUp(PointerEventData eventData) { touchPointGo.SetActive(false); touchBgGo.transform.localPosition = Vector2.zero; } public void OnDrag(PointerEventData eventData) { base.OnDrag(eventData); var contentPos = this.content.anchoredPosition; if (contentPos.magnitude > radius) { contentPos = contentPos.normalized * radius; SetContentAnchoredPosition(contentPos); } onDragCallback?.Invoke(contentPos.normalized); }}gitee地址:https://gitee.com/pangdudu010... ...

December 19, 2020 · 1 min · jiezi

关于unity:创建Unity脚本时添加文件头信息

1.将对应unity版本目录(.\Editor\Data\Resources\ScriptTemplates)下的81-C# Script-NewBehaviourScript.cs.txt 脚本模板文件中增加文件头信息 /**************************************************** 文件:#SCRIPTNAME#.cs 作者:#CreateAuthor# 日期:#CreateTime# 性能: *****************************************************/using UnityEngine;public class #SCRIPTNAME# : MonoBehaviour {}2.在Unity工程文件夹(Plugins\Editor)创立增加提示信息的脚本 using System;using System.IO;public class HeadDetailedRecoder : UnityEditor.AssetModificationProcessor { private static void OnWillCreateAsset(string path) { path = path.Replace(".meta", ""); if (path.EndsWith(".cs")) { string str = File.ReadAllText(path); str = str.Replace("#CreateAuthor#", "Pangdudu").Replace( "#CreateTime#", string.Concat(DateTime.Now.Year, "/", DateTime.Now.Month, "/", DateTime.Now.Day, " ", DateTime.Now.Hour, ":", DateTime.Now.Minute, ":", DateTime.Now.Second)); File.WriteAllText(path, str); } }}这样就能够在unity中创立脚本时生成头文件。(也能够通过Visual Assist X增加脚本头文件信息)

December 3, 2020 · 1 min · jiezi

关于unity:Unity的光照渲染

光照的基本概念 间接光照:光源间接照射到物体外表上产生的光照信息。间接光照:光源照射到物体外表后再反射到其余物体上所产生的光照信息。环境光:unity中的天空盒子和天空色彩等光照信息。全局照明:间接光照加上间接光照。(Global Illumination)unity中灯光组件的Mode模式 Realtime灯光模式:当Light Setting面板上的Realtime Lighting(Realtime Global Illumination)被勾选时,场景中的所有实时光照都会产生间接照明(Unity每一帧都会计算实时灯光,如果没有灯光也会预计算,十分耗费性能),不勾选时场景中的实时光照只会渲染间接光照。Mixed灯光模式:对应Light Setting面板上Mixed Lighting选项的三种模式。(烘焙须要将物体设置成动态物体) Baked Indirect:渲染实时的间接光照,间接光照会烘焙到贴图中;Shadowmask:渲染实时的间接光照,间接光照和暗影会烘焙到贴图中;Subtractive:常常和light中的Backed模式联合,针对动态物体,会把间接光照和间接光照都烘焙到贴图中。

December 2, 2020 · 1 min · jiezi

关于unity:Unity2019接入安卓SDK

开发环境的搭建 应用UnityHub装置所选版本的Unity,选中Android Build Support下的SDK和JDK选项,Unity会主动装置并配置好SDK相干门路。装置实现后从装置目录(.\Editor\Data\PlaybackEngines\AndroidPlayer)下拷贝NDK、OpenJDK、SDK目录到自建的目录下,当前降级SDK并且其余版本的Unity间接援用这个门路下的SDK(不须要再次装置)。配置jdk环境变量: 在cmd中查看是否装置了(命令: java -version)在零碎变量的变量名 JAVA_HOME 中增加变量值:F:\UnityAndroid\OpenJDK (OpenJDK拷贝搁置的目录)在零碎变量的变量名 CLASSPATH 中增加 %JAVA_HOME%\lib;%JAVA_HOME%\bin;在零碎变量的变量名 Path中增加 %JAVA_HOME%\lib;%JAVA_HOME%\bin;在cmd中查看java环境的版本 (OpenJDK最高版本不要超过1.8.0版本)应用adkmanager降级sdk版本 执行 .\SDK\tools\bin 目录下的sdkmanager.bat文件sdkmanager --list //查看曾经装置的信息sdkmanager --platforms;android-29 //装置安卓平台版本(在目录.\SDK\platforms\下看到装置的文件)sdkmanager --build-tools;28.0.3 //装置build-tools版本sdkmanager --uninstall build-tools;28.0.3 //卸载对应版本的build-toolsUnity2019应用Gradle打包Apk的问题: https://blog.csdn.net/qq_1483...

December 1, 2020 · 1 min · jiezi

关于unity:关于AB包中的-Sprite-Mask-在打iOS包后失效的问题适用于各种脚本在打包后失效

解决办法关上游戏进入生效脚本所在页面后搜寻Log,如果有Could not produce class with ID XXX相似字样,则实用于本问题 解决办法有3种: 点掉Player Settings->Other Settings->Strip Engine Code的对勾再从新打包(不举荐)在Scene中找个没影响的中央退出这个脚本(退出脚本就行,不必其余配置)再打包(不举荐)在[http://docs.unity3d.com/Manua...]()里找到ID 所对应的类名,而后在Assets里退出一个Link.xml再打包(举荐)Link.xml内容如下(以 ID 331为例): <linker> <assembly fullname="UnityEngine"> <type fullname="UnityEngine.SpriteMask" preserve="all"/> </assembly></linker>起因这是因为你在Player Settings->Other Settings->Strip Engine Code里打钩了。这个选项顾名思义,是容许Unity在打包时候剥离一部分的代码以节俭空间。你的脚本没有失效是因为它在这个时候给剥离掉了。那么如何让零碎不剥离呢?第一个办法是不必AB包加载,间接在Scene外面放一个被剥离脚本,这样在编译时零碎就会检测到你用了这个脚本,便主动增加成例外,不再剥离。在iOS工程中,这一点反映在TypesInScenes.xml文件里。不过更正规的办法是用Link.xml,让增加Strip的例外脚本,简略又洁净。在iOS工程中Strip例外的脚本都会存在EditorToUnityLinkerData.json里。其余如果采纳了以上办法后发现Xcode工程启动即闪退,那么删除工程中Classes->Natice文件夹下的文件和Data文件夹下的文件,从新打包即可。

December 1, 2020 · 1 min · jiezi

关于unity:Unity-HDRP烘焙技术原理及应用

从HDRP 7.0版本开始,HDRP正式脱离preview标签,成为正式版。如果你心愿取得成果炫酷的照片级渲染成果,当初正是学习HDRP的好时机。本课程全面、具体地展现了HDRP烘焙的技术原理及利用,并在文末附上了课程Demo,边学习边实现,直观感触HDRP带来的真实感! 您要找的是不是UWA学堂的《Unity HDRP烘焙技术原理及利用》? 文章简介Unity的HDRP是高清渲染管线(High Definition Render Pipeline)的简称,它以SRP(Scriptable Render Pipeline)的相干API为根底,构建出一个适宜开发高端画质的开发环境。以后,HDRP 7.x曾经上线,装置Unity 2019.3.x及以上版本即可创立基于HDRP 7.x默认工程。从HDRP 7.0版本开始,HDRP正式脱离preview标签,成为正式版,将来HDRP将不会产生太大的框架性的变动。如果你心愿取得成果炫酷的照片级渲染成果,当初正是学习HDRP的好时机。 然而,想取得十分高的画质体现,对于烘焙的了解是必不可少的。HDRP的全局光照和Build-in管线的全局光照在Planar Reflection、Screen Space Reflection、Light Layer、Reflection Hierarchy等方面是不同的,所以在肯定水平上须要重新学习。当然,有一部分常识与Build-in管线的烘焙也是重合的,例如:Lightmapper参数、光照探针、Lightmap Parameter Asset等等,所以也能够应用本教程的内容作为Build-in管线烘焙的参考。 在HDRP中,通过Lightmap、光照探针、反射探针、Planar Reflection等技术,能够取得优良的间接光和暗影,让产品的真实度上一个量级。 阐明: 1.  全文长约12000字,浏览时长约为40分钟; 2.  第9节中提供了Demo工程帮忙读者实际练习。 作者简介云影:Unity技术美术  Game jam玩家 目前次要钻研方向是实在实时渲染,对HDRP有比拟深刻的理解,是国内首批应用HDRP进行产品制作的开发者。Unity官网局部中文文档译者/审核者,曾作为游戏客户端程序/技术美术先后就任于隆重游戏、多益公司、乐动卓越公司,5年游戏/VR行业开发教训,曾参加《第十域》、《传送门骑士online》、《河汉英雄传说》的开发。 更多精彩文章,可下载【无理】APP查看~

July 24, 2020 · 1 min · jiezi

关于unity:Unity-C热更新方案-ILRuntime学习笔记二-代码跨域调用

一、主工程调用Hotfix代码假如Hotfix工程里有一个Test类,该如何调用该类的办法呢? namespace Hotfix { public class Test { // 实例办法 public string GetName() { return "test"; } // 静态方法 public static float Sum(float a, float b) { return a + b; } }}1.调用静态方法 // 获取类型IType type = appdomain.LoadedTypes["Hotfix.Test"];// 获取办法IMethod method = type.GetMethod("Sum", 2);// 调用办法object returnValue = appdomain.Invoke(method, null, 1, 2);// 输入返回值print("静态方法返回值:" + returnValue);2.调用实例办法 // 获取类型IType type = appdomain.LoadedTypes["Hotfix.Test"];// 创立实例object instance = (type as ILType).Instantiate();// 获取办法IMethod method = type.GetMethod("GetName", 0);// 调用办法object returnValue = appdomain.Invoke(method, instance);// 输入返回值print("静态方法返回值:" + returnValue);二、Hotfix调用主工程代码Hotfix调用主工程代码间接调用即可,无需特地步骤。 ...

July 19, 2020 · 2 min · jiezi

Unity下载安装Standard-Assets及报错解决

Unity降级之后不仅不自带规范包,从asset store里下载这个包之后还报一堆错。。无语凝噎。在此记录一下。 下载安装Standard Assets关上Assets Store搜Standard Assets进去的第一个就是,抉择add to my assets实现购买在Unity的导航栏关上Windows > Package Manager页面,左上角加号旁边有一个All Packages的标签(所以all packages并不显示全副的包!蛊惑。。),点一下切换到My Assets,就能看到规范包了。点击规范包,抉择好要导入的模块后点import,这些局部就会被导入到Assets\Standard Assets\目录了!以下为导入后遇到的一些问题,按模块分类。。 Standard Assets\Utility'GUIText' is obsoleteAssets\Standard Assets\Utility\SimpleActivatorMenu.cs(12,16): error CS0619: 'GUIText' is obsolete: 'GUIText has been removed. Use UI.Text instead.'这个问题最坑的是提醒的批改倡议不残缺!!貌似Unity从某个版本开始不必GUIText而改用UnityEngine.UI里的Text了,双击报错信息会间接关上这个SimpleActivatorMenu.cs文件。首先在文件头部加一条 using UnityEngine.UI;而后把报错地位的 public GUIText camSwitchButton;改成 public Text camSwitchButton;保留,回去就好了。 参考:https://answers.unity.com/que... Standard Assets\EditorEditor\Water\Water4下一系列type or namespace name ... could not be found的谬误更坑,连个提醒也没有。。 这个Water貌似依赖Environment下的Water,从新关上导入规范包的页面,import一下Standard Assets\Environment\Water,解决。 参考:https://answers.unity.com/que...

July 15, 2020 · 1 min · jiezi

Unity-C热更新方案-ILRuntime学习笔记一-Hello-World

转载请表明原文地址:https://segmentfault.com/a/11... 一、什么是ILRuntime问:什么是ILRuntime?答:一个C#热更新计划。 问:什么是热更新?答:在不从新下载安装利用包的状况下更新利用内容的形式就叫做热更新。 问:为什么要热更新?答:为了更好的用户体验。 问:那热更新和非热更新有什么不同吗?答:用户应用更不便,开发更麻烦。 问:用户不便在哪?开发麻烦在哪?答:用户不便在当利用更新时,关上利用走个进度条就能用,不必从新下载安装,省心、省时、省流量。开发麻烦在开发后期的框架搭建,开发中的细节,发开后的测试都减少了肯定难度。 问:产生这些麻烦的起因是什么?答:次要起因在苹果平台,用Unity做挪动端利用,通常兼容两个常见平台:安卓和苹果。苹果为了用户的平安,对开发者的利用做了限度,不容许开发者用惯例形式更新利用的代码(JIT)。简略来说,就是其余平台都没太大问题,能够很容易的热更新,而苹果的限度让咱们必须用其余办法做热更新。当初支流的热更新计划都是因为能够绕过苹果的限度才流行起来的。就像是你出国吃饭的时候饭店不容许你用筷子,因为他们是西餐厅,只提供了刀叉,但餐具费很贵。你既用不惯刀叉又不想花冤枉钱,然而没方法,你在国外,你只能入乡随俗。好在饭店没明确规定不能够自带餐具,于是你带了一种不是筷子但能吃饭的餐具,饭店也就睁一只眼闭一只眼了。这饭店就相当于苹果,餐具就相当于热更新计划。 问:热更新这么麻烦啊,那怎么解决这个问题呢?答:想要从基本解决这个问题,要么别去苹果饭店吃饭(那就没这个问题了),要么苹果饭店解除限制(不太可能),要么苹果饭店转行了(huang le)。因为从根本上难以解决该问题,所以当初都是折中的方法,其实是没方法的方法,你要去苹果饭店吃饭就要恪守苹果饭店的规矩。有些餐具在吃饭时遇到的问题比拟少,吃饭比较顺利。有些餐具在吃饭时遇到的问题比拟多,吃饭不太顺利。于是问题较少的就逐步风行开来,问题较多的就逐步败落,当初支流的热更新计划是Lua,ILRuntime应该算是非主流,因为他比拟小众,没流行起来。 问:那为什么不必支流的lua,要用非主流的ILRuntime?答:因为lua在Unity中用起来很好受,不是lua这门语言不好,而是因为在Unity中官网的开发语言是C#。用lua就意味着开发者要会两种语言,学习和开发成本都高,而且因为C#是强类型、面向对象的语言。lua是弱类型,非面向对象的语言。lua从编程思维和代码写法都和C#有较大差距,这一点在面对越大的我的项目时感触越显著,我的项目小的时候感觉lua还好,我的项目做大了当前会发现lua带给你的麻烦会大于便当。而ILRuntime计划是基于C#的,开发语言对立,编码更容易。不过他的毛病是理论通过验证的我的项目还是太少了,不太成熟,可能有很多坑须要填,不像是通过很多我的项目验证的lua,有比拟成熟的计划。 集体倡议:如果你用lua更纯熟,更喜爱lua,就用lua,有很多成熟框架能够用。如果你用C#更纯熟,更喜爱C#,用lua感觉很好受,能够尝试学习应用一下ILRuntime,用纯熟了当前再思考用该计划作为热更新计划。存在即是正当,lua风行有其劣势,ILRuntime呈现也有其情理,lua是第一抉择,但不是惟一的抉择,ILRuntime写代码更难受,但后方兴许有不少坑。 二、下载ILRuntimeGitHub:https://github.com/Ourpalm/IL...Unity Demo:https://github.com/Ourpalm/IL...国内码云:https://gitee.com/zhangyu800/...中文手册:http://ourpalm.github.io/ILRu... 先去GitHub上点个赞,反对一下该我的项目,再去Unity Demo上把我的项目下载下来,顺便也点个赞。如果国外地址下载慢,能够在国内码云上下载Demo。 三、导入ILRuntime1.解压缩Unity Demo关上Unity工程目录下的ProjectSettings/ProjectVersion.txt 查看工程版本。 为了防止因为不同版本导致的兼容问题,工程版本和Unity版本尽量保持一致,我下载的Demo工程版本是2019.3.6f1, 我尽量用2019.3.6 或略微高一点儿的版本导入。 2.目录构造 工程导入结束,看下目录构造。 Demo目录:在Samples/ILRuntime/1.6.2/Demo/_Scenes/Examples文件夹下 热更新加载的代码目录:热更新代码会从StreamingAssets目录下加载编译后的dll其中mdb和pdb文件都是调试时用的,公布时只须要dll。 运行Hello World Demo: 关上并运行 01_Hello World 场景。能够看到控制台输入了如下后果。 ILR是如何工作的呢?看下HelloWorld脚本。 using UnityEngine;using System.Collections;using System.IO;using ILRuntime.Runtime.Enviorment;public class HelloWorld : MonoBehaviour{ //AppDomain是ILRuntime的入口,最好是在一个单例类中保留,整个游戏全局就一个,这里为了示例不便,每个例子外面都独自做了一个 //大家在正式我的项目中请全局只创立一个AppDomain AppDomain appdomain; System.IO.MemoryStream fs; System.IO.MemoryStream p; void Start() { StartCoroutine(LoadHotFixAssembly()); } IEnumerator LoadHotFixAssembly() { //首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒 appdomain = new ILRuntime.Runtime.Enviorment.AppDomain(); //失常我的项目中应该是自行从其余中央下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示不便间接从StreammingAssets中读取, //正式公布的时候须要大家自行从其余中央读取dll //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //这个DLL文件是间接编译HotFix_Project.sln生成的,曾经在我的项目中设置好输入目录为StreamingAssets,在VS里间接编译即可生成到对应目录,无需手动拷贝 //工程目录在Assets\Samples\ILRuntime\1.6\Demo\HotFix_Project~ //以下加载写法只为演示,并没有解决在编辑器切换到Android平台的读取,须要自行批改#if UNITY_ANDROID WWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll");#else WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");#endif while (!www.isDone) yield return null; if (!string.IsNullOrEmpty(www.error)) UnityEngine.Debug.LogError(www.error); byte[] dll = www.bytes; www.Dispose(); //PDB文件是调试数据库,如须要在日志中显示报错的行号,则必须提供PDB文件,不过因为会额定耗用内存,正式公布时请将PDB去掉,上面LoadAssembly的时候pdb传null即可#if UNITY_ANDROID www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");#else www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");#endif while (!www.isDone) yield return null; if (!string.IsNullOrEmpty(www.error)) UnityEngine.Debug.LogError(www.error); byte[] pdb = www.bytes; fs = new MemoryStream(dll); p = new MemoryStream(pdb); try { appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider()); } catch { Debug.LogError("加载热更DLL失败,请确保曾经通过VS关上Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL"); } InitializeILRuntime(); OnHotFixLoaded(); } void InitializeILRuntime() {#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE) //因为Unity的Profiler接口只容许在主线程应用,为了防止出异样,须要通知ILRuntime主线程的线程ID能力正确将函数运行耗时报告给Profiler appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;#endif //这里做一些ILRuntime的注册,HelloWorld示例临时没有须要注册的 } void OnHotFixLoaded() { //HelloWorld,第一次办法调用 appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null); } private void OnDestroy() { if (fs != null) fs.Close(); if (p != null) p.Close(); fs = null; p = null; } void Update() { }}惊喜!正文是纯中文的,作者肯定是中国人!正文很具体,看正文就行了。 ...

July 11, 2020 · 3 min · jiezi

UnityCharacterController和-transformposition-冲突

CharacterController和 transform.position 抵触的问题,比方: transform.position = new Vector3(0, transform.position.y, transform.position.z);与 characterController.Move(transform.forward * speed * Time.deltaTime);会导致人物的偏移不天然。 解决办法:Physics外面设置enabling Auto Sync Transforms开启.

July 10, 2020 · 1 min · jiezi

Unity引用查找工具开源库

【博物纳新】是UWA旨在为开发者推荐新颖、易用、有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性。很多时候,我们并不知道自己想要什么,直到某一天我们遇到了它。 更多精彩内容请关注:lab.uwa4d.com 导读在UWA开源库中,有各种各样的实用工具可供大家选择。今天给大家介绍的是Unity引用查找工具,也是在研发过程中非常实用的小工具。 开源库链接:https://lab.uwa4d.com/lab/5ccea58f72745c25a817c300 一、使用介绍把这个库下载下来,它的主体代码只有三个脚本,包括了引用查找、界面展示等逻辑。需要使用时,只要把这个文件夹放进工程里即可。 将ReferenceFinder文件夹放进了一个Demo工程中,这个工程中总共包含300多个资源和文件夹,是一个比较简单的工程。 我们知道Unity自带的关于引用查找的工具有两个,分别是Find References In Scene和Select Dependencies。 但是这两个功能有两个缺点: 1、没有GUI可以直观地看到引用依赖的资源结果; 2、查找引用的对象只能针对于当前场景中的GameObject,不适用于Prefab等资源。 本文介绍的查找引用工具就可以实现这两个功能。 在工程中选择一个资源,右键菜单选择最后一项“Find References”。 就可以在Ref Finder界面中查看该资源的引用和依赖资源了。这里呈现引用关系是以树状结构呈现的,不仅能看到引用资源,还能看到这些资源之间的引用关系,非常直观。每个资源包含资源类型、资源名、资源路径、状态等信息。 点击Model按钮可以切换查看引用/依赖模式,Expand/Collapse可以快速展开/折叠树状结构: 双击列表中的资源,即可以在Project视图中定位到这一资源。 点击左侧的Refresh Data可以刷新依赖引用关系的数据。注意需要在修改数据之后先保存一下工程,否则可能不生效。 二、原理简述这个小工具主要包含三个脚本: ReferenceFinderData:引用数据生成、缓存、更新的相关逻辑。 ReferenceFinderView:定义了Editor界面UI的打开逻辑及主要GUI代码。 AssetTreeView:定义了Editor界面中根据引用数据生成的资源树状结构及资源的双击响应事件。 本工具的作者在介绍中也写到,对于这类引用关系的做法,无外乎两种: 1、每次都进行一次全局查找,保证查找的正确性。 2、缓存一次全局查找的数据,在资源变动时更新缓存数据,保持查找的正确性。 由于一次全局查找使用的资源依赖接口GetDependencies本质上是guid也就是文本的查找和比较,所以通常会很慢,尤其是对于研发后期甚至上线后的项目,项目的资源量都很大,Prefab资源也很多,就会使全局查找的时间成倍数增加。对于大型项目这个过程可能会在10分钟以上。如果希望在资源管理中,检查多个资源的依赖关系,第一种方式显然是无法接受的。 第二种方式,全局查找一次之后,就可以使用缓存数据查看所有资源的依赖关系了。同时对每个资源记录 AssetDatabase.GetAssetDependencyHash 的哈希值作为最后一次资源修改的时间标记。如果有资源修改了,通过比对这个哈希值来做增量数据更新,就可以减少数据更新的速度了。在修改资源不多的情况下,更新数据的时间也不会很长。 因此在这个工具中使用的是第二种方式,在测试过程中,确实更新数据的操作是非常快的。 缓存数据默认保存在 Library/ReferenceFinderCache 路径下,大家也可以在ReferenceFinderData中修改保存路径。对于使用SVN等工具的团队需要注意缓存文件的提交。 三、总结今天给大家介绍的是一个研发中的实用小工具——Unity引用查找工具。在UWA开源库中其实有很多类似的小工具,这个是我们目前看到的,不管在易用性、界面美观、还是效率上都有一定优势的一个小工具,在此推荐给大家。 快用UWA Lab合辑Mark好项目! 今天的推荐就到这儿啦,或者它可直接使用,或者它需要您的润色,或者它启发了您的思路...... 请不要吝啬您的点赞和转发,让我们知道我们在做对的事。当然如果您可以留言给出宝贵的意见,我们会越做越好。

June 4, 2020 · 1 min · jiezi

XCharts开源库介绍

【博物纳新】是UWA旨在为开发者推荐新颖、易用、有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性。很多时候,我们并不知道自己想要什么,直到某一天我们遇到了它。 更多精彩内容请关注:lab.uwa4d.com 导读 图表(Chart)是我们最为广泛使用的数据可视化工具。 对于简单的图表,Office系软件就完全可以胜任了。如果需要更加美观和专业,也可以用ECharts、Highcharts、D3、G2之类专门的工具。在各类编程语言中,也有各种图形库用来制作图表。 那么今天,我们来介绍一个可以在Unity的UI中绘制图表的开源库项目——XCharts。它参考了ECharts的风格,通过UGUI绘制,可以静态或使用代码动态地控制内容。 开源库链接:https://lab.uwa4d.com/lab/5bc42d5404617c5805d4d685 特性1、内置丰富示例,参数可视化配置,效果实时预览,纯源码绘制; 2、支持折线图(LineChart)、柱状图(BarChart)、饼图(PieChart)、雷达图(RadarChart); 3、支持Default、Light、Dark三种默认主题切换,自定义主题; 4、支持多数据密集图表; 5、折线图通过参数可配置出:折线图、曲线图、面积图等; 6、饼图通过参数可配置出:饼图、环形图、南丁格尔玫瑰图等。 目前项目仍在不断更新之中。 使用方法在开源库下载好XCharts后,我们可以直接作为Unity项目打开,也可以将其导入到现有的项目,然后我们只需要把对应的图表脚本添加到一个Canvas的子对象中。 这样基本的配置就完成了。更加详细的控制图表内容以及代码使用的方法,可以在项目自带的演示场景中找到。 那么既然可以在UI里动态绘制图表,我们就来尝试做一些有意义的事情吧。(以下均使用LowPoly Environment Pack的Demo1场景测试) 1、通过折线图显示帧数走势 帧数计算有多种方法,最简单的是可以取完成最后一帧的时间的倒数: fps=1/Time.deltaTime;但这实际上是用一帧的时间来估计一秒经过的帧数,而且全部显示出来会有刷新过快的情况;另一种更常用的方法是统计一下1s左右走过的帧数,如下: ftime += Time.deltaTime;frameCount++;if (ftime >= 1f) { fps = frameCount / ftime; //这里添加图表数据控制代码 ftime = 0f; frameCount = 0f; }需要添加的图表代码: chart.AddXAxisData(Time.frameCount.ToString()); //添加横轴数据,这里我们使用总帧数chart.AddData(0, fps);//添加对应数据chart.RefreshChart();//刷新图表然后我们在Inspector中把折线图脚本中的Max Cache Data Number设置为我们希望图表能够同时显示的最大数据量,超过这个值图表就会进行推移。这样简单的帧率折线图就完成了。 我们可以尝试隐藏除线条以外的元素,这在Inspector中可以很容易的控制,十分简洁的帧率走势就呈现了出来,如下图: 2、通过折线图显示Mono内存 Mono内存分为两个部分:已用内存(Used)和堆内存(Heap),因为它们特殊的关系,我们可以将他们显示在同一个折线图中。要得到这两个数据,我们可以用使用下面的两段代码: Profiler.GetMonoHeapSizeLong()Profiler.GetMonoUsedSizeLong()与查看fps相同,我们将其放入Update()里,并隔1s左右刷新: ftime += Time.deltaTime;if(ftime>=1f) { ftime = 0f; chart.AddXAxisData(Time.frameCount.ToString()); chart.AddData("Heap", Profiler.GetMonoHeapSizeLong()/ 1048576f);//堆内存 MB chart.AddData("Used", Profiler.GetMonoUsedSizeLong()/ 1048576f);//占用内存 MB chart.RefreshChart();//刷新图表 }简单地样式调整之后,得到了下面的效果图,两条折线可以很清晰地反映出Mono内存的变化。 ...

June 3, 2020 · 1 min · jiezi

UnityShader之Glitch-Art效果

【博物纳新】是UWA旨在为开发者推荐新颖、易用、有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目、前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性。很多时候,我们并不知道自己想要什么,直到某一天我们遇到了它。 更多精彩内容请关注:lab.uwa4d.com 导读电视信号受到干扰,产生画面抖动、色彩漂移等现象,这种电子设备成像故障产生的效果,被应用在赛博朋克等科幻类型的影视游戏作品中。逐渐成为一种特有的风格艺术:故障艺术(Glitch Art)。育碧在其3A大作《看门狗》系列中,频繁的采用了这种表现手法。日本知名Unity大师Keijiro的开源库项目KinoGlitch模拟了这一风格。 该项目主要模拟了两种类型的效果:Analog Glitch和Digital Glitch。(无特效时场景) 开源库链接:https://lab.uwa4d.com/lab/5b5d1c86d7f10a201feaa37f Analog Glitch这种Glitch效果类型可以分为以下四种效果: 1、Scan Line Jitter(_scanLineJitter设定为0.5时 效果图) 这种效果是以像素为单位横向拉伸不同程度地拉伸物体,从而形成抖动。可以通过后处理的方式来实现。在Shader中进行采样的时候,采样点为原图位置横向偏移一些的点。可以通过一个float类型的变量来控制偏移量。(_scanLineJitter设定为1时 效果图) 这个效果的实现重点在于采样的随机性,这样抖动的效果更加逼真。并且偏移量处在一个限定的范围内,即使抖动也能基本看出原本的模型样貌。作者设计了一个较为复杂运算来模拟随机效果,并将偏移量限定在特定范围内: //AnalogGlitch.cs中设定变量用于控制偏移量[SerializeField, Range(0, 1)]float _scanLineJitter = 0;//AnalogGlitch.shader//计算具体偏移量float jitter = nrand(v, _Time.x) * 2 - 1;jitter *= step(_ScanLineJitter.y, abs(jitter)) * _ScanLineJitter.x;float nrand(float x, float y){ return frac(sin(dot(float2(x, y), float2(12.9898, 78.233))) * 43758.5453);}//根据偏移量进行采样half4 src1 = tex2D(_MainTex, frac(float2(u + jitter, v)));2、Horizontal Shake 这种效果用于进行横向的抖动,通过设定一个float类型的偏移量。绘制时根据偏移量进行采样即可: //AnalogGlitch.cs中设定变量用于控制偏移量_material.SetFloat("_HorizontalShake", _horizontalShake * 0.2f);//AnalogGlitch.shader//计算具体偏移float shake = (nrand(_Time.x, 2) - 0.5) * _HorizontalShake;float nrand(float x, float y){ return frac(sin(dot(float2(x, y), float2(12.9898, 78.233))) * 43758.5453);//根据偏移量进行采样half4 src1 = tex2D(_MainTex, frac(float2(u + shake, v)));3、Color Drift(效果图) ...

May 27, 2020 · 2 min · jiezi

Unity-NewtonsoftJsonLitJson和SimpleJSON性能对比

Unity中Json库性能对比测试 类库大小对比: 类库文件类型大小NewtonsoftJson.dll353KBLitJson.dll56KBSimpleJSON.cs68KB解析时间对比:执行次数:10000次 测试方法NewtonsoftJsonLitJsonSimpleJSON测试1114ms158ms52ms测试2136ms288ms126ms测试3263ms542ms169ms测试4333ms747ms200ms测试代码: using UnityEngine;using System.Diagnostics;using LitJson;using SimpleJSON;using Newtonsoft.Json.Linq;/// <summary>/// JsonTest/// ZhangYu 2019-07-11/// <para>文章地址:https://segmentfault.com/a/1190000019731298</para>/// </summary>public class JsonTest : MonoBehaviour { public int count = 10000; private Stopwatch watch; private void Start () { watch = new Stopwatch(); string json1 = "{\"id\":10001,\"name\":\"test\"}"; string json2 = "[1,2,3,4,5,6,7,8,9,10]"; string json3 = "{\"id\":10000,\"username\":\"zhangyu\",\"password\":\"123456\",\"nickname\":\"冰封百度\",\"age\":20,\"gender\":1,\"phone\":12345678910,\"email\":\"zhangyu@xx.com\"}"; string json4 = "[\"test2\",[[\"key1\", \"id\"],[\"key2\", \"hp\"],[\"key3\", \"mp\"],[\"key4\", \"exp\"],[\"key5\", \"money\"],[\"key6\", \"point\"],[\"key7\", \"age\"],[\"key8\", \"sex\"]]]"; JsonParseTest(json1); JsonParseTest(json2); JsonParseTest(json3); JsonParseTest(json4); } private void JsonParseTest(string json) { print("json:" + json); bool isArray = json[0] == '['; NewtonsoftJsonTest(json, isArray); LiteJsonTest(json); SimpleJsonTest(json); print("======================"); } private void NewtonsoftJsonTest(string json, bool isArray) { watch.Reset(); watch.Start(); if (isArray) { for (int i = 0; i < count; i++) { JArray jArray = JArray.Parse(json); } } else { for (int i = 0; i < count; i++) { JObject jObj = JObject.Parse(json); } } watch.Stop(); print("NewtonsoftJson Parse Time(ms):" + watch.ElapsedMilliseconds); } private void LiteJsonTest(string json) { watch.Reset(); watch.Start(); for (int i = 0; i < count; i++) { JsonData jData = JsonMapper.ToObject(json); } watch.Stop(); print("LiteJson Parse Time(ms):" + watch.ElapsedMilliseconds); } private void SimpleJsonTest(string json) { watch.Reset(); watch.Start(); for (int i = 0; i < count; i++) { JSONNode jNode = JSON.Parse(json); } watch.Stop(); print("SimpleJson Parse Time(ms):" + watch.ElapsedMilliseconds); }} ...

July 11, 2019 · 1 min · jiezi

Unity-加载AssetBundle

随着Unity的版本更新,加载AssetBundle的API也不断变化,要熟悉这些API才能把资源包管理的更好,在这里做个总结。 WWWUnty4.x - 5.x 用WWW类加载WWW.LoadFromCacheOrDownload() 通过Url和版本号自动缓存资源包 注意必须是资源包 不能是其他格式 不能加密 www加载样例: using UnityEngine;using System.Collections;public class LoadFromCacheOrDownloadExample : MonoBehaviour{ IEnumerator Start() { while (!Caching.ready) yield return null; using (var www = WWW.LoadFromCacheOrDownload("http://myserver.com/myassetBundle.unity3d", 5)) { yield return www; if (!string.IsNullOrEmpty(www.error)) { Debug.Log(www.error); yield return null; } var myLoadedAssetBundle = www.assetBundle; var asset = myLoadedAssetBundle.mainAsset; } }}UnityWebRequestUnity2018.1以后 官方建议用UnityWebRequest类代替WWW类UnityWebRequest.GetAssetBundle() 兼容WWW.LoadFromCacheOrDownload()的功能 并提供了更多的功能 UnityWebRequest加载样例: using UnityEngine;using UnityEngine.Networking;using System.Collections;/// <summary>/// 下载测试/// <para>原文地址:https://segmentfault.com/a/1190000019656656</para>/// </summary>public class WebRequestTest : MonoBehaviour { private void Start () { StartCoroutine(DoLoadFile()); } // 下载文本或二进制文件 private IEnumerator DoLoadFile() { string url = Application.streamingAssetsPath + "/" + "test.txt"; using (UnityWebRequest request = UnityWebRequest.Get(url)) { yield return request.SendWebRequest(); if (request.isHttpError || request.isNetworkError) { // 下载出错 print(request.error); } else { // 下载完成 string text = request.downloadHandler.text; byte[] bytes = request.downloadHandler.data; } } } // 下载图片 private IEnumerator DoLoadTexture() { string url = Application.streamingAssetsPath + "/" + "test.png"; using (UnityWebRequest request = UnityWebRequestTexture.GetTexture(url)) { yield return request.SendWebRequest(); if (request.isHttpError || request.isNetworkError) { // 下载出错 print(request.error); } else { // 下载完成 Texture2D texture = (request.downloadHandler as DownloadHandlerTexture).texture; } } } // 下载AssetBundle private IEnumerator DoLoadAssetBundle() { string url = Application.streamingAssetsPath + "/" + "test.ab"; using (UnityWebRequest request = UnityWebRequestAssetBundle.GetAssetBundle(url)) { yield return request.SendWebRequest(); if (request.isHttpError || request.isNetworkError) { // 下载出错 print(request.error); } else { // 下载完成 AssetBundle assetBundle = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle; } } }}最重要的API加载本地资源包最快的API:AssetBundle.LoadFromFile(path) 同步 注意必须是AssetBundle格式 可以压缩 不能加密AssetBundle.LoadFromFileAsync(path) 异步 注意必须是AssetBundle格式 可以压缩 不能加密 ...

July 3, 2019 · 2 min · jiezi

Lets-Hackathon-索尼黑客松召集令做这条gai最酷的极客

致程序员:从触屏时代进入感应时代,Future-Close at hand。ToF技术目前在VR游戏,AR音乐等场景中实现了应用,期待更多极客加入我们,共同走进感应时代。追求极致体验和更多乐趣,不论在游戏,短视频领域,还是在教育,电商,医疗等行业,让我们一起创造更多可能性。我们邀请您加入本次ToF黑客松,一起感应未来。 **1. 什么是ToF?**这是一场基于索尼最新ToF技术,搭载索尼 SDK的手机应用编程大赛。ToF是飞行时间(Time of Flight)技术的缩写,传感器发出红外光,通过计算光线发射和反射时间差来换算被拍摄景物的距离,以产生深度信息,将物体的三维轮廓以不同颜色代表不同距离的地形图方式呈现出来。 **2. ToF的应用领域**人类正在从触屏时代进入感应时代。随着5G时代的到来,传感器的技术革新,未来的各种操作体验将变得更加酷炫。索尼的ToF技术已经被应用到VR游戏、AR音乐、VR教育等一系列的场景中。提供给个人更多乐趣和便利,也提供给社会更多便利和安全。同时ToF技术也有被运用到在健身、短视频、电商、医疗、导航等领域。ToF应用场景演示视频 **3. 王者荣耀重磅加入**自活动发起以来,大赛收到了行业多方关注,王者荣耀正式加入本次黑客松,作为赛事合作方,特别授权本次黑客松大赛对王者热门英雄模型素材的使用,其中包括12个热门英雄的模型动画音效素材,及王者荣耀主要场景、建筑、野怪、兵线和背景音乐。比赛中,参赛者可以利用这些极具人气的王者英雄素材进行创意创作,相信很多王者荣耀的游戏迷已经跃跃欲试了!本次大赛的应用领域包括:王者荣耀素材主题、游戏、电商、医疗健康、社交、教育、导航和其他(请各位选手在报名时选择对应的应用领域)。选择王者荣耀素材主题的参赛团队,在开发期间,可使用比赛专用开发机调用相关王者荣耀的十二种热门英雄模型素材,结合索尼ToF技术,VR/AR方向的制作,实现创新,可尝试1V1、多人对战、音乐类和与英雄有强烈情感交互等方向,成果不仅仅局限于游戏。 各位Unity3D,游戏,AR/VR大佬们,48小时极限编程邀请你加入!在这里你可以:接触到索尼最新未公开的ToF科技,可以利用极具人气的王者英雄素材进行创意创作,赢取10万现金大奖,更有获得索尼或王者荣耀工作/合作的机会! 点击报名链接进行报名或者扫描下图二维码进行报名

June 28, 2019 · 1 min · jiezi

Unity常用SDK集成文档

一、IAP模块 IAP即in-App Purchase的缩写,就是充钱,由于集成最方便所以第一个集成。集成方法:1.在unity环境下打开Window->Services会出现一个Services界面(可能不是弹出来而是作为标签栏的一项附着在unity环境的某个地方)2.接下来在Services界面里把IAP那一项打开,会进入IAP的界面,然后点击Welcome下的Import按钮即可。3.Android系统版本会需要Options栏里那个码来验证。如果你确认输入了正确的码但是一直验证失败,首先检查你的网络,然后点一下Services页面右上角的“Go to Dashboard”键打开那个网页,然后再回到这个界面验证就好了(我觉得这是因为网页的拉取操作能更新unity后端服务器状态)4.到此IAP的SDK就集成完毕了。二、统计模块 也就是打点的SDK,这里要集成三种,分别是Firebase、Facebook和友盟。从此开始集成模块之后都要在1.先集成Firebase,文档见 https://firebase.google.cn/docs/analytics/unity/start。2.安装FirebaseAnalytics.unitypackage。在这之前要打开unity环境中的File->Build Settings->Player Settings...->Other Settings->Configuration->Scripting Runtime Version 下选择.NET 4.x Equivalent,然后就可以安装dotnet4文件夹下的FirebaseAnalytics.unitypackage文件了。3.同时向产品要Firebase 配置文件,iOS需要GoogleService-Info.plist,安卓需要google-services.json。放到unity项目Assets文件夹下任意位置即可。4.集成Facebook,文档见https://developers.facebook.com/docs/unity/gettingstarted5.先安装下载好的unitypackage包,然后unity环境上会出现Facebook菜单,点击其中的Edit settrings选项并在弹框中输入Facebook中注册的name和id(找产品要)即可。6.友盟,文档见[https://developer.umeng.com/docs/66632/detail/67588][1]7.安装下载好的Common.unitypackage和Game.unitypackage并编译程序,iOS在编译出的工程中的UnityAppController.mm文件下加入:#import <UMCommon/UMCommon.h>并在didFinishLaunchingWithOptions方法下添加命令:[UNUMConfigure initWithAppkey:@"友盟id" channel:@"App Store"];三、广告模块 在广告模块中,我们将通过mopub来集成Admob、Facebook和Unity的广告适配器集成方法:1.集成mopub只需要直接安装mopub官网提供的unity版SDK即可。文档[https://developers.mopub.com/publishers/unity/get-started/][2]2.安装好后,你的unity环境中会出现Mopub菜单,打开菜单中的Manager SDKs一项则可以进入适配器安装栏并选择安装。也可以在[https://developers.mopub.com/publishers/mediation/integrate/][3]手动下载适配器(注意版本)3.其中Admob安装后iOS要在Xcode工程中的info.plist一栏中加入以下字段,否则会闪退: <key>GADApplicationIdentifier</key><string>ca-app-pub你的id</string>4.Facebook适配器安装后iOS在编译XCode工程时可能会包命令重复定义的错误,这应该是由于上面统计模块继承的FacebookSDK版本和广告模块Facebook适配器所依赖的Facebook版本不同所致,在General->Linked Frameworks and Libraries把FBSDKCoreKit.framework删了就行了。如果没有报错就不用这一步。5.mopubSDk具体的使用方法见[https://segmentfault.com/a/1190000019138899][4]四、其他模块 以下模块只需下载相应的包并解压即可1.Firebase 远程配置模块,文档[https://firebase.google.com/docs/reference/unity/namespace/firebase/remote-config][5]2.Firebase 推送模块,文档[https://firebase.google.com/docs/reference/unity/namespace/firebase/messaging][6]

June 26, 2019 · 1 min · jiezi

Unity-GPU-Instance大量相同网格物体合批

前言在Unity中 同网格同材质的模型是可以合批的动态批处理和静态批处理都可以合批 但是都有其限制动态批处理有顶点数不能超过900的限制 只适合比较简单的模型静态批处理的物体不能移动、旋转、缩放 并且需要消耗额外的内存来存储合并后的物体 如果动态静态批处理都无法使用 能否用其他方式合批呢?可以尝试一下GPU Instance 虽然也有所限制 但是提供了更多可能 使用GPU Instancing的条件1.Shader支持GPU Instancing2.硬件支持GPI Instancing3.代码动态绘制物体 硬件需求: GPU Instancing is available on the following platforms and APIs:·DirectX 11 and DirectX 12 on Windows·OpenGL Core 4.1+/ES3.0+ on Windows, macOS, Linux, iOS and Android·Metal on macOS and iOS·Vulkan on Windows and Android·PlayStation 4 and Xbox One·WebGL (requires WebGL 2.0 API)限制情况: 下列情况不能使用Instancing:·使用Lightmap的物体·受不同Light Probe / Reflection Probe影响的物体·使用包含多个Pass的Shader的物体,只有第一个Pass可以Instancing前向渲染时,受多个光源影响的物体只有Base Pass可以instancing,Add Passes不行GPU Instance测试GPU Instancing确实可以动态合批 但是需要Shader + 硬件 + 代码的支持好处是可以解决动态批处理解决不了的问题 没900顶点的限制 ...

June 22, 2019 · 3 min · jiezi

Unity-Asset-Store独立游戏开发者的素材插件商店

独立开发有些年头了,一直在用Unity开发游戏。经过几年的摸索与实践,从最初的小白,跟着教程学习NGUI的使用,到可以自己设计出很棒的UI;从第一款游戏花了大半年时间,到后来依据工程大小与难度1~3个月从想法到上线,最快的一款游戏从想法到上线只用时一周(不是山寨);从模仿别人到完全原创。这些年做过挣钱的游戏和APP,也做过叫好却不赚钱的产品。经历过短时间开发的产品大赚一笔,也经历过精心制作的游戏销量不佳。其实就是这个样子,有些游戏负责赚钱养家,有些产品负责诗和远方。那些得到了国内外用户鼓励和肯定的产品,更能带给我精神上的愉悦,也更能鼓舞我在这条路上继续前行。 感慨完,就开始正题吧。作为一名开发者,Coding是老本行,就算有困难,解决起来相对容易。很多人在游戏开发的最开始肯定是卡在美工素材和音乐素材上,Unity Asset Store可以帮你解决大部分问题。从分类上看,Unity Asset Store主要分成11大类。我列出其中最常用的几大板块:3D模型和动作、2D资源、音频资源、编辑器扩充与脚本、粒子系统资源。剩下的版块用处不大。如果你不用Unity而用其他游戏引擎开发游戏,音频资源和2D资源也都是适合你的。几个月前Unity Asset Store改版了,用惯了老版的可能并不习惯新版的布局,新版可以右上角切换回老版。 Unity Asset Store里有大量优质的艺术家与开发者提供资源,所以下面我从每个分类里挑选出比较优秀的开发者来介绍如何高效的使用Unity Asset Store。本打算5大分类里分别列出10家开发商的,一共列举50家。写了几个后感觉工程太庞大,所以就写了个简洁版,以后如果还有空闲时间就再增添新的内容。 一:3D模型和动作这其实是两个分类,由于Unity里面有一套通用的3D动画系统,所以不同模型和动作可以互换使用。由于本人偏爱Lowpoly风格,所以推荐的开发者也大都是这种风格 1)Synty StudiosSynty Studios擅长开发Lowpoly风格的3D模型与场景,他们共有几十个精品资源场景与人物模型,涉及军事、中世纪、地牢、西部牛仔、警匪等多种风格。单品价格从几美元到几十美元不等。 该开发者所有资源:Synty Studios所有资源链接 5个具有代表性的资源如下:(该开发者精品资源太多,只列举下面5个)a)POLYGON - Battle Royale Pack(适合吃鸡游戏场景与人物)b)POLYGON MINI - Fantasy Character Pack(60个Lowpoly风格小人,适合幻想Q版风格游戏使用)c)POLYGON - Western Pack(适合西部牛仔风格游戏)d)POLYGON - Knights Pack(适合中世纪风格游戏)e)POLYGON - Dungeons Pack(适合暗黑地牢风格游戏) 2)Quantum TheoryQuantum Theory也是一家擅长开发Lowpoly风格的资源提供商,其资源的种类和丰富度要比Synty Studios逊色一些,但他提供了一种把非Lowpoly风格资源一键变成Lowpoly风格资源的插件,真可谓化腐朽为神奇。资源价格从十几美元到几十美元。 该开发者所有资源:Quantum Theory - Asset Store2个具有代表性的资源如下:a)PolyWorld: Woodland Low Poly Toolkit(内含化腐朽为神奇的插件)b)Rome: Fantasy Pack I(适合制作大型古罗马PC游戏) 3) beffiobeffio的资源极具创造力,他的每个资源都会让人大呼过瘾,极大地激发开发者的创作潜能,这也是我非常喜爱的一家资源提供商。擅长开发Lowpoly和欧美奇幻风格的资源。beffio的资源多为大型场景资源,涉及太空、古老部落、大型城市群、中世纪和丛林等。价格一般在几十美元。 该开发者所有资源:beffio全部资源链接3个具有代表性的资源如下:a)Space Journey(Asset Store里面最适合做太空类游戏的资源,包含大量太空场景和6搜太空飞船)b)City Adventure (适合开发城市建造类游戏)c)RPG Medieval Kingdom Kit(适合制作魔幻类RPG游戏) 下面列举的开发商我就不单独列出他们某一项产品了(工程量太大),只列出这家开发商的产品集合页面。 4)Malbers AnimationsMalbers Animations这家开发商提供了AssetStore中最全的动物模型资源,包含动画。同样也是LowPoly风格。 ...

June 4, 2019 · 1 min · jiezi

Unity-DOTS-走马观花

简单介绍 Data-Oriented Technology Stack (DOTS, 数据导向型技术栈) ,其包含了 C# Job System、the Entity Component System (ECS) 和 Burst。 特点DOTS 要实现的特点有: 性能的准确性。我们希望的效果是:如果循环因为某些原因无法向量化,它应该会出现编译器错误,而不是使代码运行速度慢8倍,并得到正确结果,完全不报错。跨平台架构特性。我们编写的输入代码无论是面向 iOS 系统还是 Xbox,都应该是相同的。我们应该有不错的迭代循环。在修改代码时,可以轻松查看为所有架构生成的机器代码。机器代码“查看器”应该很好地说明或解释所有机器指令的行为。安全性。大多数游戏开发者不把安全性放在很高的优先级,但我们认为,解决 Unity 出现内存损坏问题是关键特性之一。在运行代码时应该有一个特别模式,如果读取或写入到内存界限外或取消引用 Null 时,它能够提供我们明确的错误信息。其中向量化指的是 Vectorization。 向量化的相关介绍: https://stackoverflow.com/questions/1422149/what-is-vectorizationhttps://www.wikiwand.com/en/Array_programmingBurstUnity 构建了名为 Burst 的代码生成器和编译器。 当使用 C# 时,我们对整个流程有完整的控制,包括从源代码编译到机器代码生成,如果有我们不想要的部分,我们会找到并修复它。我们会逐渐把 C++ 语言的性能敏感代码移植为 HPC# (高性能 C#,下文会提到)代码,这样会更容易得到想要的性能,更难出现 Bug,更容易进行处理。 如果 Asset Store 资源插件的开发者在资源中使用 HPC# 代码,资源插件在运行时代码会运行得更快。除此之外,高级用户也会通过使用 HPC# 编写出自定义高性能代码而受益。 ECS Track: Deep Dive into the Burst Compiler - Unite LA Burst 对于 HPC# 更详细的支持可以在下面找到: ...

May 10, 2019 · 2 min · jiezi

学习笔记Mopub在Unity环境下的集成Banner和Interstitial

首先,需要在项目中包含MoPubUnity.unitypackage,包的下载地址:https://github.com/mopub/mopu... 接下来点击菜单Assets->Import Package->Custom Package,选择包文件 至此包就被包含进去了,包里面有简单的使用用例,不过不适合直接接入项目中。我们还是要写一个新的Controller来处理广告相关的事务。 新的Controller在本文中被命名为 MopubAdsController,首先将广告id暴露到Inspector界面: #region Inspector Variables [SerializeField] private string iOSBannerID; [SerializeField] private string iOSInterstitialID; [SerializeField] private string iOSVideoID; [Space] [SerializeField] private string AndroidBannerID; [SerializeField] private string AndroidInterstitialID; [SerializeField] private string AndroidVideoID; #endregion由于广告请求的函数的参数不是string,而是string数组,所以需要另三个函数来承接上面的id private string[] _bannerAdUnits; private string[] _interstitialAdUnits; private string[] _rewardedVideoAdUnits;接下来进入Start函数: void Start() { #if UNITY_IOS _bannerAdUnits = new string[] {iOSBannerID}; _interstitialAdUnits = new string[] { iOSInterstitialID }; _rewardedVideoAdUnits = new string[] { iOSVideoID }; #elif UNITY_ANDROID || UNITY_EDITOR _bannerAdUnits = new string[] {AndroidBannerID}; _interstitialAdUnits = new string[] { AndroidInterstitialID }; _rewardedVideoAdUnits = new string[] { AndroidVideoID }; #endif var anyAdUnitId = _bannerAdUnits[0]; MoPub.InitializeSdk(new MoPub.SdkConfiguration { AdUnitId = anyAdUnitId, LogLevel = MoPubBase.LogLevel.MPLogLevelDebug, MediatedNetworks = new MoPub.MediatedNetwork[] { }, }); MoPub.LoadBannerPluginsForAdUnits(_bannerAdUnits); #if UNITY_IOS MoPub.CreateBanner(_bannerAdUnits[0], MoPubBase.AdPosition.BottomCenter); #elif UNITY_ANDROID MoPub.CreateBanner("REPLACE_BY_ANDROID_AD_UNIT_ID_HERE", MoPubAdPosition.BottomCenter ); #endif MoPub.LoadInterstitialPluginsForAdUnits(_interstitialAdUnits); MoPub.RequestInterstitialAd(_interstitialAdUnits[0]); MoPub.LoadRewardedVideoPluginsForAdUnits(_rewardedVideoAdUnits); //初始化各种广告,现在还没有使用好几个id的需求,所以每个广告类型先都用一个id,所以都取[0]。按着官方用例,如果有多种id的话可以写一个for循环直接遍历创造就好 //以下是各种回调的承接,相当于OC的XX.delegate = self; MoPubManager.OnSdkInitializedEvent += OnSdkInitializedEvent; MoPubManager.OnAdLoadedEvent += OnAdLoadedEvent; MoPubManager.OnAdFailedEvent += OnAdFailedEvent; MoPubManager.OnInterstitialLoadedEvent += OnInterstitialLoadedEvent; MoPubManager.OnInterstitialFailedEvent += OnInterstitialFailedEvent; MoPubManager.OnInterstitialDismissedEvent += OnInterstitialDismissedEvent; MoPubManager.OnRewardedVideoLoadedEvent += OnRewardedVideoLoadedEvent; MoPubManager.OnRewardedVideoFailedEvent += OnRewardedVideoFailedEvent; MoPubManager.OnRewardedVideoFailedToPlayEvent += OnRewardedVideoFailedToPlayEvent; MoPubManager.OnRewardedVideoClosedEvent += OnRewardedVideoClosedEvent; }其中: ...

May 10, 2019 · 2 min · jiezi

Unity C# for和foreach效率比较

先说测试结果:效率几乎完全一样,不用特意改变写法,喜欢哪种用哪种。测试代码:using UnityEngine;using UnityEditor;using System.Diagnostics;/// <summary>/// 执行时间测试/// ZhangYu 2019-04-13/// </summary>public class TimeTest : MonoBehaviour { private static Stopwatch watch; private void Start() { Execute(); } [MenuItem(“CONTEXT/TimeTest/执行”)] private static void Execute() { watch = new Stopwatch(); // 数据长度 int total = 100000000; int[] array = new int[total]; for (int i = 0; i < total; i++) { array[i] = i + 1; } // Foreach watch.Reset(); watch.Start(); foreachTest(array); watch.Stop(); string msgForeach = string.Format(“Foreach: {0}s”, watch.Elapsed); // For1 watch.Reset(); watch.Start(); foreachTest(array); watch.Stop(); string msgFor1 = string.Format(“For1: {0}s”, watch.Elapsed); // For2 watch.Reset(); watch.Start(); foreachTest(array); watch.Stop(); string msgFor2 = string.Format(“For2: {0}s”, watch.Elapsed); print(msgForeach); print(msgFor1); print(msgFor2); } // (1)0.7035506s // (2)0.7174406s // (3)0.7001000s // (4)0.7012998s // (5)0.7009337s public static void foreachTest(int[] array) { foreach (int item in array) { } } // (1)0.7014426s // (2)0.7172180s // (3)0.6987379s // (4)0.6987784s // (5)0.7051741s public static void forTest1(int[] array) { for (int i = 0; i < array.Length; i++) { } } // (1)0.7006860s // (2)0.7160505s // (3)0.6997564s // (4)0.7024032s // (5)0.7004985s public static void forTest2(int[] array) { int length = array.Length; for (int i = 0; i < length; i++) { } }}测试结果:排除运行环境的误差,for循环和foreach循环在数十次的测试结果中,效率基本是完全一样的,For2方法优化了一下length的取值,但没什么明显的性能差距,所以for和foreach喜欢那种用哪种吧,不用为了提高效率特意改变写法或习惯。 ...

April 13, 2019 · 1 min · jiezi