Gameframework(Procedure初探篇)

本文深入解析Gameframework游戏开发框架中的流程模块,阐述其基于有限状态机的原理与使用方法,包括流程基类ProcedureBase的继承与重写,以及具体流程如启动流程ProcedureLaunch的实例分析。

前言

流程是Gameframework里的一种概念,刚刚使用这个框架时可能比较晕,流程其实是继承了有限状态机类,为游戏提供状态切换的模块。

1.流程的原理 

建议诸位如果没有看过状态模式,先去看看状态模式到底是什么东西?具体的传送门如下:

https://blog.csdn.net/m0_37920739/article/details/104525017

上面传送门写的有限动作状态机是不可重复利用的,只是介绍使用状态模式如何模拟出Unity的动画有限状态机,而GameFramework作者设计的有限状态机可复用的,让我们来看一下流程基类继承有限状态机实现了什么,具体代码如下:

//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2020 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------

using GameFramework.Fsm;
using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedureManager>;

namespace GameFramework.Procedure
{
    /// <summary>
    /// 流程基类。
    /// </summary>
    public abstract class ProcedureBase : FsmState<IProcedureManager>
    {
        /// <summary>
        /// 状态初始化时调用。
        /// </summary>
        /// <param name="procedureOwner">流程持有者。</param>
        protected internal override void OnInit(ProcedureOwner procedureOwner)
        {
            base.OnInit(procedureOwner);
        }

        /// <summary>
        /// 进入状态时调用。
        /// </summary>
        /// <param name="procedureOwner">流程持有者。</param>
        protected internal override void OnEnter(ProcedureOwner procedureOwner)
        {
            base.OnEnter(procedureOwner);
        }

        /// <summary>
        /// 状态轮询时调用。
        /// </summary>
        /// <param name="procedureOwner">流程持有者。</param>
        /// <param name="elapseSeconds">逻辑流逝时间,以秒为单位。</param>
        /// <param name="realElapseSeconds">真实流逝时间,以秒为单位。</param>
        protected internal override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
        }

        /// <summary>
        /// 离开状态时调用。
        /// </summary>
        /// <param name="procedureOwner">流程持有者。</param>
        /// <param name="isShutdown">是否是关闭状态机时触发。</param>
        protected internal override void OnLeave(ProcedureOwner procedureOwner, bool isShutdown)
        {
            base.OnLeave(procedureOwner, isShutdown);
        }

        /// <summary>
        /// 状态销毁时调用。
        /// </summary>
        /// <param name="procedureOwner">流程持有者。</param>
        protected internal override void OnDestroy(ProcedureOwner procedureOwner)
        {
            base.OnDestroy(procedureOwner);
        }
    }
}

可以看到流程基类继承了有限状态机,并且把泛型设置成IProcedureManager,ProcedureManager和上面链接里的ActionPlayer类是差不多的,只不过多了一些参数获取和功能函数,比如是否存在状态、获取流程、初始化流程管理器等等。然后流程基类ProcedureBase相当于上面链接里的IAction接口,所以要使用流程模块时,需要继承ProcedureBase,如果需要初始化就是重写OnInit函数,流程开启时重写OnEnter函数,按照以上代码注释去重写某些函数即可,最后就是状态切换是需要重写OnUpdate里,各位不用担心还没有OnInit、OnEnter就先执行OnUpdate进行状态切换了。函数执行顺序是OnInit、OnEnter、OnUpdate,OnUpdate是不断循环判断是否可以进行跳转的。

2.流程模块的具体使用

作者给出的游戏Demo工程里,一共定义了7个流程,分别是启动流程、菜单流程、主场景流程、预制体加载流程、Splash流程(防止资源检查和预制体加载流程过长导致黑白或无反应)、场景切换流程、资源检查流程,具体图片如下:

这些脚本都是继承于ProcedureBase类,然后流程组件(ProcedureComponent)即可识别出定义了那些流程,诸位可能还是比较奇怪,这么多流程先启动那个,如果流程顺序乱了怎么办?看一下ProcedureComponent脚本就可知道原由了,具体图片如下:

继承流程基类的流程全部在这里了,然后通过设置Entrance Procedure属性来设置首先启动的流程,可以尝试一下把StarForce工程的启动流程直接换成ProcedureMain会不会出现问题,接下来看一下启动流程到底做了什么,具体代码如下:

//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2020 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------

using GameFramework.Localization;
using System;
using UnityEngine;
using UnityGameFramework.Runtime;
using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedureManager>;

namespace StarForce
{
    public class ProcedureLaunch : ProcedureBase
    {
        public override bool UseNativeDialog
        {
            get
            {
                return true;
            }
        }

        protected 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();
        }

        protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);

            ChangeState<ProcedureSplash>(procedureOwner);
        }

        private void InitLanguageSettings()
        {
            if (GameEntry.Base.EditorResourceMode && GameEntry.Base.EditorLanguage != Language.Unspecified)
            {
                // 编辑器资源模式直接使用 Inspector 上设置的语言
                return;
            }

            Language language = GameEntry.Localization.Language;
            string languageString = GameEntry.Setting.GetString(Constant.Setting.Language);
            if (!string.IsNullOrEmpty(languageString))
            {
                try
                {
                    language = (Language)Enum.Parse(typeof(Language), languageString);
                }
                catch
                {
                }
            }

            if (language != Language.English
                && language != Language.ChineseSimplified
                && language != Language.ChineseTraditional
                && language != Language.Korean)
            {
                // 若是暂不支持的语言,则使用英语
                language = Language.English;

                GameEntry.Setting.SetString(Constant.Setting.Language, language.ToString());
                GameEntry.Setting.Save();
            }

            GameEntry.Localization.Language = language;

            Log.Info("Init language settings complete, current language is '{0}'.", language.ToString());
        }

        private void InitCurrentVariant()
        {
            if (GameEntry.Base.EditorResourceMode)
            {
                // 编辑器资源模式不使用 AssetBundle,也就没有变体了
                return;
            }

            string currentVariant = null;
            switch (GameEntry.Localization.Language)
            {
                case Language.English:
                    currentVariant = "en-us";
                    break;

                case Language.ChineseSimplified:
                    currentVariant = "zh-cn";
                    break;

                case Language.ChineseTraditional:
                    currentVariant = "zh-tw";
                    break;

                case Language.Korean:
                    currentVariant = "ko-kr";
                    break;

                default:
                    currentVariant = "zh-cn";
                    break;
            }

            GameEntry.Resource.SetCurrentVariant(currentVariant);

            Log.Info("Init current variant complete.");
        }

        private void InitQualitySettings()
        {
            QualityLevelType defaultQuality = QualityLevelType.Fantastic;
            int qualityLevel = GameEntry.Setting.GetInt(Constant.Setting.QualityLevel, (int)defaultQuality);
            QualitySettings.SetQualityLevel(qualityLevel, true);

            Log.Info("Init quality settings complete.");
        }

        private void InitSoundSettings()
        {
            GameEntry.Sound.Mute("Music", GameEntry.Setting.GetBool(Constant.Setting.MusicMuted, false));
            GameEntry.Sound.SetVolume("Music", GameEntry.Setting.GetFloat(Constant.Setting.MusicVolume, 0.3f));
            GameEntry.Sound.Mute("Sound", GameEntry.Setting.GetBool(Constant.Setting.SoundMuted, false));
            GameEntry.Sound.SetVolume("Sound", GameEntry.Setting.GetFloat(Constant.Setting.SoundVolume, 1f));
            GameEntry.Sound.Mute("UISound", GameEntry.Setting.GetBool(Constant.Setting.UISoundMuted, false));
            GameEntry.Sound.SetVolume("UISound", GameEntry.Setting.GetFloat(Constant.Setting.UISoundVolume, 1f));

            Log.Info("Init sound settings complete.");
        }
    }
}

可以看到启动流程在OnEnter函数里完成了配置相关的操作,然后OnUpdate里直接跳转ProcedureSplash流程,Gameframework的流程使用就大概如此,但是各位还是好奇,到游戏主界面后也没有看见直接跳转啊!因为有变量去判断是否要跳转到游戏场景的流程,具体ProcedureMenu里OnUpdate代码如下:

        protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
        {
            base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);

            if (m_StartGame)
            {
                procedureOwner.SetData<VarInt>(Constant.ProcedureData.NextSceneId, GameEntry.Config.GetInt("Scene.Main"));
                procedureOwner.SetData<VarInt>(Constant.ProcedureData.GameMode, (int)GameMode.Survival);
                ChangeState<ProcedureChangeScene>(procedureOwner);
            }
        }

通过判断m_StartGame去判断是否可以跳转(点击开始时m_StartGame会变成True),其实我们可以提出新概念就是流程锁,不必每个流程去定义类似m_StartGame的变量,直接在ProcedureManager给它加一个锁变量,比如当点击开始时,去解锁流程锁跳转,之后再把流程锁关上即可,这个扩展功能将在流程扩展篇里实现,这里就不和各位深入探索了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值