目录
前言:
一、切换到ProcedureMain流程
二、场景布置
三、生成主角
1、EntityLogic
2、EntityData
(1)Id
(2)TypeId
(3)Position
(4)Rotation
成功!
前面已经完成了UI界面切换等功能,但是真正的游戏内容主体部分还没有开始,下面就来实现游戏的主流程。
本篇的内容又用到了GF的一个新模块:Entity。这一次我们主要实现的就是在场景中生成主角。
首先先给“开始”按钮按钮增加一下切换到ProcedureMain的功能。
之前已经写过了,按下开始按钮会设置m_StartGame为true:
//MenuForm.cs
//ProcedureMenu.cs然后就是在菜单流程中的OnUpdate里面检测m_StartGame,如果是true则利用转换流程ProcedureChangeScene到主流程:
protected override void OnUpdate(IFsm procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
if (m_StartGame)
{
//先设置要转换的下一个场景的ID:
procedureOwner.SetData("NextSceneId",GameEntry.Config.GetInt("Scene.Main"));
//然后进行场景切换流程
ChangeState(procedureOwner);
}
}
然后在场景切换流程里面,写好了切换到主流程的逻辑:
#ProcedureChangeScene.cs
protected override void OnUpdate(IFsm procedureOwner, float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
if (!m_IsChangeSceneComplete)
{
return; //还没完成场景切换
}
if (m_ChangeToMenu)
{
ChangeState(procedureOwner); //菜单
}else
{
ChangeState(procedureOwner); //游戏运行主流程
}
}
别忘了加到Build Settings里面:
然后测试试试:
从结果可以看出,场景确实切换了,但是UI并没有关闭, 经过调查是上次自己在测试的时候注释掉了“在菜单流程结束的时候关闭菜单UI界面”的逻辑,恢复后就解决了:
ProcedureMenu.cs
protected override void OnLeave(IFsm procedureOwner, bool isShutdown)
{
base.OnLeave(procedureOwner, isShutdown);
GameEntry.Event.Unsubscribe(OpenUIFormSuccessEventArgs.EventId, OnOpenUIFormSuccess);
Log.Info("退出菜单流程!");
if (m_MenuForm != null)
{
m_MenuForm.Close(isShutdown);
m_MenuForm = null;
}
}
然后就切换到主游戏场景和流程了。
目前场景是空的,因为我们这个是2D游戏所以先在场景上把简单的关卡墙体啥的摆好:
(我直接拿以前的项目的东西来做了,包括一些2D游戏需要的插件的安装这里省去了,因为文字内容重点在GF框架)。
这个关卡的任务很简单,就是主角从左下角出生,然后走到门进去,通关!
之前主角的控制脚本都是直接一把梭的,既然这里用了框架最好就是用状态机复刻一下。并且把逻辑和数据分离(参考StarForce),不过这里为了节约时间就直接用之前做好的预制体,主要是用来展示一下实体的生成方式。
然后预制体:
然后,和UI的使用方法很类似(需要实现一个继承UIFormLogic的类),我们需要为这个实体写一个继承EntityLogic 类的脚本:
然后,我们在游戏流程里加上生成实体的语句。
在此之前,因为所有实体都有一个唯一的ID用来标识,为了方便统一地生成这个ID,我们先写一个扩展方法:
然后我们就可以在主流程里写生成实体的语句:
然后运行,点击开始游戏按钮:
发现人物并没有出现。
其实如果经验够丰富的话,你应该能猜到为啥没有出现。实际上人物实体已经生成了,但是我们并没有指定它的初始位置,所以我们打开框架下的Entity模块可以找到生成的Character:
而它的位置是预制体中设置的位置:
那怎么才能在生成这个玩家Entity的时候指定它所生成的位置呢?
首先,一个比较完整的实体,应该包含逻辑(Logic)和数据(Data)吧。我们刚才写了这个Player的逻辑(Logic)(继承了EntityLogic),是不是还缺少数据部分(EntityData)?
那我们下面就来实现数据部分。
和EntityLogic使用方法不同的是,框架里面没有可以让我们来继承的EntityData类,所以我们需要自己实现自己的EntityData类。
先实现一个抽象类EntityData,让其它实体去继承它。所以我们需要考虑所有实体都共有的属性。这里我们参照StarForce的EntityData的实现:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public abstract class EntityData
{
[SerializeField]
private int m_Id = 0;
[SerializeField]
private int m_TypeId = 0;
[SerializeField]
private Vector3 m_Position = Vector3.zero;
[SerializeField]
private Quaternion m_Rotation = Quaternion.identity;
public EntityData(int entityId, int typeId)
{
m_Id = entityId;
m_TypeId = typeId;
}
///
/// 实体编号。
///
public int Id
{
get
{
return m_Id;
}
}
///
/// 实体类型编号。
///
public int TypeId
{
get
{
return m_TypeId;
}
}
///
/// 实体位置。
///
public Vector3 Position
{
get
{
return m_Position;
}
set
{
m_Position = value;
}
}
///
/// 实体朝向。
///
public Quaternion Rotation
{
get
{
return m_Rotation;
}
set
{
m_Rotation = value;
}
}
}
当然目录结构也要管理好:
下面来解释一下这个EntityData的内容:
里面有四个属性,都是在Unity中生成的实体所共有的特点。
实体编号
用于唯一标识一个实体,所有实体的ID互不相同。
实体类型编号
首先这里需要明白一件事情,根据框架作者的项目可以看出,游戏中所有对象(如怪物、玩家)都是从配置文件(数据表)中加载配置,然后再创建出来。
然后,举个例子,策划设计了史莱姆怪物,然后为它们写数据表Shilaimu.txt。然后史莱姆怪物分好多种类,比如雷史莱姆,草史莱姆,风史莱姆,火史莱姆,岩史莱姆, 冰史莱姆,然后在配置表中,大概是这样:
每一种怪物都有个ID,具体体现就是数据表的第一列的ID,这个就是TypeId。
那TypeId和Id的区别是啥呢?Id是在整个实体框架里面唯一标识一个实体,而TypeId则标识了一个种类。比如场景中可能有两个雷史莱姆,他们的TypeId是一样的,因为它们种类一样,但是他们的Id不一样,因为他们是不同的个体。
位置
每个在场景里的物体一定都有个位置,是一个三维向量。
旋转
每个在场景里的物体一定都有个朝向,是一个四元数。
抽象基类写好了下面就来写Player的PlayerData类。:
目前我们的目的只是把玩家显示到特定位置,所以这里暂且先继承一下写好的抽象基类就行。
然后改一改显示实体的代码,在playerData里面设置了初始位置。
然后运行看看:
(红色报错可以无视,是角色动画部分复制过来的时候没设置好)
emm角色还是不在场景里面。原因是这样的:
虽然我们设置了Player这个实体的数据部分PlayerData的Position信息,但是这个信息只是被实体的playerData携带了,并没有”应用“出来,而要把生成的实体“移动”到我们规定的位置,还需要在实体的EntityLogic部分对其进行实现(Player.cs)。
所以我们在Player类里面的Init()函数里面加上改变实体位置到playerData.Position处的逻辑:
CachedTransform是父类里的一个属性,缓存的是实体的Transform。这里的意思就是在实体初始化的时候设置好Transform的Position为之前在playerData里设置的Position(ShowEntity函数传递进去的playerData最终会到达这里函数的userData参数)
然后运行看看结果:
这次成功将实体加载到了场景里面,鉴于篇幅本篇先到这里为止。接下来要做的工作将围绕继续完成这一关的流程进行。