转载至:umbella.cn,更多高频Unity客户端开发面试题 - 尽在公众号:脚本开发者。
1. UGUI中的Image和RawImage的区别
相同点
在Unity的UI系统(UGUI)中,Image
和 RawImage
都是用于显示图像(通常是纹理)的组件
同个游戏对象不能同时挂载Image、RawImage,同个游戏对象只能包含一个"Graphic"组件。
这是因为Image、RawImage都继承了Graphci抽象类,并且此类被[DisallowMultipleComponent] 修饰。
在Unity游戏开发框架中,[DisallowMultipleComponent]
是一个特性(Attribute),用于指示一个组件(Component)不应该被附加到同一个GameObject上的多次。
当你在Unity的C#脚本中定义一个组件类时,有时你可能希望确保该组件在单个GameObject上只存在一次。使用[DisallowMultipleComponent]
特性可以帮助你实现这一点。
例如:
using UnityEngine;
[DisallowMultipleComponent]
public class MyUniqueComponent : MonoBehaviour
{
// ... 组件的其他代码 ...
}
在上面的代码中,如果你尝试在同一个GameObject上附加MyUniqueComponent
两次,Unity将不会允许这样做,并可能给出一个警告或错误。
然而,值得注意的是,虽然[DisallowMultipleComponent]
可以帮助你在Unity编辑器中避免错误地多次附加同一个组件,但它并不能完全阻止在运行时通过代码动态地多次添加同一个组件。如果你需要在运行时确保组件的唯一性,你可能需要在代码中添加额外的逻辑来检查和管理这一点。
举例:同个UI下多次添加Image和RawImage组件:
Image 组件
Image
组件是UGUI中最常用的用于显示图像的组件。它支持多种图像类型(如Sprite、Sprite Atlas、Texture2D等),并且提供了一些方便的属性来调整图像的显示方式,如color
(颜色)、material
(材质)、sprite
(精灵)等。
主要特点:
-
支持Sprite和Texture2D:可以直接使用Unity中的Sprite或Texture2D资源。
-
具有内置的颜色调整:可以通过
color
属性调整图像的颜色。 -
支持材质:可以通过
material
属性应用自定义的Shader效果。
示例代码:
using UnityEngine;
using UnityEngine.UI;
public class ImageExample : MonoBehaviour
{
public Image myImage; // 拖拽赋值或通过代码查找
void Start()
{
// 设置Sprite(需要先在项目中有一个Sprite资源)
myImage.sprite = Resources.Load<Sprite>("MySprite");
// 设置颜色
myImage.color = Color.red;
}
}
RawImage 组件
RawImage
组件则提供了更底层的图像显示功能。它主要用于显示Texture2D
类型的图像,并且不提供像Image
那样的高级特性(如颜色调整或Sprite支持)。但是,RawImage
具有更高的灵活性,因为它允许你直接使用原始的Texture2D数据。
主要特点:
-
仅支持Texture2D:只能使用Texture2D资源。
-
无内置颜色调整:颜色调整需要在Shader级别进行(如果需要的话)。
-
更高的灵活性:可以直接操作Texture2D数据,实现更复杂的图像效果。
示例代码:
using UnityEngine;
using UnityEngine.UI;
public class RawImageExample : MonoBehaviour
{
public RawImage myRawImage; // 拖拽赋值或通过代码查找
void Start()
{
// 假设你有一个Texture2D资源(需要先在项目中创建或加载)
Texture2D myTexture = new Texture2D(128, 128);
// ... 这里可以填充myTexture的数据 ...
// 设置Texture2D到RawImage
myRawImage.texture = myTexture;
// 注意:RawImage没有color属性,所以颜色调整需要在Shader中进行
}
}
总结
选择Image
还是RawImage
取决于具体需求。如果需要显示Sprite或需要内置的颜色调整功能,那么Image
是更好的选择。但是,如果需要直接操作Texture2D数据或需要更高的灵活性,那么RawImage
可能更适合,注意:同个游戏对象不同同时挂载Image、RawImage,同个游戏对象只能包含一个"Graphic"组件。
2. Canvas的 Render Mode(渲染模式) 三种方式
在Unity的UI系统中,Canvas
组件有一个 Render Mode
(渲染模式)属性,它决定了Canvas如何被渲染到屏幕上。Unity提供了三种主要的渲染模式:Screen Space - Overlay
(屏幕空间-覆盖层)、Screen Space - Camera
(屏幕空间-相机)和World Space
(世界空间)。
2.1. Screen Space - Overlay(屏幕空间-覆盖层)
-
说明:此模式下,Canvas始终渲染在屏幕的最上层,不受任何相机(Camera)的影响。Canvas的坐标基于屏幕像素,左上角为(0,0),右下角为(Screen.width, Screen.height)。
-
示例:显示一个始终位于屏幕中心的UI元素(如文本)。
代码实现(不需要特殊代码,只需在Unity编辑器中设置Canvas的Render Mode为Screen Space - Overlay,并放置UI元素即可):
// 例如,你可以在Canvas下添加一个Text组件,并通过代码设置其文本内容
public Text myText;
void Start()
{
myText.text = "Screen Space - Overlay mode.";
}
应用场景:
-
HUD(Head-Up Display)界面:比如游戏中的生命值、得分、时间等显示,这些UI元素需要始终显示在屏幕上方,不受场景中的相机移动和旋转影响。
-
UI菜单和选项:游戏的主菜单、暂停菜单、设置选项等,这些UI元素也需要始终保持在屏幕上的固定位置。
-
提示和通知:如游戏中的提示信息、任务完成通知等,这些信息通常会在屏幕上方短暂显示,然后消失。
2.2. Screen Space - Camera(屏幕空间-相机)
-
说明:此模式下,Canvas会渲染到指定的相机上。Canvas的坐标仍然基于屏幕像素,但会受到相机的影响(如相机的裁剪平面、视场角等)。
-
示例:在一个自定义相机视图中显示UI元素。
代码实现(同样不需要特殊代码,只需设置Canvas的Render Mode为Screen Space - Camera,并指定一个相机):
// 假设你已经有了一个Camera组件的引用,并设置它为Canvas的Render Camera
// 你可以在Canvas组件的"Render Camera"属性中选择它
// 在代码中,你可以通过Camera组件来控制Canvas的渲染效果,比如改变相机的位置和旋转
public Camera myCamera;
void Update()
{
// 示例:通过代码旋转相机
myCamera.transform.Rotate(0, Time.deltaTime * 45, 0);
}
应用场景:
-
与特定相机绑定的UI:如果你希望UI只出现在某个相机的视图中,比如第一人称射击游戏中的HUD,或者某个特定的游戏模式或关卡中,那么Screen Space - Camera模式就非常有用。
-
UI跟随相机移动:在某些游戏中,你可能希望UI元素能够跟随相机的移动而移动,但又不希望它们受到相机旋转的影响。这时,你可以将Canvas设置为Screen Space - Camera模式,并将其Render Camera设置为你的目标相机。
-
UI的层级控制:在某些情况下,你可能希望UI元素能够按照特定的顺序进行渲染,比如某个UI元素需要始终显示在其他元素之上。通过调整不同Canvas的渲染顺序(Sort Order)和深度(Depth),你可以实现这种效果。
2.3. World Space(世界空间)
-
说明:此模式下,Canvas被视为场景中的一个普通对象,具有三维坐标和旋转。Canvas会渲染到所有能看到它的相机上。
-
示例:将一个UI面板放置在三维场景中的某个位置,并随着玩家移动。
代码实现(在Unity编辑器中设置Canvas的Render Mode为World Space,并调整其位置和旋转):
// 你可以通过代码动态改变Canvas的位置和旋转
public Canvas myCanvas;
void Update()
{
// 示例:通过代码移动Canvas
myCanvas.transform.position += new Vector3(0, 0, Time.deltaTime); // 沿Z轴向前移动
// 示例:通过代码旋转Canvas
myCanvas.transform.Rotate(0, Time.deltaTime * 45, 0); // 绕Y轴旋转
}
应用场景:
-
3D UI元素:比如游戏中的3D按钮、面板等,这些UI元素需要像其他3D对象一样在场景中移动和旋转。通过将Canvas设置为World Space模式,你可以将这些UI元素放置在场景中的任意位置,并像操作其他3D对象一样操作它们。
-
与场景中的物体交互:如果你希望UI元素能够与场景中的其他物体进行交互,比如点击一个3D按钮来触发某个事件,那么World Space模式是一个很好的选择。通过将Canvas设置为World Space模式,你可以将UI元素放置在场景中,并为其添加碰撞体和事件监听器来实现交互功能。
-
动态UI布局:在某些情况下,你可能需要根据场景中的物体位置或数量来动态生成UI元素。通过将Canvas设置为World Space模式,你可以根据物体的位置信息来生成UI元素,并将它们放置在场景中的合适位置。
请注意,以上代码示例假设已经有了相应的组件引用(如Text
、Camera
或Canvas
),并且这些组件已经在Unity编辑器中设置好了。在实际项目中,可能需要通过GetComponent
、FindObjectOfType
或GameObject.Find
等方法来获取这些组件的引用。
3. Canvas怎么适配屏幕
在Unity中使用UGUI进行Canvas屏幕适配时,主要需要关注以下几个方面
3.1. Canvas Scaler组件设置
3.1.1.Render Mode:Canvas的Render Mode通常设置为“Screen Space - Camera”,这意味着Canvas会跟随指定的Camera进行渲染。
3.1.2.UI Scale Mode:
- Constant Pixel Size:UI元素的大小和位置不随屏幕分辨率而变化,适用于固定分辨率的屏幕。
- Scale With Screen Size:UI元素的大小和位置会根据屏幕分辨率进行缩放,这是适配不同分辨率屏幕的主要方式。在此模式下,需要设置Reference Resolution(参考分辨率)和Screen Match Mode(屏幕匹配模式)。
-
Reference Resolution:设计UI时使用的分辨率,如640x960。
-
Screen Match Mode:提供三种适配方案
(1)Match Width Or Height:按照短边等比缩放,长边进行自适应。
可通过设置下方的 Match 值来选择
- 适配宽度:当 Match == 0 时,适配宽度。将宽度设置为屏幕宽度,并保持默认尺寸比 例不变。如果此时高度超过屏幕高度,超出部分将会被裁切掉。
- 适配高度:当 Match == 1 时,适配高度。将高度设置为屏幕高度,并保持默认尺寸比例不变。如果此时宽度超过屏幕宽度,超出部分将会被裁切掉。
(2)Expand:保证UI中的所有元素都在屏幕内部,但可能留有空白边。
不裁切,对默认尺寸进行缩放,并保证缩放后的宽高均小于或等于实际屏幕宽高的最大尺寸。
(3)Shrink:保证不留空白边,但可能部分UI元素被裁剪。保持缩放比例,裁切,对默认尺寸进行缩放,并保证缩放后的宽高均大于或等于实际屏幕宽高的最小尺寸。
- Constant Physical Size:UI元素的大小和位置会根据屏幕的物理尺寸进行缩放,较少使用。
3.2. Anchors和Pivot设置
-
Anchors:定义UI元素相对于父级容器的位置。通过调整Anchors,可以控制UI元素在屏幕上的位置和对齐方式。
-
Pivot:当前控件坐标系的锚点位置,影响通过代码调整坐标位置时的效果。
3.3. RectTransform设置
-
Size Delta:用于设置UI元素的大小。在Anchors正确设置的情况下,Size Delta可以控制UI元素相对于锚点的尺寸。
3.4. 适配不同分辨率的屏幕
-
当实际设备的屏幕分辨率与设计分辨率不一致时,通过CanvasScaler组件进行缩放适配。
-
对于主界面、战斗界面等需要全屏拉伸的界面,可以使用Anchors的全屏拉伸设置。
3.5. 注意事项
-
当加载Prefab时,设置父节点使用
transform.SetParent(root, false)
,以避免RectTransform大小或坐标错误的问题。 -
对于异形屏(如刘海屏、圆角屏等),可能需要使用
Screen.safeArea
计算并设置Panel相关内容来避开安全区域。
3.6. 适配策略
-
根据项目的实际需求,选择合适的适配策略。例如,对于需要精确控制UI元素位置和大小的情况,可以使用Constant Pixel Size模式;对于需要适配不同分辨率屏幕的情况,使用Scale With Screen Size模式。
4.UGUI优化解决方法
Unity UGUI的性能优化需要从多个方面入手,包括Profiler工具的使用、Canvas优化、图集优化、Overdraw优化、内存优化、代码和脚本优化
Profiler工具使用
-
使用Profiler:Unity的Profiler工具可以帮助你识别性能瓶颈并找到优化点。检查UI性能并找到需要优化的地方。
Canvas优化
-
多个Canvas:对于较复杂的面板,建议各自成一个Canvas,以便更好地管理和优化。这样可以避免当一个Canvas下的元素更新时,导致整个Canvas重绘。
-
动静分离:将动态元素(如游戏中的移动摇杆或技能释放)与静态元素(如游戏主页面UI)分离到不同的Canvas中。这样可以减少不必要的重绘,减少UIMesh动态更新。
在某些比较复杂,常驻的界面可以这样优化。
小的界面就没必要了,因为不必要的节点有可能会造成Draw Call的增加的。
动静划分举例:
分数、操作按钮或者是对话框的文字等这些我们把它划分为动的UI,
不常用的任务、头像按钮这些划分为静的UI。
图集优化
-
打包图集:将一个面板的UI资源放到一个图集里面,以减少Draw Call的数量。注意,背景大图不要和小图放在一个图集中,以避免不必要的内存占用。
-
图片图集压缩:使用ETC和PVRTC等流行的压缩格式来压缩图集,以减小包体大小和内存占用。
Overdraw优化
-
减少Overdraw:Overdraw是指在渲染过程中绘制了超过一次相同像素的现象。当多个UI元素重叠时,尝试优化布局以减少不必要的Overdraw。
内存优化
-
使用图集:通过合并多个小图到一个图集中来减少内存占用。同时,注意图集的大小和压缩方式以平衡内存占用和渲染性能。
-
图片压缩:除了图集压缩外,还可以考虑使用更高效的图片格式或压缩算法来进一步减少内存占用。
代码和脚本优化
-
减少不必要的更新:避免在Update或LateUpdate中执行不必要的UI更新操作。使用协程(Coroutines)或事件系统来管理UI状态变化。
-
优化布局计算:对于复杂的UI布局,考虑使用更高效的布局算法或优化布局逻辑以减少CPU占用。
合理增加UI层级的深度
在不必要的情况下,我们尽量减少UI层级的深度。
在UGUI中Hierarchy面板节点的的深度,表现的就是UI层级的深度。
UI中有N层,N越大越靠前,会遮住后面的组件。
当深度越深,不处在同一层级的UI就越多,Draw Call就会越大。
mask使用的次数要少
每存在一个mask,把mask以内和以外的UI分割成两个“部分”,依次计算两个“部分 ”的Draw Call,然后再相加。原因是mask以内和以外的UI不能通过Unity一次渲 染(Batch)。所以在使用mask的时候要仔细思考,能不用就不用,实在要用可以考 虑用带透明通道的图片代替mask的遮罩功能。
不要图文交叉 图文混排
图文交叉指Text和lmage的交叉。这里也不是单单的这两种,还有按钮和一张图片以及文字相互交叉或重叠时,影响合批处理。DrawCall多余,会有多余的内存的消耗。
关闭不必要的Image和Text组件的Raycast Target属性
在组件不需要射线检测的时候,我们可以尽可能的把射线检测去掉,在运行的时候 就可以减少不必要的性能开销。
其他优化
-
Shared UI Mesh:注意Shared UI Mesh的大小,它与当前场景中所有激活的UI元素所生成的网格数相关。当界面上UI元素较多时,尝试优化布局以减少网格数。
-
AssetBundle优化:将Packing Tag相同的源纹理文件打包到同一个AssetBundle中,以避免Atlas的冗余。这样可以更加灵活地更新UI资源。
-
ScrollRect优化:ScrollRect在滚动时会产生额外的开销。考虑使用其他方法或优化滚动逻辑来减少这些开销。
校招、社招想要自学Unity就业,缺乏项目经历又想要跳槽的伙伴们,
可以关注umbella.cn,这里有全面的自学资料以及游戏开发经验分享。
需要紧急就业,可以直接私信联系。