笨木头  2018-02-27 07:42     Unity3D,Game Framework     阅读(16956)     评论(18)
转载请注明,原文地址: http://www.benmutou.com/archives/2507
文章来源:笨木头与游戏开发
本文 Game Framework 版本:3.1.0

本文 Unity3D 版本:2017.3

更多GF教程和实例:https://github.com/mutouzdl/gameframework_demo.git

 
转载请注明,原文地址:http://www.benmutou.com/archives/2507 文章来源:笨木头与游戏开发
上一篇我们聊得不是很愉快,我觉得我们不是很适合个屁啊!都说些什么呢。

上一篇我们只是发现了框架的作者是怎么让游戏进入到游戏主场景的,真正重要的是游戏主场景里都做了些什么事情,这一篇,我们就来瞎猜ProcedureMain里都做了些什么吧。(旁白:果然从一开始就是骗人的啊,一直都瞎猜的啊!)

1.OnInit

ProcedureMain也是一个流程,自然也就按照流程的生命周期来研究它了,首先是OnInit,这个就算瞎猜都知道,它是在OnEnter之前执行的,我非常肯定….

后来我还是研究了一下,发现,OnInit确实是在OnEnter之前调用的,而且是非常前,在流程初始化时就调用(此时还没切换到Main流程)。如果没看错的话,是这么个逻辑,以后再深入研究。
protected override void OnInit(ProcedureOwner procedureOwner)
{
        Debug.LogError("OnInit");
        base.OnInit(procedureOwner);

        m_Games.Add(GameMode.Survival, new SurvivalGame()); }
 

OnInit里只做了一件事情——添加游戏类型对应的处理类。

 

m_Games只是一个字典对象,用来存放不同游戏类型对应的处理类(比如生存、无限、关卡模式)。

这里不是非得要这个m_Games对象的,只是StarForce这个项目这么做了,与Game Framework框架本身没有什么关系。

由于StartForce只有生存模式,所以这里只添加了一种类型。

2.OnEnter

最重要的是OnEnter函数:
protected override void OnEnter(ProcedureOwner procedureOwner)
{
    Debug.LogError("OnEnter");
    base.OnEnter(procedureOwner);

// 控制是否返回菜单的标记 m_GotoMenu = false;

// 获取游戏类型(GameMode只是一个枚举而已) GameMode gameMode = (GameMode)procedureOwner.GetData<VarInt>(Constant.ProcedureData.GameMode).Value;

// 获取游戏类型对应的处理器(暂且这么称呼吧) m_CurrentGame = m_Games[gameMode];

// 初始化游戏 m_CurrentGame.Initialize(); }
 

OnEnter其实也很简单,只做了一件事情——初始化游戏。

但是,StarForce并不是随意写的一个demo,它做了一些简单的封装(比如游戏模式)。

这里需要先获取游戏模式对应的处理类,然后再调用处理类进行初始化操作。

这里的操作并不是固定的套路,大家完全可以按照自己的方式初始化游戏。

可能大家注意到了一个VarInt类型,它其实就是int类型(最终),但是框架作者为了避免一些bug,在某些情况下需要用到封装后的类型。具体请参考:http://gameframework.cn/archives/227

3.游戏逻辑(GameBase

上一步我们已经看到了,在OnInit的时候会获取SurvivalGame类对游戏进行初始化。

如果大家有认真听我讲,并按我说的去做的话,应该已经发现了,SurvivalGame是继承了GameBase的(旁白:完全没有讲!压根没提到过!别瞎忽悠。)

所以我们要先来看看GameBase,它是StarForce的一个类,并不是框架自带的,所以,GameBase里做的所有事情,大家都可以按照自己的方式来实现,不要被框住了。

GameBase的函数有好几个,其他都不重要,我只和大家说一下初始化函数:
public virtual void Initialize()
{
        // 订阅显示实体的消息
    GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
    GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);

SceneBackground = Object.FindObjectOfType<ScrollableBackground>(); if (SceneBackground == null) { Log.Warning("Can not find scene background."); return; }

SceneBackground.VisibleBoundary.gameObject.GetOrAddComponent<HideByBoundary>();

// 重点!显示玩家的飞机实体,这里是做了几层封装的 GameEntry.Entity.ShowMyAircraft(new MyAircraftData(GameEntry.Entity.GenerateSerialId(), 10000) { Name = "My Aircraft", Position = Vector3.zero, });

GameOver = false; m_MyAircraft = null; }
 

于初始化函数,我只想说一个——它是如何加载玩家的飞机的?

 

代码中我已经做了注释,加载飞机是调用了ShowMyAircraft函数,这也不是框架的函数,而是StarForce的函数。

一层层跟进后,大家应该能找到,该函数最终调用的是EntityExtension.cs里的ShowEntity函数:
private static void ShowEntity(this EntityComponent entityComponent, Type logicType, string entityGroup, EntityData data)
{
        if (data == null)
        {
                Log.Warning("Data is invalid.");
                return;
        }

        IDataTable<DREntity> dtEntity = GameEntry.DataTable.GetDataTable<DREntity>();         DREntity drEntity = dtEntity.GetDataRow(data.TypeId);         if (drEntity == null)         {                 Log.Warning("Can not load entity id '{0}' from data table.", data.TypeId.ToString());                 return;         }

        entityComponent.ShowEntity(data.Id, logicType, AssetUtility.GetEntityAsset(drEntity.AssetName), entityGroup, data); }
 

重点是GetDataRow和ShowEntity两个函数。

 

这里大致是做了这么两件事情:

a.通过玩家飞机的ID读取配置文件

b.根据配置文件的配置加载实体

至于配置文件是如何加载的,这里不深入研究,但是我们可以看看配置文件长什么样的(Assets\GameMain\DataTables目录):



此时再来回忆一下GameBase的初始化函数:



是不是想起了什么神奇的事情?(旁白:完全没有)

我们再来理一遍,整个玩家飞机实体的创建过程:

a.GameBase里的初始化函数,调用了ShowMyAircraft函数,并且参数是一个MyAircraftData类型,指定了配置文件id(typeID:10000)

b.根据typeID从Entity.txt配置文件里读取数据

c.根据读取的配置数据调用框架的ShowEntity函数加载实体

d.至于实体是如何被加载的,这里就暂不深入了

这是一个很简单的流程,大家多看看代码,就能理解了。

如果看不懂的话可能是你还没有到达需要学习框架的程度,这种情况的话,可以先去多写几个游戏。

4.SurvivalGame处理类

GameBase只是基础类,它是所有游戏模式的基础,因为所有游戏模式都需要创建玩家飞机实体,所以它才做了这件事情。

我们要研究的是生存模式下游戏逻辑是如何处理的,所以,我们现在来看SurvivalGame类(继承了GameBase)。

SurvivalGame很简单,只有一个Update函数:
public override void Update(float elapseSeconds, float realElapseSeconds)
{
        base.Update(elapseSeconds, realElapseSeconds);

        m_ElapseSeconds += elapseSeconds;

        // 每隔1秒创建一个怪物         if (m_ElapseSeconds >= 1f)         {                 m_ElapseSeconds = 0f;                 IDataTable<DRAsteroid> dtAsteroid = GameEntry.DataTable.GetDataTable<DRAsteroid>();

                // 随机的x、z坐标                 float randomPositionX = SceneBackground.EnemySpawnBoundary.bounds.min.x + SceneBackground.EnemySpawnBoundary.bounds.size.x * (float)Utility.Random.GetRandomDouble();                 float randomPositionZ = SceneBackground.EnemySpawnBoundary.bounds.min.z + SceneBackground.EnemySpawnBoundary.bounds.size.z * (float)Utility.Random.GetRandomDouble();                                  // 加载怪物实体                 GameEntry.Entity.ShowAsteroid(new AsteroidData(GameEntry.Entity.GenerateSerialId(), 60000 + Utility.Random.GetRandom(dtAsteroid.Count))                 {                         Position = new Vector3(randomPositionX, 0f, randomPositionZ),                 });         } }
 

 

SurviValGame只做一件事情——定时创建怪物。

创建怪物的方式很简单,和创建玩家飞机实体是一样的。

每隔1秒钟,获取一个随机坐标,然后调用ShowAsteroid函数创建怪物实体(typeID为60000)。

ShowAsteroid函数我们不需要跟进去就能知道它做了什么事情了(读取配置文件,根据配置加载实体)。

5.唠叨一下

从以上的介绍中,我们大致发现了框架作者的一个秘密——游戏中所有对象(如怪物、玩家)都是从配置文件中加载配置,然后再创建出来。(旁白:是的,框架作者在一开始就说过了,然而你花了一篇的时间来解释

到目前为止,我们还没进入最重要的部分,下一篇,我们就一起来看看玩家飞机的逻辑处理类是怎么和飞机对象绑定的吧。
18 条评论
  • 小学生 2023-07-31 17:02:24

    你这篇教程的点赞咋是负数呢?问题我也没看到能踩啊,哈哈
    0回复
    • 博主 笨木头 2023-08-01 13:49:32

      有个低级bug,有空再修,哈哈
      0回复
  • VitoHu 2023-05-05 15:22:30

    小白完全懵了,这个配置文件txt到底是用Tab间隔开还是用空格,稍微去掉一个空格或者增加一个都会读不出来出问题
    1回复
    • 博主 笨木头 2023-05-25 08:36:50

      没记错的话,是用Tab隔开,我印象中这个分隔符是可以自定义的,你可以传递给它,我个人还是习惯用Csv。
      太久了,我也记不太清
      1回复
  • 吃豆人 2020-05-15 15:49:19

    博主骨子里藏了个吐槽星人ㄟ( ▔, ▔ )ㄏ
    4回复
    • 吃豆人 2020-05-16 14:23:30

      再次感谢分享,经络大通
      0回复
  • AJ 2018-12-14 17:15:01

    看到这里,发现gamescene都是单一游戏内容,最近遇到关卡式的项目,就是一关过完解锁那种,不知道流程该怎么来处理
    0回复
    • 博主 笨木头 2018-12-15 17:06:17

      关卡其实也是进入同一个场景,只不过读取不同关卡的配置,然后生成不同的游戏对象,就变成不同的关卡了。
      你是指这种吗?
      0回复
      • AJ 2018-12-17 10:55:44

        三消一类关卡差异不大的,确实是这样,现在就是每一关差异很大,基本上都需要重写游戏逻辑,但是又类似三消类一样通关,不知有啥思路
        0回复
        • 博主 笨木头 2018-12-17 11:38:59

          我想知道是多大的差异?方便说出你的关卡逻辑吗?
          因为毕竟是同一个游戏,不可能说每一关都是一个新玩法吧?
          0回复
          • AJ 2018-12-17 11:48:16

            还真的就是每一关都是不一样的玩法,是个益智类游戏,第一关是交通的玩法,第二个就是个找东西的玩法,然后第三个可能还是个搭桥梁的玩法,确实挺崩溃的
            0回复
            • 博主 笨木头 2018-12-17 13:38:57

              这个....好像也没什么好办法了
              0回复
            • 123 2022-03-12 23:10:10

              可以搞两个流程哈,一个里面嵌入一个,里面一个管理关卡
              0回复
    • cooaka 2021-05-18 17:00:06

      我也遇到这种项目,请问有什么好的思路吗?
      0回复
  • 千喜 2018-06-09 22:28:44

    在OnEnter的时候会获取SurvivalGame类对游戏进行初始化 应该是在OnInit里面,虽然不是什么大问题~
    0回复
    • 博主 笨木头 2018-06-10 12:58:29

      谢谢,改正过来了
      0回复
  • BINSECT 2018-03-26 18:56:06

    博主说的很好,谢谢分享~~~正在学习UGF
    0回复
    • 博主 笨木头 2018-03-27 08:43:59

      谢谢,共勉~
      0回复
发表评论
粤ICP备16043700号

本博客基于 BlazorAnt Design Blazor 开发