第三人称——骑马系统以及交互动画

骑马系统

人物在马上的脚本

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

1754068895025

人在马上的状态机设置

1754120821831

1754120652248

1754120659929

效果如下:

1754121777384

在马上的部分做好了

先学习怎么做场景交互

以常见的开宝箱交互为例:

1)先建一个可开盖宝箱的模型

1754062960628

1754063029492

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

1754062978661

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

1754063145644

1754063426945

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

1754063484600

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

1754063758536

注:角色动画是mixamo里找的

1754063830643

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

1754064357709

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

1754065815065

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

1754063621486

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

7)为Box加上Tag-Box

1754064496681

8)为Box加上Trigger碰撞体

1754064627861

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

1754065712494

1754064639759

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);
            }
        }
    }
  
}

脚本挂在角色身上

效果如下:

1754068660289

上下马交互系统

效果难崩,后面加入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();
        }
    }
}
posted @ 2025-08-02 01:23  EanoJiang  阅读(397)  评论(2)    收藏  举报