当前位置: 代码网 > it编程>游戏开发>ar > 【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用

【unity小技巧】unity最完美的CharacterController 3d角色控制器,实现移动、跳跃、下蹲、奔跑、上下坡、物理碰撞效果,复制粘贴即用

2024年08月01日 ar 我要评论
其实一开始我是不打算写的,我感觉这种简单的功能,网上随便一搜一大堆,但是我发现网上很多都是复制粘贴,要么没有实操过,要么就是功能不全,或者毫无解释的把代码丢出来,我自以为简单的3D角色控制,我整整花了3-4天才研究明白(虽然每天只花几个小时),下面是记录我的一些思路过程,希望对你有帮助。【用unity实现100个游戏之18】从零开始制作一个类CSGO/CS2、CF第一人称FPS射击游戏——基础篇1。

最终效果

在这里插入图片描述

前言

其实一开始我是不打算写的,我感觉这种简单的功能,网上应该随便一搜一大堆,但是实际去搜会发现网上很多都是复制粘贴,要么没有实操过,要么就是功能不全,或者毫无解释的把代码丢出来,所以特地花时间去研究了研究,下面是记录我的一些思路过程,希望对你有帮助。

其实之前实战有做过fps移动控制,只是没有说的很全面,感兴趣可以查看之前的文章:
【用unity实现100个游戏之18】从零开始制作一个类csgo/cs2、cf第一人称fps射击游戏——基础篇1

为什么使用charactercontroller

unity中常用的三种角色移动方式如下:

  1. 使用刚体(rigidbody)组件:这种方式将角色对象添加刚体组件,并通过力(force)或者速度(velocity)来控制移动。你可以使用输入控制器(例如键盘、手柄)获取移动输入,然后将对应的力或速度施加给角色刚体,从而实现移动。

  2. 使用transform组件的translate方法:这种方式直接使用transform组件的translate方法来移动角色。你可以通过获取输入控制器的输入,计算出移动方向和距离,然后调用translate方法将角色移动到指定位置。

  3. 使用character controller组件:这种方式需要将角色对象添加character controller组件,并使用character controller提供的move方法来移动角色。你可以通过输入控制器获取移动输入,然后将输入转换为移动向量,并传递给character controller的move方法来实现移动。

刚体自带重力和物理效果,但是对于爬坡,走楼梯要单独处理,比较麻烦。(ps:当然,后面有机会我在研究rigidbody如何控制人物,可以关注期待一下

transform呢,不带重力又不带碰撞,移动不受物理引擎控制,可能导致穿透墙壁或其他对象。不支持碰撞和重力等物理效果。所以我是直接pass的

charactercontroller主要是不适用于需要处理复杂物理交互的情况,例如推动物体等。但是对于爬坡,楼梯自带了处理方式,完美解决了刚体的痛点,而如果你想推动物体也可以直接给物体一个推力即可解决(文章最后面我分享解决方案

simplemove和move如何选择?

1. simplemove

  • 不受y轴速度影响,自带重力效果,无法实现跳跃功能
  • 返回值为bool,当角色接触地面返回true,反之为false。
  • simplemove方法是charactercontroller组件提供的一个用于处理角色平面移动的简化方法,它自动处理了角色与地面的碰撞* 检测和摩擦,但它不支持跳跃等垂直方向的动作

2. move

  • 无重力效果,自行实现重力,可做跳跃功能
  • 返回值(collisionflags对象),返回角色与物体碰撞的信息

可以看到simplemove看着虽好,但是最大的痛点是无法实现跳跃,所以我们只能忍痛pass掉

配置charactercontroller参数

新增一个胶囊体,代表角色,在角色身上新增charactercontroller组件,参数配置如下
在这里插入图片描述

控制相机

将相机拖入为玩家的子物体,放置在角色的头部位置,修改
新增mouselook脚本,挂载在相机上,控制相机视角

public class mouselook : monobehaviour
{
    // 鼠标灵敏度
    public float mousesensitivity = 1000f;

    // 玩家的身体transform组件,用于旋转
    public transform playerbody;

    // x轴的旋转角度
    float xrotation = 0f;
    void start()
    {
        // 锁定光标到屏幕中心,并隐藏光标
        cursor.lockstate = cursorlockmode.locked;
    }

    // update在每一帧调用
    void update()
    {
        // 执行自由视角查看功能
        freelook();
    }

    // 自由视角查看功能的实现
    void freelook()
    {
        // 获取鼠标x轴和y轴的移动量,乘以灵敏度和时间,得到平滑的移动速率
        float mousex = input.getaxis("mouse x") * mousesensitivity * time.deltatime;
        float mousey = input.getaxis("mouse y") * mousesensitivity * time.deltatime;
        //限制旋转角度在-90到90度之间,防止过度翻转
        xrotation = mathf.clamp(xrotation, -90f, 90f);

        // 累计x轴上的旋转量
        xrotation -= mousey;

        // 应用摄像头的x轴旋转
        transform.localrotation = quaternion.euler(xrotation, 0f, 0f);

        // 应用玩家身体的y轴旋转
        playerbody.rotate(vector3.up * mousex);
    }
}

效果
在这里插入图片描述

移动

经过上面的分享,我们使用move实现一下人物的移动
新增movementscript脚本,挂载在角色身上

public class movementscript : monobehaviour
{
	[tooltip("角色控制器")] public charactercontroller charactercontroller;
	private float horizontal;
    private float vertical;
    
    [header("移动")]
    [tooltip("角色行走的速度")] public float walkspeed = 6f;
    [tooltip("当前速度")] private float speed;
    [tooltip("角色移动的方向")] private vector3 movedirection;
    
    void start()
    {
        speed = walkspeed;
    }
    void update()
    {
        horizontal = input.getaxis("horizontal");
        vertical = input.getaxis("vertical");
        movedirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
        //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向,效果和上面的一样
        // movedirection = transform.transformdirection(new vector3(h, 0, v));
        movedirection = movedirection.normalized; // 归一化移动方向,避免斜向移动速度过快
        charactercontroller.move(movedirection * time.deltatime * speed);
    }
}

效果
在这里插入图片描述

跳跃

方式一

补充:使用charatercontroller.isgrounded实现地面检测,更加简单,但是这个方式虽然简单,但是下坡时检测可能出现问题,导致跳不起来(也可能是我使用方式不对),如果你不在乎这个影响,可以酌情选择,因为它的使用真的很简单

void update(){
	//地面检测
    isground = charactercontroller.isgrounded;
    setjump();
}

//控制跳跃
void setjump()
{
    bool jump = input.getbuttondown("jump");
    if (isground)
    {
        // 在着地时阻止垂直速度无限下降
        if (_verticalvelocity < 0.0f)
        {
            _verticalvelocity = -2f;
        }

        if (jump)
        {
            _verticalvelocity = jumpheight;
        }
    }
    else
    {
        //随时间施加重力
        _verticalvelocity += gravity * time.deltatime;
    }
    charactercontroller.move(movedirection * speed * time.deltatime + new vector3(0.0f, _verticalvelocity, 0.0f) * time.deltatime);
}

方式二

地面检测我们就用圆形球体把,因为人物是胶囊体,这种方法最合适

[header("地面检测")]
[tooltip("地面检测位置")] public transform groundcheck;
[tooltip("地面检测半径")] public float sphereradius = 0.5f;
[tooltip("是否在地面")] private bool isground;

[header("跳跃")]
[tooltip("角色跳跃的高度")] public float jumpheight = 1.5f;
[tooltip("判断是否在跳跃")] private bool isjumping;
private float _verticalvelocity;

void update()
{
	//地面检测
	isground = isgrounded();
	setjump();
}

//控制跳跃
void setjump()
{
     bool jump = input.getbuttondown("jump");
     if (isground)
      {
          isjumping = false;

          // 在着地时阻止垂直速度无限下降
          if (_verticalvelocity < 0.0f)
          {
              _verticalvelocity = -2f;
          }

          // 跳跃
          if (jump)
          {
              // h * -2 * g 的平方根 = 达到期望高度所需的速度
              _verticalvelocity = mathf.sqrt(jumpheight * -2f * gravity);
          }
      }
      else
      {
          isjumping = true;
      }

      // 随时间施加重力
      _verticalvelocity += gravity * time.deltatime;
}

//是否在地面
bool isgrounded()
{
    collider[] colliders = physics.overlapsphere(groundcheck.position, sphereradius);
    foreach (collider collider in colliders)
    {
        if (collider.gameobject != gameobject && !ischildof(collider.transform, transform)) // 忽略角色自身和所有子集碰撞体
        {
            return true;
        }
    }
    return false;
}

//判断child是否是parent的子集
bool ischildof(transform child, transform parent)
{
    while (child != null)
    {
        if (child == parent)
        {
            return true;
        }
        child = child.parent;
    }
    return false;
}

//在场景视图显示检测,方便调试
private void ondrawgizmos()
{
    gizmos.color = color.red;
    
    //地面检测可视化
    gizmos.drawwiresphere(groundcheck.position, sphereradius);
}

配置地面检测点位置
在这里插入图片描述
效果
在这里插入图片描述

下蹲

下蹲的逻辑就是让charactercontroller 的高度减半,还有中心点的位置也跟着减半,当然还有摄像机的高度,还需要注意的是人物如果头顶有东西的时候我们是不允许他起立的,不然会穿模,所以还需要一个头顶检测,头顶我们使用盒子检测最好,可以覆盖整个头部

[header("相机")]
[tooltip("摄像机相机")] public transform maincamera;
[tooltip("摄像机高度变化的平滑值")] public float interpolationspeed = 10f;
[tooltip("当前摄像机的位置")] private vector3 cameralocalposition;
[tooltip("当前摄像机的高度")] private float height;

[header("头顶检测")]
[tooltip("头顶检测位置")] public transform headcheck;
[tooltip("盒子半长、半宽、半高")] public vector3 halfextents = new vector3(0.4f, 0.5f, 0.4f);
[tooltip("判断玩家是否可以站立")] private bool iscanstand;

[header("下蹲")]
[tooltip("下蹲时候的玩家高度")] private float crouchheight;
[tooltip("判断玩家是否在下蹲")] private bool iscrouching;
[tooltip("正常站立时玩家高度")] private float standheight;

void start()
{
    standheight = charactercontroller.height;
    crouchheight = standheight / 2;
    cameralocalposition = maincamera.localposition;
    speed = walkspeed;
}

void update()
{
    //头顶检测
    iscanstand = canstand();

    setcrouch();
}
    
//控制下蹲
void setcrouch()
{
    if (input.getkey(keycode.leftcontrol))
    {
        crouch(true);
    }
    else
    {
        crouch(false);
    }
}

//newcrouching控制下蹲起立
public void crouch(bool newcrouching)
{
    if (!newcrouching && !iscanstand) return; //准备起立时且头顶有东西,不能进行站立
    iscrouching = newcrouching;
    float targetheight = iscrouching ? crouchheight : standheight;
    charactercontroller.height = targetheight; //根据下蹲状态设置下蹲时候的高度和站立的高度
    charactercontroller.center = new vector3(0, targetheight / 2, 0); //将角色控制器的中心位置y,从头顶往下减少1半的高度

	// 设置下蹲站立时候的摄像机高度
    float heighttarget = iscrouching ? cameralocalposition.y / 2 + charactercontroller.center.y : cameralocalposition.y;
    height = mathf.lerp(height, heighttarget, interpolationspeed * time.deltatime);//平滑过渡
    maincamera.localposition = new vector3(cameralocalposition.x, height, cameralocalposition.z);
}

//是否可以起立,及头顶是否有物品
bool canstand()
{
    collider[] colliders = physics.overlapbox(headcheck.position, halfextents);
    foreach (collider collider in colliders)
    {
        //忽略角色自身和所有子集碰撞体
        if (collider.gameobject != gameobject && !ischildof(collider.transform, transform))
        {
            return false;
        }
    }
    return true;
}

//在场景视图显示检测,方便调试
private void ondrawgizmos()
{
    gizmos.color = color.red;
    
    //头顶检测可视化
    gizmos.drawwirecube(headcheck.position, halfextents * 2f);
}

配置头顶检测
在这里插入图片描述
效果
在这里插入图片描述

处理下坡抖动问题

你的人物在下斜坡时可能会出现一个问题,人物下坡出现抖动,抖动其实本身可以理解,但是这个下坡抖动会影响地面检测准度,导致我们移动下坡时可能跳不起来,当然这不是我们想要的,具体的处理思路就是当我们判断在斜面时,给人物一个向下的压力,让人物没那么容易离地,而判断在斜面的方法就从人物向下打一条射线,判断射线和地面的法线如果非90度就是在斜面了

[header("斜坡检测")]
[tooltip("斜坡射线长度")] public float slopeforceraylength = 0.2f;
[tooltip("是否在斜坡")] private bool isslope;

[header("斜坡")]
[tooltip("走斜坡时向下施加的力度")] public float slopeforce = 6.0f;

void update()
{
	//斜坡检测
	isslope = onslope();
	setjump();
}

//控制跳跃
void setjump()
{
    //。。。
	
	//为了不影响跳跃,一定要在isjumping = false之前加力
    setslope();

    isjumping = false;
}
    
//控制斜坡
public void setslope()
{
    //如果处于斜坡
    if (isslope && !isjumping)
    {
        //向下增加力
        movedirection.y = charactercontroller.height / 2 * slopeforceraylength;
        charactercontroller.move(vector3.down * charactercontroller.height / 2 * slopeforce * time.deltatime);
    }
}

//是否在斜面
public bool onslope()
{
    raycasthit hit;

    // 向下打出射线(检测是否在斜坡上)
    if (physics.raycast(transform.position + charactercontroller.height / 2 * vector3.down, vector3.down, out hit, charactercontroller.height / 2 * slopeforceraylength))
    {
        // 如果接触到的点的法线,不在(0,1,0)的方向上,那么人物就在斜坡上
        if (hit.normal != vector3.up)
            return true;
    }
    return false;
}

//在场景视图显示检测,方便调试
private void ondrawgizmos()
{
    gizmos.color = color.red;

    //斜坡检测可视化
    debug.drawray(transform.position + charactercontroller.height / 2 * vector3.down, vector3.down * charactercontroller.height / 2 * slopeforceraylength, color.blue);
}

斜坡检测线
在这里插入图片描述
效果
在这里插入图片描述

实现奔跑和不同移速控制

[header("移动")]
[tooltip("角色行走的速度")] public float walkspeed = 6f;
[tooltip("角色奔跑的速度")] public float runspeed = 9f;
[tooltip("角色下蹲的速度")] public float crouchspeed = 3f;
[tooltip("角色移动的方向")] private vector3 movedirection;
[tooltip("当前速度")] private float speed;
[tooltip("是否奔跑")] private bool isrun;
    
//速度设置
void setspeed()
{
    if (isrun)
    {
        speed = runspeed;
    }
    else if (iscrouching)
    {
        speed = crouchspeed;
    }
    else
    {
        speed = walkspeed;
    }
}

//控制奔跑
void setrun()
{
    if (input.getkey(keycode.leftshift) && !iscrouching)
    {
        isrun = true;
    }
    else
    {
        isrun = false;
    }
}

效果
在这里插入图片描述

完整代码

注意:代码的参数都是经过我测试过的,复制即可使用,并不推荐大家修改,除非你知道自己在干什么

[requirecomponent(typeof(charactercontroller))]
public class movementscript : monobehaviour
{
    [tooltip("角色控制器")] public charactercontroller charactercontroller;
    [tooltip("重力加速度")] private float gravity = 9.8f;
    private float horizontal;
    private float vertical;

    [header("相机")]
    [tooltip("摄像机相机")] public transform maincamera;
    [tooltip("摄像机高度变化的平滑值")] public float interpolationspeed = 10f;
    [tooltip("当前摄像机的位置")] private vector3 cameralocalposition;
    [tooltip("当前摄像机的高度")] private float height;

    [header("移动")]
    [tooltip("角色行走的速度")] public float walkspeed = 6f;
    [tooltip("角色奔跑的速度")] public float runspeed = 9f;
    [tooltip("角色下蹲的速度")] public float crouchspeed = 3f;
    [tooltip("角色移动的方向")] private vector3 movedirection;
    [tooltip("当前速度")] private float speed;
    [tooltip("是否奔跑")] private bool isrun;

    [header("地面检测")]
    [tooltip("地面检测位置")] public transform groundcheck;
    [tooltip("地面检测半径")] public float sphereradius = 0.4f;
    [tooltip("是否在地面")] private bool isground;

    [header("头顶检测")]
    [tooltip("头顶检测位置")] public transform headcheck;
    [tooltip("盒子半长、半宽、半高")] public vector3 halfextents = new vector3(0.4f, 0.5f, 0.4f);
    [tooltip("判断玩家是否可以站立")] private bool iscanstand;

    [header("斜坡检测")]
    [tooltip("斜坡射线长度")] public float slopeforceraylength = 0.2f;
    [tooltip("是否在斜坡")] private bool isslope;

    [header("跳跃")]
    [tooltip("角色跳跃的高度")] public float jumpheight = 2.5f;
    [tooltip("判断是否在跳跃")] private bool isjumping;

    [header("下蹲")]
    [tooltip("下蹲时候的玩家高度")] private float crouchheight;
    [tooltip("判断玩家是否在下蹲")] private bool iscrouching;
    [tooltip("正常站立时玩家高度")] private float standheight;

    [header("斜坡")]
    [tooltip("走斜坡时施加的力度")] public float slopeforce = 6.0f;

    void start()
    {
        standheight = charactercontroller.height;
        crouchheight = standheight / 2;
        cameralocalposition = maincamera.localposition;
        speed = walkspeed;
    }

    void update()
    {
        horizontal = input.getaxis("horizontal");
        vertical = input.getaxis("vertical");

        //地面检测
        isground = isgrounded();

        //头顶检测
        iscanstand = canstand();

        //斜坡检测
        isslope = onslope();

        setspeed();

        setrun();

        setcrouch();

        setmove();

        setjump();
    }

    //速度设置
    void setspeed()
    {
        if (isrun)
        {
            speed = runspeed;
        }
        else if (iscrouching)
        {
            speed = crouchspeed;
        }
        else
        {
            speed = walkspeed;
        }
    }

    //控制奔跑
    void setrun()
    {
        if (input.getkey(keycode.leftshift) && !iscrouching)
        {
            isrun = true;
        }
        else
        {
            isrun = false;
        }
    }

    //控制下蹲
    void setcrouch()
    {
        if (input.getkey(keycode.leftcontrol))
        {
            crouch(true);
        }
        else
        {
            crouch(false);
        }
    }

    //控制移动
    void setmove()
    {
        if (isground)
        {
            movedirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
            //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
            // movedirection = transform.transformdirection(new vector3(h, 0, v));
            movedirection = movedirection.normalized; // 归一化移动方向,避免斜向移动速度过快  
        }
    }

    //控制跳跃
    void setjump()
    {
        if (input.getbuttondown("jump") && isground)
        {
            isjumping = true;
            movedirection.y = jumpheight;
        }
        movedirection.y -= gravity * time.deltatime;
        charactercontroller.move(movedirection * time.deltatime * speed);

        //为了不影响跳跃,一定要在isjumping = false之前加力
        setslope();

        isjumping = false;
    }

    //控制斜坡
    public void setslope()
    {
        //如果处于斜坡
        if (isslope && !isjumping)
        {
            //向下增加力
            movedirection.y = charactercontroller.height / 2 * slopeforceraylength;
            charactercontroller.move(vector3.down * charactercontroller.height / 2 * slopeforce * time.deltatime);
        }
    }

    //newcrouching控制下蹲起立
    public void crouch(bool newcrouching)
    {
        if (!newcrouching && !iscanstand) return; //准备起立时且头顶有东西,不能进行站立
        iscrouching = newcrouching;
        float targetheight = iscrouching ? crouchheight : standheight;
        float heightchange = targetheight - charactercontroller.height; //计算高度变化
        charactercontroller.height = targetheight; //根据下蹲状态设置下蹲时候的高度和站立的高度
        charactercontroller.center += new vector3(0, heightchange / 2, 0); //根据高度变化调整中心位置

        // 设置下蹲站立时候的摄像机高度
        float heighttarget = iscrouching ? cameralocalposition.y / 2 + charactercontroller.center.y : cameralocalposition.y;
        height = mathf.lerp(height, heighttarget, interpolationspeed * time.deltatime);
        maincamera.localposition = new vector3(cameralocalposition.x, height, cameralocalposition.z);
    }

    //是否可以起立,及头顶是否有物品
    bool canstand()
    {
        collider[] colliders = physics.overlapbox(headcheck.position, halfextents);
        foreach (collider collider in colliders)
        {
            //忽略角色自身和所有子集碰撞体
            if (collider.gameobject != gameobject && !ischildof(collider.transform, transform))
            {
                return false;
            }
        }
        return true;
    }

    //是否在地面
    bool isgrounded()
    {
        collider[] colliders = physics.overlapsphere(groundcheck.position, sphereradius);
        foreach (collider collider in colliders)
        {
            if (collider.gameobject != gameobject && !ischildof(collider.transform, transform)) // 忽略角色自身和所有子集碰撞体
            {
                return true;
            }
        }
        return false;
    }

    //是否在斜面
    public bool onslope()
    {
        raycasthit hit;

        // 向下打出射线(检测是否在斜坡上)
        if (physics.raycast(transform.position + charactercontroller.height / 2 * vector3.down, vector3.down, out hit, charactercontroller.height / 2 * slopeforceraylength))
        {
            // 如果接触到的点的法线,不在(0,1,0)的方向上,那么人物就在斜坡上
            if (hit.normal != vector3.up)
                return true;
        }
        return false;
    }

    //判断child是否是parent的子集
    bool ischildof(transform child, transform parent)
    {
        while (child != null)
        {
            if (child == parent)
            {
                return true;
            }
            child = child.parent;
        }
        return false;
    }


    //在场景视图显示检测,方便调试
    private void ondrawgizmos()
    {
        gizmos.color = color.red;

        //头顶检测可视化
        gizmos.drawwirecube(headcheck.position, halfextents * 2f);

        //地面检测可视化
        gizmos.drawwiresphere(groundcheck.position, sphereradius);

        //斜坡检测可视化
        debug.drawray(transform.position + charactercontroller.height / 2 * vector3.down, vector3.down * charactercontroller.height / 2 * slopeforceraylength, color.blue);
    }
}

补充,简单版本

using unityengine;

[requirecomponent(typeof(charactercontroller))]
public class movementscript : monobehaviour
{
    [tooltip("角色控制器")] public charactercontroller charactercontroller;
    [tooltip("重力加速度")] private float gravity = -19.8f;
    private float horizontal;
    private float vertical;

    [header("相机")]
    [tooltip("摄像机相机")] public transform maincamera;
    [tooltip("摄像机高度变化的平滑值")] public float interpolationspeed = 10f;
    [tooltip("当前摄像机的位置")] private vector3 cameralocalposition;
    [tooltip("当前摄像机的高度")] private float height;

    [header("移动")]
    [tooltip("角色行走的速度")] public float walkspeed = 6f;
    [tooltip("角色奔跑的速度")] public float runspeed = 9f;
    [tooltip("角色下蹲的速度")] public float crouchspeed = 3f;
    [tooltip("角色移动的方向")] private vector3 movedirection;
    [tooltip("当前速度")] private float speed;
    [tooltip("是否奔跑")] private bool isrun;

    [header("地面检测")]
    [tooltip("是否在地面")] private bool isground;

    [header("头顶检测")]
    [tooltip("头顶检测位置")] public transform headcheck;
    [tooltip("盒子半长、半宽、半高")] public vector3 halfextents = new vector3(0.4f, 0.5f, 0.4f);
    [tooltip("判断玩家是否可以站立")] private bool iscanstand;

    [header("跳跃")]
    [tooltip("角色跳跃的高度")] public float jumpheight = 2.5f;
    private float _verticalvelocity;

    [header("下蹲")]
    [tooltip("下蹲时候的玩家高度")] private float crouchheight;
    [tooltip("判断玩家是否在下蹲")] private bool iscrouching;
    [tooltip("正常站立时玩家高度")] private float standheight;

    void start()
    {
        standheight = charactercontroller.height;
        crouchheight = standheight / 2;
        cameralocalposition = maincamera.localposition;
        speed = walkspeed;
    }

    void update()
    {
        horizontal = input.getaxis("horizontal");
        vertical = input.getaxis("vertical");

        //地面检测
        isground = charactercontroller.isgrounded;

        //头顶检测
        iscanstand = canstand();

        setspeed();

        setrun();

        setcrouch();

        setmove();

        setjump();
    }

    //速度设置
    void setspeed()
    {
        if (isrun)
        {
            speed = runspeed;
        }
        else if (iscrouching)
        {
            speed = crouchspeed;
        }
        else
        {
            speed = walkspeed;
        }
    }

    //控制奔跑
    void setrun()
    {
        if (input.getkey(keycode.leftshift) && !iscrouching)
        {
            isrun = true;
        }
        else
        {
            isrun = false;
        }
    }

    //控制下蹲
    void setcrouch()
    {
        if (input.getkey(keycode.leftcontrol))
        {
            crouch(true);
        }
        else
        {
            crouch(false);
        }
    }

    //控制移动
    void setmove()
    {
        movedirection = transform.right * horizontal + transform.forward * vertical; // 计算移动方向
        //将该向量从局部坐标系转换为世界坐标系,得到最终的移动方向
        // movedirection = transform.transformdirection(new vector3(h, 0, v));
        movedirection = movedirection.normalized; // 归一化移动方向,避免斜向移动速度过快  
    }

    //控制跳跃
    void setjump()
    {
        bool jump = input.getbuttondown("jump");
        if (isground)
        {
            // 在着地时阻止垂直速度无限下降
            if (_verticalvelocity < 0.0f)
            {
                _verticalvelocity = -2f;
            }

            if (jump)
            {
                _verticalvelocity = jumpheight;
            }
        }
        else
        {
            //随时间施加重力
            _verticalvelocity += gravity * time.deltatime;
        }
        charactercontroller.move(movedirection * speed * time.deltatime + new vector3(0.0f, _verticalvelocity, 0.0f) * time.deltatime);
    }

    //newcrouching控制下蹲起立
    public void crouch(bool newcrouching)
    {
        if (!newcrouching && !iscanstand) return; //准备起立时且头顶有东西,不能进行站立
        iscrouching = newcrouching;
        float targetheight = iscrouching ? crouchheight : standheight;
        float heightchange = targetheight - charactercontroller.height; //计算高度变化
        charactercontroller.height = targetheight; //根据下蹲状态设置下蹲时候的高度和站立的高度
        charactercontroller.center += new vector3(0, heightchange / 2, 0); //根据高度变化调整中心位置

        // 设置下蹲站立时候的摄像机高度
        float heighttarget = iscrouching ? cameralocalposition.y / 2 + charactercontroller.center.y : cameralocalposition.y;
        height = mathf.lerp(height, heighttarget, interpolationspeed * time.deltatime);
        maincamera.localposition = new vector3(cameralocalposition.x, height, cameralocalposition.z);
    }

    //是否可以起立,及头顶是否有物品
    bool canstand()
    {
        collider[] colliders = physics.overlapbox(headcheck.position, halfextents);
        foreach (collider collider in colliders)
        {
            //忽略角色自身和所有子集碰撞体
            if (collider.gameobject != gameobject && !ischildof(collider.transform, transform))
            {
                return false;
            }
        }
        return true;
    }

    //判断child是否是parent的子集
    bool ischildof(transform child, transform parent)
    {
        while (child != null)
        {
            if (child == parent)
            {
                return true;
            }
            child = child.parent;
        }
        return false;
    }


    //在场景视图显示检测,方便调试
    private void ondrawgizmos()
    {
        gizmos.color = color.red;

        //头顶检测可视化
        gizmos.drawwirecube(headcheck.position, halfextents * 2f);
    }
}


实现物理碰撞效果(2024/01/02补充)

这段代码的作用是在角色控制器与其他碰撞体发生碰撞时,给碰撞体施加一个基于角色控制器速度的冲量,以模拟碰撞的物理效果。

private collisionflags m_collisionflags; // 碰撞标记

m_collisionflags = charactercontroller.move(...); // 移动角色控制器
        
private void oncontrollercolliderhit(controllercolliderhit hit)
{
    debug.log("与其他碰撞体发生碰撞");
    //获取碰撞体上的 rigidbody 组件。
    rigidbody body = hit.collider.attachedrigidbody;
    //collisionflags.below 表示角色控制器与碰撞体之间是底部接触
    if (m_collisionflags == collisionflags.below)
    {
        return;
    }
    //然后,再次检查获取到的 rigidbody 是否为空或者是否为静态物体(iskinematic),如果是,则同样不进行任何处理,直接返回。
    if (body == null || body.iskinematic)
    {
        return;
    }
    body.addforceatposition(charactercontroller.velocity * 0.1f, hit.point, forcemode.impulse); // 在碰撞点施加冲量
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇

一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

在这里插入图片描述

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com