第三人称——骑马系统以及交互动画
骑马系统
人物在马上的脚本
using MalbersAnimations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonRidingHorse : MonoBehaviour
{
[Header("骑马参数")]
public GameObject horse;
public bool isOnHorse;
//void OnInteract()
//{
// var thirdPersonMove = GetComponent<ThirdPersonMove>();
// thirdPersonMove.enabled = false;
// var pos = horse.transform.Find("Pos_UpToHorse");
//}
CharacterController characterController;
Animator animator;
ThirdPersonMove thirdPersonMove;
private void Awake()
{
characterController = GetComponent<CharacterController>();
animator = GetComponent<Animator>();
thirdPersonMove = GetComponent<ThirdPersonMove>();
}
private void Update()
{
if (isOnHorse)
{
var axisX = Input.GetAxis("Horizontal");
var axisY = Input.GetAxis("Vertical");
animator.SetFloat("AxisX", axisX);
animator.SetFloat("AxisY", axisY);
}
Ride();
}
void Ride()
{
//上马
if (!isOnHorse)
{
if (Input.GetKeyDown(KeyCode.F))
{
isOnHorse = true;
transform.rotation = horse.transform.rotation;
transform.position = horse.transform.position;
//将角色放到马上
var playerPoint = horse.transform.Find("PlayerPoint");
transform.SetParent(playerPoint);
transform.localPosition = Vector3.zero;
//在马上禁用角色的characterController和move
characterController.enabled = false;
thirdPersonMove.enabled = false;
//开启马的输入控制脚本
horse.GetComponent<MalbersInput>().enabled = true;
//切换马上动作状态,即权重从0到1
animator.SetLayerWeight(2, 1f);
}
}
//下马
else
{
if (Input.GetKeyDown(KeyCode.F))
{
isOnHorse = false;
if (horse != null)
{
//删除马之前设置角色位置
transform.SetParent(null);
transform.position = horse.transform.position;
transform.rotation = horse.transform.rotation;
//下马后恢复角色的characterController和move
characterController.enabled = true;
thirdPersonMove.enabled = true;
//关闭马的输入控制脚本
horse.GetComponent<MalbersInput>().enabled = false;
//关闭马上动作层
animator.SetLayerWeight(2, 0f);
}
}
}
}
}
马的部分——用的是插件里的马预制体:Horse Animset Pro Riding System 4.0.1.unitypackage

人在马上的状态机设置



效果如下:

在马上的部分做好了
先学习怎么做场景交互
以常见的开宝箱交互为例:
1)先建一个可开盖宝箱的模型


2)在Box的子级中建立一个空的GameObject,当作角色开始播放交互动画的位置

3)在Box的animation窗口中建立动画——Box的开盖动画


4)这里需要把开盖动画的Loop Time给取消勾选

5)来到Timeline窗口,新建一个Box的Timeline,把Box的开盖动画和角色交互的动画拖进去

注:角色动画是mixamo里找的

角色的Track在k帧的时候选上 角色的animator,在k完之后就记得要取消勾选animator

也要记得修改该动画的名字,后面脚本会用到

为了保证开盖动画在人物动画播完后仍然还在播,点开开盖动画的Animation Track,设置为continue

6)把人物移到和PlayerStandPosition同一个位置,追求完美可以k一下开盖动画和角色动画的匹配度,并加上过渡动画(这里我就懒得弄了,因为只是学习怎么做动画交互系统)
7)为Box加上Tag-Box

8)为Box加上Trigger碰撞体

9)取消勾选Play On Awake,不然还没触发就开始播动画了


OK,交互动画匹配好了,下面写脚本控制角色到达一个Box周围的Trigger碰撞体区域,按下交互的Input按键才触发动画
脚本逻辑:
在碰撞体区域按下按键->开始找tag为Box的GameObject,找到的对象就是Box->在Box的子级中找名为PlayerStandPosition的对象->更新角色位置、朝向->在Box的playerableAsset中找到PlayerTrack,播放相应的BoxTimeline
脚本如下:
PlayerOpenBox.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.InputSystem;
public class PlayerOpenBox : MonoBehaviour
{
bool isPlaying = false;
IEnumerator OnInteract()
{
if (list.Count > 0 && isPlaying == false)
{
isPlaying = true;
var thirdPersonMove = GetComponent<ThirdPersonMove>();
thirdPersonMove.enabled = false;
var thirdPersonJump = GetComponent<ThirdPersonJump>();
thirdPersonJump.enabled = false;
var thirdPersonRoll = GetComponent<ThirdPersonRoll>();
thirdPersonRoll.enabled = false;
var director = list[0];
list.RemoveAt(0);
var pos = director.transform.Find("PlayerStandPosition");
transform.position = pos.position;
//Debug.Log(pos.position);
transform.rotation = pos.rotation;
var animator = GetComponent<Animator>();
foreach (var output in director.playableAsset.outputs)
{
if (output.streamName == "PlayerTrack")
{
director.SetGenericBinding(output.sourceObject, animator);
break;
}
}
director.Play();
while(director.state == PlayState.Playing)
{
yield return null;
}
thirdPersonMove.enabled = true;
thirdPersonJump.enabled = true;
thirdPersonRoll.enabled = true;
isPlaying = false;
}
}
List<PlayableDirector> list = new List<PlayableDirector>();
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Box")
{
var director = other.gameObject.GetComponent<PlayableDirector>();
if (director != null && !list.Contains(director))
{
list.Add(director);
}
//Debug.Log(transform.position);
}
}
private void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Box")
{
var director = other.gameObject.GetComponent<PlayableDirector>();
if (director != null && list.Contains(director))
{
list.Remove(director);
}
}
}
}
脚本挂在角色身上
效果如下:

上下马交互系统
效果难崩,后面加入Ik匹配
using MalbersAnimations;
using MalbersAnimations.HAP;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ThirdPersonRidingHorse : MonoBehaviour
{
[Header("骑马参数")]
public GameObject horse;
public bool isOnHorse = false;
[Header("动画参数")]
public int mountLayerIndex = 3;
public int ridingLayerIndex = 2;
public int dismountLayerIndex = 4; // 下马动画层
CharacterController characterController;
Animator animator;
ThirdPersonMove thirdPersonMove;
// 上马状态管理
private bool isMounting = false;
private bool isDismounting = false;
private MountTriggers currentMountTrigger;
private Coroutine mountCoroutine;
private Coroutine dismountCoroutine;
private void Awake()
{
characterController = GetComponent<CharacterController>();
animator = GetComponent<Animator>();
thirdPersonMove = GetComponent<ThirdPersonMove>();
}
private void Update()
{
HandleInput();
if (isOnHorse)
{
var axisX = Input.GetAxis("Horizontal");
var axisY = Input.GetAxis("Vertical");
animator.SetFloat("AxisX", axisX);
animator.SetFloat("AxisY", axisY);
}
Ride();
}
void Ride()
{
//上马
if (isOnHorse && !isDismounting)
{
Update_MountPlayerPosition();
}
}
/// <summary>
/// 更新上马时的位置朝向
/// </summary>
private void Update_MountPlayerPosition()
{
transform.rotation = horse.transform.rotation;
transform.position = horse.transform.position;
//将角色放到马上
var playerPoint = horse.transform.Find("PlayerPoint");
transform.SetParent(playerPoint);
transform.localPosition = Vector3.zero;
//在马上禁用角色的characterController和move
characterController.enabled = false;
thirdPersonMove.enabled = false;
//开启马的输入控制脚本
horse.GetComponent<MalbersInput>().enabled = true;
//切换马上动作状态,即权重从0到1
animator.SetLayerWeight(ridingLayerIndex, 1f);
}
/// <summary>
/// 上马触发器
/// </summary>
/// <param name="other"></param>
void OnTriggerEnter(Collider other)
{
//在马下面进入触发器
if(other.gameObject.tag == "MountTrigger" && !isOnHorse && !isMounting)
{
// 获取MountTriggers组件
MountTriggers mountTrigger = other.GetComponent<MountTriggers>();
if (mountTrigger != null)
{
currentMountTrigger = mountTrigger;
Debug.Log("进入上马触发区域,按F键上马");
}
}
}
/// <summary>
/// 离开上马触发器
/// </summary>
/// <param name="other"></param>
void OnTriggerExit(Collider other)
{
if(other.gameObject.tag == "MountTrigger" && !isOnHorse && !isMounting)
{
currentMountTrigger = null;
Debug.Log("离开上马触发区域");
}
}
/// <summary>
/// 开始上马流程
/// </summary>
private void StartMounting()
{
if (currentMountTrigger == null || isMounting || isOnHorse) return;
isMounting = true;
// 获取玩家的Animator组件
Animator playerAnimator = GetComponent<Animator>();
if (playerAnimator != null)
{
// 设置Mount Layer权重为1
playerAnimator.SetLayerWeight(mountLayerIndex, 1f);
// 播放上马动画
playerAnimator.Play(currentMountTrigger.MountAnimation, mountLayerIndex);
// 开始协程监听动画播放完成
mountCoroutine = StartCoroutine(WaitForMountAnimationComplete());
}
}
/// <summary>
/// 等待上马动画播放完成
/// </summary>
private IEnumerator WaitForMountAnimationComplete()
{
Animator playerAnimator = GetComponent<Animator>();
// 等待动画播放完成
yield return new WaitForSeconds(0.1f); // 等待动画开始
// 获取动画状态信息
AnimatorStateInfo stateInfo = playerAnimator.GetCurrentAnimatorStateInfo(mountLayerIndex);
// 等待动画播放到50%就完成上马
while (stateInfo.normalizedTime < 0.5f)
{
yield return null;
stateInfo = playerAnimator.GetCurrentAnimatorStateInfo(mountLayerIndex);
}
// 动画播放到50%,执行上马完成逻辑
CompleteMounting();
}
/// <summary>
/// 完成上马
/// </summary>
private void CompleteMounting()
{
// 立即停止Mount Layer动画并设置权重为0
animator.SetLayerWeight(mountLayerIndex, 0f);
animator.Play("Empty", mountLayerIndex); // 播放空动画来停止当前动画
// 设置上马状态
isOnHorse = true;
isMounting = false;
// 移动到PlayerPoint位置
var playerPoint = horse.transform.Find("PlayerPoint");
if (playerPoint != null)
{
transform.SetParent(playerPoint);
transform.localPosition = Vector3.zero;
transform.localRotation = Quaternion.identity;
}
// 禁用角色控制器和移动脚本
characterController.enabled = false;
thirdPersonMove.enabled = false;
// 开启马的输入控制脚本
horse.GetComponent<MalbersInput>().enabled = true;
// 立即设置RidingHorse动画层权重为1,过渡到骑马状态
animator.SetLayerWeight(ridingLayerIndex, 1f);
currentMountTrigger = null;
mountCoroutine = null;
Debug.Log("上马完成");
}
/// <summary>
/// 开始下马流程
/// </summary>
private void StartDismounting()
{
if (!isOnHorse || isDismounting) return;
isDismounting = true;
// 获取玩家的Animator组件
Animator playerAnimator = GetComponent<Animator>();
if (playerAnimator != null)
{
// 设置Dismount Layer权重为1
playerAnimator.SetLayerWeight(dismountLayerIndex, 1f);
// 播放下马动画(可以根据需要设置不同的下马动画)
playerAnimator.Play("Rider_Dismount_Right", dismountLayerIndex);
// 开始协程监听动画播放完成
dismountCoroutine = StartCoroutine(WaitForDismountAnimationComplete());
}
}
/// <summary>
/// 等待下马动画播放完成
/// </summary>
private IEnumerator WaitForDismountAnimationComplete()
{
Animator playerAnimator = GetComponent<Animator>();
// 等待动画播放完成
yield return new WaitForSeconds(0.1f); // 等待动画开始
// 获取动画状态信息
AnimatorStateInfo stateInfo = playerAnimator.GetCurrentAnimatorStateInfo(dismountLayerIndex);
// 等待动画完全播放完毕
while (stateInfo.normalizedTime < 1.0f)
{
yield return null;
stateInfo = playerAnimator.GetCurrentAnimatorStateInfo(dismountLayerIndex);
}
// 动画播放完毕,执行下马完成逻辑
CompleteDismounting();
}
/// <summary>
/// 完成下马
/// </summary>
private void CompleteDismounting()
{
// 立即停止Dismount Layer动画并设置权重为0
animator.SetLayerWeight(dismountLayerIndex, 0f);
animator.Play("Empty", dismountLayerIndex); // 播放空动画来停止当前动画
// 设置下马状态
isOnHorse = false;
isDismounting = false;
if (horse != null)
{
// 设置角色位置(从马上下来)
transform.SetParent(null);
// 计算下马后的位置(从马的右边下马)
Vector3 dismountPosition = horse.transform.position + horse.transform.right * 2f;
transform.position = dismountPosition;
transform.rotation = horse.transform.rotation;
// 下马后恢复角色的characterController和move
characterController.enabled = true;
thirdPersonMove.enabled = true;
// 关闭马的输入控制脚本
horse.GetComponent<MalbersInput>().enabled = false;
// 立即关闭马上动作层,过渡到正常状态
animator.SetLayerWeight(ridingLayerIndex, 0f);
}
dismountCoroutine = null;
Debug.Log("下马完成");
}
/// <summary>
/// 处理输入
/// </summary>
private void HandleInput()
{
// 在触发器内按F键开始上马
if (currentMountTrigger != null && Input.GetKeyDown(KeyCode.F) && !isMounting && !isOnHorse)
{
StartMounting();
}
// 在马上按F键开始下马
if (isOnHorse && Input.GetKeyDown(KeyCode.F) && !isDismounting)
{
StartDismounting();
}
}
}

浙公网安备 33010602011771号