You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity3D松鼠角色下坡时浮空问题求助

Unity松鼠角色下坡浮空问题解决方案

问题说明

开发以松鼠为角色的Unity3D游戏时,角色行走或奔跑下坡会出现浮空现象。尝试过AddForce、调整移动速度、设置重力,也通过CharacterController.isGrounded判断着地状态,但始终无法让角色紧密贴合坡面,怀疑问题与水平动画有关。

原角色控制脚本

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using System;

public class Squirrel : MonoBehaviour {

    Animator squirrel;
    public float gravity = 1.0f;
    private Vector3 moveDirection = Vector3.zero;

    float axisH, axisV;

    public static int munitions = 0;

    Rigidbody rb;

    [SerializeField]
    float walkSpeed = 2f, runSpeed = 8f, rotSpeed = 100f, jumpForce = 350;

    private bool isJumpKeyDown = false;
    [SerializeField] bool isJumping = false;

    Animator characterAnimator;

    int JumpCount = 0;
    public int MaxJumps = 1; //Maximum amount of jumps (i.e. 2 for double jumps)

    [SerializeField] GameObject nb_munitions;

    CharacterController characterController;

    // Use this for initialization
    void Start () {

        munitions = 0;
        squirrel = GetComponent<Animator>();
        rb = GetComponentInChildren<Rigidbody>();
        characterAnimator = GetComponent<Animator>();
        JumpCount = MaxJumps;
        characterController = GetComponent<CharacterController>();
    }


    // Update is called once per frame
    void Update()
    {
        GetInput();
        nb_munitions.GetComponent<Text>().text = "Glands : " + munitions; //Affichage du score
        Move();
    }

    private void FixedUpdate()
    {
        if (isJumpKeyDown)
        {
            squirrel.SetTrigger("jump");
            JumpCount -= 1;
            isJumpKeyDown = false;
        }
    }
    public void GetInput()
    {
        axisV = Input.GetAxis("Vertical");
        axisH = Input.GetAxis("Horizontal");
    }

    private void Move()
    {
        if (characterController.isGrounded)
        {
            //On the ground
        }
        else
        {
            //on the air
        }


        if (axisV > 0)
        {
            if (Input.GetKeyDown(KeyCode.LeftControl))
            {
                transform.position += Vector3.forward * walkSpeed * Time.deltaTime;
                squirrel.SetBool("walk", true);
            }
            else
            {
                transform.position += Vector3.forward * runSpeed * Time.deltaTime;

                squirrel.SetFloat("run", axisV);
                squirrel.SetBool("walk", false);
            }
        }
        else
        {
            squirrel.SetFloat("run", 0);
        }

        if (axisH != 0 && axisV == 0)
        {
            squirrel.SetFloat("h", axisH);
        }
        else
        {
            squirrel.SetFloat("h", 0);
        }

        if (axisH != 0)
        {
            transform.Rotate(Vector3.up * rotSpeed * Time.deltaTime * axisH);
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (JumpCount > 0)
            {
                isJumpKeyDown = true;
            }
        }

        //Call munitions
        if (Input.GetKeyDown(KeyCode.LeftShift))
        {
            if (Squirrel.munitions > 0)
            {
                SpawnerScript.Instance.NewSpawnRequest();
                munitions--;
            }
        }

    }

}

核心问题与修复方案

1. 停止直接修改Transform位置

当前代码用transform.position += Vector3.forward * speed * Time.deltaTime直接移动角色,完全绕过了CharacterController的碰撞与地形贴合逻辑,这是浮空的核心原因。必须改用CharacterController.Move()方法执行移动,该方法会自动处理角色与坡面的碰撞贴合。

2. 正确实现重力与坡面贴合逻辑

在Move()方法中,需要根据角色是否着地动态调整重力,并基于输入计算世界空间下的移动方向:

  • 着地时重置重力方向,计算移动方向并转换为世界空间
  • 空中时持续施加重力
  • 用characterController.Move()执行移动

3. 移除冲突组件

同时使用CharacterController和Rigidbody会导致物理逻辑冲突,角色主体不需要Rigidbody,直接删除该组件(若子物体有物理需求保留即可)。

4. 同步动画与移动状态

动画触发逻辑需与实际移动状态绑定,而非单纯依赖输入轴,避免动画与移动脱节。

修改后的核心Move方法示例

private void Move()
{
    if (characterController.isGrounded)
    {
        // 重置跳跃计数与状态
        JumpCount = MaxJumps;
        isJumping = false;

        // 基于输入计算本地移动方向,转换为世界空间
        moveDirection = new Vector3(axisH, 0, axisV);
        moveDirection = transform.TransformDirection(moveDirection);

        // 根据输入设置移动速度与动画
        if (Input.GetKey(KeyCode.LeftControl) && axisV > 0)
        {
            moveDirection *= walkSpeed;
            squirrel.SetBool("walk", true);
            squirrel.SetFloat("run", 0);
        }
        else if (axisV > 0)
        {
            moveDirection *= runSpeed;
            squirrel.SetFloat("run", axisV);
            squirrel.SetBool("walk", false);
        }
        else
        {
            moveDirection = Vector3.zero;
            squirrel.SetFloat("run", 0);
            squirrel.SetBool("walk", false);
        }

        // 跳跃处理
        if (Input.GetKeyDown(KeyCode.Space) && JumpCount > 0)
        {
            // 计算跳跃初速度(基于重力和跳跃力)
            moveDirection.y = Mathf.Sqrt(jumpForce * 2f * gravity);
            isJumping = true;
            squirrel.SetTrigger("jump");
            JumpCount--;
        }
    }
    else
    {
        // 空中持续施加重力
        moveDirection.y -= gravity * Time.deltaTime;
        isJumping = true;
    }

    // 水平旋转与动画控制
    if (axisH != 0)
    {
        transform.Rotate(Vector3.up * rotSpeed * Time.deltaTime * axisH);
        squirrel.SetFloat("h", axisV == 0 ? axisH : 0);
    }
    else
    {
        squirrel.SetFloat("h", 0);
    }

    // 执行CharacterController移动
    characterController.Move(moveDirection * Time.deltaTime);

    // 弹药调用逻辑
    if (Input.GetKeyDown(KeyCode.LeftShift))
    {
        if (munitions > 0)
        {
            SpawnerScript.Instance.NewSpawnRequest();
            munitions--;
        }
    }
}

额外优化建议

  • 调整gravity值:原1.0f过小,建议设置为9.8f模拟真实重力,或根据游戏手感调整至5-15之间
  • 检查CharacterController参数:确保Slope Limit不低于坡面角度,否则角色无法正常上下坡
  • 动画根运动设置:若用代码控制移动,关闭动画的根运动选项,避免与代码移动冲突

内容的提问来源于stack exchange,提问作者Sans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 05:30:59