GameFramework使用指南
一、前言
这是我自己出的一篇GameFramework的使用指南,不会详细讲底层原理,主要讲如何快速使用。虽然说是指南,但是本人其实也只是初学者,不过是将自己学习的过程做成了笔记,因此文章仅供参考,如有不对之处欢迎指出。
如果对GameFramework不太了解的,可以去网上找些文章来看,了解一下,然后下载官方的StarForce项目,自己也大概看下,有个基本的了解,我这个文章也基本会基于官方项目来弄。
这里列出GameFramework相关的网址:
1、GameFramework食用指南 - 小紫苏 - 博客园
主要讲解了GameFramework的基本使用,不涉及底层实现
讲解GameFramework底层原理,讲解的比较详细,可惜只出了几篇
3、从零开始的UnityGameFramework使用教程(壹) - 知乎
也是只讲了GameFramework的使用,从基础搭建到使用都有解释
4、EllanJiang/StarForce: This is a demo made with Game Framework.
想详细了解GameFramework的运作和使用方式,可以下载官方的StarForce项目
二、框架概述
1、基本组成
这里大致讲下框架的组成,具体源码就不分析了,了解组成之后可以自己点进源码去研究下。
框架主要分为两个部分,一部分为GameFramework(简称GF),一部分为Unity GameFramework(简称UGF)。简单地说,框架各模块的主要实现都在GF中,且GF完全不依赖Unity。而依赖于Unity的实现则在UGF部分,如GameObject,AssetBundle,Editor相关内容。具体游戏逻辑直接跟UGF对接。
GF框架分为了19个内置模块,包含UI、事件、实体、声音、配置等供开发者使用。当然文章里只会讲我常用的几个。
在GF框架中,有一个静态类GameFrameworkEntry作为游戏框架入口,用于管理所有的GameFrameworkModule(模块),对外提供获取模块的方法。而每个模块的实现类其实就是一个个Manager(继承抽象类GameFrameworkModule),比如UI模块对应的就是UIManager,所以想看GF源码,可以从各个模块的Manager入手。
在UGF框架中,则是用各个 GameFrameworkComponent(组件) 来代表各个模块,比如UI模块就是UIComponent,而Component就是挂载到场景物体的一个组件,这样就可以使用Unity的生命周期了。而与GF类似的,这些Component被静态类GameEntry管理,同时对外提供获取组件的方法。
GameFrameworkComponent其实就是对GameFrameworkModule的一层封装,点进各个Component源码,都会看到他有XXXManager的成员变量,在Awake或Start时初始化,从GameFrameworkEntry获取模块,然后初始化模块。组件中对应的一些方法也会调用Manager的方法进行逻辑处理。因此,实际使用框架的模块时,我们只需要通过Component访问即可。
在框架中,经常会看到调用的是接口而不是实现类,因为使用了接口进行解耦。想要看实现类也挺简单,调用的接口都是IXXX形式的,我们直接看XXX就行了,比如UIComponent调用的是IUIManager,我们直接看UIManager就行了。
简而言之,使用模块时我们只需要使用各个模块的Component即可。
想看源码,先从Component入手,这是UGF的部分,如果想深入了解,那么再看GF部分,即各个Manager的源码
2、游戏如何跑起来的?
核心的入口就是BaseComponent,在Awake阶段做Text,Log,版本,json辅助器的初始化;在Update阶段调用GF层的GameFrameworkEntry的Update方法,轮询实行所有GF层Module的Update,这样整个框架就运行起来了。
当然游戏真正能跑起来还是Procedure流程模块的作用,框架将游戏运行的各个状态分成了各个流程,Procedure模块就负责管理游戏的各个流程,原理就是有限状态机,每次只能位于一个流程中,然后执行该流程的逻辑,我们只需要将逻辑写在这个对应流程中就可以了。
这个流程怎么理解?我举个例子就懂了:比如游戏开始是位于入口流程,进行一些初始化;之后进入预加载流程,预加载一些游戏资源;然后进入主菜单流程,逻辑就是打开主菜单UI;若游戏开始,则切换到游戏流程,在这个流程里就可以编写游戏相关的逻辑了。当然流程怎么写后面会讲。
三、框架搭建
(一)源码替换
1、去官网下载GF框架和UGF框架,导入到Unity中。
网址:EllanJiang (Ellan Jiang)
2、将UnityGameFramework拖入工程中,可以在Assets下新建一个文件夹(比如叫GameFramework)再拖进去。然后打开它的Libraries文件夹,删除其中的GameFramework.dll,因为我们要替换成源码。
3、将GameFramework放到Libraries文件夹中,同时在Libraries文件夹中右键,新建一个Assembly Definition(程序集定义),命名为GameFramework吧。
4、在Runtime文件夹中UnityGameFramework.Runtime程序集需要引用刚才创建的GameFramework程序集
5、在Editor文件夹中UnityGameFramework.Editor程序集需要引用上面两个程序集(UnityGameFramework.Runtime和GameFramework)
至此源码就替换完了,Libraries下的就是GF框架,剩余的就是UGF框架,UGF中的核心逻辑都在Runtime文件夹下。
(二)引入组件
自己从头搭建一个比较麻烦,对新手不太友好,我们直接把StarForce的GameMain文件夹拿过来(或者参考下,慢慢把目前需要的拿过来或自己创建一个),然后对着讲解即可。
弄好之后,整个项目就有两个核心文件夹,如果你在源码替换那部分新建了文件夹并放入框架源码,则第一个核心文件夹就是GameFramework,存放框架的核心代码。另一个就是GameMain文件夹,存放资源和核心代码逻辑。
游戏是从Launch场景进入的,所以我们需要先打开Launch场景。可以发现里面有一个空对象Game Framework,下面有两个子物体Builtin和Customs,Builtin就是存放框架内置的那十几个组件,而Customs就是存放自定义组件(对的,我们可以自定义组件,后面也会讲下怎么使用)。
Game Framework对象上挂在了一个脚本:GameEntry,点开看,其实就是对各个Component的封装。官方文档建议,在实际游戏开发阶段,对UGF的GameEntry再做一层封装——业务层的GameEntry。这样我们就不要要用长长的一串代码获取组件了,而是用更简短的方式获取,如下:
- 原本获取UI组件的方式:
UnityGameFramework.Runtime.GameEntry.GetComponent<UIComponent>();
- 现在获取UI组件的方式:
GameEntry.UI;
既然框架源码导入了,也知道怎么获取组件了,那么接下来就直接开始讲下各个模块的使用吧!
四、Procedure模块
1、概述
框架概述中讲过,Procedure模块就是控制各个游戏流程的,实际就是运用了有限状态机,每个时刻只能有一个状态存在,也就是每个时刻只有一个流程运行。(因此这里也会包含一些FSM模块的东西)
那么怎么使用呢?
我们点开入口流程ProcedureLaunch,一直往上看它继承的基类,可以看到它继承了ProcedureBase,而它的源码如下:
public abstract class ProcedureBase : FsmState<IProcedureManager>
{
protected ProcedureBase();
protected internal override void OnDestroy(IFsm<IProcedureManager> procedureOwner);
protected internal override void OnEnter(IFsm<IProcedureManager> procedureOwner);
protected internal override void OnInit(IFsm<IProcedureManager> procedureOwner);
protected internal override void OnLeave(IFsm<IProcedureManager> procedureOwner, bool isShutdown);
protected internal override void OnUpdate(IFsm<IProcedureManager> procedureOwner, float elapseSeconds, float realElapseSeconds);
}
它继承了FsmState,实际上就是状态机的一个状态,我们只需要实现对应的OnEnter、OnUpdate、OnLeave等方法即可,状态机状态切换、运行时就会自动调用对应的方法。
FsmState里还有ChangeState方法用于切换状态,这里也就是切换流程。
2、使用
流程的运行过程和流程多少可以自定义,根据实际情况增加或减少一些流程都可以。不过我们就先按照官方的来,首先第一个执行的流程就是ProcedureLaunch,主要就是初始化一些配置,看下它是怎么实现流程的,或者再看其他的一些流程,也就基本会写了。
以ProcedureMenu为例:
- 首先创建一个类ProcedureMenu,继承ProcedureBase
public class ProcedureMenu : ProcedureBase
- 根据需要实现对应的OnEnter、OnUpdate、OnLeave等方法,根据逻辑需要自己添加额外的方法和变量
protected override void OnEnter(ProcedureOwner procedureOwner)
{
...
}
protected override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown)
{
...
}
...
- 如果需要在状态机中传递共享变量,使得所有状态都可以共享该变量,方法如下:
IFsm<T>.SetData<TData>(string name, TData data);
IFsm<T>.HasData(string name);
IFsm<T>.GetData<TData>(string name);
如在OnUpdate逻辑中,转换场景时,需要传递转换的场景ID:
protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
if (m_StartGame)
{
// 设置共享变量
procedureOwner.SetData<VarInt32>("NextSceneId", GameEntry.Config.GetInt("Scene.Main"));
procedureOwner.SetData<VarByte>("GameMode", (byte)GameMode.Survival);
ChangeState<ProcedureChangeScene>(procedureOwner);
}
}
- 需要转换流程时,使用ChangeState方法,如上面OnUpdate最后的逻辑,方法如下:
FsmState<T>.ChangeState<TState>(IFsm<T> fsm);
- 编写完新的流程,记得在场景中找到Procedure组件,在Inspector窗口勾选上流程,这样就可以将该状态添加进状态机中(即添加进流程中)。
五、UI模块
1、概述
UI模块中,每个UI面板就是一个Form(就是Panel之类的,叫法不同而已),具体实现流程就是拼UI,然后挂载对应UIForm脚本到UI上,设置好UI Group即可。
关于这个脚本,其实就是继承了UIFormLogic的类,而StarForce项目对这个UIFormLogic进行了封装,即UGuiForm类,增加了面板淡入淡出和多语言一些功能(这个多语言就是初始化OnInit时对文本进行相应替换,多语言模块后面会讲),所以我们实现UI脚本时,继承UGuiForm就行了。
这里提一嘴,官方的UGuiForm用的是Text,如果项目需要使用TextMeshPro,需要将该类的Text和Font替换成TMP_Text和TMP_FontAsset。
关于UI Group,其实就是UI分组用的,如果UI在同一个组,那么当一个UI显示时,同组内的其他UI会被覆盖隐藏,不同组的话面板就可以同时存在。而且UI Group可以设置Depth(深度),好像是Depth越小UI越靠前。
还有你会看到游戏运行时,一个UI上面其实挂有UIFormLogic和UIForm两个类,我目前区分的也不是很清楚,大概就是UIForm负责属性和生命周期管理,而UIFormLogic负责业务逻辑。不过它们其实可以相互获取,调用UIComponent中的方法时,你看需要传的是UIFormLogic还是UIForm就行。
还有官方项目写了个UIExtension对UI组件方法进行了拓展和封装,所以在调用UIForm时都使用的是拓展方法,但是由于这样套了太多层有点难看懂,所以我会先讲下最基本的调用方法,然后再讲项目封装的拓展方法。
UI模块的拓展方法会用到DataTable模块,所以不懂的话可以跳到DataTable章节看,这样可能更容易理解些。
2、使用
(1)UI创建
以MenuForm为例:
- 新建类MenuForm继承UguiForm
public class MenuForm : UGuiForm
- 根据需要实现OnOpen、OnClose等方法
- 关于OnOpen等等方法的形参,是一个
object userData,这是哪里传的呢?其实就是UI组件的OpenUIForm方法中传入的,在后面调用那里会讲。
比如这里需要接收ProcedureMenu,用于改变该流程中m_StartGame的布尔参数,这样ProcedureMenu就知道游戏开始了,需要转换流程。
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
m_ProcedureMenu = (ProcedureMenu)userData; // 接收参数
// 处理后续逻辑...
}
- 拼好UI,做成预制体,将MenuForm挂载到UI上,如果脚本需要参数,设置好脚本参数即可。
- 编写完新的UIForm,在场景中找到UI组件,设置UI Groups。关于这个组名怎么看呢?其实可以随便取,但需要保证和GameMain/DataTable文件夹下的UIForm配置表的UIGroupName属性一致即可。也就是说按照配置表来设置,因为UI组件的拓展方法用到了配置表。
(2)UI调用
以ProcedureMenu为例:
先讲一下不用UIExtension拓展的方法,用UIComponent自带的方法:
- 开启面板:需要传入资源名、组名、优先级、自定义数据等等
OpenUIForm(string uiFormAssetName, string uiGroupName, int priority, bool pauseCoveredUIForm, object userData)
- 关闭面板:传入UIForm(前面说了,可以从
UIFormLogic.UIForm获取)和自定义数据
public void CloseUIForm(UIForm uiForm, object userData)
项目中为了简化方法的调用,使用UIExtension对UIComponent做了拓展:将这些数据都在配置表中配好,比如ID、UI资源路径、UI组名等等。然后从配置表中根据ID读取配置,直接按照配置创建。
封装后,调用的方法如下:
- 打开面板:获取UI组件,并调用OpenUIForm方法,传入配置表中对应UI的ID。如果需要传入参数则可以传入,这里因为MenuForm需要获取自己,所以将自己作为参数传进去。
protected override void OnEnter(ProcedureOwner procedureOwner)
{
// ...
m_StartGame = false;
// 打开主菜单面板并将自己作为参数传入
GameEntry.UI.OpenUIForm(UIFormId.MenuForm, this);
}
- 关闭面板:获取UI组件,并调用CloseUIForm方法(这里UguiForm对拓展的CloseUIForm方法封装了一层,所以调用UguiForm的Close方法了,当然你也可以不用这个方法,很灵活的)
protected override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown)
{
// ...
if (m_MenuForm != null)
{
// 调用UguiForm的关闭面板方法
m_MenuForm.Close(isShutdown);
m_MenuForm = null;
}
}
当然UI组件不止这两个方法,可以自己去探索其他方法。
六、Entity模块
1、概述
实体模块算是比较重要的模块了,所有需要动态创建的物体都称为实体(Entity)。比如官方项目的子弹、陨石、飞船等等。具体实现流程就是编写实体脚本,然后挂载到实体上,设置好Entity Group即可。
关于这个脚本,其实就是继承了EntityLogic的类,正和UI模块的UIFormLogic和UIForm一样,Entity模块也有Entity和EntityLogic两个类,它们的关系是差不多的,我们编写逻辑,继承的是EntityLogic。和UI模块类似的,项目对EntityLogic进行了封装,名字也叫Entity(实现业务逻辑的,别搞混了),新增了一些属性和初始Transform的设置。为了避免混淆,后续说到的Entity都是指继承了EntityLogic的这个,而我们编写实体时,也都继承的是这个Entity。(继承链如下:我们的Entity脚本->Entity->EntityLogic)
关于EntityData:*,在编写自己的Entity脚本时,你会发现官方项目中Entity还会搭配一个变量EntityData**,这个就不是框架自带的了,这是项目自己定的,这个Data是在ShowEntity时传入的,因此在接收时,只需要类似这样m_EntityData = userData as EntityData即可。也就是说,一个EntityLogic搭配一个EntityData,我们也按照这个方式来。
**关于Entity Group,**也和UI模块类似,不过组名就不是在DataTable配置的了,而是采用和资源名相同的名字,这里我们也按照官方Demo的来(方式其实很灵活)。
关于ID,你会发现EntityData有两个ID,一个是Id,一个是TypeId。区别是什么呢?其中这个Id是唯一的,每个不同的实体都有一个唯一的ID表示自己。而TypeId是表示类型的Id,同一类型的TypeId都一样。比如配置表中,飞船Id为1001,那么所有飞船的TypeId都为1001。这个TypeId可以从数据表配置,那么这个Id哪里来呢?框架好像并没有提供,因此项目中在EntityExtension中提供了GenerateSerialId静态方法生成唯一ID。
与UI模块类似,Entity也对组件做了一些方法的拓展。
项目对其他的一些模块都做了拓展,后面就不再赘述了,有了前面UI模块拓展方法的讲述,应该就不难理解这些方法是怎么封装的了,想了解内部细节直接点进源码看下就行。因此,后续我会直接讲下用法,至于原理、用的是拓展方法还是内部方法我就不细讲了。
2、使用
(1)实体创建
以Bullet为例:
- 新建脚本继承Entity,作为实体逻辑
public class Bullet : Entity
- 根据需要实现OnInit、OnShow、OnHide等方法
- 新建一个脚本继承EntityData,作为实体数据
public class BulletData : EntityData
- 在EntityData中写入想要用到的数据和方法
- 别忘了在Entity脚本中弄一个成员变量表示对应的EntityData,并接收它
public class Bullet : Entity
{
[SerializeField] private BulletData m_BulletData = null; // 定义数据
protected override void OnShow(object userData)
{
base.OnShow(userData);
m_BulletData = userData as BulletData; // 接收
// ...
}
}
- 完善Entity的脚本逻辑后,将其挂载到对应实体上
我记得官方项目的Entity脚本不能挂载到实体上,否则运行时会有重复挂载的问题。
修复方案我记得就是在GF框架的DefaultEntityHelper.cs的CreateEntity方法中,将
return gameObject.AddComponent<Entity>();改成return gameObject.GetOrAddComponent<Entity>();
- 编写完新的实体,在场景中找到Entity组件,设置Entity Groups。因为项目是按照实体名来设置组名的,所以我们按照这个来,新建组名为Entity的名字。
(2)实体调用
以SurvivalGame脚本为例:
- 显示实体:调用封装的拓展方法,传入实体数据即可,这里就是new一个AsteroidData
GameEntry.Entity.ShowAsteroid(new AsteroidData(GameEntry.Entity.GenerateSerialId(), 60000 + Utility.Random.GetRandom(dtAsteroid.Count))
{
Position = new Vector3(randomPositionX, 0f, randomPositionZ),
});
以TargetableObject脚本为例:
- 隐藏实体:调用封装的拓展方法,传入Entity即可
protected virtual void OnDead(Entity attacker)
{
GameEntry.Entity.HideEntity(this);
}
学习了Procedure、UI、Entity模块,其实就相当于完成一半了,接下来再讲下几个常用的模块就差不多了。
七、DataTable模块
1、概述
DataTable数据表其实就可以理解为一张Excel表,存放了游戏的各项配置,我们按照它的格式进行配置,之后利用项目提供的自动转换工具,将Excel转成二进制文件,并生成相应代码,之后就可以愉快的使用数据表配置了。
DataTable默认是Excel复制下来的结构,像CSV但分隔符是/t;在GameMain/Configs文件夹中的DataTableCodeTemplate,用于生成数据行(DataRow)的C#类模板;关于这个格式,简单游戏够了,复杂点的还需要加个导表工具或者魔改一下。
关于DataTable的结构,DataTable存储的是一个个数据行(DataRow),DataRow就是表中配置的一行数据,获取到DataTable后,可以获取其中的DataRow,然后再从DataRow中获取某一个属性值。
2、使用
- 配置Excel表,参照其他表的配置结构,然后要转换成后最为txt格式的。
- 在PreLoad流程中,将所需要加载的DataTable名字添加到列表中(生成工具是这么写的,不是框架要求)
public static readonly string[] DataTableNames = new string[]
{
"Aircraft",
"Armor",
// 表名...
};
- 点击Unity上方工具栏的Star Force/Generate DataTables,生成数据行类和将对应DataTable转成二进制文件。
- 生成之后,就可以在代码中使用了,先获取DataTable,再获取DataRow,再获取属性以UIExtension为例:
// 获取数据表
IDataTable<DRUIForm> dtUIForm = GameEntry.DataTable.GetDataTable<DRUIForm>();
// 获取数据行
DRUIForm drUIForm = dtUIForm.GetDataRow(uiFormId);
// 获取某一项属性
string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
关于DataTable如何加载,可以在PreLoad流程中查看
八、Event模块
1、概述
事件模块的使用不难,只需要了解观察者模式和事件系统即可。无非就是订阅、取消订阅和发送事件。
关于自定义事件,则需要继承GameEventArgs类,对外提供一个ID和Create创建事件的工厂方法,实现Clear清除引用即可(事件创建用了引用池,归还时需要清除引用)。可以参照OpenUIFormSuccessEventArgs事件,模仿着写。
2、使用
(1)创建自定义事件
- 参照OpenUIFormSuccessEventArgs,创建一个事件的模板如下:
public class XXEventArgs : GameEventArgs
{
public static readonly int EventId = typeof(XXEventArgs).GetHashCode();
public override int Id => EventId;
// 一些变量,调用Create方法时可以传入参数来初始化变量
public static XXEventArgs Create()
{
XXEventArgs args = ReferencePool.Acquire<XXEventArgs>();
// 初始化事件变量
return args;
}
public override void Clear()
{
// 清除变量的引用
}
}
(2)订阅和取消事件
以ProcedureMenu为例,订阅了打开UI成功的事件:
- 订阅事件:
GameEntry.Event.Subscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
- 取消订阅事件:
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
- 事件委托:先将GameEventArgs转成对应的事件类,再获取事件里的数据,如这里获取的是UI面板
private void OnOpenUIFormSuccess(object sender, GameEventArgs e)
{
OpenUIFormSuccessEventArgs ne = (OpenUIFormSuccessEventArgs)e;
if (ne.UserData != this)
{
return;
}
m_MenuForm = (MenuForm)ne.UIForm.Logic;
}
(3)发送事件
模板如下:
GameEntry.Event.Fire(sender, XXEventArgs.Create());
九、Config模块
1、概述
Config模块就是用来获取全局配置的,和DataTable有点像。用于存储一些全局的只读的游戏配置,如玩家初始速度、游戏初始音量等。
在项目的GameMain/Config文件夹中,有一个DefaultConfig,就是配置了全局相关的一些配置。
在PreLoad流程中,对Config的数据进行了加载,使用也很简单,就调用API获取就行了,比如什么GetInt,GetString等等。
2、使用
以获取SceneMenuID为例:
- 获取属性值:传入名字即可
GameEntry.Config.GetInt("Scene.Menu");
十、Setting模块
1、概述
Setting模块就是存储数据用的。可以在Setting组件上的Setting Helper中选择不同的存储方式,比如使用Playerprefs存储、或者使用二进制存储游戏数据。
2、使用
- 设置数据:包括SetString、SetInt等等
GameEntry.Setting.SetString("名称",值);
- 获取数据:包括GetString、GetInt等等
GameEntry.Setting.GetString("名称");
- 是否有设置:如果之前Set了就返回true,否则返回false
GameEntry.Setting.HasSetting("名称");
- 保存设置:
GameEntry.Setting.Save();
十一、Sound模块
1、概述
用于播放音乐音效的。参照官方项目,需要有一个AudioMixer,在AudioMixer中分好组以及调整一些配置,之后就可以拖给Sound组件了,同时Sound组件也需要设置相应的组,至此,基础配置就完成了。
再使用Sound时,我们就按照官方项目的方式,使用Sound的拓展方法:在DataTable中配置好音乐音效相关配置,之后直接调用方法使用即可。
2、使用
- 播放音乐:
GameEntry.Sound.PlayMusic(音乐ID);
- 播放音效:
GameEntry.Sound.PlaySound(音效ID);
十二、Localization模块
1、概述
多语言模块用于实现游戏的多种语言,使用方式也是非常简单。
在GameMain\Localization下,会发现每个语言都有一个XML文件,XML文件中存储着一个个键值对,在Preload流程中,会加载并解析对应语言的XML文件。
然后我们只需要将文本设置为键的内容,游戏中我们就可以通过Localization模块,获取键对应的值的内容,并替换对应的文本。不同语言的值内容不同,但键是相同的,这样就可以实现多语言了。
2、使用
- 打开对应语言的XML文件,可以将对应键的值修改为自己想要翻译的内容,也可以自己增加键值对
- 在游戏文本中,将文本替换成对应键的内容即可,在前面的UguiForm中,我们说过了,初始化面板时会自动帮我们把键替换成值的内容。
- 如果想游戏过程中动态获取键的值怎么办?很简单:
GameEntry.Localization.GetString("键");
- 获取和修改当前语言:参考Launch流程
GameEntry.Localization.Language;
十三、自定义组件
如果我们需要自定义组件,那么也很简单,可以参照项目的那两个自定义组件:
- 创建一个类,继承GameFrameworkComponent
- 类中实现自己想要的功能
- 在GameEntry.Custom类中,添加获取自定义组件的属性,并初始化,这样方便外部访问组件
十四、打包
1、概述
当完成游戏,需要发布游戏时,就可以选择打包资源了。这里还需要简单对资源部分进行讲解:
在官方项目的ProcedureSplash流程中,可以发现分成了编辑器模式、单机模式和可更新模式。
- 编辑器模式就是直接从Assets/目录加载资源
- 单机模式就是需要将资源打包成AssetBundle并放在StreamingAssets/目录,然后安装时随包一起发布,资源从本地加载
- 可更新模式就是用了热更新,首次启动从服务器下载资源,并且支持版本检查和增量更新
如果游戏最终要打包发布了,那么就需要将Builtin对象的Base组件的Editor Resource Mode关掉,即不用编辑器模式。
然后再Resources模块中,选择Package单机模式(无法热更)、或者Updatable可更新模式(游戏开始前更新完进入游戏)、或者Updatable while playing可更新分包加载(游戏运行时需要使用资源时即时下载)
2、使用
- 编辑打包数据
在上方工具栏,打开Game Framework/Resource Tools/Resource Editor,这样就可以看到资源打包页面。右侧是你场景中的资源,左侧是要打包的资源,中间则是可以查看左边打包的资源包里有啥。将需要打包的资源都移到左边打包好后,点击保存,相关资源打包信息会在GameMain/Config/ResourceCollection中体现。
- 资源打包
在上方工具栏,打开Game Framework/Resource Tools/Resource Builder,选择打包路径,直接打包即可。如果出现打包错误,可以在打包路径下找到BuildReport文件夹,里面有打包的一些信息,可以查看是哪里出现了问题。
- 游戏打包
如果是单机模式,需要将Package文件夹下对应文件拖到StreamingAssets文件夹下(好像打包时会自动添加到这个文件夹,如果发现有问题则进行文件替换)。
如果是可更新模式,则将Full文件夹上传到服务器。
完成之后打包游戏即可。