我想知道 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.1 在 Assets\GameMain\Entities 中创建 NewUEntity 类的 Perfab 资源,根据一定规律命名,如 NewUEntity001, NewUEntity002
    1.2 指定 NewUEntity 类的 Perfab 资源的 id,如NewUEntity001, NewUEntity002id60001, 60002。打开 Assets/GameMain/DataTables/UEntity.txt,添加 <id,Perfab名> 的键值对
    1.3 在 Hierarchy - Game Framework - Builtin - UEntity - Entity - Entity Group 中添加 NewUEntity
  2. 创建 DRNewUEntity
    2.1 使用 Excel 根据 GF 给定格式创建 NewUEntity 类的数据表(数据表项注意删掉空格等)保存为 UTF - 8 编码的 txt 文件到 Assets/GameMain/DataTables,文件名为 NewUEntity.txt
    2.2 在 ProcedurePreload 中的 DataTableNames 中添加 "NewUEntity"
    2.3.确保工程代码成功编译,点击菜单栏中的生成工具,将会自动生成二进制文件的数据表和 DRNewUEntity
  3. 创建 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, NewUEntity002id60001, 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;// cinemachineprivate bool _isCameraFixed = false;private float _cameraAngleOverride = 0f;private GameObject _cinemachineCameraTarget = null;private GameObject _playerFollowCamera = null;private float _cinemachineTargetYaw = 0f;private float _cinemachineTargetPitch = 0f;// playerprivate 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 deltatimeprivate float _jumpTimeoutDelta = 0f;private float _fallTimeoutDelta = 0f;// animation IDsprivate 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 (_hasAnimator)SetAnimatorValue();// 跳跃ThirdPersonControllerUtility.JumpAndGravity(_input, _isGrounded, ref _fallTimeoutDelta, ref _jumpTimeoutDelta,playerArmatureData.FallTimeout, playerArmatureData.JumpTimeout,ref _verticalVelocity, playerArmatureData.JumpHeight, playerArmatureData.Gravity, _terminalVelocity);// 着地_isGrounded = ThirdPersonControllerUtility.IsGrounded(transform, playerArmatureData.GroundedOffset,playerArmatureData.GroundedRadius, playerArmatureData.GroundLayers);// 移动ThirdPersonControllerUtility.Move(_input, _controller, transform, _mainCamera,playerArmatureData.MoveSpeed, playerArmatureData.SpeedChangeRate, ref _speed, _verticalVelocity,ref _rotationVelocity, playerArmatureData.RotationSmoothTime, ref _animationBlend, ref _inputMagnitude);}protected void LateUpdate(){// 摄像机运动ThirdPersonControllerUtility.CameraRotation(_input, _cinemachineCameraTarget, Threshold, _isCameraFixed,ref _cinemachineTargetYaw, ref _cinemachineTargetPitch, playerArmatureData.BottomClamp,playerArmatureData.TopClamp, _cameraAngleOverride);}private void OnDrawGizmosSelected(){Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);if (_isGrounded) Gizmos.color = transparentGreen;else Gizmos.color = transparentRed;// when selected, draw a gizmo in the position of, and matching radius of, the grounded colliderGizmos.DrawSphere(new Vector3(transform.position.x, transform.position.y - playerArmatureData.GroundedOffset, transform.position.z), playerArmatureData.GroundedRadius);}private void AssignAnimationIDs(){_animIDSpeed = Animator.StringToHash("Speed");_animIDGrounded = Animator.StringToHash("Grounded");_animIDJump = Animator.StringToHash("Jump");_animIDFreeFall = Animator.StringToHash("FreeFall");_animIDMotionSpeed = Animator.StringToHash("MotionSpeed");}private void SetAnimatorValue(){// 着地_animator.SetBool(_animIDGrounded, _isGrounded);// 跳跃if (_isGrounded){_animator.SetBool(_animIDJump, false);_animator.SetBool(_animIDFreeFall, false);// Jumpif (_input.jump && _jumpTimeoutDelta <= 0.0f)_animator.SetBool(_animIDJump, true);}// 自由下落else if(_fallTimeoutDelta < 0.0f)_animator.SetBool(_animIDFreeFall, true);// 移动_animator.SetFloat(_animIDSpeed, _animationBlend);_animator.SetFloat(_animIDMotionSpeed, _inputMagnitude);}}
}

Assets/GameMain/Scripts/Utility/ThirdPersonControllerUtility.cs

using UnityEngine;namespace OaksMayFall
{public static class ThirdPersonControllerUtility{public static bool IsGrounded(Transform selfTransform, float groundedOffset, float groundedRadius, int groundLayers){Vector3 spherePosition = new Vector3(selfTransform.position.x, selfTransform.position.y - groundedOffset, selfTransform.position.z);bool isGrounded = Physics.CheckSphere(spherePosition, groundedRadius, groundLayers, QueryTriggerInteraction.Ignore);return isGrounded;}public static void JumpAndGravity(OaksMayFallInputController input, bool grounded, ref float fallTimeoutDelta, ref float jumpTimeoutDelta, float fallTimeout, float jumpTimeout,ref float verticalVelocity, float jumpHeight, float gravity, float terminalVelocity){if (grounded){// reset the fall timeout timerfallTimeoutDelta = fallTimeout;// stop our velocity dropping infinitely when groundedif (verticalVelocity < 0.0f)verticalVelocity = -2f;// Jumpif (input.jump && jumpTimeoutDelta <= 0.0f)// the square root of H * -2 * G = how much velocity needed to reach desired heightverticalVelocity = Mathf.Sqrt(jumpHeight * -2f * gravity);// jump timeoutif (jumpTimeoutDelta >= 0.0f)jumpTimeoutDelta -= Time.deltaTime;}else{// reset the jump timeout timerjumpTimeoutDelta = jumpTimeout;// fall timeoutif (fallTimeoutDelta >= 0.0f)fallTimeoutDelta -= Time.deltaTime;// if we are not grounded, do not jumpinput.jump = false;}// apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)if (verticalVelocity < terminalVelocity)verticalVelocity += gravity * Time.deltaTime;}public static void Move(OaksMayFallInputController input, CharacterController controller, Transform selfTransform, GameObject mainCamera,float moveSpeed, float speedChangeRate, ref float speed, float verticalVelocity, ref float rotationVelocity, float rotationSmoothTime,ref float animationBlend, ref float inputMagnitude){// set target speed based on move speed, sprint speed and if sprint is pressed// float targetSpeed = input.sprint ? SprintSpeed : MoveSpeed;float targetSpeed = moveSpeed;// 目标旋转float targetRotation = 0.0f;// a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon// note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude// if there is no input, set the target speed to 0if (input.move == Vector2.zero) targetSpeed = 0.0f;// a reference to the players current horizontal velocityfloat currentHorizontalSpeed = new Vector3(controller.velocity.x, 0.0f, controller.velocity.z).magnitude;float speedOffset = 0.1f;inputMagnitude = input.analogMovement ? input.move.magnitude : 1f;// accelerate or decelerate to target speedif (currentHorizontalSpeed < targetSpeed - speedOffset || currentHorizontalSpeed > targetSpeed + speedOffset){// creates curved result rather than a linear one giving a more organic speed change// note T in Lerp is clamped, so we don't need to clamp our speedspeed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude, Time.deltaTime * speedChangeRate);// round speed to 3 decimal placesspeed = Mathf.Round(speed * 1000f) / 1000f;}else{speed = targetSpeed;}animationBlend = Mathf.Lerp(animationBlend, targetSpeed, Time.deltaTime * speedChangeRate);// normalise input directionVector3 inputDirection = new Vector3(input.move.x, 0.0f, input.move.y).normalized;// note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude// if there is a move input rotate player when the player is movingif (input.move != Vector2.zero){// 如果出现人物只有上下左右四个朝向的情况,说明 mainCamera.transform.eulerAngles.y == 0// 如果摄像机不转动targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + mainCamera.transform.eulerAngles.y;float rotation = Mathf.SmoothDampAngle(selfTransform.eulerAngles.y, targetRotation, ref rotationVelocity, rotationSmoothTime);// rotate to face input direction relative to camera positionselfTransform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);}Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation, 0.0f) * Vector3.forward;// move the playercontroller.Move(targetDirection.normalized * (speed * Time.deltaTime) + new Vector3(0.0f, verticalVelocity, 0.0f) * Time.deltaTime);}public static void CameraRotation(OaksMayFallInputController input, GameObject cinemachineCameraTarget, float threshold, bool isCameraFixed,ref float cinemachineTargetYaw, ref float cinemachineTargetPitch, float bottomClamp, float topClamp, float cameraAngleOverride){// if there is an input and camera position is not fixedif (input.look.sqrMagnitude >= threshold && !isCameraFixed){cinemachineTargetYaw += input.look.x * Time.deltaTime;cinemachineTargetPitch += input.look.y * Time.deltaTime;}// clamp our rotations so our values are limited 360 degreescinemachineTargetYaw = MathUtility.ClampAngle(cinemachineTargetYaw, float.MinValue, float.MaxValue);cinemachineTargetPitch = MathUtility.ClampAngle(cinemachineTargetPitch, bottomClamp, topClamp);// Cinemachine will follow this targetcinemachineCameraTarget.transform.rotation = Quaternion.Euler(cinemachineTargetPitch + cameraAngleOverride, cinemachineTargetYaw, 0.0f);}}
}

Assets/GameMain/Scripts/Utility/SimRigidBodyPush.cs

using UnityEngine;namespace OaksMayFall
{public class SimRigidBodyPush : MonoBehaviour{public LayerMask pushLayers;public bool canPush;[Range(0.5f, 5f)] public float strength = 1.1f;private void OnControllerColliderHit(ControllerColliderHit hit){if (canPush) PushRigidBodies(hit);}private void PushRigidBodies(ControllerColliderHit hit){// https://docs.unity3d.com/ScriptReference/CharacterController.OnControllerColliderHit.html// make sure we hit a non kinematic rigidbodyRigidbody body = hit.collider.attachedRigidbody;if (body == null || body.isKinematic) return;// make sure we only push desired layer(s)var bodyLayerMask = 1 << body.gameObject.layer;if ((bodyLayerMask & pushLayers.value) == 0) return;// We dont want to push objects below usif (hit.moveDirection.y < -0.3f) return;// Calculate push direction from move direction, horizontal motion onlyVector3 pushDir = new Vector3(hit.moveDirection.x, 0.0f, hit.moveDirection.z);// Apply the push and take strength into accountbody.AddForce(pushDir * strength, ForceMode.Impulse);}}
}

Assets/GameMain/Scripts/Utility/MathUtility.cs

using UnityEngine;namespace OaksMayFall
{public static class MathUtility{public static float ClampAngle(float lfAngle, float lfMin, float lfMax){if (lfAngle < -360f) lfAngle += 360f;if (lfAngle > 360f) lfAngle -= 360f;return Mathf.Clamp(lfAngle, lfMin, lfMax);}}
}

3.3 在 Assets/GameMain/Scripts/Definition/Constant/Constant.AssetPriority.cs 中添加 NewUEntityAsset 的优先级

Assets/GameMain/Scripts/Definition/Constant/Constant.AssetPriority.cs

namespace OaksMayFall
{public static partial class Constant{/// <summary>/// 资源优先级。/// </summary>public static class AssetPriority{public const int ConfigAsset = 100;public const int DataTableAsset = 100;public const int DictionaryAsset = 100;public const int FontAsset = 50;public const int MusicAsset = 20;public const int SceneAsset = 0;public const int SoundAsset = 30;public const int UIFormAsset = 50;public const int UISoundAsset = 30;public const int PlayerArmatureAsset = 90;}}
}

3.4 在 Assets/GameMain/Scripts/Entity/EntityExtension.cs 中添加 ShowNewUEntity 方法

Assets/GameMain/Scripts/Entity/EntityExtension.cs

        public static void ShowPlayerArmature(this EntityComponent entityCompoennt, PlayerArmatureData data){entityCompoennt.ShowEntity(typeof(PlayerArmature), "PlayerArmature", Constant.AssetPriority.PlayerArmatureAsset, data);}

Assets/GameMain/Scripts/Game/TestGame.cs

//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------using GameFramework;
using GameFramework.DataTable;
using UnityEngine;namespace OaksMayFall
{public class TestGame: GameBase{private float m_ElapseSeconds = 0f;public override GameMode GameMode{get{return GameMode.Test;}}public override void Initialize(){base.Initialize();IDataTable<DRPlayerArmature> dtPlayerArmature = GameEntry.DataTable.GetDataTable<DRPlayerArmature>();GameEntry.Entity.ShowPlayerArmature(new PlayerArmatureData(GameEntry.Entity.GenerateSerialId(),10000, CampType.Player){Position = new Vector3(0f, 1f, 0f),});}public override void Update(float elapseSeconds, float realElapseSeconds){base.Update(elapseSeconds, realElapseSeconds);}}
}

ok……成了

啊……后面要添加功能的时候我就觉得难了
要添加一个变量,要在data里面改,也要在 logic 里面改,然后定义一大堆属性啥的
我错了,果然,这个东西,是不同的,不应该强塞到 entity 框架中

改版之后的第三人称控制器

Assets/GameMain/Scripts/Entity/EntityLogic/PlayerArmature.cs

using Cinemachine;
using UnityEngine;
using UnityGameFramework.Runtime;namespace OaksMayFall
{/// <summary>/// 玩家类。/// </summary>public class PlayerArmature : UEntity{[SerializeField] private PlayerArmatureData playerArmatureData;private CharacterController _controller;private Animator _animator;private OaksMayFallInputController _input;private GameObject _cinemachineCameraTarget;private GameObject _playerFollowCamera;private GameObject _mainCamera;private SimRigidBodyPush _simRigidBodyPush;private ThirdPersonController _thirdPersonController;protected override void OnShow(object userData){base.OnShow(userData);playerArmatureData = userData as PlayerArmatureData;if (playerArmatureData == null){Log.Error("PlayerArmatureData is invalid.");return;}_controller = GetComponent<CharacterController>();_animator = GetComponent<Animator>();_input = gameObject.AddComponent<OaksMayFallInputController>();_cinemachineCameraTarget = transform.Find("PlayerCameraRoot").gameObject;_playerFollowCamera = GameObject.Find("PlayerFollowCamera");_playerFollowCamera.GetComponent<CinemachineVirtualCamera>().Follow = _cinemachineCameraTarget.transform;_mainCamera = GameObject.FindGameObjectWithTag("MainCamera");_simRigidBodyPush = gameObject.AddComponent<SimRigidBodyPush>();_thirdPersonController = new ThirdPersonController(transform, _controller, _animator, _input,_cinemachineCameraTarget, _mainCamera);_thirdPersonController.AssignAnimationIDs();}protected override void OnUpdate(float elapseSeconds, float realElapseSeconds){base.OnUpdate(elapseSeconds, realElapseSeconds);_thirdPersonController.ApplyGravity();_thirdPersonController.GroundedCheck();_thirdPersonController.Move();_thirdPersonController.RotateToMoveDir();_thirdPersonController.SetAnimatorValue();}protected void LateUpdate(){_thirdPersonController.CameraRotate();}}
}

Assets/GameMain/Scripts/Entity/EntityData/PlayerArmatureData.cs

using System;
using UnityEngine;
using GameFramework.DataTable;namespace OaksMayFall
{[Serializable]public class PlayerArmatureData : UEntityData{/// <summary>/// 阵营/// </summary>[SerializeField] private CampType ownerCamp = CampType.Unknown;/// <summary>/// 生命最大值/// </summary>[SerializeField] private float maxHP;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;maxHP = drPlayerArmature.MaxHP;}/// <summary>/// 阵营/// </summary>public CampType OwnerCamp => ownerCamp;/// <summary>/// 生命最大值/// </summary>public float MaxHP => maxHP;}
}

Assets/GameMain/Scripts/Utility/ThirdPersonController.cs

using UnityEngine;namespace OaksMayFall
{public class ThirdPersonController{// 常量/// <summary>/// 微量/// </summary>private const float Threshold = 0.01f;// 移动相关/// <summary>/// 移动速度/// </summary>private float _walkSpeed = 5f;/// <summary>/// 冲刺速度/// </summary>private float _sprintSpeed = 30f;/// <summary>/// 转身速度/// </summary>private float _rotationSmoothTime = 0.2f;/// <summary>/// 移动速度的变化速率/// </summary>private float _moveSpeedChangeRate = 10f;// 冲刺相关// 冲刺计时器private float _sprintTimeoutDelta;// 冲刺时间private float _sprintTimeout = 0.25f;// 物理相关/// <summary>/// 重力系数/// </summary>private float _gravity = -15f;/// <summary>/// 最大下落速度/// </summary>private float _terminalVelocity = 53f;// 落地相关/// <summary>/// 是否落地/// </summary>private bool _isGrounded = true;/// <summary>/// 是否落地/// </summary>public bool IsGrounded => _isGrounded;/// <summary>/// 落地球形碰撞检测中心点的竖向偏移量/// </summary>private float _groundedOffset = -0.14f;/// <summary>/// 落地球形碰撞检测的半径/// </summary>private float _groundedRadius = 0.28f;/// <summary>/// 落地球形碰撞检测的层级/// </summary>private int _groundLayers = 1;// 摄像机相关private float _cameraAngleOverride = 0f;/// <summary>/// 摄像机转动的速度/// </summary>private float _cameraRotSpeed = 30f;/// <summary>/// 摄像机最大俯仰角/// </summary>private float _topClamp = 70f; /// <summary>/// 摄像机最小俯仰角/// </summary>private float _bottomClamp = -30f;// 动画相关/// <summary>/// 动画状态机的速度参数的 id/// </summary>private int _animIDSpeed;/// <summary>/// 动画状态机的落地参数的 id/// </summary>private int _animIDGrounded;/// <summary>/// 动画状态机的自由落体参数的 id/// </summary>private int _animIDFreeFall;// 控制缓存/// <summary>/// 第三人称控制器的使用者的变换/// </summary>private Transform _userTransform;/// <summary>/// 第三人称控制器的使用者的角色控制器/// </summary>private CharacterController _userCharacterController;/// <summary>/// 第三人称控制器的使用者的动画控制器/// </summary>private Animator _userAnimator;/// <summary>/// 第三人称控制器的使用者的输入控制器/// </summary>private OaksMayFallInputController _userInput;/// <summary>/// 第三人称控制器的使用者的摄像机跟随点/// </summary>private GameObject _cinemachineCameraFollowTarget;/// <summary>/// 主摄像机/// </summary>private GameObject _mainCamera;/// <summary>/// 角色当前速度/// </summary>private float _currSpeed;/// <summary>/// 行走动画的混合值/// </summary>private float _animationBlend;/// <summary>/// 速度容差/// </summary>private float _speedOffset = 0.1f;/// <summary>/// 旋转速度/// </summary>private float _rotationVelocity;/// <summary>/// 竖向速度/// </summary>private float _verticalVelocity;/// <summary>/// 最后一次运动的速度/// </summary>private Vector3 _lastTargetDirection = Vector3.zero;/// <summary>/// 摄像机是否固定/// </summary>private bool _isCameraFixed = false;/// <summary>/// 摄像机偏航角/// </summary>private float _cinemachineTargetYaw;/// <summary>/// 摄像机俯仰角/// </summary>private float _cinemachineTargetPitch;/// <summary>/// 构造函数/// </summary>/// <param name="userTransform">第三人称控制器的使用者的变换</param>/// <param name="userCharacterController">第三人称控制器的使用者的角色控制器</param>/// <param name="userAnimator">第三人称控制器的使用者的动画控制器</param>/// <param name="userInput">第三人称控制器的使用者的输入控制器</param>/// <param name="cinemachineCameraFollowTarget">第三人称控制器的使用者的摄像机跟随点</param>/// <param name="mainCamera">主摄像机</param>public ThirdPersonController(Transform userTransform, CharacterController userCharacterController,Animator userAnimator, OaksMayFallInputController userInput, GameObject cinemachineCameraFollowTarget,GameObject mainCamera){_userTransform = userTransform;_userCharacterController = userCharacterController;_userAnimator = userAnimator;_userInput = userInput;_cinemachineCameraFollowTarget = cinemachineCameraFollowTarget;_mainCamera = mainCamera;}/// <summary>/// 落地检查/// </summary>public void GroundedCheck(){var spherePosition = new Vector3(_userTransform.position.x, _userTransform.position.y - _groundedOffset, _userTransform.position.z);_isGrounded = Physics.CheckSphere(spherePosition, _groundedRadius, _groundLayers, QueryTriggerInteraction.Ignore);}/// <summary>/// 应用重力/// </summary>public void ApplyGravity(){if (_isGrounded && _verticalVelocity < 0.0f)_verticalVelocity = -2f;else if (_verticalVelocity < _terminalVelocity)_verticalVelocity += _gravity * Time.deltaTime;}/// <summary>/// 移动/// </summary>public void Move(){// 期望速度默认为行走速度float targetSpeed = _walkSpeed;// 输入移动方向Vector3 inputDirection = new Vector3(_userInput.Move.x, 0.0f, _userInput.Move.y).normalized;// 期望旋转float targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;// 期望移动方向Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation, 0.0f) * Vector3.forward;// 如果按下冲刺,并且不在冲刺,那么就重置冲刺计时器if (_userInput.Sprint && _sprintTimeoutDelta < 0f)_sprintTimeoutDelta = _sprintTimeout;// 如果冲刺计时器大于 0,说明当前正在冲刺if (_sprintTimeoutDelta > 0f){// 冲刺计时器工作_sprintTimeoutDelta -= Time.deltaTime;// 期望速度为冲刺速度targetSpeed = _sprintSpeed;}// 否则说明不在冲刺// 如果不在冲刺,并且移动输入为 0,那么期望速度为 0else if (_userInput.Move == Vector2.zero)targetSpeed = 0.0f;// 当前速度与目标速度相差较大时,当前速度需要变化if (_currSpeed < targetSpeed - _speedOffset || _currSpeed > targetSpeed + _speedOffset){// 取当前速度_currSpeed = new Vector3(_userCharacterController.velocity.x, 0.0f, _userCharacterController.velocity.z).magnitude;// 当前速度向期望速度插值_currSpeed = Mathf.Lerp(_currSpeed, targetSpeed, Time.deltaTime * _moveSpeedChangeRate);// 取三位小数_currSpeed = Mathf.Round(_currSpeed * 1000f) / 1000f;}// 当前速度与目标速度相差较小时,当前速度就是目标速度else{_currSpeed = targetSpeed;}_animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * _moveSpeedChangeRate);// 玩家移动_userCharacterController.Move(targetDirection.normalized * (_currSpeed * Time.deltaTime) + new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);}/// <summary>/// 向移动方向旋转/// </summary>public void RotateToMoveDir(){// 移动方向Vector3 inputDirection = new Vector3(_userInput.Move.x, 0.0f, _userInput.Move.y).normalized;// note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude// if there is a move input rotate player when the player is movingif (_userInput.Move != Vector2.zero){float targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;float rotation = Mathf.SmoothDampAngle(_userTransform.eulerAngles.y, targetRotation, ref _rotationVelocity, _rotationSmoothTime);// 玩家旋转_userTransform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);}}public void RotateToCameraDir(){float targetRotation = _mainCamera.transform.eulerAngles.y;float rotation = Mathf.SmoothDampAngle(_userTransform.eulerAngles.y, targetRotation, ref _rotationVelocity, _rotationSmoothTime);// 玩家旋转_userTransform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);}/// <summary>/// 摄像机旋转/// </summary>public void CameraRotate(){// if there is an input and camera position is not fixedif (_userInput.Look.sqrMagnitude >= Threshold && !_isCameraFixed){_cinemachineTargetYaw += _userInput.Look.x * Time.deltaTime * _cameraRotSpeed / 100.0f;_cinemachineTargetPitch += _userInput.Look.y * Time.deltaTime * _cameraRotSpeed / 100.0f;}// clamp our rotations so our values are limited 360 degrees_cinemachineTargetYaw = MathUtility.ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);_cinemachineTargetPitch = MathUtility.ClampAngle(_cinemachineTargetPitch, _bottomClamp, _topClamp);// Cinemachine will follow this target_cinemachineCameraFollowTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + _cameraAngleOverride, _cinemachineTargetYaw, 0.0f);}/// <summary>/// 初始化动画状态机参数/// </summary>public void AssignAnimationIDs(){_animIDSpeed = Animator.StringToHash("Speed");_animIDGrounded = Animator.StringToHash("Grounded");_animIDFreeFall = Animator.StringToHash("FreeFall");}/// <summary>/// 设置动画状态机参数/// </summary>public void SetAnimatorValue(){// 着地_userAnimator.SetBool(_animIDGrounded, _isGrounded);// 跳跃if (_isGrounded)_userAnimator.SetBool(_animIDFreeFall, false);// 自由下落else_userAnimator.SetBool(_animIDFreeFall, true);// 移动_userAnimator.SetFloat(_animIDSpeed, _animationBlend);}}
}

Assets/GameMain/InputSystem/OaksMayFallInputController.cs

using UnityEngine;
#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKED
using UnityEngine.InputSystem;
#endifnamespace OaksMayFall
{public class OaksMayFallInputController : MonoBehaviour{private Vector2 move;private Vector2 look;private bool jump;private bool sprint;public Vector2 Move => move;public Vector2 Look => look;public bool Sprint => sprint;#if !UNITY_IOS || !UNITY_ANDROID[Header("Mouse Cursor Settings")]public bool cursorLocked = true;public bool cursorInputForLook = true;
#endif#if ENABLE_INPUT_SYSTEM && STARTER_ASSETS_PACKAGES_CHECKEDprivate void OnMove(InputValue value){move = value.Get<Vector2>();}private void OnLook(InputValue value){if (cursorInputForLook)look = value.Get<Vector2>();}private void OnSprint(InputValue value){sprint = value.isPressed;}
#else// old input sys if we do decide to have it (most likely wont)...
#endif#if !UNITY_IOS || !UNITY_ANDROIDprivate void OnApplicationFocus(bool hasFocus){SetCursorState(cursorLocked);}private void SetCursorState(bool newState){Cursor.lockState = newState ? CursorLockMode.Locked : CursorLockMode.None;}#endif}}

后面要改冲刺逻辑的时候,又感觉这种附加速度的方式不太好,还是把冲刺独立出来了
然后也在很多地方用了 SmoothDamp,真的舒服,我以前想到平滑插值,只想到 UE4 的 Timeline,他给我的印象是很难用的,因为他是一个独立出来的线程,你还要控制它的开启关闭,但是 SmoothDamp 是作用在一帧的,就很灵活

using System;
using UnityEngine;namespace OaksMayFall
{public class ThirdPersonController{// 常量/// <summary>/// 微量/// </summary>private const float Threshold = 0.01f;// 移动相关/// <summary>/// 移动速度/// </summary>private float _walkSpeed = 7f;/// <summary>/// 玩家旋转的过渡时间/// </summary>private float _rotationSmoothTime = 0.2f;/// <summary>/// 水平速度/// </summary>/// <returns></returns>private Vector3 _horizontalVelocity;/// <summary>/// 竖向速度/// </summary>private Vector3 _verticalVelocity;/// <summary>/// 旋转角的过渡速度/// </summary>private float _rotationSmoothVelocity;/// <summary>/// 行走速度的过渡速度/// </summary>private Vector3 _walkSmoothVelocity;/// <summary>/// 玩家行走的过渡时间/// </summary>private float _walkSmoothTime = 0.2f;// 冲刺相关/// <summary>/// 冲刺速度/// </summary>private float _sprintSpeed = 20f;// 冲刺计时器private float _sprintTimeoutDelta;// 冲刺的冷却时间private float _sprintTimeout = 0.25f;/// <summary>/// 冲刺速度的过渡速度/// </summary>private Vector3 _sprintSmoothVelocity;/// <summary>/// 玩家冲刺速度的过渡时间/// </summary>private float _sprintSmoothTime = 0.2f;// 物理相关/// <summary>/// 重力系数/// </summary>private float _gravity = -9.8f;/// <summary>/// 最大下落速度/// </summary>private float _terminalVelocity = 53f;// 落地相关/// <summary>/// 是否落地/// </summary>private bool _isGrounded = true;/// <summary>/// 是否落地/// </summary>public bool IsGrounded => _isGrounded;/// <summary>/// 落地球形碰撞检测中心点的竖向偏移量/// </summary>private float _groundedOffset = -0.14f;/// <summary>/// 落地球形碰撞检测的半径/// </summary>private float _groundedRadius = 0.28f;/// <summary>/// 落地球形碰撞检测的层级/// </summary>private int _groundLayers = 1;// 摄像机相关private float _cameraAngleOverride = 0f;/// <summary>/// 摄像机转动的速度/// </summary>private float _cameraRotSpeed = 25f;/// <summary>/// 摄像机最大俯仰角/// </summary>private float _topClamp = 70f; /// <summary>/// 摄像机最小俯仰角/// </summary>private float _bottomClamp = -30f;/// <summary>/// 摄像机跟随点的当前俯仰角的过渡时间/// </summary>private float _cinemachinePitchSmoothTime = 0.1f;/// <summary>/// 摄像机跟随点的当前偏航角的过渡时间/// </summary>private float _cinemachineYawSmoothTime = 0.1f;/// <summary>/// 摄像机是否固定/// </summary>private bool _isCameraFixed = false;/// <summary>/// 摄像机跟随点的期望俯仰角/// </summary>private float _cinemachineTargetPitch;/// <summary>/// 摄像机跟随点的期望偏航角/// </summary>private float _cinemachineTargetYaw;/// <summary>/// 摄像机跟随点的当前俯仰角和摄像机跟随点的当前偏航角组成的向量/// </summary>private Vector2 _cinemachineCurrPY;/// <summary>/// 摄像机跟随点的当前俯仰角的过渡速度/// </summary>private float _cinemachinePitchSmoothVelocity;/// <summary>/// 摄像机跟随点的当前俯仰角的过渡速度/// </summary>private float _cinemachineYawSmoothVelocity;// 动画相关/// <summary>/// 动画状态机的速度参数的 id/// </summary>private int _animIDSpeed;/// <summary>/// 动画状态机的落地参数的 id/// </summary>private int _animIDGrounded;/// <summary>/// 动画状态机的自由落体参数的 id/// </summary>private int _animIDFreeFall;/// <summary>/// 跑步动画混合值/// </summary>private float _moveAnimBlend;/// <summary>/// 跑步动画混合值的过渡速度/// </summary>private float _moveAnimBlendSmoothVelocity;/// <summary>/// 跑步动画混合值的过渡时间/// </summary>private float _moveAnimBlendSmoothTime = 0.2f;// 组件/// <summary>/// 第三人称控制器的使用者的变换/// </summary>private Transform _userTransform;/// <summary>/// 第三人称控制器的使用者的角色控制器/// </summary>private CharacterController _userCharacterController;/// <summary>/// 第三人称控制器的使用者的动画控制器/// </summary>private Animator _userAnimator;/// <summary>/// 第三人称控制器的使用者的输入控制器/// </summary>private OaksMayFallInputController _userInput;/// <summary>/// 第三人称控制器的使用者的摄像机跟随点/// </summary>private GameObject _cinemachineCameraFollowTarget;/// <summary>/// 主摄像机/// </summary>private GameObject _mainCamera;/// <summary>/// 构造函数/// </summary>/// <param name="userTransform">第三人称控制器的使用者的变换</param>/// <param name="userCharacterController">第三人称控制器的使用者的角色控制器</param>/// <param name="userAnimator">第三人称控制器的使用者的动画控制器</param>/// <param name="userInput">第三人称控制器的使用者的输入控制器</param>/// <param name="cinemachineCameraFollowTarget">第三人称控制器的使用者的摄像机跟随点</param>/// <param name="mainCamera">主摄像机</param>public ThirdPersonController(Transform userTransform, CharacterController userCharacterController,Animator userAnimator, OaksMayFallInputController userInput, GameObject cinemachineCameraFollowTarget,GameObject mainCamera){_userTransform = userTransform;_userCharacterController = userCharacterController;_userAnimator = userAnimator;_userInput = userInput;_cinemachineCameraFollowTarget = cinemachineCameraFollowTarget;_mainCamera = mainCamera;}/// <summary>/// 落地检查/// </summary>public void GroundedCheck(){var spherePosition = new Vector3(_userTransform.position.x, _userTransform.position.y - _groundedOffset, _userTransform.position.z);_isGrounded = Physics.CheckSphere(spherePosition, _groundedRadius, _groundLayers, QueryTriggerInteraction.Ignore);}/// <summary>/// 应用重力/// </summary>public void ApplyGravity(){if (_isGrounded && _verticalVelocity.y < 0.0f)_verticalVelocity.y = -2f;else if (_verticalVelocity.y < _terminalVelocity)_verticalVelocity.y += _gravity * Time.deltaTime;}/// <summary>/// 移动/// </summary>public void Move(){// 一开始的想法是,想把冲刺做成一个常规速度之外的附加速度// 之后 debug 的时候发现不好调,还是要做成分离的// 只要正在冲刺,就只使用冲刺速度UpdateSprintTimeout();_horizontalVelocity = _sprintTimeoutDelta > 0f ? GetSprintSpeed() : GetNormalSpeed();_userCharacterController.Move((_horizontalVelocity + _verticalVelocity) * Time.deltaTime);}private Vector3 GetSprintSpeed(){// 输入移动方向Vector3 inputDirection = new Vector3(_userInput.Move.x, 0.0f, _userInput.Move.y).normalized;// 期望旋转// 因为摄像机呼吸,_mainCamera.transform.eulerAngles.y 会发生抖动,进而导致玩家在起步的时候有一个微小抖动// 而 _cinemachineTargetYaw 不会抖动,因此采用 _cinemachineTargetYawfloat targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _cinemachineTargetYaw;// 期望移动方向Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation, 0.0f) * Vector3.forward;// 如果按下冲刺,那么初始化冲刺if (_userInput.Sprint){// 当前附加速度初始化为冲刺速度// 不需要 SmoothDamp,这是突变的// 如果没有运动输入的话,那么冲刺方向为角色当前朝向if (_userInput.Move == Vector2.zero)return _userTransform.forward * _sprintSpeed;// 有运动的输入的话,那么冲刺方向为运动输入的方向elsereturn targetDirection.normalized * _sprintSpeed;}// 否则冲刺速度趋向 0elsereturn Vector3.SmoothDamp(_horizontalVelocity, Vector3.zero, ref _sprintSmoothVelocity, _sprintSmoothTime);}private Vector3 GetNormalSpeed(){// 输入移动方向Vector3 inputDirection = new Vector3(_userInput.Move.x, 0.0f, _userInput.Move.y).normalized;// 期望旋转// 因为摄像机呼吸,_mainCamera.transform.eulerAngles.y 会发生抖动,进而导致玩家在起步的时候有一个微小抖动// 而 _cinemachineTargetYaw 不会抖动,因此采用 _cinemachineTargetYawfloat targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _cinemachineTargetYaw;// 期望移动方向Vector3 targetDirection = Quaternion.Euler(0.0f, targetRotation, 0.0f) * Vector3.forward;Vector3 targetVelocity = (_userInput.Move == Vector2.zero) ? Vector3.zero : targetDirection * _walkSpeed;return Vector3.SmoothDamp(_horizontalVelocity, targetVelocity, ref _walkSmoothVelocity, _walkSmoothTime);}private void UpdateSprintTimeout(){// 如果正在冲刺,并且计时器小于 0,说明可以开始冲刺if (_userInput.Sprint && _sprintTimeoutDelta <= 0f)_sprintTimeoutDelta = _sprintTimeout;// 如果冲刺计时器大于 0,说明当前正在冲刺if (_sprintTimeoutDelta > 0f)// 冲刺计时器工作_sprintTimeoutDelta -= Time.deltaTime;}/// <summary>/// 向移动方向旋转/// </summary>public void RotateToMoveDir(){// 移动方向Vector3 inputDirection = new Vector3(_userInput.Move.x, 0.0f, _userInput.Move.y).normalized;// note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude// if there is a move input rotate player when the player is movingif (_userInput.Move != Vector2.zero){float targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg + _mainCamera.transform.eulerAngles.y;float rotation = Mathf.SmoothDampAngle(_userTransform.eulerAngles.y, targetRotation, ref _rotationSmoothVelocity, _rotationSmoothTime);// 玩家旋转_userTransform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);}}public void RotateToCameraDir(){float targetRotation = _mainCamera.transform.eulerAngles.y;float rotation = Mathf.SmoothDampAngle(_userTransform.eulerAngles.y, targetRotation, ref _rotationSmoothVelocity, _rotationSmoothTime);// 玩家旋转_userTransform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);}/// <summary>/// 摄像机旋转/// </summary>public void CameraRotate(){// if there is an input and camera position is not fixedif (_userInput.Look.sqrMagnitude >= Threshold && !_isCameraFixed){_cinemachineTargetPitch += _userInput.Look.y * Time.deltaTime * _cameraRotSpeed / 100.0f;_cinemachineTargetYaw += _userInput.Look.x * Time.deltaTime * _cameraRotSpeed / 100.0f;}// clamp our rotations so our values are limited 360 degrees_cinemachineTargetPitch = MathUtility.ClampAngle(_cinemachineTargetPitch, _bottomClamp, _topClamp);_cinemachineTargetYaw = MathUtility.ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);// 平滑_cinemachineCurrPY.x = Mathf.SmoothDampAngle(_cinemachineCurrPY.x, _cinemachineTargetPitch,ref _cinemachinePitchSmoothVelocity, _cinemachinePitchSmoothTime);_cinemachineCurrPY.y = Mathf.SmoothDampAngle(_cinemachineCurrPY.y, _cinemachineTargetYaw,ref _cinemachineYawSmoothVelocity, _cinemachineYawSmoothTime);// Cinemachine will follow this target_cinemachineCameraFollowTarget.transform.rotation = Quaternion.Euler(_cinemachineCurrPY.x + _cameraAngleOverride, _cinemachineCurrPY.y, 0.0f);}/// <summary>/// 初始化动画状态机参数/// </summary>public void AssignAnimationIDs(){_animIDSpeed = Animator.StringToHash("Speed");_animIDGrounded = Animator.StringToHash("Grounded");_animIDFreeFall = Animator.StringToHash("FreeFall");}/// <summary>/// 设置动画状态机参数/// </summary>public void SetAnimatorValue(){// 着地_userAnimator.SetBool(_animIDGrounded, _isGrounded);// 跳跃if (_isGrounded)_userAnimator.SetBool(_animIDFreeFall, false);// 自由下落else_userAnimator.SetBool(_animIDFreeFall, true);// 移动_moveAnimBlend = Mathf.SmoothDamp(_moveAnimBlend, _horizontalVelocity.magnitude, ref _moveAnimBlendSmoothVelocity, _moveAnimBlendSmoothTime);_userAnimator.SetFloat(_animIDSpeed, _moveAnimBlend);}}
}

[Unity] GameFramework 学习记录 4:第三人称控制器相关推荐

  1. [Unity] GameFramework 学习记录 2

    嗯--要开始尝试做自己的事了 一开始策划给我列了这些 工程仓库搭建 工程程序框架 第三人称相机实现 主角基础移动实现 主角闪避实现 主角战斗实现 主角能量条实现 近战敌人战斗实现 远程敌人战斗实现 U ...

  2. [Unity] GameFramework 学习记录 3

    首先要仿照飞船的创建流程创建一个主角 首先直接导入 Starter Assets - Third Person Character Controller 太爽了 我看看啊 他这个是除了 ThirdPe ...

  3. [Unity] GameFramework 学习记录 1

    下载框架作者的示例工程 https://github.com/EllanJiang/StarForce 移动到 unity 空项目中 然后再下载 Gameframework https://githu ...

  4. [Unity] GameFramework 学习记录 5

    jetbrain rider 可以反编译 原来在 visual studio 看不到的,写在 dll 里面的,接口的实现,经过反编译之后就能看到 这样就不用费心思多开一个阅读源码的 IDE 窗口了 牛 ...

  5. [Unity] GameFramework 学习记录 6:计时器

    一开始我想不断传递 Delegate public class TimerHandler{private float _elapsedTime;public float ElapsedTime{get ...

  6. 使用新的输入系统在 Unity 中构建第三人称控制器

    如果你随机挑选几款游戏,每款游戏可能会有不同的艺术风格和机制.不同的故事,甚至根本没有故事,但它们都有一个共同点:所有游戏都需要读取和处理输入来自键盘.鼠标.游戏手柄.操纵杆.VR 控制器等设备. 构 ...

  7. 摄像机旋转,视距调节,第一/第三人称控制器可切换

    摄像机位置自己看着摆放 Camera 跟随player MainCameraModeilocationRecord 是空物体放在MaiCamera 同一位置 head 是头部放在你认为的第一人称视角的 ...

  8. Unity+Hololens学习记录-射线应用

    Unity+Hololens学习记录-射线应用 前言 射线介绍: 射线应用元素介绍: Ray RaycastHit Raycast 射线应用实例: 射线碰撞信息获取 Camera发出Ray Gaze射 ...

  9. [Unity] ACT 战斗系统学习 4:重构前的第三人称控制器

    重构前,我的控制器是这样子 Assets/MeowACT/Scripts/ThirdPerson/ThirdPerson.cs // --------------------------------- ...

最新文章

  1. Adam又要“退休”了?耶鲁大学团队提出AdaBelief
  2. vs2013中的error c4996的问题
  3. 数人科技:打造服务传统金融平台
  4. 【SDL的编程】VC环境搭建
  5. C#委托、事件学习之(三)——热水器烧水案例
  6. Oracle12081,【Oracle介质】Oracle 12C Linux x86-64 最新OPatch patch 6880880 12.2.0.1.7
  7. 谈谈button标签和input标签的区别
  8. mysql字符串比较数字
  9. python中time模块的时间戳和格式化日期_Python中的time模块与datetime模块
  10. 扇贝有道每日一句180904
  11. Oracle队列锁enq:TS,Temporary Segment (also TableSpace)
  12. 程序员必读书籍及导读指南
  13. 制作精良、功能强大、毫秒精度、专业级的定时任务执行软件功能详解 —— 定时执行专家
  14. 匠心独运解读Mybatis源码,纯手工打造开源框架
  15. 有趣题目和认知合集(持续更新)
  16. 倾斜摄影相机焦距与实景三维模型效果的关系
  17. 东华大学专业英语 词汇学习
  18. python爬取《春风十里不如你》分析
  19. 12AU7设计中的一个小技巧
  20. 【C语言】利用条件运算符的嵌套完成学习成绩的例题

热门文章

  1. Keras:Transfer learning
  2. (转) Oracle性能优化-读懂执行计划
  3. 新建maven的pom.xml第一行出错的解决思路
  4. react让我怀疑自己没有当程序员的天分怎么破?
  5. Unity插件扩展中组件常用的几个方法
  6. ospf路由协议源码学习
  7. 使用正则表达式小心换行和回车
  8. 前端倒计时不准的问题
  9. hibernate自带的注解和jpa注解的冠希
  10. [转]Windows 7 蓝屏后获取 MEMORY.DMP 文件及注意事项