..

读书笔记:《游戏架构:核心技术与面试精粹》

UI 交互

绑定事件响应

问题: 请列出为UI控件绑定事件响应的方法,并分析不同方法之间的优劣。

面对问题时,最重要的是确定它的边界。对于这个问题,如何绑定当然是关键,但UI控件的范围也很重要。不是所有的UI控件都能绑定事件,但能绑定的也不是只有按钮,至少还应包含:

  1. 拖动条
  2. 输入框
  3. 开关
  4. 滚动区域

从操作层面看,绑定响应的方式有两种,但它们的核心是相同的。

  1. 组件中添加
  2. 代码中添加

绑定方式优劣比较

绝大多数程序员喜欢用代码添加的方式。一般来说,程序员不会轻易动别人的代码,但预设很可能被其他人更改。当Bug出现时,不使用代码编写绑定,开发人员心里就很没底,无法确认是否是自己的原因。

如果有很好的框架支撑,编辑器设置绑定的方式也是可取的。就好像没人质疑在Unity 3D中,Start函数是否会在一开始调用一样。如果有一目了然的绑定显示,并且有稳固的框架支撑,那么在编辑器中绑定也没什么不好。在编辑器中绑定有个优势,就是支持动态更新。由于绑定关系存储在预设中,因此意味着整个逻辑可以跟随AssetBundle更新。如果C#代码逻辑写错了,只更新预设也是白费力气,但可以在基类里提供一个默认的出错处理,以便最大限度地降低影响。

事件传递流程

问题: 请从结构的角度概述,控件的消息模型包含哪些部分,并解释 UGUI 中点击事件与响应调用是如何关联在一起的。

很多Unity 3D项目都使用了UGUI,但并不是所有人都研究过它的内部结构。针对事件的传递过程,会问住大多数未深入思考过的开发者。

首先说明,事件体系的基础是设计模式中的观察者模式,因此按照标准的Subject和Observer来解读没有任何问题。但在这里,笔者更希望以功能为导向,将事件体系划分成更易于理解的模块。按这种划分方式,事件体系由四部分组成,分别是:

  1. 监测器(Monitor)
  2. 采集器(Collector)
  3. 派发器(Dispatcher)
  4. 响应器(Receiver)
flowchart TD

Monitor <--> Collector

Monitor --> Dispatcher

Monitor --> Receiver

User((USER)) --> Collector

在以上示例图中,用户的操作被监测器驱动的采集器捕获,接着监测器将反馈的信息通知到派发器中,最后通过派发器将事件传播出去。

Unity3D 中的对应关系如下:

  • 监测器(EventSystem):重写了MonoBehavior的Update方法,会在每一帧更新挂载在同一个GameObject上的采集器组件状态,并判断是否应该激活派发器。如果是,则调用各个派发器中的派发函数Process
  • 采集器(Collector):对应的实现类为 BaseInputModule 与 BaseRaycaster,组件为StandaloneInputModule和GraphicRaycaster。
  • 派发器(Dispatcher):对应的实现类同样是 BaseInputModule,常用的是其子类 StandaloneInputMoudle。
  • 响应器(Receiver):对应的实现类为IEventSystemHandler及其子类,例如最常用的IPointerClickHandler,它的作用是处理点击事件。
classDiagram

class UIBehaviour {
    # OnEnable
    # OnDisable
}

class BaseRaycaster

class GraphicRaycaster

class RaycasterManager

class EventSystem {
    + RaycastAll()
    + Update()
    + UpdateModules()
}

class BaseInputModule {
    + EventSystem mEventSystem
}

class PointerInputModule {
    # GetMousePointerEventData()
    # GetTouchPointerEventData()
}

class StandaloneInputModule {
    + ProcessMouseEvent()
}

class ExecuteEvents {
    + ExecuteHierarchy()
    + Execute()
    + GetEventList()
}

class Button {
    + OnClick()
}

UIBehaviour <|-- BaseRaycaster
BaseRaycaster <|-- GraphicRaycaster

BaseRaycaster <-- RaycasterManager
RaycasterManager <.. EventSystem

UIBehaviour <|-- EventSystem

BaseInputModule *--* EventSystem

UIBehaviour <|-- BaseInputModule
BaseInputModule <|-- PointerInputModule
PointerInputModule <|-- StandaloneInputModule

StandaloneInputModule ..> ExecuteEvents

ExecuteEvents *-- IEventSystemHandler

IEventSystemHandler <|-- IPointerClickHandler

IPointerClickHandler <|-- Button

玩法底层

游戏循环

问题: 请简述Unity 3D中常用的生命周期函数,并以此为基础简述游戏循环的设计要点。

主循环分为物理模拟、游戏逻辑、渲染绘制三个子循环。物理循环和游戏逻辑循环在同一个线程中,渲染循环从 5.x 版本之后便被放在了其他线程执行,这样做可以在固定帧率下降低逻辑循环的压力,减少掉帧情况的发生。

游戏引擎的循环分为了 非阻塞的用户输入、更新游戏逻辑状态、渲染游戏画面 三个部分。常说的帧率就是指每秒钟能渲染多少次游戏画面。

游戏的渲染分为了 固定帧率模式和追赶模式。

  • 固定帧率模式:我们有个期望的帧率,如果每帧的运行时间短,那么帧率就会超过预定的标准,因此我们通常会在循环的末尾加入延期等待。
  • 追赶模式:可以更好地处理掉帧引发的逻辑问题。大体思路是,当出现掉帧时,只运行逻辑,不绘制画面,用节省下来的时间追赶落后的帧。这种策略会降低图形绘制的频率,但可以保证逻辑的执行。

在 Unity3D 中,FixedUpdate 对应的就是追赶模式,引擎设置中的 Fixed Timestep 可以控制 FixedUpdate 速率,其数值为时长周期。如果 FixedUpdate 在限定的时间内执行不完,则图形绘制频率就会降低,以保证物理的执行。

游戏同步

问题: 请问在游戏同步方面有哪些分类方式?帧同步与状态同步有何优劣?

从游戏模式上:

  • 多人在线游戏(Multiplayer Online, MO):少量玩家聚集在一起的竞技游戏。它的侧重点是实时对战的战斗乐趣,因此游戏追求高速的响应时间,以及频繁的交互操作。
  • 大型多人在线游戏(Massively Multiplayer Online, MMO):大量玩家进行长期的社交性游戏,它追求的是一种在虚拟社区中的存在感,游戏周期通常几个月以上。由于要处理大量的玩家数据,难免会牺牲响应时间,因此在MMO游戏中,通常不会有高速的交互。为了能长期运行,系统的稳定性与可维护性也是框架的重中之重。

MO与MMO在游戏内容上有很大差别,所需的技术也截然不同,但一个游戏中同时存在两种模式并不冲突。常见的做法是,将MO类型的游戏以副本的形式嵌到 MMO 中,并分别采用独立结构对游戏逻辑提供支持。这样就可以兼顾 MMO 的玩法多样性与MO对战的乐趣性。在 WoW 中,野外战斗与大部分副本都是以 MMO 架构实现的,而少数在短时间内需要玩家紧密配合、历尽艰难才能通关的副本则是通过响应更快的MO架构实现的。

从通信方式上:

  • CS 架构
  • P2P 架构

从数据模式上:

  • 全网状数据模式:每个客户端需要给网络中的每个设备广播自己的数据。因此每个设备上都有完整的数据,各个终端之间传递的只是控制设备的输入信息。
  • 星型数据模式:网络中的成员并不平等,所有终端都要依从中央终端的数据。

同步方式上:

  • 状态同步:状态同步指的是将其他玩家的状态行为同步,游戏的数据由服务器运算,只是将结果下发给客户端,客户端根据得到的数据驱动显示即可。
  • 帧同步:客户端之间只同步用户的输入指令,不同的客户端各自计算自己的结果。

优化方案:

在帧同步游戏中,由于广播的频率非常高,因此每次广播的数据就要足够小,这样可以节省很多消息处理的时间。

对于消息,可以将需要所有客户端同时发生的内容提前广播给其他用户,采用时钟同步。

客户端逻辑先行,显示通过平滑追赶的方式处理。

传输层,移动端的同步建议使用UDP作为传输协议。TCP为了保证传输的可信性,很多机制不太适合波动较大的移动网络。在弱网络环境下,UDP的RTT几乎不受影响,而TCP的RTT波动比较大,特别是在丢包重发时影响比较明显。虽然使用UDP会引入丢包、乱序的问题,但可以通过冗余的方式来解决这个问题。比如每帧三个数据包,实际上是包含了过去两帧的数据,也就是每次发三帧的数据来对抗丢包。

艺术资源

贴图种类

问题: 请简要介绍一下项目中用到的材质相关的贴图种类及其作用。

固有色贴图(Diffuse):是物体在白色阳光下所呈现的固有颜色。固有色,可以准确地控制色相,使角色呈现一个合理的饱和度。

透明贴图:一张只由黑白像素构成的灰度图,通常使用它来实现磨砂或半透明效果。在这种图中,纯黑色代表全透明,纯白色代表不透明。

AO贴图(Ambient Occlusion):也被称为环境光吸收贴图,主要用来制作模型的阴影。为了不受外界光照变换的影响,AO贴图采用“吸收”光线的计算方法,模拟全局光照效果来改善阴影的细节。使用这种技术可以优化墙角阴影浅淡、缝隙阴影发虚等问题,以加强明暗对比,增加空间的层次,在灯光复杂的场景中比较有用。

高度贴图(Height Map):记录物体表面高度的灰阶图,黑色代表高度的最小值,白色代表最大值。它主要用于加强法线贴图的表现效果,例如视差技术或浮雕效果。

法线贴图(Normal Map):作为模型表面的扩展,包括了模型像素点的高度值。

高光贴图(Specular):主要用来控制模型的质感。

自发光贴图(Glow):用来标定模型发光的区域。贴图的白色区域渲染为完全发光,黑色区域则保持原样,灰色区域渲染为部分自发光。

反射贴图(Reflection):标注在有光泽物体表面反射的效果。

渐变贴图(Ramp):常用来辅助计算光照角度引起的不同变化。通过创建高度自定义的渐变,或使用双向反射贴图,可以模拟复杂的光照效果。

噪声贴图(Noise):通过噪声函数计算出的贴图。通过调整混合算法,它可用于模拟云朵、火焰等自然现象,或者大理石、木材等天然材质。

材质效果

问题: 请问常见的材质效果有哪些,并简述如何在Unity 3D中实现对应效果。

从效果的实现原理的角度来看,可以按照是否使用PBR(Physically-Based Rendering)作为标准划分。即使用PBR流程组织美术资源的为一类,其他的为另一类。

边缘光

无论哪种渲染模式,光感的效果都是核心。对于卡通渲染最大的特点就是利落的边缘光或阴影。

核心是使用一张渐变图做控制,然后根据照度在图上做强度的采样

1float_t rimlightDot=saturate( 0.5 * ( dot( normalVec, i.lightDir ) + 1.0 ) );
2falloffU=saturate( rimlightDot * falloffU );
3falloffU=tex2D( _RimLightSampler, float2( falloffU, 0.25f ) ).r;
4float3_t lightColor=diffSamplerColor.rgb; // * 2.0;
5combinedColor +=falloffU * lightColor;

高光效果

物体的质感几乎完全取决于高光的效果。

通过一张高光图记录反光的倍率与色值,然后再与标准高光计算出的结果相乘。

1float4_t reflectionMaskColor=tex2D( _SpecularReflectionSampler, i.uv.xy );
2float_t specularDot=dot( normalVec, i.eyeDir.xyz );
3float4_t lighting=lit( normalDotEye, specularDot, _SpecularPower );
4float3_t specularColor=saturate( lighting.z ) * reflectionMaskColor.rgb *
5diffSamplerColor.rgb;
6combinedColor +=specularColor;

渐变贴图

渐变贴图(Ramp)或者叫衰减贴图(FallOff),它更多是以一种加强色彩控制的形式出现。

实现方面,使用视线与平面法线点乘的值作为标准,在渐变图中采样。最终,通过渐变图的透明通道作为插值标准,与固有色进行混合。

1float_t normalDotEye=dot( i.normal, i.eyeDir );
2float_t falloffU=clamp( 1- abs( normalDotEye ), 0.02, 0.98 );
3float4_t falloffSamplerColor=FALLOFF_POWER * tex2D( _FalloffSampler,
4float2( falloffU, 0.25f ) );
5float3_t combinedColor=lerp( diffSamplerColor.rgb, falloffSamplerColor.rgb
6* diffSamplerColor.rgb, falloffSamplerColor.a );
7combinedColor=diffSamplerColor;

自发光

这种效果最大的优势是,可以使画面看起来有更多的光源。

材质捕获贴图

材质捕获贴图(Material Capture),通常被称为MatCap,在很多3D软件中都可以生成。它最大的特点是能真实地表现反射效果,而不需要在场景中提供对应的灯光。

从本质上讲,它是将所有的光照信息都存储到了贴图中的。代码实现也不复杂,在运行时,只需将法线从模型空间转到视口空间,再将对应UV映射到贴图上就可以了。

动画分类

问题: 请问游戏中常见的动画表现方式有哪些,请简述对应的技术细节?

从动画的本质上讲,它利用了人的视觉暂留(Persistence of Vision)。当物体在快速运动时,人眼所看到的影像消失后,仍能继续保留影像约0.1~0.4秒。游戏中动画也利用相同的原理,在每帧更新时刷新图像。

简化制作数据通常是使用插值技术。插值是指对离散的数据进行处理,进而得到一个连续的结果。在动画方面,通常的应用场景是,对定义边界值的中间细节内容进行填充。

序列帧动画:一系列动画的图片,按照次序播放,最终形成的动画。

骨骼动画:3D模型只能通过骨骼的蒙皮来做动画,因此当谈及骨骼动画时,通常指2D层面。核心是将图片绑定到骨骼上。制作动画时,直接操作骨骼。只要导出骨骼数据,运行时关联图片文件即可播放动画。

帧动画:使用关键帧控制很多元素的属性,例如UI移动、图片缩放、材质参数变化等,然后通过插值的方式补全过渡效果。

变形目标动画:定义了所有顶点移动方式的动画。由于数据量非常大,因此这种技术通常只用于需要控制细节的人脸表情。

蒙皮动画:每个 3D 模型在绑骨之后,要为每个顶点分配对应骨头的权重,以防止动作出现撕裂模型表面的现象。因此,蒙皮的核心是权重的分配。当游戏运行时,顶点的位置是根据模型数据通过CPU实时计算出来的。如果是相同角色很多的场景,则可以预先将动作中顶点的每帧位置计算好,以节省运算。

后处理效果

模糊效果

问题: 对于非全屏UI,策划希望将背面的场景模糊掉,以突出UI界面的显示,请问该如何实现模糊效果?

通常来说,覆盖全场景的效果,都会在摄像机上想办法实现。改变画面风格,通常使用后处理技术来实现。

后处理(Post-Process Effect)技术是一种对渲染之后的画面进行再加工的技术。具体来说,针对每一个摄像机,在绘制到用户窗口之前,我们都有机会对这个画面进行二次加工,再将装饰过的画面呈现给用户。因此,后处理能够方便地制作全局效果,但毫无疑问,这样的操作会带来性能消耗。

我们可以将摄像机照射出的内容渲染到一张图中,它被称为RT(RenderTexture,渲染图)。而将摄像机内容绘制到渲染图的过程被称为RTT(Render to Texture)。

在Unity 3D中,RT对应的数据结构就是RenderTexture,我们既可以动态创建内存中的RT,也可以在工程目录下创建一个RT资源。

多年近视的经历告诉我们,当颜色的边缘不清楚时,就会呈现出模糊效果。因此模糊算法的核心在于,如何处理当前颜色受周围颜色的影响。最容易想到的是均匀采集周围的像素值与原值混合,这样自身色值就不那么突出了,从而达到模糊效果。这种模糊的策略叫作均值模糊。

代码实现也非常简单,用两层for循环分别取得一个方形区域的点,算出这些颜色的平均值,这个计算过程叫作卷积。

 1Shader "Custom/Blur"
 2{
 3    Properties
 4    {
 5        _MainTex ("Texture", 2D)="white" {}
 6        _BlurRadius ("_BlurRadius", Range(1,10) )=5
 7        _TextureSizeX ("_TextureSizeX", Float)=256
 8        _TextureSizeY ("_TextureSizeY", Float)=256
 9    }
10    SubShader
11    {
12        Tags { "RenderType"="Opaque" }
13        LOD 100
14
15        Pass
16        {
17            CGPROGRAM
18            #pragma vertex vert
19            #pragma fragment frag
20            // make fog work
21            #pragma multi_compile_fog
22
23            #include "UnityCG.cginc"
24
25            struct appdata
26            {
27                float4 vertex : POSITION;
28                float2 uv : TEXCOORD0;
29            };
30
31            struct v2f
32            {
33                float2 uv : TEXCOORD0;
34                UNITY_FOG_COORDS(1)
35                float4 vertex : SV_POSITION;
36            };
37
38            sampler2D _MainTex;
39            float4 _MainTex_ST;
40            int _BlurRadius;
41            float _TextureSizeX;
42            float _TextureSizeY;
43
44            fixed4 BlurTexture( float2 uv, float blurRadius, float
45                                  textureSizeX, float textureSizeY)
46            {
47                float pixelDisX=1.0/textureSizeX;  //像素间距X
48                float pixelDisY=1.0/textureSizeY;  //像素间距Y
49                int count=blurRadius * 2 +1; //每行的像素数量
50                count *=count;
51
52                float4 tmpColor=float4(0,0,0,0);
53                for( int x=-blurRadius ; x <=blurRadius ; x++ )
54                {
55                    for( int y=-blurRadius ; y <=blurRadius ; y++ )
56                    {
57                        float4 color=tex2D(_MainTex, uv+ float2(x*pixelDisX, y
58                                            * pixelDisY));
59                        tmpColor +=color;
60                    }
61                }
62                return tmpColor/count;
63            }
64
65            v2f vert (appdata v)
66            {
67                v2f o;
68                o.vertex=UnityObjectToClipPos(v.vertex);
69                o.uv=TRANSFORM_TEX(v.uv, _MainTex);
70                UNITY_TRANSFER_FOG(o, o.vertex);
71                return o;
72            }
73
74            fixed4 frag (v2f i) : SV_Target
75            {
76                // sample the texture
77                fixed4 col=BlurTexture(i.uv, _BlurRadius, _TextureSizeX, _
78                                          TextureSizeY);
79                // apply fog
80                UNITY_APPLY_FOG(i.fogCoord, col);
81                return col;
82            }
83
84            ENDCG
85        }
86    }
87}

泛光效果

问题: 现在想制作一个全屏泛光的效果,请简述如何实现。

全屏泛光(Bloom)是一种在实际项目中常用的技术,用于模拟强光下的效果。它的技术实现并不复杂,简单来说,全屏泛光只是在模糊后的渲染结果基础上,再叠加原场景效果。

虽说是全屏泛光,但在实现时还是要限制泛光区域,否则就会过亮。通常的做法是通过阈值颜色来控制。当颜色大于设置的阈值时,才被认为是有效颜色,而只有有效颜色才可以计入模糊的采样色中。

 1Shader "Custom/Bloom"
 2{
 3    Properties
 4    {
 5        _MainTex ("Texture", 2D)="white" {}
 6        _BlurRadius ("_BlurRadius", Range(1,20) )=5
 7        _BloomFactor ("_BloomFactor", Range(0,1))=0.5
 8        _ColorThreshold ("_ColorThreshold", Color)=(0.5,0.5,0.5,1)
 9        _TextureSizeX ("_TextureSizeX", Float)=256
10        _TextureSizeY ("_TextureSizeY", Float)=256
11    }
12    SubShader
13    {
14        Tags { "RenderType"="Opaque" }
15        LOD 100
16
17        Pass
18        {
19            CGPROGRAM
20            #pragma vertex vert
21            #pragma fragment frag
22            // make fog work
23            #pragma multi_compile_fog
24
25            #include "UnityCG.cginc"
26
27            struct appdata
28            {
29                float4 vertex : POSITION;
30                float2 uv : TEXCOORD0;
31            };
32
33            struct v2f
34            {
35                float2 uv : TEXCOORD0;
36                UNITY_FOG_COORDS(1)
37                float4 vertex : SV_POSITION;
38            };
39
40            sampler2D _MainTex;
41            float4 _MainTex_ST;
42            int _BlurRadius;
43            float _TextureSizeX;
44            float _TextureSizeY;
45            float _BloomFactor;
46            fixed4 _ColorThreshold;
47
48            fixed4 BlurTexture( float2 uv, float blurRadius, float
49                                textureSizeX, float textureSizeY)
50            {
51                float pixelDisX=1.0/textureSizeX;  //像素间距
52                float pixelDisY=1.0/textureSizeY;  //像素间距
53                int count=blurRadius * 2 +1; //每行的像素数量
54                count *=count;
55
56                float4 tmpColor=float4(0,0,0,0);
57                for( int x=-blurRadius ; x <=blurRadius ; x++ )
58                {
59                    for( int y=-blurRadius ; y <=blurRadius ; y++ )
60                    {
61                        float4 color=tex2D(_MainTex, uv+ float2(x*pixelDisX, y
62                                                                  * pixelDisY));
63                        color=saturate(color - _ColorThreshold);
64                        tmpColor +=color;
65                    }
66                }
67                return tmpColor/count;
68            }
69
70            v2f vert (appdata v)
71            {
72                v2f o;
73                o.vertex=UnityObjectToClipPos(v.vertex);
74                o.uv=TRANSFORM_TEX(v.uv, _MainTex);
75                UNITY_TRANSFER_FOG(o, o.vertex);
76                return o;
77            }
78
79            fixed4 frag (v2f i) : SV_Target
80            {
81                // sample the texture
82                fixed4 orgColor=tex2D(_MainTex, i.uv);
83                fixed4 blurColor= BlurTexture(i.uv,
84                                              _BlurRadius,
85                                              _TextureSizeX,
86                                              _TextureSizeY);
87                fixed4 final=orgColor + blurColor * _BloomFactor;
88                // apply fog
89                UNITY_APPLY_FOG(i.fogCoord, final);
90                return final;
91            }
92
93            ENDCG
94        }
95    }
96}

辉光效果

问题: 在看过全屏泛光效果之后,组内讨论一致认为,赛博朋克那种风格会更好一些。现在想做场景有些辉光的物体,但由于性能和效果等因素,不可以使用额外光源。请实现这种效果。

辉光效果(Glow)是全屏泛光效果的升级版本。它们的不同之处在于,辉光效果要求场景中只有部分物体泛光。

因此,我们需要在制作全屏泛光之前,区分出忽略哪些物体。在Unity 3D中,可以用下面的函数,动态替换摄像机下物体的Shader:

1GetComponent<Camera>().RenderWithShader(m_renderShader, "RenderType");

有了这个函数,我们就可以利用RenderTexture来制作辉光效果了。大体思路是,用一个额外的摄像机,在这个摄像机下,所有的非泛光物体都是黑色的,而泛光物体则保持原样。将这个结果渲染到一个RT上,然后对它进行模糊。最后将模糊后的泛光图叠加到主摄像机的渲染图像上,即可达成效果。

景深

问题: 随着游戏的开发,场景中的物件越来越多,会导致玩家注意力分散。为了改变这种情况,现希望制作景深效果,使画面更有层次感。具体来说,当物体距离较远时可以自动模糊,可以参考单反摄像的失焦效果。

在准备做一个未知效果时,我们需要将其拆分成已知的效果,再找到技术难点并给出解决方案。对于景深效果(Depth OfField,简称DOF),我们可以将一张图拆分为模糊区域与清晰区域两部分。那么哪部分可以模糊呢?显然这里需要模糊的是远处的物体,因此我们要解决的问题就演化为如何区分场景中物体与摄像机的距离。

在Unity 3D中,可以通过下面的代码开启摄像机深度检测。

1GetComponent<Camera>().depthTextureMode |=DepthTextureMode.Depth;

然后就可以在 Shader 中通过如下代码获取到景深值。当获取深度后,就可以根据景深与焦距来判断需要模糊哪些像素。根据焦距,将原图与远景图进行混色,即可完成景深的效果。

1float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
2depth = Linear01Depth(depth);

资源工作流

图片格式更改

问题: 项目中有很多 UI 图片,默认都开启了 MipMap 属性,现在希望将其关闭。一般来说直接选中一批图片更改就好了,但现在UI目录的层级比较复杂,分目录查找比较耗费精力。另一方面,图片还会源源不断地提交到各个文件夹中,每有更新都检查资源也不方便,请问有没有好的解决方案。

在Unity中,AssetPostprocessor类能够在导入资源时,或导入资源后,捕获到相应的信息。

在纹理图片导入的过程中,钩子函数的调用顺序如下:

  1. OnPreporcessTexture:在进入导入阶段前,会调用这个函数。我们能够重写TextureImporter的设置来更改导入资源的通用信息。
  2. OnPostprocessTexture:这是导入之后执行的函数,当函数运行完成时对象也创建完成。这个函数有一个参数,就是这个实例对象自己。由于是引用对象,因此改变属性的操作可以带出函数作用域,保存到最终的预设中。

因此,要想完格式转换的任务,可以在当有图片需要导入项目中时,触发回调函数,在回调函数中设置参数并保存对应属性。

 1using UnityEngine;
 2using System.Collections;
 3using UnityEditor;
 4
 5public class TextureChange :  AssetPostprocessor
 6{
 7    void OnPostprocessTexture(Texture2D texture)
 8    {
 9        TextureImporter importer=assetImporter as TextureImporter;
10        Debug.Log(importer.assetPath);
11        //Set mipmap disable
12        importer.mipmapEnabled=false;
13        importer.textureType=TextureImporterType.Sprite;
14        //Save the changes
15        EditorUtility.SetDirty(importer);
16    }
17}

文件移动检测

问题: 代码中有些资源加载路径是硬编码的,文件如果被移动就会引起加载不到预设的问题。虽然可以在加载逻辑中做保护,但大家更倾向于在编辑阶段解决这个问题。请问是否有办法让这个文件无法被移走?

Unity中的AssetModificationProcessor类能处理类似的问题。通过重写它的OnWillMoveAsset函数,可以阻止文件的移动。

 1using UnityEngine;
 2using System.Collections;
 3using UnityEditor;
 4
 5public class FileMove : AssetModificationProcessor
 6{
 7    private static string prefabPath="Assets/FileMove/Res/TestPrefab.prefab";
 8
 9    public static AssetMoveResult OnWillMoveAsset(string oldPath, string newPath)
10    {
11        AssetMoveResult result=AssetMoveResult.DidNotMove;
12        if (oldPath==prefabPath)
13        {
14            result=AssetMoveResult.FailedMove;
15            EditorUtility.DisplayDialog("Attention", "You shouldn't move this asset", "Ok");
16        }
17        return result;
18    }
19}
There is nothing new under the sun.