GameFramework使用教程

       今年开始接触这个框架,最开始技术选型的时候是准备使用这个框架进行弱联网小游戏的开发,做一些休闲小游戏的开发。由于一些原因项目在demo阶段告一段落,算起来使用这个框架一共是4个月左右的时间。不知道下一个项目还会不会采用这个框架,利用缓冲时间来整理一下技术以及自己在项目中的收获。

       一.框架介绍:

Game Framework 是一个基于 Unity 引擎的游戏框架,主要对游戏开发过程中常用模块进行了封装,很大程度地规范开发过程、加快开发速度并保证产品质量。

GameFramework的官方网站:https://gameframework.cn/

GameFramework的开源地址: https://github.com/EllanJiang/GameFramework/

具体的接入使用细节在官方网站都可以进行查阅,这里说一些常见的注意点:

1.在E神的github可以看到这个框架是分了一下层的,Framework是可以脱离unity单独使用的,在unity中的使用需要结合UnityGameFramework,Runtime中的代码是与Mono紧密结合的。在UnityGameFramework中prefabs文件夹中可以找到一个GameFramework的预制体,这个预制体可以看成一个基础组件,放在初始场景中贯穿整个游戏。

GameFramework使用教程_第1张图片

2.GF通过组件控制各个模块,GameEntry是整个游戏的入口,负责初始化框架的基础组件。

private static void InitBuiltinComponents()
        {
            Base = UnityGameFramework.Runtime.GameEntry.GetComponent();
            Config = UnityGameFramework.Runtime.GameEntry.GetComponent();
            DataNode =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            DataTable =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Debugger =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Download =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Entity = UnityGameFramework.Runtime.GameEntry.GetComponent();
            Event = UnityGameFramework.Runtime.GameEntry.GetComponent();
            Fsm = UnityGameFramework.Runtime.GameEntry.GetComponent();
            Localization =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Network =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            ObjectPool =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Procedure =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Resource =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Scene = UnityGameFramework.Runtime.GameEntry.GetComponent();
            Setting =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            Sound = UnityGameFramework.Runtime.GameEntry.GetComponent();
            UI = UnityGameFramework.Runtime.GameEntry.GetComponent();
            WebRequest =  UnityGameFramework.Runtime.GameEntry.GetComponent();
            CD = UnityGameFramework.Runtime.GameEntry.GetComponent();
            Timer = UnityGameFramework.Runtime.GameEntry.GetComponent();
            SimpleEvent =  UnityGameFramework.Runtime.GameEntry.GetComponent();
        }

 

下面CD Timer SimpleEvent 是根据项目需要添加的,其他组件的功能在官网的介绍中都可以了解到,基本上很多组件单独拎出来解释都可以作为一篇教程,后面也会介绍自己使用的比较多的组件。

3.这个框架是通过流程组件来设置游戏的当前的一个状态。

GameFramework使用教程_第2张图片

首先选择初始流程,一般会在初始流程中初始化config的信息等,每个流程实质都是一个状态机,都有OnInit(),OnEnter(),OnUpdata(),OnLeave(),方法,在进入流程的时候做一些初始化方法。

 public override void OnEnter(ProcedureOwner procedureOwner)
        {
            base.OnEnter(procedureOwner);
            // 构建信息:发布版本时,把一些数据以 Json 的格式写入  Assets/GameMain/Configs/BuildInfo.txt,供游戏逻辑读取。
            //GameEntry.BuiltinData.InitBuildInfo();
            // 语言配置:设置当前使用的语言,如果不设置,则默认使用操作系统语言。
            InitLanguageSettings();
            // 变体配置:根据使用的语言,通知底层加载对应的资源变体。
            InitCurrentVariant();
            // 画质配置:根据检测到的硬件信息 Assets/Main/Configs/DeviceModelConfig 和用户配置数据,设置即将使用的画质选项。
            InitQualitySettings();
            // 声音配置:根据用户配置数据,设置即将使用的声音选项。
            InitSoundSettings();
            // 默认字典:加载默认字典文件 Assets/GameMain/Configs/DefaultDictionary.xml。
            // 此字典文件记录了资源更新前使用的各种语言的字符串,会随 App 一起发布,故不可更新。
            //GameEntry.BuiltinData.InitDefaultDictionary();
        }

切换流程直接调用ChangeState()方法,我们项目在launch之后会进到一个热更流程,检查版本更新,进行资源热更,然后在进入到load流程开始正式的load资源,然后在进入到main流程和game流程等开始游戏。在load流程中有一个坑,在编辑器模式下直接调PreloadResources()方法就可以了,但是打出真机包这里要改为GameEntry.Resource.InitResources(OnInitResourcesComplete)然后在OnInitResourcesComplete的回调中去调PreloadResources().

4.这个框架很多数据是直接采用配表的方式,在load的DatatableNames中写入配好的表的名称,在编辑器有一个GenerateDataTables可以直接将表数据转化成vs的数据格式,可以自己定义好数据类接收这些数据,例如我们项目的部分角色数据按如下方式获取:

public RoleData(int entityId, int typeId) : base(entityId, typeId)
        {
            IDataTable dtRole = GameEntry.DataTable.GetDataTable();
            DRRole dtRoleData = dtRole.GetDataRow(typeId);
            if (dtRoleData == null)
            {
                return;
            }
            assetPath = dtRoleData.AssetPath;
            MaxHp = dtRoleData.MaxHP;
            currentHp = dtRoleData.MaxHP;
            Defence = dtRoleData.Defense;
            lockDistance = dtRoleData.LockDistane;
            MoveSpeed = dtRoleData.MoveSpeed;
            m_RotateSpeed = dtRoleData.RotateSpeed;
         }

  二.框架组件

框架组件的调用比较方便,因为在GameEntry中已经初始化了组件,并且组件是一直存在项目中的,调用组件只需要GameEntry点出组件名称就可以调用组件的方法了。框架组件这里我就按在我们项目中的使用顺序介绍一些比较重要的吧。

1.UI组件:GameFramework使用教程_第3张图片这里需要关注的一般就是UIGroups,根据自己的项目设置好UI分层。我们项目暂时是分的4层,最底层是放类似血条UI等,中间层放一些选择界面,顶层放摇杆界面,属性介绍界面等;最顶层算是弹出框之类的。指定好depth就ok了。然后在InstanceRoot中指定Canvas,配好表以后就可以使用了。打开面板调用的是OpenUIForm传入配好的id就可以打开面板了,关于如何读取表中数据,以及如何去进行显示可以查看

public static int? OpenUIForm(this UIComponent uiComponent, UIFormId uiFormId, object  userData = null)
        {
            return uiComponent.OpenUIForm((int)uiFormId, userData);
        }
        public static int? OpenUIForm(this UIComponent uiComponent, int uiFormId, object  userData = null)
        {
            IDataTable dtUIForm = GameEntry.DataTable.GetDataTable();
            DRUIForm drUIForm = dtUIForm.GetDataRow(uiFormId);
            if (drUIForm == null)
            {
                Log.Warning("Can not load UI form '{0}' from data table.",  uiFormId.ToString());
                return null;
            }
            string assetName = AssetUtility.GetUIFormAsset(drUIForm.AssetName);
            if (!drUIForm.AllowMultiInstance)
            {
                if (uiComponent.IsLoadingUIForm(assetName))
                {
                    return null;
                }
                if (uiComponent.HasUIForm(assetName))
                {
                    return null;
                }
            }
            return uiComponent.OpenUIForm(assetName, drUIForm.UIGroupName,  Constant.AssetPriority.UIFormAsset, drUIForm.PauseCoveredUIForm, userData);
        }

最后跳进OpenUIForm中查看可以在UIManager中找到打开界面的具体操作:

/// 
        /// 打开界面。
        /// 
        /// 界面资源名称。
        /// 界面组名称。
        /// 加载界面资源的优先级。
        /// 是否暂停被覆盖的界面。
        /// 用户自定义数据。
        /// 界面的序列编号。
        public int OpenUIForm(string uiFormAssetName, string uiGroupName, int priority,  bool pauseCoveredUIForm, object userData)
        {
            if (m_ResourceManager == null)
            {
                throw new GameFrameworkException("You must set resource manager first.");
            }
            if (m_UIFormHelper == null)
            {
                throw new GameFrameworkException("You must set UI form helper first.");
            }
            if (string.IsNullOrEmpty(uiFormAssetName))
            {
                throw new GameFrameworkException("UI form asset name is invalid.");
            }
            if (string.IsNullOrEmpty(uiGroupName))
            {
                throw new GameFrameworkException("UI group name is invalid.");
            }
            UIGroup uiGroup = (UIGroup)GetUIGroup(uiGroupName);
            if (uiGroup == null)
            {
                throw new GameFrameworkException(Utility.Text.Format("UI group '{0}' is  not exist.", uiGroupName));
            }
            int serialId = ++m_Serial;
            UIFormInstanceObject uiFormInstanceObject =  m_InstancePool.Spawn(uiFormAssetName);
            if (uiFormInstanceObject == null)
            {
                m_UIFormsBeingLoaded.Add(serialId, uiFormAssetName);
                m_ResourceManager.LoadAsset(uiFormAssetName, priority,  m_LoadAssetCallbacks, OpenUIFormInfo.Create(serialId, uiGroup, pauseCoveredUIForm,  userData));
            }
            else
            {
                InternalOpenUIForm(serialId, uiFormAssetName, uiGroup,  uiFormInstanceObject.Target, pauseCoveredUIForm, false, 0f, userData);
            }
            return serialId;
        }

因为这个项目暂停的比较早,UI界面这一块其实基本是还没开始做的,可能还有很多可以学习的地方,以后涉及到再慢慢总结。

2.实体组件:EntityGameFramework使用教程_第4张图片同样是先指定实体组,一般根据类型指定来设置实体组吧,相同类型放一个组,每一个组还可以设置对象池的个数等等,一般使用默认的就好。同样在EntityExtension中写对应的show方法,传入对应的数据类就行了。数据类中可以指定实体的出生位置。类似于这样调用:

 

/// 
        /// 出场特效
        /// 
        /// 特效的序号
        /// 特效的位置
        /// 挂载的父实体的Id
        protected void ApperanceEffect(int index,Vector3 bornPos,int ownerId)
        {
            EffectData effectData = new EffectData(GameEntry.Entity.GenerateSerialId(),  Data.SkillModel.EffectIdArray[index], ownerId)
            {
                Position = bornPos
            };
            GameEntry.Entity.ShowEffect(effectData);
        }

实体都要继承EntityBase,所以每个实体都是有OnInit,update,OnHide方法的,可以很好地执行实体的逻辑。实体这里有一个比较好用的方法OnAttachTo可以实现把当前实体绑定到其他实体的某个节点上,然后当前实体会跟着父实体移动和旋转。

在加载多个实体的时候出现加载非常慢的问题,这个是对框架的资源加载方式不熟练导致的,后面会专门写一篇记录自己对这个框架的资源加载的理解。

3.FSM有限状态机组件

我们项目使用了框架的有限状态机去做主角的状态切换,并跟动画状态机结合进行角色的动画播放。首先是写了一个状态基类继承FsmState需要指定有限状态机持有者类型,我这边给的是角色实体基类。然后就是根据需求创建角色的其他状态,Idle,Attack,Die,Run,Skill状态。在角色实体基类里面需要初始化状态机。

public GameFramework.Fsm.IFsm RoleFsm;           //当前角色的状态机
private void InitRoleFsm()
        {
            FsmState[] roleStates = new FsmState[]
         {
                new RoleIdleState(),
                new RoleRunState(),
                new RoleAttackState(),
                new RoleDieState(),
                new RoleSkillState(),
         };
            //创建状态机
            RoleFsm = GameEntry.Fsm.CreateFsm(this, roleStates);
            //启动状态机
            RoleFsm.Start();
        }

把状态注册进状态机然后指定初始状态启动状态机,就可以正常执行逻辑了,非常方便。

基本上掌握了框架流程组件,配表读表,UI组件,实体组件,可以正常开发了,但是框架很多东西还是要自己深入去看去了解,自己其实到现在也没有深入理解这个框架,很多复杂的东西东西框架已经帮我们做好了,但是自己不去理解的话还是很容易掉坑里。

暂时写到这吧,后面会在补充框架的使用和项目完成其他的功能,框架的资源加载,声音控制器,Easytouch人物移动控制,简单事件系统,技能系统,buff系统,背包系统,血条显示,渲染合批,常用工具类,性能分析工具等,这些都会分篇来回顾总结。

 

 

 

 

 

 

 

你可能感兴趣的:(项目笔记)