Unity Arpg学习日志六:跑酷攀爬功能———爬墙,悬挂,横向攀爬


前言

本章来实现爬墙的功能,示例如下:
在这里插入图片描述
在这里插入图片描述

一、动画设置

在这里插入图片描述
enter为上墙动作,hold为悬挂动作,exit为往上爬后站立,left和right为横向攀爬,fall为往下落,land为落地动作。状态转换关系:fall->land为接触地面,其他转换均为动画结束后转换。横向攀爬为inplace动作,其他均为rootMotion,可以根据自己需求进行设置。

二、墙体检测

1、原理

首先可以参考我上一期教程
Unity Arpg学习日志五:跑酷攀爬功能———低障碍物撑跳、翻越(检测算法的改进)

与上期不同,上期翻越低障碍物要考虑厚度,这次不用考虑,故我们采用Physics.CapsuleCast向前检测,最先获取前提的前点,检测原理如下
在这里插入图片描述
二次检测
在这里插入图片描述

2、参数

这里是用到的参数
在这里插入图片描述

3、代码

protected override void Check()
{
    Vector3 p1 = player.position + player.up * (minValueHeight + capsuleCastRadius);
    Vector3 p2 = player.position + player.up * (maxValueHeight - capsuleCastRadius);

    //debug为我自己编写的一个绘制检测物体的脚本,大家可以从上一期找到
    if (debug)
    {
        debug.DrawCapsule(p1+player.forward*maxCheckDistance, p2+player.forward*maxCheckDistance, capsuleCastRadius, Color.black);
        

    }

    //检测前方是否有合适墙体
    if (Physics.CapsuleCast(p1,p2,capsuleCastRadius,player.forward,out RaycastHit checkHit,maxCheckDistance,mask,QueryTriggerInteraction.Collide))
    {
        
        //没有爬出边界
        outWall = false ;

        //碰头
        if(Physics.Raycast(player.position+player.up*HeadBumpHeight,player.forward,maxCheckDistance+1f,Physics.AllLayers))
        {
            //检测失败
            Debug.Log("碰头了");
            isClimbUp=false;
            SetClimbTypeActive(ClimbType.Short, false);
            
        }
        else isClimbUp = true;//往上爬不碰头的话可以继续往上爬

        //最高检测点
        Vector3 startTop=checkHit.point;
        startTop.y=player.position.y+maxValueHeight+capsuleCastRadius;

        //绘制检测到的墙体信息
        if (debug)
        {
            debug.DrawSphere(checkHit.point, capsuleCastRadius, Color.yellow);
        }

        //检测最高点
        if (Physics.SphereCast(startTop,capsuleCastRadius,Vector3.down,out RaycastHit top,maxValueHeight-minValueHeight,mask,QueryTriggerInteraction.Collide))
        {
            //匹配点为顶点
            matchTarget1 = top.point;
                     
            //绘制顶点和最高检测点
            if (debug)
            {
                debug.DrawSphere(startTop, capsuleCastRadius, Color.red);
                debug.DrawSphere(matchTarget1, capsuleCastRadius, Color.blue);
            }             
            //检测成功
            //修正玩家朝向,让玩家正面对墙体
            playerDir = -checkHit.normal;
            //Debug.Log("检测成功");
        }
        else
        {
            //检测失败
            //Debug.Log("太高了");       
        }
    }
    else
    {
        //检测失败
        //Debug.Log("没检测到");
        outWall=true;//爬出墙外了
    }
}

4、示例

在这里插入图片描述

三.enter与hold

代码如下(示例):

进入状态时直接播放enter动画

protected override void Enter()
{
    base.Enter();
    animator.Play("climb_short_enter");
    
    //状态启用
    isRun = true;
          
    animator.applyRootMotion = true;
    rb.isKinematic = true;  
}

进行目标匹配

void AnimMatch()
{
    Vector3 offset1 = player.forward * offsetType1.z + player.right * offsetType1.x + player.up * offsetType1.y;
    Vector3 offset2 = player.forward * offsetType2.z + player.right * offsetType2.x + player.up * offsetType2.y;
    if (animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_enter"))
    {
        //让玩家一直朝向墙体
        player.forward = playerDir;
        animator.MatchTarget(matchTarget1 + offset2, Quaternion.identity, AvatarTarget.RightHand, new MatchTargetWeightMask(Vector3.one, 0), 0.0f, 0.2f);
    }
}

这里让玩家一直朝向墙体,在玩家在有一定角度的情况下进行攀爬可以尽量避免穿模,否则。。。。
在这里插入图片描述

当enter动画播放完后会进入hold的动画,表现为一直悬挂在墙面上
这时创建一个StateCheck()函数进行状态判定

 void StateCheck()
 {
     float h = PlayerInputManager.Instance.MoveInput().x;
     float v=PlayerInputManager.Instance.MoveInput().y;
     
     if(animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_hold"))
     {
         if (h == 0 && v > 0 && PlayerInputManager.Instance.JumpInput() > 0.99f)
         {
             animator.Play("climb_short_exit");
         }
         else if ((h == 0 && v < 0)&&PlayerInputManager.Instance.JumpInput()>0.99f)
         {
             animator.Play("climb_short_fall");
             //刚体在这里进行特别处理,否则无法检测已经落地
             rb.isKinematic = false;
         }
         
         else if(v==0&&h>0)
         {
            
             animator.Play("climb_short_right");
         }

         else if (v == 0 && h < 0)
         {
             
             animator.Play("climb_short_left");
         }

     }
}

状态判定和目标匹配均在帧更新中进行

public override void OnLateUpdate()
{
    base.OnLateUpdate();
    if (isRun)
    {

        AnimMatch();
        StateCheck();
        
    }

}

演示
在这里插入图片描述

四.exit和fall->land

1、exit

当完成输入后,动作由悬挂转为爬上墙体,目标匹配如下

 else if (animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_exit"))
 {
     float time=animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
     if(time<0.5f)
     {
         animator.MatchTarget(matchTarget1, Quaternion.identity, AvatarTarget.LeftFoot, new MatchTargetWeightMask(Vector3.one, 0), 0.1f, 0.9f);
         //手部IK大家可以先不管
         OpenHandIk(0.5f, matchTarget1 +offset1 , 0.5f, matchTarget1 + offset1);
     }
     else if(time >0.5f&&time<0.6f)
     {
     	 //手部IK大家可以先不管
         OffHandIk();
     }
     else if(time>0.99f)
     {
         Exit();
         //执行退出状态,回到Idle或其他状态
         
     }
    

 }

在代码中可以看到我对手部动画进行了IK,原因是
在这里插入图片描述
可以看到,即使我使用了目标匹配,但可能是由于动画的原因,还是会出现非常明显的穿模现象,这时进行手部IK可以大幅改进,但是由于我的IK技术不成熟,手臂动画还是会显得比较扭曲。。。
在这里插入图片描述
关于IK控制可以参考我这篇文章
Unity插件学习之Final IK:(一)Full Body Biped IK的简单使用
我使用了一个单例类脚本专门控制各部位Ik,大家可以参考参考

 public void OpenHandIk(float leftHandWeight, Vector3 leftHandTarget, float rightHandWeight, Vector3 rightHandTarget)
{
    //权重
    fullBodyBipedIK.solver.leftHandEffector.positionWeight = leftHandWeight;
    //位置
    fullBodyBipedIK.solver.leftHandEffector.target.transform.position = leftHandTarget;


    fullBodyBipedIK.solver.rightHandEffector.positionWeight = rightHandWeight;
    fullBodyBipedIK.solver.rightHandEffector.target.transform.position = rightHandTarget;
}

public void OffHandIk()
{
    fullBodyBipedIK.solver.leftHandEffector.positionWeight = 0f;
    fullBodyBipedIK.solver.rightHandEffector.positionWeight = 0f;
}

2、fall->land

当完成输入后,动作由悬挂转为下落动作,当下落时判定落地后,则转换为落地动作,地面检测代码大家自行完成,只需要给动画状态机提供地面检测的参数即可
在这里插入图片描述
fall时为RootMotion 不需要目标匹配,然后再land动画结束后,退出状态

  else if (animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_land"))
 {
     float time = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
     if(time>0.99f)
     {
         Exit();
         //执行退出状态
     }
 }

演示
在这里插入图片描述

五、横向攀爬

StateCheck()中关于横向攀爬

else if((animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_left")|| animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_right")))
{
    //如果爬出边界则下落
    if(outWall)
    {
        animator.Play("climb_short_fall");
       
        //刚体在这里进行特别处理
        rb.isKinematic = false;
    }
    //防止按住按键时多次重复播放动画
    if(animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 0.8f)
    {
        if (v == 0 && h > 0)
        {
            animator.Play("climb_short_right");
        }


        else if (v == 0 && h < 0)
        {
            animator.Play("climb_short_left");
        }
    }
}

关于位移只是简单的处理

else if(animator.GetCurrentAnimatorStateInfo(0).IsName("climb_short_right"))
{
    //让玩家一直朝向墙体
    player.forward = playerDir;

    player.position += player.right * 2f*Time.deltaTime;
    
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值