我想知道 UEntity 数据表中的编号跟每个实体表自己的编号之间是否需要统一,现在看来好像不需要统一,但是我也不知道是不是因为我还没看到这两个表有交互的时候
哦我才注意到,这个实体表是用来定义资源的啊hhhh
那就不得了了,那就真的是靠 id 对应 资源名称
然后根据这个资源名称到 Assets\GameMain\Entities 中找 perfab
第三人称控制器里面所需要的数据都是直接公开到编辑器里面的
我不知道如果做成数据表合不合适呢
不过先试试吧
前面已经说过了建数据表的流程
假设已经有了 DR+数据表名(某实体类) 类
在 Assets/GameMain/Scripts/Entity/EntityData 中创建 某实体类+Data.cs,它继承另一实体 Data 类
Assets/GameMain/Scripts/Entity/EntityData/某实体类+Data.cs
public 某实体类+Data(int entityId, int typeId)
: base(entityId, typeId, CampType.Neutral)
{
IDataTable<DR+某实体类> dt+某实体类 = GameEntry.DataTable.GetDataTable<DR+某实体类>();
DR+某实体类 dr+某实体类 = dt+某实体类.GetDataRow(TypeId);
在 某实体类+Data
类的构造函数中通过 GameEntry.DataTable.GetDataTable
得到 IDataTable
类的,泛型为DR+某实体类
类的变量1,再通过 IDataTable.GetDataRow
得到 DR+某实体类
的变量2,这个变量2就是相当于把数据表变成了一个结构体,可以使用点运算符得到数据表的成员,用于 某实体类+Data
类的初始化
其中 typeId 传入之后首先会再传入父类的构造函数,一直层层上传,最后上传到 UEntityData 的构造函数,初始化 UEntityData 的 TypeId
最终子类就可以使用这个 TypeId 了,其实跟直接使用传入的 typeId 是一样的
在 Assets\GameMain\Scripts\Entity\EntityLogic 中创建 某实体类.cs,它继承另一实体类
比如飞船继承可被视为目标类
可被视为目标类继承实体类
Assets\GameMain\Scripts\Entity\EntityLogic\某实体类.cs
public class 某实体类名 : 另一实体类名
{
然后定义私有成员 m_某实体类名+Data
这个 m_某实体类名+Data
就是用来为逻辑提供数据的
虽然 rider 建议使用小写字母开头作为私有属性
但是既然 gf 作者都这么写了,那就还是跟着框架来hhh
Assets\GameMain\Scripts\Entity\EntityLogic\某实体类.cs
[SerializeField]
private 某实体类名+Data m_某实体类名+Data = null;
然后在 OnShow()
中使用输入的参数 userData
初始化 m_某实体类名+Data
Assets\GameMain\Scripts\Entity\EntityLogic\某实体类.cs
protected override void OnShow(object userData)
{
base.OnShow(userData);
m_某实体类名+Data = userData as 某实体类名+Data;
if (m_某实体类名+Data == null)
{
Log.Error("某实体类名 data is invalid.");
return;
}
// ...
}
至此,就可以通过 m_某实体类名+Data
使用数据表中配置的数据了
此外,OnUpdate
, OnDead
, GetImpactData
都是自定义的行为了,比如 GetImpactData
用于判断伤害,这个伤害流程是自定义的,自然 GetImpactData
为伤害流程提供什么东西也是自定义的
然后之后要显示这个实体
这个实体不是 GameObject 的 SetActive,而是由框架中有一套基于对象池的逻辑控制的
框架中把这些实体类统称为 Entity,所以我才不太想在 Assets\GameMain\Scripts\Entity 中再创建一个 Entity 类,但是官方示例又是那么写的,所以我改成了 UEntity
之前看了半天也没看懂框架里面的显示实体的逻辑具体是怎么样的,时间又不够,就没研究了
总之能用
Assets/GameMain/Scripts/Definition/Constant/Constant.AssetPriority.cs 中添加 某实体类名+Asset
的优先级
public static partial class Constant
{
public const int 某实体类名+Asset = 优先级;
Assets/GameMain/Scripts/Entity/EntityExtension.cs 中,在 EntityExtension 类中添加 Show+某实体类名
方法
public static void Show+某实体类名(this EntityComponent entityComponent, 某实体类名+Data data)
{
entityComponent.ShowEntity(typeof(某实体类名), "某实体类名", Constant.AssetPriority.某实体类名+Asset, data);
}
接下来就可以使用这个 Show+某实体类名
方法,同时还可以在创建 某实体类名+Data 的时候提供初始化
GameEntry.Entity.Show+某实体类名(new 某实体类名+Data(GameEntry.Entity.GenerateSerialId(), 某实体类的 type)
{
某实体类名+Data 中的属性 = xxx;
}) ;
总结:
GF 创建新实体类并使用的流程:
- 配置
1.1 在 Assets\GameMain\Entities 中创建NewUEntity
类的Perfab
资源,根据一定规律命名,如NewUEntity001
,NewUEntity002
1.2 指定NewUEntity
类的Perfab
资源的id
,如NewUEntity001
,NewUEntity002
的id
为60001
,60002
。打开 Assets/GameMain/DataTables/UEntity.txt,添加<id,Perfab名>
的键值对
1.3 在 Hierarchy - Game Framework - Builtin - UEntity - Entity - Entity Group 中添加 NewUEntity - 创建
DRNewUEntity
类
2.1 使用 Excel 根据 GF 给定格式创建NewUEntity
类的数据表(数据表项注意删掉空格等)保存为 UTF - 8 编码的 txt 文件到 Assets/GameMain/DataTables,文件名为 NewUEntity.txt
2.2 在 ProcedurePreload 中的 DataTableNames 中添加"NewUEntity"
2.3.确保工程代码成功编译,点击菜单栏中的生成工具,将会自动生成二进制文件的数据表和DRNewUEntity
类 - 创建
NewUEntityData
类,NewUEntity
类
3.1 在 Assets/GameMain/Scripts/Entity/EntityData 中创建 NewUEntityData.cs,构造函数中使用IDataTable<DRNewUEntity> dtNewUEntity = GameEntry.DataTable.GetDataTable<DRNewUEntity>(); DRNewUEntity drNewUEntity = dtNewUEntity.GetDataRow(TypeId);
得到数据表行类的实例,使用这个实例的属性初始化NewUEntityData
的属性
3.2 在 Assets/GameMain/Scripts/Entity/EntityLogic 中创建 NewUEntity.cs,定义私有成员m_NewUEntityData
,在OnShow()
中使用输入的参数userData
初始化m_NewUEntityData
3.3 在 Assets/GameMain/Scripts/Definition/Constant/Constant.AssetPriority.cs 中添加NewUEntityAsset
的优先级
3.4 在 Assets/GameMain/Scripts/Entity/EntityExtension.cs 中添加ShowNewUEntity
方法
(NewUEntity 指代期望创建的实体类的名称)
虽然还有场景和音乐等等流程
让我试试这个实体流程对不对先
唉……其实我觉得这些参数用表配反而麻烦
不过既然要用框架还是要守规矩
呃……等到我开始写的时候感觉不对了,这个……控制器真不应该是用表的
因为他不是一个实体
实体是用来增删改查的,因此一个实体都有一个对象池,但控制器不是
我倒感觉这个第三人称控制器像 component,比如血条
HPBarComponent 是总控 继承 GameFrameworkComponent
HPBarItemObject 是在对象池中的对象 继承 ObjectBase
HPBarItem 是对象的内容 是主体逻辑 继承 MonoBehaviour
但是第三人称控制器不需要对象池
哦我又觉得他有点像 weapon
此时大有可为啊我觉得
当时我第一次看飞机的代码的时候我就感觉奇怪,不知道为什么看不到点击射击的代码
然后之后才看到他这个是把两个部分分开,飞船和武器之间没有直接的父子关系,它们之间的关系是通过 Data 类的建立关系而建立的。就是说,基于飞船类的数据创建武器类,武器类就可以通过数据知道自己的父级
或许我也要使用这个 Data 的建立关系确定第三人称控制器的父级
但是我又感觉如果真的要用 Data+Logic 的方式做第三人称控制器的话
感觉这一层 Data 是白封装了
那么现在就是有三种方式封装游戏逻辑
一种是 Data+Logic
一种是类似 HPBarComponent 的自定义组件
一种是类似 BuiltinComponent 的自定义组件
做 Data+Logic,我觉得封装不必要
做 HPBarComponent 类,我觉得不需要对象池
做 BuiltinComponent 类,我觉得不需要接口,还少了生命周期
嗯我觉得还是硬塞到 entityData+entityLogic
或者说做一个新的类型,那就是 Data+Logic 的部分中只有 Logic 没有 Data
因为首先这个人物是一个实体,他是要参与到实体之间的关系里面的,比如伤害啊啥的
那么既然主角是实体,那么主角的生命周期就必然是由实体基类控制,因为不可能同时出现主角的生命周期和第三人称控制器的周期
其实本来要是要求很低的话,两个生命周期也不是不行,但是我要是要求 act 的话,我的实体的逻辑和我的行为是息息相关的,比如我受到了攻击就要控制移动速度,动画等等,这样,两个生命周期感觉就有点麻烦,可能会有多个跨类的调用
或者说更重要的是时序的问题,假设某一帧,控制器生命周期到了 Update,主角移动了这一帧,然后相差半帧之后,主角逻辑的生命周期到了 Update,主角被禁锢了,但是主角在半帧之前已经移动了,就好像被禁锢了但是仍然移动了一样
或许我的执念始终在于,我认为这个第三人称控制器是一个可以被独立出来的完整组件
这可能只是我的固有印象
因为一旦技能涉及到移动等,那么就必然要影响到控制器,所以我必然要把控制器拆开
我应该是,从输入得到运动信息输入之后,对运动信息进行一套处理,然后才能把运动信息传回给控制器的负责运动的部分
这个“对运动信息进行一套处理”就是技能影响的部分
算了,先做吧
1.1 在 Assets\GameMain\Entities 中创建 NewUEntity
类的 Perfab
资源,根据一定规律命名,如 NewUEntity001
, NewUEntity002
我是直接把 StartAsset 里面的素材搬过来了
1.2 指定 NewUEntity
类的 Perfab
资源的 id
,如NewUEntity001
, NewUEntity002
的 id
为 60001
, 60002
。打开 Assets/GameMain/DataTables/UEntity.txt,添加 <id,Perfab名>
的键值对
# | 实体表 | ||
---|---|---|---|
# | Id | AssetName | |
# | int | string | |
# | 实体编号 | 策划备注 | 资源名称 |
10000 | 玩家 | PlayerArmature |
1.3 在 Hierarchy - Game Framework - Builtin - UEntity - Entity - Entity Group 中添加 NewUEntity
2.1 使用 Excel 根据 GF 给定格式创建 NewUEntity
类的数据表(数据表项注意删掉空格等)保存为 UTF - 8 编码的 txt 文件到 Assets/GameMain/DataTables,文件名为 NewUEntity.txt
# | 主角表 | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
# | Id | MoveSpeed | RotationSmoothTime | SpeedChangeRate | JumpHeight | Gravity | JumpTimeout | FallTimeout | GroundedOffset | GroundedRadius | GroundLayers | TopClamp | BottomClamp | |
# | int | float | float | float | float | float | float | float | float | float | int | float | float | |
# | 主角编号 | 策划备注 | 移动速度 | 转身速度 | 运动加速度 | 跳跃高度 | 重力系数 | 两次跳跃之间的间隔 | 起跳到下落之间的间隔 | 落地球形碰撞检测中心点的竖向偏移量 | 落地球形碰撞检测的半径 | 落地球形碰撞检测的层级 | 摄像机最大俯仰角 | 摄像机最小俯仰角 |
10000 | 主角 | 2 | 0.12 | 10 | 1.2 | -15 | 0.5 | 0.15 | -0.14 | 0.28 | 1 | 70 | -30 |
2.2 在 ProcedurePreload 中的 DataTableNames 中添加 "NewUEntity"
public static readonly string[] DataTableNames = new string[]
{
"UEntity",
"PlayerArmature",
2.3.确保工程代码成功编译,点击菜单栏中的生成工具,将会自动生成二进制文件的数据表和 DRNewUEntity
类
ok,数据表没问题就没问题
3.1 在 Assets/GameMain/Scripts/Entity/EntityData 中创建 NewUEntityData.cs,构造函数中使用 IDataTable<DRNewUEntity> dtNewUEntity = GameEntry.DataTable.GetDataTable<DRNewUEntity>(); DRNewUEntity drNewUEntity = dtNewUEntity.GetDataRow(TypeId);
得到数据表行类的实例,使用这个实例的属性初始化 NewUEntityData
的属性
Assets/GameMain/Scripts/Entity/EntityData/PlayerArmatureData.cs
using System;
using UnityEngine;
using GameFramework.DataTable;
namespace OaksMayFall
{
[Serializable]
public class PlayerArmatureData : UEntityData
{
// 阵营
[SerializeField]
private CampType ownerCamp = CampType.Unknown;
[SerializeField]
private float moveSpeed = 0f;
[SerializeField]
private float rotationSmoothTime = 0f;
[SerializeField]
private float speedChangeRate = 0f;
[SerializeField]
private float jumpHeight = 0f;
[SerializeField]
private float gravity = 0f;
[SerializeField]
private float jumpTimeout = 0f;
[SerializeField]
private float fallTimeout = 0f;
[SerializeField]
private float groundedOffset = 0f;
[SerializeField]
private float groundedRadius = 0f;
[SerializeField]
private int groundLayers = 0;
[SerializeField]
private float topClamp = 0f;
[SerializeField]
private float bottomClamp = 0f;
public PlayerArmatureData(int entityId, int typeId, CampType ownerCamp)
: base(entityId, typeId)
{
IDataTable<DRPlayerArmature> dtPlayerArmature = GameEntry.DataTable.GetDataTable<DRPlayerArmature>();
DRPlayerArmature drPlayerArmature = dtPlayerArmature.GetDataRow(TypeId);
this.ownerCamp = ownerCamp;
moveSpeed = drPlayerArmature.MoveSpeed;
rotationSmoothTime = drPlayerArmature.RotationSmoothTime;
speedChangeRate = drPlayerArmature.SpeedChangeRate;
jumpHeight = drPlayerArmature.JumpHeight;
gravity = drPlayerArmature.Gravity;
jumpTimeout = drPlayerArmature.JumpTimeout;
fallTimeout = drPlayerArmature.FallTimeout;
groundedOffset = drPlayerArmature.GroundedOffset;
groundedRadius = drPlayerArmature.GroundedRadius;
groundLayers = drPlayerArmature.GroundLayers;
topClamp = drPlayerArmature.TopClamp;
bottomClamp = drPlayerArmature.BottomClamp;
}
public CampType OwnerCamp => ownerCamp;
public float MoveSpeed => moveSpeed;
public float RotationSmoothTime => rotationSmoothTime;
public float SpeedChangeRate => speedChangeRate;
public float JumpHeight => jumpHeight;
public float Gravity => gravity;
public float JumpTimeout => jumpTimeout;
public float FallTimeout => fallTimeout;
public float GroundedOffset => groundedOffset;
public float GroundedRadius => groundedRadius;
public int GroundLayers => groundLayers;
public float TopClamp => topClamp;
public float BottomClamp => bottomClamp;
}
}
3.2 在 Assets/GameMain/Scripts/Entity/EntityLogic 中创建 NewUEntity.cs,定义私有成员 m_NewUEntity+Data
,在 OnShow()
中使用输入的参数 userData
初始化 m_NewUEntityData
using Cinemachine;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace OaksMayFall
{
/// <summary>
/// 玩家类。
/// </summary>
public class PlayerArmature : UEntity
{
[SerializeField] private PlayerArmatureData playerArmatureData = null;
private bool _isGrounded = true;
// cinemachine
private bool _isCameraFixed = false;
private float _cameraAngleOverride = 0f;
private GameObject _cinemachineCameraTarget = null;
private GameObject _playerFollowCamera = null;
private float _cinemachineTargetYaw = 0f;
private float _cinemachineTargetPitch = 0f;
// player
private float _speed = 0f;
private float _animationBlend = 0f;
private float _inputMagnitude = 0f;
private float _rotationVelocity = 0f;
private float _verticalVelocity = 0f;
private float _terminalVelocity = 53.0f;
// timeout deltatime
private float _jumpTimeoutDelta = 0f;
private float _fallTimeoutDelta = 0f;
// animation IDs
private int _animIDSpeed = 0;
private int _animIDGrounded = 0;
private int _animIDJump = 0;
private int _animIDFreeFall = 0;
private int _animIDMotionSpeed = 0;
private Animator _animator = null;
private CharacterController _controller = null;
private OaksMayFallInputController _input = null;
private GameObject _mainCamera = null;
private SimRigidBodyPush _simRigidBodyPush = null;
private const float Threshold = 0.01f;
private bool _hasAnimator = false;
protected override void OnInit(object userData)
{
base.OnInit(userData);
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
playerArmatureData = userData as PlayerArmatureData;
if (playerArmatureData == null)
{
Log.Error("Bullet data is invalid.");
return;
}
// 获取主摄像机的引用
if (_mainCamera == null)
_mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
// 添加输入控制器
_input = gameObject.AddComponent<OaksMayFallInputController>();
// 添加刚体推力
_simRigidBodyPush = gameObject.AddComponent<SimRigidBodyPush>();
_cinemachineCameraTarget = transform.Find("PlayerCameraRoot").gameObject;
_playerFollowCamera = GameObject.Find("PlayerFollowCamera");
_playerFollowCamera.GetComponent<CinemachineVirtualCamera>().Follow = _cinemachineCameraTarget.transform;
_hasAnimator = TryGetComponent(out _animator);
_controller = GetComponent<CharacterController>();
_input = GetComponent<OaksMayFallInputController>();
// 初始化动画状态机参数
AssignAnimationIDs();
// 重置跳跃计时器
_jumpTimeoutDelta = playerArmatureData.JumpTimeout;
_fallTimeoutDelta = playerArmatureData.FallTimeout;
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
// 设置动画状态机参数
if (_hasAnim