前言
流程是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给它加一个锁变量,比如当点击开始时,去解锁流程锁跳转,之后再把流程锁关上即可,这个扩展功能将在流程扩展篇里实现,这里就不和各位深入探索了。
本文深入解析Gameframework游戏开发框架中的流程模块,阐述其基于有限状态机的原理与使用方法,包括流程基类ProcedureBase的继承与重写,以及具体流程如启动流程ProcedureLaunch的实例分析。

1920

被折叠的 条评论
为什么被折叠?



