网站如何提高百度排名,做网站思想,推广策略的定义,要怎么做网站Root motion动画可以将角色的根节点#xff08;通常是角色的骨盆或脚部#xff09;的运动直接应用到游戏对象上#xff0c;从而实现角色的自然移动和旋转#xff0c;避免出现脚底打滑的现象。采用Root motion动画的游戏对象#xff0c;通常是重载了onAnimatorMove函数通常是角色的骨盆或脚部的运动直接应用到游戏对象上从而实现角色的自然移动和旋转避免出现脚底打滑的现象。采用Root motion动画的游戏对象通常是重载了onAnimatorMove函数在脚本中来设置动画的速度从而实现角色的移动。Unity的Navigation系统是一个用于实现游戏世界中的寻路和导航功能的组件。它允许游戏角色在复杂的游戏环境中自动找到从一点到另一点的最短路径。如果我们对采用Root motion动画的游戏对象应用Navigation就会产生冲突因为这两个组件都会尝试控制游戏对象的移动。有两个解决方式
一是让动画跟随Navigation agent通过获取agent.velocity来设置root motion的速度从而大致匹配Agent的移动到动画的移动。这个方式最简单但是可能会出现脚底打滑的现象。
二是让Agent跟随动画关闭agent的updatePosition和updateRotation通过计算agent的nextPosition和动画根节点的rootPosition的插值来进行控制。这种方式比方式一要复杂但是效果更好。以下将以一个游戏场景为例子详细介绍一下如何实现方式二。
游戏场景
在游戏中对于NPC角色当前设置了几个状态分别是漫游Wander瞄准Aim以及追踪Chase。NPC刚开始是漫游状态在场景中自由地进行移动这时是通过Root motion来驱动的。当NPC检测到玩家时会进入瞄准状态。如果玩家进行躲避NPC则NPC会进入追踪状态自动跑到上一次发现玩家的位置这时NPC是由Navigation来驱动实现自动寻路。可见对于NPC是需要按照不同的场景来用Root motion或Navigation来驱动的。
Animator设置
建立一个名为Enemy的Animator包含了两个状态分别是Aim和Move设置如下 添加两个Trigger分别为Aim和Walk用于切换状态。定义一个名为Speed的Float变量用于控制Root motion的移动速度。
Move状态是一个BlendTree通过Speed来进行IdleWalkRun这三种动作的混合改变Speed的值可以看到人物动作的改变。 Unity Blendtree动画 改变Speed的值可以看到人物的动作的改变。
实现漫游状态
现在给游戏对象增加一个名为EnemyAI的脚本文件实现游戏对象在场景中漫游。代码如下
public class EnemyAI : MonoBehaviour
{[Header(Enemy eyeview)]public float eyeviewDistance 500.0f;public float viewAngle 120f;public float obstacleRange 3.0f;[Header(Enemy Property)]public float enemyHeight 1.8f;public float enemyWidth 1.2f;public float rotateSpeed 2.0f;public float maxDetectDistance 10f;private float _walkSpeed 1.5f;private float _runSpeed 3.5f;private Animator _animator;private Transform _transform;private float _currentSpeed;private float _targetSpeed;private float _statusDuration 1.0f;private bool _isStatusTimerEnds true;private bool _isDetectTimerEnds true;[Flags]private enum EnemyStatus {Aim,Shoot,Wander,Chase}private EnemyStatus _enemyStatus;void Start(){_animator GetComponentAnimator();_rb GetComponentRigidbody();_transform transform;_enemyStatus EnemyStatus.Wander;rayCastOffset new Vector3(0f, enemyHeight - 0.6f, 0f);}void Update(){if (_enemyStatus EnemyStatus.Wander) {Wander();} Detect();}private void OnAnimatorMove() {if (_currentSpeed ! _targetSpeed) {if (Mathf.Abs(_currentSpeed - _targetSpeed) 0.1) {_currentSpeed Mathf.Lerp(_currentSpeed, _targetSpeed, 0.5f);} else {_currentSpeed _targetSpeed;} }_animator.SetFloat(Speed, _currentSpeed); Vector3 speed new Vector3(_animator.velocity.x, _rb.velocity.y, _animator.velocity.z);_rb.velocity speed;}void Wander() {if (_isStatusTimerEnds) {_targetSpeed UnityEngine.Random.Range(0, 2) 0 ? 0f : _walkSpeed;_statusDuration UnityEngine.Random.Range(5f, 10f);_isStatusTimerEnds false;StartCoroutine(StatusTimer());}}IEnumerator StatusTimer() {float timer 0;while (timer _statusDuration) {timer Time.deltaTime;yield return null; }_isStatusTimerEnds true;}IEnumerator DetectTimer() {float timer 0;while (timer _detectDuration) {timer Time.deltaTime;yield return null; }_isDetectTimerEnds true;}float DetectObstacle(float angle) {RaycastHit hit;int layerMask ~(1 8);Quaternion rotation Quaternion.AngleAxis(angle, Vector3.up);bool hitDetect Physics.BoxCast(_transform.position rayCastOffset, new Vector3(enemyWidth/2, rayCastOffset.y/2, 0.2f), rotation * _transform.forward, out hit,transform.rotation * rotation,maxDetectDistance,layerMask);if (hitDetect) {return hit.distance;} else {return 9999.0f;}}void Detect() {if (_isDetectTimerEnds) {_isDetectTimerEnds false;StartCoroutine(DetectTimer());if (_currentSpeed 0.2) {float distance DetectObstacle(0f);if (distance obstacleRange) {float leftDistance DetectObstacle(-90f);float rightDistance DetectObstacle(90f);float startAngle -45f;float endAngle -110f;if (rightDistance obstacleRange leftDistance obstacleRange) {startAngle 180f;endAngle 180.01f;} else {if (leftDistance rightDistance) {startAngle * -1f;endAngle * -1f;} }_targetAngle UnityEngine.Random.Range(startAngle, endAngle);_currentAngle 0f;}}} else {if (Mathf.Abs(_currentAngle - _targetAngle) 0.1) {_prevAngle _currentAngle;_currentAngle Mathf.Lerp(_currentAngle, _targetAngle, rotateSpeed * Time.deltaTime);_transform.Rotate(0, _currentAngle - _prevAngle, 0);} else {_currentAngle _targetAngle;}}}
}
以上代码大致逻辑是一开始设置状态为漫游状态然后通过一个StatusTimer来计时每次计时器到时就随机设置一个速度值。在onAnimatorMove函数中通过插值的方法来平滑改变速度值并设置Animator的speed值实现通过root motion动画来驱动游戏对象。另外还设置一个DetectTimer来计时定期调用DetectObstacle函数来检测游戏对象行进方向上是否有障碍物如有则进行随机转向。运行场景可以看到游戏对象在场景中可以自由地进行漫步。
实现瞄准状态
现在我们要增加一个检测玩家的功能让游戏对象在漫步过程中能发现玩家并且进入瞄准状态。对以上代码做改动
public class EnemyAI : MonoBehaviour
{...void Update(){if (_enemyStatus EnemyStatus.Aim) {_prevAngle _currentAngle;_currentAngle Mathf.Lerp(_currentAngle, _targetAngle, rotateSpeed * Time.deltaTime);_transform.Rotate(0, _currentAngle - _prevAngle, 0);if (Mathf.Abs(_currentAngle - _targetAngle) 0.5) {_currentAngle _targetAngle;}} ...}bool DetectPlayer() {bool findPlayer false;Vector3 position _transform.position new Vector3(0f, enemyHeight-0.2f, 0f);_spottedPlayers Physics.OverlapSphere(position, eyeviewDistance, LayerMask.GetMask(Character));for (int i0;i_spottedPlayers.Length;i) {Vector3 playerPosition _spottedPlayers[i].transform.position;float angle Vector3.SignedAngle(transform.forward, playerPosition - position, Vector3.up);if (angle viewAngle/2 angle -viewAngle/2) {RaycastHit info;int layermask LayerMask.GetMask(Character, Default);Physics.Raycast(position, playerPosition - position, out info, eyeviewDistance, layermask);if (info.collider _spottedPlayers[i]) {if (_currentSpeed 0.1) {_targetSpeed 0f;_currentSpeed Mathf.Lerp(_currentSpeed, _targetSpeed, 0.75f);_animator.SetFloat(Speed, _currentSpeed);} else {_prevPlayerPosition playerPosition;_foundPlayer true;_enemyStatus EnemyStatus.Aim;_animator.SetTrigger(Aim);_currentAngle 0;_targetAngle angle;_currentSpeed 0f;findPlayer true;}}}}return findPlayer;} void Detect() {if (_isDetectTimerEnds) {...DetectPlayer()}...}
}
在原来的Detect代码中增加一个对检测玩家的DetectPlayer的调用当检测到玩家时设置状态为Aim并且设置Animator的Aim触发器播放瞄准动作。
实现追踪状态
当游戏对象检测到玩家之后玩家可以躲避游戏对象的瞄准例如跑到一旁的障碍物隐藏。游戏对象找不到玩家这时应该跑去之前发现玩家的地方进行搜索。要实现这个功能简单的一个想法是通过Unity的Navigation自动寻路功能来实现让游戏对象自行寻路而不是通过代码来控制。但是如前面提到的Navigation和Root motion同时驱动游戏对象就会产生冲突因此我们可以采取方式二来解决即让Navigation agent跟随动画来移动。
给游戏对象增加一个Navmesh agent组件然后对代码进行如下改动
public class EnemyAI : MonoBehaviour
{...private NavMeshAgent _agent;Vector2 smoothDeltaPosition Vector2.zero;Vector2 velocity Vector2.zero;void Start(){..._agent GetComponentNavMeshAgent();_agent.updatePosition false;_agent.speed _runSpeed;}void Update(){...if (_enemyStatus EnemyStatus.Chase) {Vector3 worldDeltaPosition _agent.nextPosition - _transform.position;// Map worldDeltaPosition to local spacefloat dx Vector3.Dot(_transform.right, worldDeltaPosition);float dy Vector3.Dot(_transform.forward, worldDeltaPosition);Vector2 deltaPosition new Vector2(dx, dy);// Low-pass filter the deltaMovefloat smooth Mathf.Min(1.0f, Time.deltaTime/0.15f);smoothDeltaPosition Vector2.Lerp (smoothDeltaPosition, deltaPosition, smooth);// Update velocity if time advancesif (Time.deltaTime 1e-5f)velocity smoothDeltaPosition / Time.deltaTime;//Debug.LogFormat(Chase, speed:{0}, velocity.magnitude);_animator.SetFloat(Speed, velocity.magnitude); _transform.LookAt(_agent.steeringTarget transform.forward);if (_agent.remainingDistance _agent.radius) {_enemyStatus EnemyStatus.Wander;}}Detect()}private void OnAnimatorMove() {if (_enemyStatus EnemyStatus.Chase) {_transform.position _agent.nextPosition;}else {if (_currentSpeed ! _targetSpeed) {if (Mathf.Abs(_currentSpeed - _targetSpeed) 0.1) {_currentSpeed Mathf.Lerp(_currentSpeed, _targetSpeed, 0.5f);} else {_currentSpeed _targetSpeed;} }_animator.SetFloat(Speed, _currentSpeed); Vector3 speed new Vector3(_animator.velocity.x, _rb.velocity.y, _animator.velocity.z);_rb.velocity speed;}}void Detect() {if (_isDetectTimerEnds) {...//DetectPlayer();if (!DetectPlayer()) {if (_foundPlayer) {_foundPlayer false;_agent.nextPosition _transform.position;_agent.destination _prevPlayerPosition;_enemyStatus EnemyStatus.Chase;_animator.SetTrigger(Walk);_targetSpeed _runSpeed;}} ...}
}
以上的代码值得详细讲解一下在Start函数中设置了agent的updatePosition为false即不让agent来移动游戏对象同时设置agent的最大速度不要超过runspeed。在Update函数中判断如果当前是Chase状态那么计算agent的nextPosition与当前位置的差值然后计算在deltaTime时间间隔中需要以什么速度来移动并设置animator的speed使得游戏对象的动作与移动速度保持同步不会出现脚底打滑的现象。在onAnimatorMove函数中通过设置transform的位置为agent的nextPosition来实现移动。在Detect函数中进行修改如果之前发现玩家但现在没有发现则进入Chase状态 把之前发现玩家的位置设置为agent的目的地让agent来进行自动寻路。注意在进入Chase状态时需要更新一下agent的nextPosition为当前游戏对象的位置因为我们之前设置了updatePostion为false所以agent的当前位置并不同步。
实现效果 Root Motion动画与Navigation结合 FPS教程
另外我之前也写了一系列文章介绍如何实现FPS游戏有兴趣的可以了解一下
Unity开发一个FPS游戏_unity 模仿开发fps 游戏-CSDN博客
Unity开发一个FPS游戏之二_unity 模仿开发fps 游戏-CSDN博客
Unity开发一个FPS游戏之三-CSDN博客
Unity开发一个FPS游戏之四_unity fps-CSDN博客
Unity开发一个FPS游戏之五-CSDN博客