文章目录

  • 一、前言
  • 二、Unity3D技能树
  • 三、UnityXFramework框架,开源
  • 四、入口场景:Main.unity
  • 五、目录说明
  • 六、框架启动流程
  • 七、框架使用说明
    • 1、配置表
      • 1.1、Excel转xml、lua、json
      • 1.2、C#加载xml配置
      • 1.3、lua加载lua配置
    • 2、资源加载
      • 2.1、资源存放目录
      • 2.2、资源路径配置
      • 2.3、资源加载与实例化
    • 3、网络连接与通信
      • 3.1、网络连接
      • 3.2、网络通信
        • 3.2.1、协议文件
        • 3.2.2、客户端发送协议给服务端:C#层
        • 3.2.3、客户端发送协议给服务端:Lua层
        • 3.2.3、服务端下发协议给客户端:C#层
        • 3.2.4、服务端下发协议给客户端:Lua层
    • 4、版本号管理
      • 4.1、版本号文件:version.bytes
      • 4.2、版本号管理器:VersionMgr.cs
    • 5、日志管理
      • 5.1、日志打印
      • 5.2、输出日志文件
      • 5.3、运行时日志预览
      • 5.4、更多日志玩法
    • 6、声音管理
      • 6.1、声音配置
      • 6.2、背景音乐播放
      • 6.3、音效播放
      • 6.4、音量调节
    • 7、事件管理
      • 7.1、定义事件名
      • 7.2、事件订阅
      • 7.3、抛出事件
    • 8、界面管理
      • 8.1、界面模板:C#
      • 8.2、界面模板:Lua
      • 8.1、显示界面
      • 8.2、关闭界面
    • 9、图集管理
      • 9.1、精灵散图存放位置
      • 9.2、开启图集功能
      • 9.3、安装2D Sprite插件
      • 9.4、创建图集文件
      • 9.5、执行打图集
      • 9.6、图集资源配置
      • 9.7、生成精灵图集映射表
      • 9.8、代码动态设置精灵图
    • 10、特效管理
      • 10.1、利用对象池
      • 10.2、ParticleManager
    • 11、多语言支持
      • 11.1、国际化语言配置:I18N
      • 11.2、根据I18N的ID设置文本
      • 11.3、预设上使用I18NText组件
      • 11.4、切换语言
      • 11.5、配置表的多语言支持
    • 12、预设绑定器
      • 12.1、给预设挂PrefabBinder
      • 12.2、添加对象绑定
      • 12.3、通过PrefabBinder获取对象
    • 13、UGUI拓展
      • 13.1、新手引导遮罩
      • 13.2、国际化多语言文本
      • 13.3、精灵图轴对称
      • 13.4、循环复用列表
      • 13.5、拓展补充
    • 14、热更新
      • 14.1、热更新相关的代码
      • 14.2、热更新的界面预设
      • 14.3、热更新流程
    • 15、常用工具类
      • 15.1、C#层工具类
      • 15.2、Lua层工具类
    • 16、打包工具
      • 16.1、打AssetBundle
      • 16.2、打APP整包
      • 16.3、打热更包
    • 17、LuaFramework框架
    • 18、红点系统
  • 八、Q&A
  • 九、完毕

一、前言

嗨,大家好,我是新发。
很多同学评论和私信我,催我出一个Unity3D通用游戏框架,其实一周前我已经写得差不多了,然后我又去搞虚幻引擎了,今天收尾了一下,给大家一个交代,为了表示对广大粉丝的谢意,我做了一份Unity技能树,感谢大家~

二、Unity3D技能树

如下,长图,可放大查看,如果你是Unity3D初学者,不知道从哪着手,希望我下面的技能树可以给你一些帮助,共勉!

三、UnityXFramework框架,开源

回归正题,Unity3D通用游戏框架来了,框架1.0版本我已经上传到CODE CHINA,框架名字我起为UnityXFramework

项目地址:https://codechina.csdn.net/linxinfa/UnityXFramework
注:我使用的Unity版本为2021.1.7f1c1,如果你使用低版本的Unity打开可能会有兼容问题。

目前版本包含的内容:
1、集成了tolua Framework框架,集成了sproto协议通信(包括C#lua);
2、封装了各类管理器:配置加载、资源管理器、网络管理器、版本管理器、声音管理器、日志管理器、界面管理器、特效管理器、图集管理器、事件管理器、常用工具类等;
3、拓展了UGUI,包括精灵图轴对称、新手引导镂空遮罩、国际化多语言Text、循环复用列表等;
4、多语言支持,中文简体、中文繁体、英语,可自行添加更多语言;
5、热更新,支持资源热更新和Lua代码热更新,包括热更包版本检测、下载、校验、解压;
6、打包工具封装,包括打整包APPAssetBundle和增量包ZIP
7、…

画个图,方便大家理解,

下面,我就介绍一下UnityXFramework框架的使用方法,时间有限,可能有一些地方封装得不是很好,希望各位大佬提出改进意见,不过为了考虑通用性,也不宜加太多东西~

四、入口场景:Main.unity

在介绍细节之前,我们先打开入口场景Main.unity,看能否正常运行,

运行效果如下,

如果你运行也能正常进入,恭喜你,你已经成功运行框架了~

五、目录说明

现在,我们来看看项目目录结构,如下,

目录说明:

目录 说明
Editor 存放一些编辑器脚本,比如打包工具
GameRes 存放熟肉资源,比如界面预设、模型预设、特效预设等,该目录下的文件会打包成AssetBundle并放在StreamingAssets目录中
LuaFramework tolua框架
Plugins 存放SDK和库文件
RawAssets 存放生肉资源,比如预设依赖的图片、字体等,模型依赖的网格、材质、贴图等
Resources 存放APP启动时必要的一些基础资源,非必要的建议存放到GameRes中,方便资源热更
Scenes 存放场景文件
Scripts 存放C#脚本,子目录见下面的表格说明
StreamingAssets 存放AssetBundle文件和一些Raw资源(打包时Unity不会帮我们做加密)

其中,展开Scripts文件夹,可以看到子目录结构如下,

Scripts目录说明:

目录 说明
3rd 第上方库源码,比如LitJson
Framework 存放框架代码
Animation 动画相关的代码,动画帧事件
Audio 声音相关的代码,声音管理器
Common 一些共用模块的代码,比如全局变量、Util工具类脚本等
Debug 调试相关的代码,日志输出、写日志文件和运行时预览
Encrypt 加密相关的代码,对资源和Lua代码进行解密和解密
Event 事件相关的代码,事件触发器,事件定义
I18N 国际化语言相关的代码,支持多语言切换
Network 网络相关的代码,网络连接、消息通信
Panel 界面管理器相关的代码,界面管理器
Particle 粒子管理器相关代码,粒子管理器
Resource 资源加载相关的代码,资源管理器
Sprite 图集精灵管理相关的代码,图集精灵管理器
sproto sprotoC#封装
SprotoDef sprotoC#协议定义
Timer 定时器相关的代码
UGUIExpand UGUI拓展,比如精灵图轴对称、新手引导镂空遮罩、国际化多语言Text、循环复用列表等
Version 版本号相关的代码,版本号管理器
Logic 游戏逻辑相关的代码
View UI界面交互相关的代码
StartUp.cs 启动脚本

后续游戏业务相关的C#代码,添加到LogicView目录即可,不过,建议游戏业务使用Lua来开发,方便热更迭代和Bug修复。
Lua游戏业务代码放在Luaframework目录下的LogicView目录中。

其他目录在下文讲解具体模块时我再展开说~

六、框架启动流程

框架启动流程如下,入口场景是Main.unity,入口脚本是StartUp.cs,在Awake函数中执行初始化,调用热更新逻辑,回调后再初始化一些模块,然后启动LuaFramework框架,会载入Lua脚本,启动完毕后,执行Main.lua脚本,接下来就是Lua的业务代码了,比如登录界面和登录逻辑都使用Lua来实现,在Main.lua中打开登录界面,我写的演示代码就是这个流程。

七、框架使用说明

1、配置表

配置表可以是xmlluajson,你可以手动创建这些配置表,也可以通过Excel来生成。

1.1、Excel转xml、lua、json

我也写了一个Excelxmlluajsonpython工具,放在UnityXFramework\Tools\ExcelCfg目录中,

注:你可以继续在Tools目录中添加一些python工具。


你需要安装python3openpyxl库,我之前写过一篇教程,可以参见我这篇文章:《教你使用python读写Excel表格(增删改查操作),使用openpyxl库》
Excel文件放在Excels文件夹中,

gen.py中指定你要转换的Excel,如下,

执行python脚本即可,你也可以直接双击gen.bat

生成的配置表在output文件夹中,你可以自行修改生成的路径,

1.2、C#加载xml配置

C#加载的配置表放在Assets/GameRes/Config目录中,以声音配置audioConfig.bytes为例,

因为打包AssetBundle无法识别xml,所以使用bytes作为文件后缀名,内容其实是xml格式的,如下

注:其中的id 6是资源id,所有动态加载的资源都会有一个唯一的资源id,资源在resources.bytes中配置。


要加载audioConfig.bytes配置表,我们需要定义一个字段名与xml字段名一致的item类,继承ConfigItem类,如下

public class AudioCfgItem : ConfigItem
{public int id;public string name;public float volume;public int channel;public override string GetKey(){return name;}
}

然后就可以通过ConfigFile对象来直接加载配置表了,如下

// 声明一个ConfigFile变量
private ConfigFile<AudioCfgItem> m_audioCfg;// ...
// 加载配置表
m_audioCfg = new ConfigFile<AudioCfgItem>("audioConfig");

接着我们就可以封装一些方法来查询配置了,如下

public AudioCfgItem GetAudioCfg(string audioName)
{if (null == m_audioCfg) return null;var cfgItem = m_audioCfg.GetItem(audioName);if (null == cfgItem){GameLogger.LogError("null == audioCfg, name: " + audioName);}return cfgItem;
}
1.3、lua加载lua配置

lua加载配置更简单,首先把lua配置表放在Assets/LuaFramework/Lua/Config目录中,比如TestLuaCfg.lua

配置内容如下,

要加载配置表,只需要require即可,例:

-- 测试lua配置加载
local TestLuaCfg = require "Config/TestLuaCfg"-- 打印配置表
LuaUtil.PrintTable(TestLuaCfg)

2、资源加载

2.1、资源存放目录

资源分为生肉资源和熟肉资源,生肉资源就是未加工包装的资源,熟肉资源就是加工包装后的资源。
比如,我们制作一个界面预设,这个界面预设依赖的精灵图就是生肉资源,界面预设本身就是熟肉资源。
生肉资源放在RawAssets目录中,生肉资源放在GameRes目录中。

2.2、资源路径配置

动态加载的熟肉资源,需要配置在resources.bytes中,

如下

上面的配置你可以手动配置,也可以点击菜单Tools/Aux/资源添加编辑器(或者按快捷键Alt + G),

此时会打开一个窗口,

把资源拖到资源对象栏中,然后输入资源描述,最后点击Add按钮即可,它会自动在resources.bytes中添加一行配置;如果是已添加过渡,则会显示Update按钮,如下,

2.3、资源加载与实例化

加载资源的时候,可以通过资源id加载,也可以通过uri加载(即配置中的editorPath字段的值),例:

// 通过uri加载并实例化
var panelObj = ResourceManager.instance.Instantiate<GameObject>("BaseRes/HotUpdatePanel.prefab");// 通过id加载并实例化
var cubeObj = ResourceManager.instance.Instantiate<GameObject>(2);

框架底层我封装的资源加载流程是这样的,

注:非Editor环境,资源会以AssetBundle的方式加载,打AssetBundle的逻辑我已经在打包工具中实现了,具体见下文中打包工具的介绍。

3、网络连接与通信

网络连接使用的是System.Net.Sockets命名空间下的Socket封装的。

3.1、网络连接

C#层示例:

using UnityEngine;public class Test : MonoBehaviour, INetStateListener
{void Awake(){// 设置网络状态监听ClientNet.instance.AddNetStateListener(this);}void Start(){// 测试网络连接ClientNet.instance.Connect("127.0.0.1", 8888);}public void OnNetStateChanged(NetState state, object param = null){switch (state){case NetState.ConnectSuccess:{GameLogger.LogGreen("连接服务端成功");break;}case NetState.ConnectFail:{GameLogger.LogYellow("连接服务端失败");break;}default:{GameLogger.Log("网络状态更新: " + state);break;}}}
}

lua层示例:

Network.Connect('127.0.0.1', 8888, function (ok)-- 连接回调end)
3.2、网络通信
3.2.1、协议文件

网络通信,框架中封装的通信协议是sproto,你也可以改成protobuf协议之类的。我这里以sproto协议的通信为例。
我们需要定义协议,包括协议名,协议字段和数据类型等,协议又分为c2ss2c,其中c2s表示client to server,即客户端发送给服务端的消息,同理,s2c就是server to client,即服务端下发给客户端的消息。
比如有一个sayhelloc2s的协议,sayhello是协议名,what是字段名,它的数据类型是stringresponse表示服务端返回,返回了error_codemsg两个字段,如下,

sayhello 1 {request {what 0 : string}response {error_code 0 : integermsg 1 : string}
}

因为需求上需要兼顾C#lua,所以分别定义C#协议文件和lua协议文件,画个图方便理解,如下,

3.2.2、客户端发送协议给服务端:C#层

C#层协议发送通过ClientNetSend方法来发送,

注意:发送协议时需要先正常连接服务端之后再发


示例:

// 构造SpObject对象
SpObject spObj = new SpObject(SpObject.ArgType.Table, "what", "hi, i am c#");
// 通过ClientNet发送协议消息
ClientNet.instance.Send("sayhello", spObj, (protoname, spobject) =>
{// 服务端response返回var error_code = SpObject.AsInt(spobject, "error_code", -1);var msg = SpObject.AsString(spobject, "msg", "");GameLogger.LogFormat("{0}: {1}", error_code, msg);});
3.2.3、客户端发送协议给服务端:Lua层

lua层协议发送通过Network.SendData方法,

示例:

-- 构造数据
local data = { what = "hi, i am unity from lua" }
-- 发送协议数据
Network.SendData("sayhello", data, function(data)-- 回到log("on response: " .. data.error_code .. " " .. data.msg)end
)
3.2.3、服务端下发协议给客户端:C#层

服务端下发协议给客户端,如果要在C#层处理,需要提前注册协议的响应函数,示例:

ProtocolProcessor.AddCallback(SpRpcOp.Request, "协议名", 响应函数);

建议统一放在NetworkMsgEventRegister脚本中进行注册,例:

public class NetworkMsgEventRegister
{public void RegistNetworkMsgEvent(){ProtocolProcessor.AddCallback(SpRpcOp.Request, "heartbeat", OnHeartBeat);}private void OnHeartBeat(string protoName, SpObject data){// TODO}
}
3.2.4、服务端下发协议给客户端:Lua层

服务端下发协议给客户端,如果要在Lua层处理,也需要提前注册协议的响应函数,建议统一在s2cProcessTab.lua脚本中,注册,例:

s2cProcessTab =
{-- 数字1表示C#层和lua层都进行响应-- 如果是2,则表示只在lua中进行响应,即使你在C#层注册了响应也不会在C#层触发heartbeat = { 1, HeartMgr.OnHeartBeat },
}

其中响应函数如下

-- 响应函数
function HeartMgr.OnHeartBeat(protoName, data)-- TODO
end

4、版本号管理

4.1、版本号文件:version.bytes

游戏的版本号包括app_versionres_version,保存在Assets/Resources目录的version.bytes文件中,

如下:

{"app_version":"1.0.0.0","res_version":"1.0.0.0"}

app_version是打整包时的整包版本号;res_version是资源版本号,打整包时默认与app_version相等,打增量包时,需要对res_version加一。因为version.bytes是放在Resources目录中的,出包后它不能动态修改,所以我们需要另外缓存一个当前最新的版本号,增量更新时,缓存一个version.bytesApplication.persistentDataPath + "/update/"目录中,如下:

4.2、版本号管理器:VersionMgr.cs

版本号管理器逻辑封装在VersionMgr.cs脚本中,

// 获取app版本号
var appVer = VersionMgr.instance.appVersion;
// 获取res版本号
var resVer = VersionMgr.instance.resVersion;

5、日志管理

5.1、日志打印

打印日志封装了一个GameLogger类,可以根据需要答应带颜色的日志,例:

GameLogger.Log("普通日志");
GameLogger.LogGreen("绿色文本日志");
GameLogger.LogYellow("黄色文本日志");
GameLogger.LogCyan("蓝绿色文本日志");
GameLogger.LogRed("红色文本日志");

效果:

5.2、输出日志文件

另外,会自动保存日志文件,Editor环境保存在Assets统计目录的gamelog文件夹中,

Editor环境,则保存到Application.persistentDataPath + "/gamelog/"文件夹中。

5.3、运行时日志预览

封装了一个LogCat.cs脚本,支持运行时对日志进行预览,在PC环境下按F4,在移动端使用四根手指头同时点击屏幕即可打开日志预览窗口,如下:

5.4、更多日志玩法

我之前写过一篇文章,推荐大家看下:《【游戏开发进阶】新发带你玩转Unity日志打印技巧(彩色日志 | 日志存储与上传 | 日志开关 | 日志双击溯源)》

6、声音管理

6.1、声音配置

声音作为资源,也需要进行资源配置,见上面第2.2节的资源路径配置的操作,同时会自动添加一个声音名字到资源id的映射到audioConfig.bytes中,

如下,

6.2、背景音乐播放

封装了一个声音管理器AudioMgr.cs,播放背景音乐的接口如下

// AudioMgr.cs/// <summary>
/// 播放音乐,比如背景音乐
/// </summary>
/// <param name="audioName">声音名字,需要带后缀</param>
/// <param name="loop">是否循环</param>
/// <param name="fadeIn">是否键入</param>
/// <param name="pauseOther">是否停止其他背景音乐</param>
public void PlayMusic(string audioName, bool loop, bool fadeIn, bool pauseOther)

例:

AudioMgr.instance.PlayMusic("bg.wav", true, true, true);
6.3、音效播放

播放音效的接口如下

// AudioMgr.cs/// <summary>
/// 播放音效
/// </summary>
/// <param name="audioName">声音名字,需要带后缀</param>
public void PlaySound(string audioName)/// <summary>
/// 播放音效
/// </summary>
/// <param name="audioName">声音名字,需要带后缀</param>
/// <param name="loop">是否循环</param>
/// <param name="fadeIn">是否渐入</param>
public void PlaySoundEx(string audioName, bool loop, bool fadeIn)

例:

AudioMgr.instance.PlaySound("coin.ogg");AudioMgr.instance.PlaySoundEx("award.ogg", false, true);
6.4、音量调节

我提供了音乐音量和音效音量两个接口,接口

// AudioMgr.cs/// <summary>
/// 调节音效音量
/// </summary>
/// <param name="factor">0到1</param>
public void UpdateSoundVolume(float factor)/// <summary>
/// 调节音乐音量
/// </summary>
/// <param name="factor">0到1</param>
public void UpdateMusicVolume(float factor)

示例

AudioMgr.instance.UpdateSoundVolume(0.8f);AudioMgr.instance.UpdateMusicVolume(0.5f);

7、事件管理

根据观察者模式封装了一个事件管理器:EventDispatcher.cs,可以订阅事件,然后通过抛事件的方式来触发一些响应逻辑。

7.1、定义事件名

建议在EventNameDef.cs中定义事件名,例:

7.2、事件订阅

接口

// EventDispatcher.cs/// <summary>
/// 注册事件
/// </summary>
/// <param name="evt">事件名</param>
/// <param name="handler">响应函数</param>
public void Regist(string evt, MyEventHandler handler)/// <summary>
/// 注销事件
/// </summary>
/// <param name="evt">事件名</param>
/// <param name="handler">响应函数</param>
public void UnRegist(string evt, MyEventHandler handler)

示例:

// 注册事件
EventDispatcher.instance.Regist(EventNameDef.LANGUAGE_TYPE_CHANGED, OnLanguageChange);// 注销事件
EventDispatcher.instance.UnRegist(EventNameDef.LANGUAGE_TYPE_CHANGED, OnLanguageChange);// 响应函数
void OnLanguageChange(params object[] args)
{}
7.3、抛出事件

接口

// EventDispatcher.cs/// <summary>
/// 抛出事件
/// </summary>
/// <param name="evt">事件名</param>
/// <param name="objs">参数</param>
public void DispatchEvent(string evt, params object[] objs)

示例:

EventDispatcher.instance.DispatchEvent(EventNameDef.LANGUAGE_TYPE_CHANGED);

8、界面管理

界面具备生命周期,为了方便管理,封装了界面类和界面管理器,画个图方便大家理解,

8.1、界面模板:C#

界面基类是BasePanel,我们去写具体的界面代码时要继承BasePanel,按照固定的模板来写即可,如下:

using UnityEngine;
using UnityEngine.UI;public class XXXPanel : BasePanel
{public static void Show(){// GlobalObjs.s_gamePanel:根据具体的界面所在的层来定PanelMgr.instance.ShowPanel<HotUpdatePanel>(界面ID, GlobalObjs.s_gamePanel);}protected override void OnShow(Transform parent){base.OnShow(parent);var panelObj = ResourceManager.instance.Instantiate<GameObject>(界面预设资源ID);panelObj.transform.SetParent(parent, false);var binder = panelObj.GetComponent<PrefabBinder>();SetUi(binder);}void SetUi(PrefabBinder binder){// TODO: UI交互/* 示例UGUITool.SetButton(binder, "closeBtn", (btn) => {this.Hide();});*/}// 注册事件protected override void RegistEvent(){base.RegistEvent();// EventDispatcher.instance.Regist(EventNameDef.事件名, 响应函数);}// 注销事件protected override void UnRegistEvent(){base.UnRegistEvent();// EventDispatcher.instance.UnRegist(EventNameDef.事件名, 响应函数);}
}
8.2、界面模板:Lua

同理,用Lua写界面时也按照模板来写,如下

-- XXX界面XXXPanel = XXXPanel or {}
XXXPanel.__index = XXXPanel local instance = nilfunction XXXPanel.Show()instance = UITool.CreatePanelObj(instance, XXXPanel , 'XXXPanel ', PANEL_ID.界面ID, GlobalObjs.s_gamePanel)
endfunction XXXPanel.Hide()UITool.HidePanel(instance)
endfunction XXXPanel:OnShow(parent)local panelObj = UITool.Instantiate(parent, 界面预设资源ID)self.panelObj = panelObjlocal binder = panelObj:GetComponent("PrefabBinder")self:SetUi(binder)
end-- UI交互
function XXXPanel:SetUi(binder)--[[示例:UGUITool.SetButton(binder, "closeBtn", function (btn)self.Hide()end)]]
endfunction LoginPanel:OnHide()LuaUtil.SafeDestroyObj(self.panelObj)instance = nil
end-- 注册事件
function XXXPanel:RegistEvent()-- EventDispatcher.instance:Regist(事件名, 响应函数)
end-- 注销事件
function XXXPanel:UnRegistEvent()-- EventDispatcher.instance:UnRegist(事件名, 响应函数)
end
8.1、显示界面

接口

// PanelMgr.cs/// <summary>
/// 显示界面(供C#层调用)
/// </summary>
/// <param name="panelId">界面ID</param>
/// <param name="parent">父节点</param>
/// <typeparam name="T">界面类</typeparam>
/// <returns></returns>
public T ShowPanel<T>(int panelId, Transform parent) where T : BasePanel/// <summary>
/// 显示界面(供lua层调用)
/// </summary>
/// <param name="panelId">界面ID</param>
/// <param name="luaObj">lua界面脚本对象,是一个lua table</param>
/// <param name="parent">父节点</param>
/// <returns></returns>
public BasePanel ShowPanel(int panelId, LuaInterface.LuaTable luaObj, Transform parent)

比如在C#层显示热更新界面,

var panel = PanelMgr.instance.ShowPanel<HotUpdatePanel>(1, GlobalObjs.s_topPanel);

其中HotUpdatePanel是继承BasePanel的,效果如下,

lua层显示界面我封装了一个UITool.CreatePanelObj方法,如下

function UITool.CreatePanelObj(instance, base, panelName, panelId, parent)

具体可以参见登录界面LoginPanel.lua和大厅界面GameHallPanel.lua的写法。

8.2、关闭界面

可以通过PanelMgrHidePanel方法,也可以直接通过BasePanel对象的Hide方法。
例:

PanelMgr.instance.HidePanel(界面ID);
// this是界面自身对象,只能在界面类内部使用该方法
this.Hide();

lua层封装了一个UITool.HidePanel接口,参数instance就是lua界面自身对象,如下

-- instance是界面对象实例
UITool.HidePanel(instance)

9、图集管理

框架中使用的UI系统是UGUI,精灵图建议进行归类打成图集,这样可以合并DrawCall,提高性能。

9.1、精灵散图存放位置

精灵散图存放在Assets/RawAssets/UI目录中,可自行创建子目录进行分类,

说明:精灵散图是还没有组合加工的资源,属于生肉资源,所以是放在RawAssets中。

9.2、开启图集功能

首先打开Project Settings,进入Editor标签页,把Sprite PackerMode设置为Sprite Atlas V1 - Always Enabled,这样方便我们在编辑器环境下测试图集打包,如下,

9.3、安装2D Sprite插件

确保你安装了2D Sprite插件,没有安装的话无法创建图集,可以打开Package Manager进行安装,如下,

9.4、创建图集文件

接着在Project视图中鼠标右键,点击菜单Create / 2D / Sprite Atlas,即可创建图集文件,

图集文件存放在Assets/GameRes/Atlas目录中,建议图集文件的命名与精灵散图所在的文件夹同名,这样方便对应,

9.5、执行打图集

选中图集文件,把精灵散图文件夹拖到图集文件的Objects for Packing槽中,点击Pack Preview按钮即可,如下,

9.6、图集资源配置

假设我们现在做一个背包模块,打开背包界面,有很多道具,每个道具有对应的图标,这些图标一般会配置在道具配置表中,我们的背包模块的代码就可以根据配置的图标来设置精灵图啦。
问题来了,要在代码中设置某个精灵图,我们需要先找到对应的图集,然后再从图集中找到对应的精灵图,假设策划配置的是精灵图名称,但是我们有好多个图集,代码中如何通过精灵图映射到图集呢?
答案就是我们生成一份映射表,从精灵名映射到图集,框架中要加载资源,是通过资源id来加载的,所以图集需要配置的资源配置resources.bytes中,可以参见我们上面2.2节中的操作进行资源配置,

最终可以在resources.bytes中看到添加的配置,

9.7、生成精灵图集映射表

上面我们说过,要有一个精灵到图集的映射表,我写了配套的生成工具,点击菜单Tools/命令/生成精灵映射配置

即可在Assets/GameRes/Config中生成sprite2atlas.bytes

内容如下

9.8、代码动态设置精灵图

接口

// SpriteManager.cs/// <summary>
/// 给Image设置精灵图
/// </summary>
/// <param name="image">Image对象</param>
/// <param name="spriteName">精灵图名称</param>
public void SetSprite(Image image, string spriteName)

假设我们现在界面上有一个Image对象,我们要根据配置表来设置它的图片diamond.png,示例:

SpriteManager.instance.SetSprite(image, "diamond");

10、特效管理

10.1、利用对象池

战斗场景中可能会有些特效需要重复播放,如果我们一直创建销毁创建销毁的话,很浪费CPU,所以我利用对象池管理封装了一个特效管理器:ParticleManager.cs

10.2、ParticleManager

接口

// ParticleManager.cs/// <summary>
/// 播放粒子特效
/// </summary>
/// <param name="resId">资源ID</param>
/// <param name="duration">持续事件</param>
/// <param name="cache">是否缓存到对象池中</param>
/// <returns></returns>
public GameObject PlayParticle(int resId, float duration, bool cache)

示例

ParticleManager.instance.PlayParticle(4, 5.5f, true);

11、多语言支持

项目如果出海的话,多语言支持是基本的要求,如果到了项目后期才做多语言支持,那将会是一个很恶心的事情,所以在最早期的框架阶段就要做好多语言支持。

11.1、国际化语言配置:I18N

Assets/GameRes/Config目录中放了一份i18nAppStrings.bytes配置,

内如如下,目前配置的语言是中文简体、中文繁体和英文,可自行根据需求进行拓展,

11.2、根据I18N的ID设置文本

游戏运行时,我们不能直接给Text设置死的文本文字,否则就无法动态根据语言设置显示对应的语言了。设置Text文本的时候,要通过I18Nid
示例

// Text myText;// 错误做法
// myText.text = "这是一个Unity通用游戏框架";// 正确做法
myText.text = I18N.GetStr(1);
11.3、预设上使用I18NText组件

为了方便在预设上使用多语言文本,我封装了一个I18NText组件(继承Text),
比如登录界面的这个文本,

创建I18NText的方法和创建Text一样,右键菜单点击UI/I18NText,如下

我们给I18NText设置好I18N id即可,它会自动查找对应的文本文字,如下

11.4、切换语言

目前定义的语言类型如下

// LanguageMgr.cspublic enum LanguageType
{// 中文简体ZH_CN = 0,// 中文繁体ZH_TW,// 英语English,
}

切换语言的接口

// LanguageMgr.cs/// <summary>
/// 切换语言
/// </summary>
/// <param name="index">语言索引,参见LanguageType枚举</param>
public void ChangeLanguageType(int index)

示例

// 切换为英语
LanguageMgr.instance.ChangeLanguageType(2);

效果

11.5、配置表的多语言支持

在配置表中,也不要直接配置具体的文本,而是配置为I18Nid

12、预设绑定器

我们做的预设,会有很多具体的节点或者节点内部的组件对象需要引用,如果我们通过代码transform.Find之类的接口去查找节点,如果美术把节点换个名字,运行时就会导致对象查找不到,还有如果我们想要引用的是节点上的某个组件,我们还需要通过GetComponent方法,这样的方式写起来也是很烦,所以,封装了一个预设绑定器:PrefabBinder.cs,由它来进行节点或组件对象的绑定,我们运行时通过PrefabBinder来找到对应的节点或组件对象。

12.1、给预设挂PrefabBinder

我们在预设上挂PrefabBinder组件(一般挂在根节点上,特殊情况也可以在子节点中继续挂PrefabBinder组件),如下

12.2、添加对象绑定

点击PrefabBinder组件的打开PrefabBinder编辑器按钮,如下

此时会打开一个PrefabBinder窗口,

我以绑定登录按钮的Button组件对象为例,如下,选中LoginButton节点,然后选择Button组件,输入绑定的名称,最后点击Add Item按钮即可,

最终以这种key - value键值对的方式实现名称与对象的绑定,

12.3、通过PrefabBinder获取对象

运行时,我们首先通过GetComponent获取到PrefabBinder组件,然后通过它来获取我们事先绑定号的节点或组件对象。
示例

var bindeer = panelObj.GetComponent<PrefabBinder>();// 写法一
var loginBtn = binder.GetObj<Button>("loginBtn");
loginBtn.onClick.AddListener(()=>{// TODO 响应逻辑
});// 写法二
UGUITool.SetButton(binder, "loginBtn", (btn)=>{// TODO 响应逻辑
});

13、UGUI拓展

UGUI拓展相关的代码放在Assets/Scripts/Framework/UGUIExpand目录中,

13.1、新手引导遮罩

游戏开发避免不了新手引导,而新手引导中经常需要一个目标镂空的遮罩,实现的代码放在GuideMask文件夹中,如下

为了方便使用,包装成了一个预设,

通过GuideMask.Create(target)这个接口来创建一个目标镂空遮罩引导,
示例

local loginBtn = binder:GetObj("loginBtn")
local mask = GuideMask.Create(loginBtn)-- 销毁遮罩
-- mask:Destroy()

效果如下

13.2、国际化多语言文本


用法见在上面的11.3小节。

13.3、精灵图轴对称

有一些精灵图是轴对称的,比如左右对称,上下对称,或者上下左右对称,这个时候出图时可以只出一部分,另外的对称部分由程序自动生成,封装了一个MirrorImage组件(继承Image),

创建MirrorImage和创建Image一样,鼠标右键点击菜单UI/MirrorImage,如下

然后设置Mirror Type即可,

比如左右对称

上下对称

上下左右对称

13.4、循环复用列表

游戏中经常需要做一些排行榜,基本都要求显示前50或前100名,如果使用普通的列表,创建50100UI对象,实在是浪费性能,我们一般会采用循环复用列表的方式,我封装在RecycllingScrollView文件夹中,如下,

关于循环复用列表的详细教程,我之前单独写过一篇教程,可以看我之前写的这篇文章:《【游戏开发实战】Unity UGUI实现循环复用列表,显示巨量列表信息,含Demo工程源码》

13.5、拓展补充

还有很多UGUI拓展的内容,可以继续放在UGUIExpand文件夹中,事件优先,先做这么多,后续有空再继续添加。

14、热更新

热更新可以说是游戏必备的功能了。本框架集成了LuaFramework,并编写了热更新逻辑。

14.1、热更新相关的代码

热更新相关的代码放在Scripts/Logic/HotUpdateScripts/View/HotUpdate文件夹中,如下

14.2、热更新的界面预设

热更新界面预设是HotUpdatePanel.prefab

如下

14.3、热更新流程

关于热更新的流程,我之前写过一篇详细教程,可以看我这篇文章:《【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)》

15、常用工具类

开发中经常要到的一些公用的方法可以封装到工具类中。

15.1、C#层工具类

C#层封装了一个UnityUtil.cs脚本,可以继续在里面补充方法;另外还有一些比较针对的工具类,比如JsonUtilXMLUtil等,

15.2、Lua层工具类

同理Lua层封装了一个LuaUtil.lua,也可继续补充,

16、打包工具

实际项目中,一般都会独立写一套打包工具,包括打AssetBundle、打APP整包、打热更包,有一些可能还要打拓展包。
打包工具相关的代码放在Editor/Build目录中,

16.1、打AssetBundle

点击菜单Tools/打包APP

此时会弹出一个窗口,点击Build AssetBundle按钮即可开始打AssetBundle

生成的AssetBundle会放在StreamingAssets/res目录中,如下

哪些目录会打成什么AssetBundle是在BuildUtils.cs中指定的,如下,可自行添加或修改,

16.2、打APP整包

在打包工具中点击Build APP按钮即可开始打包,可以先设置好version版本号,点击Save后再进行打包,

生成的包放在Bin目录中,如下

如果要打Android平台,需要在Build Settings中先切换到Android平台,

注:需要安装JDK和配置Android SDK、Gradle等。


如果要打iOS包,则需要在MacOS操作系统中,并切换到iOS平台,另外需要安装XCode,集成XCodeAPI,安装打包证书(.p12文件和.mobileprovision文件),最后通过xcodebuild命令导出ipa,我之前写过一些相关教程:
《Unity打iOS包之xcodeapi的使用》
《iOS企业版app部署到自己服务器(不通过AppStore,在iOS设备上直接安装ipa)》

16.3、打热更包

打整包时会生成一份Lua代码的md5列表,用于打增量包时进行文件对比,只有发生变化的lua文件才会打入增量包中,

我们点击菜单Build/打热更包

设置一下热更包的版本号,一般是最后一位+1,比如整包版本号是1.0.0.0,那么此时热更包版本号为1.0.0.1,如果继续打下一个热更包,则版本号为1.0.0.2
接着设置一下LuaFrameworkFiles.json的版本,即整包时的版本号;
如果有代码以外的资源要热更,比如界面预设,则点击+按钮,然后把界面拖到资源槽中,比如我们要热更LoginPanel这个界面预设,如下,最后点击打热更包按钮即可,

生成的热更包如下,将其部署到服务端上(包括服务端生成最新的版本信息,将zip文件上传到云盘上)

之后我们启动客户端即可检测到版本更新并下载到最新的热更包了。

17、LuaFramework框架

本框架集成了LuaFramework框架,关于LuaFramework框架的详细使用教程,我之前写过一篇文章,推荐大家看下:《Unity使用tolua框架教程: LuaFramewrk》,本文就不再赘述啦。
另外,如果你要对重新编译tolua runtime库,我也有写过一篇详细教程,《【游戏开发进阶】教你在Windows平台编译tolua runtime的各个平台库(Unity | 热更新 | tolua | 交叉编译)》

18、红点系统

红点系统的实现可以看我这篇博客,《【游戏开发实战】手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)》

八、Q&A

大家有什么疑问可以在评论区里提,也可以私信,我会把常见问题补充到Q&A中。

九、完毕

好啦,就先到这里吧~
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~

【游戏开发框架】自制Unity通用游戏框架UnityXFramework,详细教程(Unity3D技能树 | tolua | 框架 | 热更新)相关推荐

  1. 自制Unity小游戏TankHero-2D(3)开始玩起来

    自制Unity小游戏TankHero-2D(3)开始玩起来 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的.仅 ...

  2. 自制Unity小游戏TankHero-2D(1)制作主角坦克

    自制Unity小游戏TankHero-2D(1)制作主角坦克 我在做这样一个坦克游戏,是仿照(http://game.kid.qq.com/a/20140221/028931.htm)这个游戏制作的. ...

  3. ThinkJS框架入门详细教程(二)新手入门项目

    一.准备工作 参考前一篇:ThinkJS框架入门详细教程(一)开发环境 安装thinkJS命令 npm install -g think-cli 监测是否安装成功 thinkjs -v 二.创建项目 ...

  4. android+祖玛游戏源码,unity祖玛游戏Zuma Ball Blast源码

    压缩包内容概览: unity祖玛游戏Zuma Ball src ; 汇编C锐器编辑器 ; 汇编-C夏普编辑器 ; 组件-C夏普vs ; C夏普组件 ; 汇编统一脚本vs ; 汇编统一体脚本 ; 资产 ...

  5. 【游戏开发高阶】从零到一教你Unity使用ToLua实现热更新(含Demo工程 | LuaFramework | 增量 | HotUpdate)

    文章目录 零.前言 一.我做的热更新Demo 1.效果演示 2.流程图 3.工程源码 二.为什么要有热更新 三.Unity如何支持热更新 1.热更C#代码 2.热更lua代码与资源 四.Unity中集 ...

  6. Scut游戏服务器引擎6.1.5.6发布,直接可运行,支持热更新

    1. 增加exe版(console),web版本(IIS)的游戏服宿主程序 2. 增加Model支持脚本化,实现不停服更新 3. 增加Language支持脚本化 4. 修改Sns与Pay Center ...

  7. B站微服务框架Kratos详细教程(1)- 安装搭建

    Kratos Kratos是bilibili开源的一套Go微服务框架,包含大量微服务相关框架及工具. 名字来源于:<战神>游戏以希腊神话为背景,讲述由凡人成为战神的奎托斯(Kratos)成 ...

  8. java框架ssm整合_SSM三大框架整合详细教程(Spring+SpringMVC+MyBatis)

    使用 SSM ( Spring . SpringMVC 和 Mybatis )已经有三个多月了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当然肯定有很多可以改进的地方.之前没 ...

  9. Spring+SpringMVC+MyBatis框架搭建-----详细教程

    1.基本概念 1.1Spring Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J ...

  10. Spring Boot如何在最短时间里快速搭建微服务框架,详细教程贡上

    前言: Spring Boot是为了简化Spring应用的创建.运行.调试.部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置. 简单来说,它提供了一堆依赖打包,并 ...

最新文章

  1. linux 64平台上编译32位程序: GCC编译选项 -m64 -m32 -mx32
  2. 主模式和野蛮模式_网络野蛮行为的含混性和观念
  3. BIOS——[PXE-E61:Media test failure,check cable]解决方案
  4. 迅为工业级iMX6Q开发板全新升级兼容PLUS版本|四核商业级|工业级|双核商业级...
  5. Spring Cloud 7:Gateway
  6. scrapy学习笔记(二)进阶使用
  7. 阿里女员工遭遇「杀猪盘」背后的思考
  8. Precedence Problems of C Operators
  9. arm指令集 c语言,这些Cortex-A处理器支持的指令集,您都知道吗?
  10. C# 汉字转拼音(支持GB2312字符集中所有汉字)
  11. 高盛VR/AR报告完整解读版
  12. BP神经网络的初步介绍
  13. 2005-11-11
  14. 微信拍一拍怎么撤回(无法撤回原因是这样的)
  15. 抖音短视频创业,抖音机房怎么样搭建?有哪些技巧
  16. 2022年第四届长安杯电子取证竞赛-exe部分
  17. C语言之实用调试技巧
  18. 名帖367 邓文原 章草《临皇象急就章》
  19. 情人节送玫瑰花数的含义,男的必看。(转)
  20. [UE4]不错的音效插件WWISE

热门文章

  1. Java——猜数字游戏
  2. python期货自动交易软件_python自动股票交易软件,求比较好用的股票自动交易软件...
  3. 微信小程序怎么反编译,获取微信小程序源码
  4. ATV 开发 三 DRM技术简介
  5. 外部碎片和内部碎片的区别
  6. mysql api百度云盘_利用百度云盘API上传文件至百度云盘
  7. 做减肥产品微商地推用什么做引流?如何选择转化率较高的地推方式
  8. 从零开发一款Android RTMP播放器
  9. MyEclipse配置Tomcat 7
  10. Python语言概述