NGUI在Unity中的应用示例教程手把手教你搭建游戏界面从入门到实战全解析
好,今天咱们来聊聊NGUI在Unity里的用法。如果你是个刚接触Unity的新手,或者曾经被UGUI搞得心力交瘁,那NGUI可能是你值得一试的老伙计——虽然它已经被官方UGUI取代了,但很多老项目、老游戏还在用,而且它的UI逻辑写得优雅、性能表现好,学起来也特别顺手。
我先不啰嗦,直接开干。
一、NGUI到底是什么?先搞清楚再动手
NGUI全称是Next-Gen UI,是Tasharen公司开发的Unity UI插件。它早在Unity原生UGUI出来之前就存在了,算是Unity UI界的”前辈”。
它用的是Atlas(图集)技术——把很多小图标打包成一张大图,减少Draw Call,这在移动端性能优化上非常重要。UGUI虽然现在也有图集,但NGUI当年在这块做得相当扎实。
简单来说:
- NGUI = 一套独立的UI框架
- 它不是Unity内置的,需要额外导入
- 运行效率和视觉效果在当年非常能打
- 核心依赖:Sprite、Label、Button、Widget 这些基础组件
二、环境搭建:把NGUI装进Unity
2.1 下载和导入
你需要去NGUI的官网或者Asset Store下载插件。推荐下载较稳定的版本(比如3.11.x系列),然后:
- 打开Unity编辑器
- 点击 Assets → Import Package → Custom Package
- 选择下载好的NGUI包
- 点击Import,等待导入完成
2.2 创建NGUI工程结构
导入完成后,在Project窗口你会看到NGUI文件夹。我们新建一个场景,然后执行:
NGUI → Create → New UI
这会自动创建一个包含以下内容的场景:
| 节点名称 | 作用 |
|---|---|
| Camera | NGUI专用相机,渲染UI |
| Root | UIPanel 容器,管理UI渲染层级 |
| Context | UIContextMenu 挂载点 |
| EventManager | 事件分发管理器 |
你可以直接删除Context节点,保留Root就够了。
三、核心概念:NGUI的几个重要组件
在学习具体操作之前,你得理解NGUI的几个核心概念,不然后面会懵。
3.1 Widget — 所有UI元素的基类
在NGUI里,任何一个能显示在屏幕上的东西都叫 Widget。按钮、图片、标签、面板——它们都继承自Widget。
Widget有几个关键属性:
- Transform:位置和大小
- Alpha:透明度
- Depth:渲染深度,数值越大越在上层
- Collision:碰撞检测(用于点击检测)
3.2 UIPanel — 面板
UIPanel负责批处理渲染。多个Widget放在同一个UIPanel下,它们可以合并成一次Draw Call渲染,性能更好。
你可以理解为:UIPanel = 一个”渲染批次”的容器。
3.3 UILabel — 文字
NGUI的文字用的是BMFont格式(位图字体),不是动态生成的字体。这需要你在材质上挂一个Sprite Atlas来实现。
四、第一步:做一个简单的开始界面
现在我们动手搭一个游戏开始界面,包含:标题、开始按钮、版本号显示。
4.1 创建背景和标题
在Hierarchy窗口,选中Root节点,右键 → UI → Sprite,创建一个背景精灵。
然后在Sprite上添加背景图片:
- 导入你的背景图到Project
- 右键背景图 → Create → New Atlas
- 在Atlas窗口勾选这张图,点击OK
- 回到场景,选中背景Sprite
- 在Inspector的Sprite槽里选择刚创建的图集里的图片
接着创建标题:
右键 → UI → Label,输入游戏名称,调整位置到屏幕中央。
Label的文字颜色、字体大小、对齐方式都可以在Inspector里设置。
4.2 创建按钮
NGUI的按钮其实就是一个Sprite + UILabel的组合,然后挂上 UIButton 脚本。
操作方式:
- 右键 → UI → Button,Unity会自动创建一个包含背景、高亮、标签的按钮
- 选中按钮的Label节点,修改文字为”开始游戏”
- 调整按钮大小和位置
4.3 按钮点击逻辑
双击按钮,选择 OnClick 事件,可以绑定脚本方法。
我们创建一个C#脚本 GameStartMenu:
using UnityEngine;
public class GameStartMenu : MonoBehaviour
{
// 开始游戏按钮点击
public void OnStartGamePressed()
{
Debug.Log("开始游戏按钮被点击了!");
// 加载主游戏场景
UnityEngine.SceneManagement.SceneManager.LoadScene("MainGame");
}
// 退出游戏按钮点击
public void OnExitPressed()
{
Debug.Log("退出游戏");
Application.Quit();
}
// 设置界面按钮点击
public void OnSettingsPressed()
{
Debug.Log("打开设置界面");
// 可以切换UI面板
UIManager.Instance.SwitchPanel("SettingsPanel");
}
}
然后在Unity里,把脚本拖到某个空节点上,把按钮的OnClick事件绑定到这个脚本的方法上。
五、Atlas图集的创建和使用
这一节非常关键。NGUI的性能优势很大程度上来自图集。
5.1 为什么要用图集?
手机上的GPU一次绘制调用(Draw Call)是有开销的。如果你每个按钮、每个图标都单独一张图,Draw Call会爆炸。图集把所有小图合并成一张大图,统一渲染,效率极高。
5.2 创建图集的步骤
- 把所有需要用的UI图片放进Project的某个文件夹,比如
Assets/UI/AtlasSource/ - 右键这个文件夹 → NGUI → Create Atlas
- 在弹出的窗口里设置:
- Texture Type:选择
Advanced - Wrap Mode:Clamp(防止边缘采样问题)
- Filter Mode:Point(像素风格用)或Bilinear(平滑风格)
- Max Size:根据目标平台设定,比如512、1024或2048
- Padding:2~4像素(避免贴图边缘混色)
- Texture Type:选择
- 点击 Apply,Unity会自动生成图集
5.3 给UI元素指定图集
创建好图集后,每个Sprite组件的 Atlas 槽都要指向对应的图集,然后在 Sprite 槽里选择具体那张子图。
这里有个技巧——你可以写一个自动分配的工具:
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
public class AtlasBuilder : Editor
{
[MenuItem("NGUI Tools/Auto Build Atlas")]
public static void AutoBuildAtlas()
{
string[] files = Directory.GetFiles("Assets/UI/AtlasSource/", "*.png");
List<Texture2D> textures = new List<Texture2D>();
foreach (string file in files)
{
Texture2D tex = AssetDatabase.LoadAssetAtPath<Texture2D>(file);
if (tex != null)
textures.Add(tex);
}
// 创建或查找现有图集
string atlasPath = "Assets/UI/UIAtlas";
UIAtlas atlas = AssetDatabase.LoadAssetAtPath<UIAtlas>(atlasPath + ".prefab");
if (atlas == null)
{
// 创建新图集
GameObject go = new GameObject("UIAtlas");
atlas = go.AddComponent<UIAtlas>();
GameObject.Instantiate(atlas);
}
// 更新图集引用
atlas.spriteList.Clear();
foreach (var tex in textures)
{
UISpriteData data = new UISpriteData();
data.name = Path.GetFileNameWithoutExtension(tex.name);
data.x = 0;
data.y = 0;
data.width = tex.width;
data.height = tex.height;
atlas.spriteList.Add(data);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("图集构建完成,共 " + textures.Count + " 张图");
}
}
注意:这段代码是用Unity Editor扩展写的,只能在编辑器模式下运行。实际项目中Atlas的生成通常在CI/CD流程中自动完成。
六、UIPanel的深度管理
NGUI的渲染顺序由 Depth 值决定。
- 数值越大 → 越在上面
- 同层级的Widget,Depth大的覆盖Depth小的
- UIPanel也有Depth,控制面板之间的层级
6.1 手动调整Depth
在Inspector里找到 Depth 字段,直接改数字即可。
6.2 自动排序脚本
你可以写一个工具脚本,在运行时动态调整UI层级:
using UnityEngine;
using System.Collections.Generic;
public class UIDepthManager : MonoBehaviour
{
[System.Serializable]
public class UIDepthConfig
{
public string panelName;
public int depth;
}
public List<UIDepthConfig> depthConfigs = new List<UIDepthConfig>();
void Start()
{
ApplyDepths();
}
public void ApplyDepths()
{
foreach (var config in depthConfigs)
{
GameObject panel = transform.Find(config.panelName).gameObject;
if (panel != null)
{
UIPanel panelComp = panel.GetComponent<UIPanel>();
if (panelComp != null)
{
panelComp.depth = config.depth;
}
}
}
}
// 弹出某个面板并设置最高深度
public void BringToFront(string panelName)
{
int maxDepth = 0;
foreach (UIPanel panel in FindObjectsOfType<UIPanel>())
{
if (panel.depth > maxDepth)
maxDepth = panel.depth;
}
GameObject panel = transform.Find(panelName).gameObject;
if (panel != null)
{
UIPanel panelComp = panel.GetComponent<UIPanel>();
if (panelComp != null)
{
panelComp.depth = maxDepth + 1;
panel.SetActive(true);
}
}
}
}
七、UI自适应:让界面在不同屏幕上正常显示
这是新手最容易踩坑的地方。
7.1 NGUI的自适应方案
NGUI通过 Camera的裁剪模式 和 Widget的缩放方式 来实现适配。
在NGUI的Camera组件上,找到 Clipping 属性:
- Soft Clip:边缘有软裁剪,适合横竖屏都能用的情况
- Pixel Perfect:像素级精确,适合像素风游戏
- Never:不裁剪
7.2 用Anchor锚点固定位置
在Widget上挂上 UIAnchor 脚本,可以固定元素相对于父节点的位置。
比如让一个按钮始终贴在屏幕右下角:
- 选中按钮
- 添加UIAnchor组件
- 设置Anchor为 Bottom Right
- 调整Offset X和Offset Y微调位置
7.3 响应式布局脚本
下面这个脚本可以根据屏幕宽高比自动缩放UI:
using UnityEngine;
[ExecuteInEditMode]
public class UIResponsiveScaler : MonoBehaviour
{
public float referenceWidth = 1280f;
public float referenceHeight = 720f;
private Vector3 originalScale;
private UIPanel panel;
void Start()
{
panel = GetComponent<UIPanel>();
if (panel == null)
panel = GetComponentInParent<UIPanel>();
originalScale = transform.localScale;
ApplyScale();
}
void Update()
{
// 编辑器模式下实时更新
if (!Application.isPlaying)
ApplyScale();
}
void ApplyScale()
{
float screenWidth = Camera.main.viewportWidth;
float screenHeight = Camera.main.viewportHeight;
float scaleX = screenWidth / referenceWidth;
float scaleY = screenHeight / referenceHeight;
// 保持比例缩放
float scaleFactor = Mathf.Min(scaleX, scaleY);
transform.localScale = originalScale * scaleFactor;
}
}
把这个脚本挂在Root节点上,你的整个UI就会根据屏幕比例自动缩放,不会出现拉伸变形。
八、实战:做一个完整的背包系统界面
光说不练假把式。我们来做一个实战案例——游戏背包UI。
8.1 背包数据结构
首先定义背包的数据模型:
using System.Collections.Generic;
[System.Serializable]
public class ItemData
{
public int itemId;
public string itemName;
public int stackCount;
public int maxStack;
public string iconPath;
public string description;
public ItemQuality quality;
}
public enum ItemQuality
{
Common, // 普通 - 白色
Uncommon, // 稀有 - 绿色
Rare, // 罕见 - 蓝色
Epic, // 史诗 - 紫色
Legendary // 传说 - 橙色
}
public class BackpackManager : MonoBehaviour
{
public const int MAX_SLOTS = 30;
public List<ItemData> items = new List<ItemData>();
void Awake()
{
// 初始化空背包
if (items.Count == 0)
{
for (int i = 0; i < MAX_SLOTS; i++)
items.Add(null);
}
}
// 添加物品到背包
public bool AddItem(ItemData item)
{
// 先找相同物品的堆叠空格
for (int i = 0; i < items.Count; i++)
{
if (items[i] != null && items[i].itemId == item.itemId)
{
if (items[i].stackCount + item.stackCount <= item.maxStack)
{
items[i].stackCount += item.stackCount;
return true;
}
}
}
// 再找空位
for (int i = 0; i < items.Count; i++)
{
if (items[i] == null)
{
items[i] = item;
return true;
}
}
return false; // 背包已满
}
// 移除物品
public bool RemoveItem(int itemId, int count = 1)
{
for (int i = 0; i < items.Count; i++)
{
if (items[i] != null && items[i].itemId == itemId)
{
items[i].stackCount -= count;
if (items[i].stackCount <= 0)
items[i] = null;
return true;
}
}
return false;
}
}
8.2 背包格子预制件
我们需要创建一个格子预制件 SlotPrefab,它包含:
- 背景Sprite(根据品质变色)
- 物品图标Sprite
- 数量Label
- 品质边框Sprite(可选)
using UnityEngine;
using System.Collections.Generic;
public class SlotItem : MonoBehaviour
{
public UISprite bgSprite;
public UISprite iconSprite;
public UILabel countLabel;
public GameObject highlightPanel;
private ItemData item;
private int slotIndex;
public void Init(int index, ItemData data)
{
slotIndex = index;
item = data;
Refresh();
}
public void Refresh()
{
if (item == null)
{
iconSprite.spriteName = "";
iconSprite.gameObject.SetActive(false);
countLabel.text = "";
countLabel.gameObject.SetActive(false);
SetQualityColor(ItemQuality.Common);
return;
}
iconSprite.gameObject.SetActive(true);
iconSprite.spriteName = item.iconPath;
countLabel.gameObject.SetActive(item.stackCount > 1);
countLabel.text = item.stackCount.ToString();
SetQualityColor(item.quality);
}
void SetQualityColor(ItemQuality quality)
{
Color qualityColor;
switch (quality)
{
case ItemQuality.Common:
qualityColor = new Color(0.8f, 0.8f, 0.8f);
break;
case ItemQuality.Uncommon:
qualityColor = new Color(0.2f, 0.8f, 0.2f);
break;
case ItemQuality.Rare:
qualityColor = new Color(0.2f, 0.4f, 1f);
break;
case ItemQuality.Epic:
qualityColor = new Color(0.6f, 0.2f, 0.8f);
break;
case ItemQuality.Legendary:
qualityColor = new Color(1f, 0.6f, 0.1f);
break;
default:
qualityColor = Color.white;
break;
}
bgSprite.color = qualityColor;
}
public void OnClick()
{
// 点击格子,弹出物品详情
if (item != null)
{
UIManager.Instance.ShowItemDetail(item);
}
}
public void OnRightClick()
{
// 右键点击,丢弃或查看
if (item != null)
{
UIManager.Instance.ShowItemOptions(item, slotIndex);
}
}
}
8.3 背包面板控制器
using UnityEngine;
using System.Collections.Generic;
public class BackpackPanel : MonoBehaviour
{
public GameObject slotPrefab;
public Transform slotsContainer;
public UILabel titleLabel;
public UILabel goldLabel;
public UIPanel contentPanel;
private BackpackManager backpack;
private List<SlotItem> slots = new List<SlotItem>();
private int selectedSlot = -1;
void Start()
{
backpack = FindObjectOfType<BackpackManager>();
if (backpack == null)
{
Debug.LogError("找不到BackpackManager!");
return;
}
CreateSlots();
RefreshAllSlots();
UpdateTitle();
}
void CreateSlots()
{
// 清掉旧的
foreach (Transform child in slotsContainer)
Destroy(child.gameObject);
slots.Clear();
int columns = 6;
int rows = 5;
float slotWidth = 64f;
float slotHeight = 64f;
float padding = 8f;
for (int i = 0; i < BackpackManager.MAX_SLOTS; i++)
{
int col = i % columns;
int row = i / columns;
GameObject slotGo = Instantiate(slotPrefab) as GameObject;
slotGo.transform.SetParent(slotsContainer, false);
// 设置位置
float xPos = col * (slotWidth + padding);
float yPos = -row * (slotHeight + padding);
slotGo.transform.localPosition = new Vector3(xPos, yPos, 0);
SlotItem slot = slotGo.GetComponent<SlotItem>();
slot.Init(i, backpack.items[i]);
slots.Add(slot);
// 绑定点击事件
UIButton btn = slotGo.GetComponentInChildren<UIButton>();
if (btn != null)
{
// 左键点击
btn.onClick.Add(new EventDelegate(() => OnSlotClicked(i)));
}
}
}
void RefreshAllSlots()
{
for (int i = 0; i < slots.Count; i++)
{
if (i < backpack.items.Count)
slots[i].Init(i, backpack.items[i]);
else
slots[i].Init(i, null);
}
}
void OnSlotClicked(int index)
{
// 清除之前的选中高亮
if (selectedSlot >= 0 && selectedSlot < slots.Count)
slots[selectedSlot].gameObject.SetActive(false);
selectedSlot = index;
// 高亮选中的格子
if (backpack.items[index] != null)
{
// 可以弹出物品详情面板
UIManager.Instance.ShowItemDetail(backpack.items[index]);
}
}
void UpdateTitle()
{
int itemCount = 0;
foreach (var item in backpack.items)
{
if (item != null) itemCount++;
}
titleLabel.text = "背包 (" + itemCount + "/" + BackpackManager.MAX_SLOTS + ")";
}
// 当背包数据变化时调用
public void OnBackpackChanged()
{
RefreshAllSlots();
UpdateTitle();
}
}
九、UI动画:让界面”活”起来
NGUI支持Tween动画,可以很优雅地实现界面弹出、收起效果。
9.1 用TweenAlpha做淡入淡出
选中你要动画的UI节点,添加 Tween Alpha 组件:
using UnityEngine;
public class UIPopupAnimation : MonoBehaviour
{
public TweenAlpha fadeIn; // 淡入动画
public TweenAlpha fadeOut; // 淡出动画
public TweenPosition positionIn;
public TweenPosition positionOut;
public float fadeInDuration = 0.3f;
public float fadeOutDuration = 0.2f;
void Start()
{
// 初始隐藏
gameObject.SetActive(false);
// 配置淡入
if (fadeIn != null)
fadeIn.duration = fadeInDuration;
// 配置淡出
if (fadeOut != null)
fadeOut.duration = fadeOutDuration;
}
public void Show()
{
gameObject.SetActive(true);
// 从完全透明开始
if (fadeIn != null)
{
TweenAlpha.PlayForward(fadeIn);
}
// 从屏幕外滑入
if (positionIn != null)
{
TweenPosition.PlayForward(positionIn);
}
}
public void Hide()
{
// 先淡出
if (fadeOut != null)
{
TweenAlpha.PlayForward(fadeOut);
fadeOut.onFinished.Add(new EventDelegate(HideAfterFade));
}
else
{
gameObject.SetActive(false);
}
}
void HideAfterFade()
{
gameObject.SetActive(false);
}
}
9.2 按钮交互动画
每个按钮最好有鼠标悬停和点击的反馈:
using UnityEngine;
public class UIButtonAnimation : MonoBehaviour
{
public TweenColor hoverColor; // 悬停颜色变化
public TweenScale hoverScale; // 悬停缩放
public TweenColor pressedColor; // 按下颜色
public TweenScale pressedScale; // 按下缩放
void Start()
{
SetupEvents();
}
void SetupEvents()
{
UIButton button = GetComponent<UIButton>();
if (button == null) return;
// 鼠标悬停
button.onMouseHover += OnHover;
// 鼠标离开
button.onMouseHoverOut += OnHoverOut;
// 按下
button.onPress += OnPress;
// 释放
button.onPress += OnRelease;
}
void OnHover(GameObject obj, bool isPressed)
{
if (hoverColor != null)
TweenColor.PlayForward(hoverColor);
if (hoverScale != null)
TweenScale.PlayForward(hoverScale);
}
void OnHoverOut(GameObject obj)
{
// 恢复默认颜色
if (hoverColor != null)
TweenColor.PlayBackwards(hoverColor);
if (hoverScale != null)
TweenScale.PlayBackwards(hoverScale);
}
void OnPress(GameObject obj, bool isPressed)
{
if (isPressed)
{
if (pressedScale != null)
TweenScale.PlayForward(pressedScale);
}
else
{
if (pressedScale != null)
TweenScale.PlayBackwards(pressedScale);
}
}
void OnRelease(GameObject obj)
{
if (pressedScale != null)
TweenScale.PlayBackwards(pressedScale);
}
}
十、NGUI的UIManager:管理所有界面的中心枢纽
一个完整的游戏,UI非常多。你需要一个管理器来统一控制。
using UnityEngine;
using System.Collections.Generic;
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
// 所有已打开的UI面板
private Dictionary<string, GameObject> openPanels = new Dictionary<string, GameObject>();
// 面板预制件映射
[System.Serializable]
public class PanelConfig
{
public string panelName;
public GameObject prefab;
public Transform parent; // 父节点,为空则挂在Root下
}
public List<PanelConfig> panelConfigs = new List<PanelConfig>();
// 默认Root节点
public GameObject defaultRoot;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
// 打开面板
public void OpenPanel(string panelName)
{
if (openPanels.ContainsKey(panelName))
{
Debug.LogWarning("面板已经打开: " + panelName);
return;
}
GameObject prefab = GetPanelPrefab(panelName);
if (prefab == null)
{
Debug.LogError("找不到面板预制件: " + panelName);
return;
}
Transform parent = defaultRoot.transform;
foreach (var config in panelConfigs)
{
if (config.panelName == panelName && config.parent != null)
{
parent = config.parent;
break;
}
}
GameObject panel = Instantiate(prefab) as GameObject;
panel.transform.SetParent(parent, false);
panel.SetActive(true);
openPanels[panelName] = panel;
Debug.Log("打开面板: " + panelName);
}
// 关闭面板
public void ClosePanel(string panelName)
{
if (!openPanels.ContainsKey(panelName))
{
Debug.LogWarning("面板未打开: " + panelName);
return;
}
GameObject panel = openPanels[panelName];
openPanels.Remove(panelName);
Destroy(panel);
Debug.Log("关闭面板: " + panelName);
}
// 切换面板(先关闭旧的,再打开新的)
public void SwitchPanel(string newPanelName)
{
// 关闭所有面板
List<string> panelsToClose = new List<string>(openPanels.Keys);
foreach (string name in panelsToClose)
{
ClosePanel(name);
}
// 打开新的
OpenPanel(newPanelName);
}
// 获取面板预制件
private GameObject GetPanelPrefab(string panelName)
{
foreach (var config in panelConfigs)
{
if (config.panelName == panelName)
return config.prefab;
}
return null;
}
// 检查面板是否已打开
public bool IsPanelOpen(string panelName)
{
return openPanels.ContainsKey(panelName);
}
}
十一、NGUI与UGUI的对比:什么时候该用谁?
这是很多人纠结的问题。
| 特性 | NGUI | UGUI(Unity内置) |
|---|---|---|
| 学习成本 | 中等,概念略多 | 低,Unity原生文档丰富 |
| 性能(移动端) | 优秀,Atlas批处理成熟 | 良好,但需手动优化 |
| 动画系统 | Tween组件,手写较繁琐 | CanvasGroup + DOTween等插件 |
| 社区支持 | 社区逐渐萎缩 | 官方支持,资源丰富 |
| 跨平台 | 支持所有平台 | 支持所有平台 |
| 维护状态 | 已停止主要更新 | 持续更新 |
我的建议:
- 如果你维护的是老项目,已经用了NGUI,那继续用没问题,稳定可靠
- 如果你是新项目,建议直接用UGUI,生态更好,未来不用头疼迁移
- 如果你是学习目的,NGUI的逻辑非常清晰,学完它对UI系统的理解会更深
十二、常见问题和解决方案
问题1:图片模糊
原因:图集的Texture类型设置不对,或者Filter Mode设成了Bilinear。
解决:
Atlas设置 → Filter Mode = Point(像素风)
或
Texture Settings → Import Format = Truecolor(无损)
问题2:按钮点击没反应
检查:
- 按钮上有没有 Box Collider 或 Event Trigger
- 相机的 Culling Mask 是否包含了UI层
- 按钮的 Collision 属性是否设置为 None 以外的值
问题3:文字显示不出来
NGUI的Label需要绑定Atlas才能显示文字图片。确保:
- Label的 Font 字段指向正确的BMFont资源
- Label的 Material 使用了正确的Atlas材质
问题4:UI穿透了其他UI
检查Depth值。被遮挡的UI,它的Depth应该比遮挡它的UI更小。可以用上面的 UIDepthManager 脚本统一管理。
好了,这个教程从NGUI的基础概念、环境搭建,到具体的背包系统实战,再到UI管理器和常见问题,基本覆盖了NGUI使用的核心内容。
NGUI虽然已经不算”新”了,但它的思想——图集批处理、Depth层级管理、Widget化组件——在现代UI开发中依然有参考价值。你现在学NGUI,等以后转UGUI或者看别人的老项目代码,都会顺畅很多。
如果有什么具体环节没搞明白,随时问我。
