上一篇我们聊得不是很愉快,我觉得我们不是很适合…个屁啊!都说些什么呢。
上一篇我们只是发现了框架的作者是怎么让游戏进入到游戏主场景的,真正重要的是游戏主场景里都做了些什么事情,这一篇,我们就来瞎猜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.唠叨一下
从以上的介绍中,我们大致发现了框架作者的一个秘密——游戏中所有对象(如怪物、玩家)都是从配置文件中加载配置,然后再创建出来。(旁白:是的,框架作者在一开始就说过了,然而你花了一篇的时间来解释)
到目前为止,我们还没进入最重要的部分,下一篇,我们就一起来看看玩家飞机的逻辑处理类是怎么和飞机对象绑定的吧。