
2026-04-18157次浏览
0评论
0收藏
0点赞
分享
Unity界面大致如图1所示。分别对各个子窗口或区域进行介绍:
图1 Unity引擎界面简介
当我们新建一个Unity工程时,会自动生成如图2所示的目录结构:
图2 Unity工程的目录结构
Unity有一批保留相关逻辑或API的特殊目录,比如:
平时我们主要使用的是Assets目录,在其之中没有自动创建的子文件夹,但是我们可以自行创建一系列带保留逻辑的特殊目录:
在Assets目录下,所有的目录和文件,都会由Unity自动生成同名的以.meta为扩展的文件。meta文件主要包含以下内容:
图3 一个典型的meta文件内容
guid是在资源被新建时就确定的,由Library/assetDatabase负责管理,即使手动删除meta文件,Unity再次自动生成时,guid也不会变化,同时也保证了全局唯一性,不会出现重复的问题,所以当发现guid有diff,应当审慎处理,从资源被创建开始,guid不应该会有变化,除非是手动把Library目录删掉了。在timeCreated属性被移除后,其实meta冲突的可能性已经下降不少,以下列举部分本人遇到过的meta文件在多人协作下遇到过的两个多发问题:
guid冲突可以通过Unity的Console发现,导入资源后,但凡有冲突的,Unity会以warning的形式打印出来,如图4所示:
图4 Unity导入资源时关于guid冲突的warning
GameObject时Unity中的基本操作对象,类似于UE4中的Actor。如前面所述,任何可以出现在Hierarche里的东西都是GameObject,包括灯光、镜头、3D物体、2D的UI、粒子系统等。GameObject都包含最基本的Transform组件,其中包含position、rotation和scale属性,对于2D GameObject,可能会以Rect Transform代替Transform。其余各种组件(Component)可以根据需要自行添加,包括Unity内置的各种组件,或者是使用者自己编写的,继承自Monobehaviour的C#脚本。
Monobehaviour类提供了多种API,可以通过gameObject和transform属性方便地调用,以获取当前GameObject的各项组件实时参数或状态。这里一个常见的问题是使用者新建了一个脚本,但是在编辑器中Add Component时,无法搜到它,导致无法添加到GameObject上。这种问题一般的原因是类没有继承Monobehaviour,可以着重检查以下。
GameObject是实际在场景中作用的物体,而Prefab(预制体)则是GameObject的描述文件,或者说是母体/模板,我们可以利用Prefab,快速批量地、动态地生成GameObject。实际项目中一般都是先提前编辑好各种需要的GameObject,存成Prefab,运行时通过代码把Prefab加载到内存,并利用它实例化出GameObject。
需要说明的是,Prefab本身并不包含任何模型贴图等真正的素材文件,它只是一个虚拟的“参考”,它实质上是通过文本文件的形式,将GameObject的各项属性和引用等,记录下来。利用GameObject生成Prefab的方法很简单,直接从Hierarche拖动到Project里需要的目录就行,具体见图5。
图5 拖动GameObject生成Prefab
可以注意到,Hierarche中,不是由Prefab实例化而来的GameObject是灰色方块图标,否则就是浅蓝色方块图标,Cube生成对应Prefab后,其本身也变成了浅蓝色,表明是由Prefab-Cube实例化而来,此后对该Cube实例做的修改,都可以同步保存到Prefab-Cube中。
相反地,如果我们想在编辑器中直接依据某Prefab生成Gameobject,则反向拖动,由Project窗口拖动到Hierarchy里即可。
在2018.3之后,Unity的Prefab系统大改,增添了诸如Nested Prefab,Prefab Override,Prefab Variant,功能强大了不少,但操作复杂度也略有增加,由于这部分内容较多,这里暂不做具体介绍,读者可以自行了解。
Scene也是可以实际存在于硬盘中的一种Unity内置的文件类型,后缀名为.scene。Scene是场景单元,一个场景单元可以保存其所挂载的所有GameObject状态及参数(不一定是通过Prefab生成的),一旦加载便以最后保存时的状态展示,场景可以通过Application.LoadLevel系列函数进行不同场景文件间的切换。场景切换会把隶属于当前场景的所有GameObject统一销毁,如果希望保留部分GameObject,应在切换前,使用DontDestroyOnLoad()方法,对其进行保护。DontDestryOnLoad会创建一个独立的Scene,其中的GameObject不会随着切场景被销毁,如图6所示。
图6 DontDestroyOnLoad创建独立的Scene
需要说明的是,切场景不一定需要依赖多个Scene文件和LoadLevel方法完成,本人曾经任职的项目组就是从头到尾单一Scene的架构,其中所有的切场景都是完全手动指定的GameObject销毁和创建。多Scene在场景管理上更加清晰和方便,但需要注意数据同步的问题,数据尽量不要依赖于GameObject存储,如果实在需要,应使用DontDestroyOnLoad。而单Scene则需要自行管理好当前场景中所有的物体,确保切场景时不存在错清漏清的情况。两种方案各有优点,可根据实际情况选取。
简单3D模型,包括可以通过Unity直接创建的所有3D GameObject,如立方体、球体、椭圆体、平面等。3D模型主要使用Mesh进行渲染,简单3D模型生成后都会带有Mesh Filter和Mesh Renderer组件。前者指定了该物体的Mesh,值得一说的是,该Component的Mesh可以通过代码指定顶点数据和UV等参数后动态生成并赋值,而简单3D模型生成时都会自带Unity默认的对应模型Mesh。而后者则是使用Mesh Filter所指定的Mesh作为输入,并使用指定的Material(材质球)进行渲染。因此,Mesh Filter和Mesh Renderer都是成对出现的。Renderer对于需要需要显示在游戏里的GameObject来说是必不可少的,Unity对于场景GameObject的渲染处理逻辑是遍历场景中所有的GameObject并取含有Renderer的部分作渲染。对于3D模型是Mesh Renderer或者Skinned Mesh Renderer,对于2D的UI来说则是Canvas Renderer。除此以外还有其他类型的Renderer,这里不一一赘述。
图7 一个简单立方体的Components
如果我们需要自定义一个模型的渲染方式,一般是使用Shader(着色器)。在Unity中,我们需要先在Project窗口右键,生成Shader文件,Unity的Shader使用CG语言,新建之后已经自带一些默认代码,这里对于Shader逻辑编写和CG语言不做具体描述,主要介绍下Shader索引路径:
图8 Shader索引路径
由于我们创建的Shader,需要配套地创建Metarial(材质球)才能使用,为材质球指定我们自定义的Shader时,就会用上这一路径:
图9 为材质球指定自定义的Shader
此后,再把该材质球绑定到Mesh Renderer中,就能让对应的3D模型按自定义Shader的逻辑进行绘制。
在Unity4.6版本以前,没有一个官方的UI解决方案,最著名的第三方UI解决方案是NGUI。此后4.6版本,官方UI方案——UGUI出现,UGUI很多方面都参考了NGUI,但在此后很长一段时间的更新中,UGUI作为官方原生支持的UI方案,具有了更多的优势。由于笔者的项目经验仅限于UGUI,本文后续的UI介绍也以UGUI为主,望见谅。
Canvas(画布)是UGUI所有功能性UI组件所必须的根节点。若我们直接在Hierarchy中创建一个UI GameObject(下面简称UI),但场景中未有含有Canvas组件的GameObject,则Unity会自动帮我们创建一层名为Canvas的GameObject,其中就含有包括Canvas组件,并自动地把我们要创建的UI组件挂载在Canvas下作为子节点。
一个自动创建的Canvas GameObject如图10所示:
图10 一个自动生成的Canvas GameObject
Canvas负责其子节点下所有带有Canvas Renderer的UI的渲染(UI创建的时候都默认带有Canvas Renderer),而不挂在Canvas下,或者不带Canvas Renderer的UI,都是无法被绘制的。
对于Canvas,其中有个参数是Render Mode,这是一个三选一的选项参数,其中三个参数分别解释如下:
Canvas必备组件之一,用于UI的射线检测,当点击或其他输入事件发生时,会检查该Canvas上哪个UI被射线碰撞到。
与Canvas类似地,EventSystem也是不可或缺的。但与Canvas不同的是,Canvas可以存在多个,但EventSystem时全局唯一的。创建UI时,Unity也会检查并自动创建带EventSystem的GameObject。
图11 自动创建的Event System GameObjct
Event System主要负责对整个输入事件系统管理,并进行事件分发。
BaseInputModule是一个基类,StandaloneInputModule是Unity自带的对该基类的一个继承实现,负责接收用户输入,是整个Event System的输入源。用户也可以实现自己的InputModule。
我们已经有了输入事件的生产者EventSystem,但为了开发与用户输入事件相关的逻辑,我们还需要有输入事件的监听者。这里以按钮的点击事件为例,以如下步骤可添加一个事件监听者:
图12 给Button OnClick事件添加监听者
Unity的动画系统主要由三部分组成:
最基本的动画单元,可以用关键帧KEY动画,或者使用曲线来KEY,可以在任意有效持续时间内的帧上添加事件帧(白色长条),并绑定相关GameObject上的脚本方法:
图13 Animation的关键帧视图和曲线视图
Animator Controller的重点在于状态跳转的配置,状态跳转主要有两种类型:播放结束自动跳转/参数控制跳转。其中前者通过状态跳转配置中勾选Has Exit Time并填写Exit Time,并且保持Condition为空即可。动画片段播放持续到Exit Time后,将开始跳转。后者主要靠Condition中的条件完成跳转,这个要求Controller有相关的控制参数(Parameters),我们可以配置多种类型的参数,并且在跳转中设置相应的条件,然后通过脚本进行参数的取值控制或触发,以逻辑驱动状态跳转。当然我们也可以同时填写Condition和勾选Has Exit Time,这种情况下,跳转发生则需要同时满足两个条件:参数条件符合,播放长度达Exit Time。
另外,使用者可以配置动画跳转的融合,如图14右所示,使用者可以通过Transition Duration、Transition Offset配置融合参数,也可以通过下边的图形直接拖动配置,【需要注意的是,跳转源动画在融合区间结束时间点之后的部分A,以及跳转目标动画在融合区间开始时间点之前的部分B,是不会被播放的,意味着A和B内的事件帧是无法被触发的,真实案例】。所以安全起见,不建议把UI清理或交互阻塞等关键逻辑建立在动画事件帧上,因为你无法保证它一定被执行!
图14 Animator Controller及其状态跳转配置
Animator是挂载在GameObject上的Component,需要指定Animator Controller才能播放动画,若Animator在GameObject被激活/生成时即处于Enabled状态,则动画的默认状态(图16左,黄色状态框)会立即播放,若不希望Animator自动播放,应当先把Animator设置为Disabled,按需激活。另外Animator对象可以通过脚本获取,通过相关API设置Controller内对应参数,或者直接播放指定状态等,具体可自行参考Unity Script指引。
所有的Unity API可以参考: https://docs.unity3d.com/2018.4/Documentation/ScriptReference/ (注意选择Unity版本)
这里主要介绍以下GameObject全生命周期的Mono类方法,和序列化相关。
序列化是Unity对于脚本组件参数支持图形化编辑的一种解决方案。一般而言,脚本类中定义为public或带[SerializeField]的可序列化对象,会出现在Inspector里,开放给使用者直接赋值和编辑,相当于暴露参数。对于序列化的域,有如下要点:
而对于字典这种无法被序列化的类型,我们也可以使用ISerializationCallbackReceiver这个接口实现间接的序列化,具体可以参考Unity官方给的例子:https://docs.unity3d.com/2018.4/Documentation/ScriptReference/ISerializationCallbackReceiver.html
1 |
PlayerPrefs.SetFloat(“Score”,0.1f); |
2 |
PlayerPrefs.Save(); |
3 |
float a = PlayerPrefs.GetFloat(“Score”); |
评论 0