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

Unity中CharacterController velocity始终为0,头部晃动脚本失效求助

问题:HeadBobController无法获取CharacterController的velocity值

我花了两天编写头部晃动(HeadBob)脚本,始终卡在获取CharacterController的velocity环节,该值一直返回(0,0,0)。玩家对象上挂载了两个脚本:PlayerMotor负责角色移动,HeadBobController基于controller velocity计算并应用头部晃动效果。PlayerMotor中调用controller.velocity可正常获取值,但HeadBobController中始终为(0,0,0),怀疑是脚本执行顺序导致的问题?我完全按照教程操作,仅对相机和移动脚本做了细微修改。

相关脚本

PlayerMotor脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerMotor : MonoBehaviour
{
    private CharacterController controller;
    private Vector3 playerVelocity;
    private PlayerLook look;

    private float speed = 5f;
    public float crouchSpeed = 2.5f;
    public float walkSpeed = 5f;
    public float sprintSpeed = 8f;
    public float jumpHeight = 0.6f;
    private float crouchTimer = 1f;
    public float gravity = -9.8f;
    private float standingHeight = 2;
    private float crouchingHeight = 1;
    private float targetHeight = 2;
    private bool isGrounded;
    private bool crouching= false;
    private bool sprinting = false;
    private bool lerpCrouch=false;
   
    
    Vector3 initialCameraPosition;
    void Start()
    {
        controller = GetComponent<CharacterController>();
        look = GetComponent<PlayerLook>();
        initialCameraPosition = look.cam.transform.localPosition;
    }

    // Update is called once per frame
    void Update()
    {
        isGrounded = controller.isGrounded;
        if(lerpCrouch)
        {
            crouchTimer += Time.deltaTime;
            float p = crouchTimer / 1;
            p *= p;
            if(controller.height<standingHeight && targetHeight == standingHeight)
                {           
                var castOrigin = transform.position + new Vector3(0, controller.height / 2, 0);
                if (Physics.Raycast(castOrigin, Vector3.up, out RaycastHit hit, 0.2f))
                {
                    var distanceToCeiling = hit.point.y - castOrigin.y;
                    targetHeight = Mathf.Max (controller.height + distanceToCeiling -0.1f,crouchingHeight);
                }
            }
            controller.height = Mathf.Lerp(controller.height, targetHeight, p);         
            if(p>1)
            {
                lerpCrouch = false;
                crouchTimer = 0f;

            }
        }
    }
    public void ProcessMove(Vector2 input)
    {
        Vector3 moveDirection = Vector3.zero;
        moveDirection.x = input.x;
        moveDirection.z = input.y;  
        controller.Move(transform.TransformDirection(moveDirection)*speed*Time.deltaTime);
        playerVelocity.y += gravity * Time.deltaTime;
        if(isGrounded &&playerVelocity.y <0)
        {
            playerVelocity.y = -2f;
        }
      
        if (!Mathf.Approximately(targetHeight, controller.height))
        {
            var halfHeightDifference = new Vector3(0, (standingHeight - controller.height) / 2, 0);
            var newCameraPosition = initialCameraPosition - halfHeightDifference;
            look.cam.transform.localPosition = newCameraPosition;
        }
        
        controller.Move(playerVelocity*Time.deltaTime);
    }
    public void Jump()
    { 
        if(isGrounded)
        {
            playerVelocity.y = Mathf.Sqrt(jumpHeight*-3.0f * gravity);
        }
    }
    public void Crouch()
    {
        crouching = !crouching;
        sprinting = false;
        if (crouching)
        {
            targetHeight = crouchingHeight;
            speed = crouchSpeed;
        }
        else
        {
            targetHeight = standingHeight;
            speed = walkSpeed;
        }
        crouchTimer = 0;
        lerpCrouch = true;
    }
    public void Sprint()
    {
        if(!crouching)
        {
            sprinting = !sprinting;
            if (sprinting)
            {
                speed = sprintSpeed;
            }
            else
            {
                speed = walkSpeed;
            }
        }
    }
}

HeadBobController脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HeadBobController : MonoBehaviour
{
    [SerializeField] private bool _enable = true;

    [SerializeField, Range(0, 0.1f)] private float _amplitude = 0.015f;
    [SerializeField, Range(0, 30)] private float _frequency = 10.0f;

    [SerializeField] private Transform _camera = null;
    [SerializeField] private Transform _cameraHolder = null;

    private float _toggleSpeed = 3.0f;
    private Vector3 _startPos;
    private CharacterController _controller;

    private void Awake()
    {
        _controller = GetComponent<CharacterController>();
        _startPos = _cameraHolder.localPosition;
    }

    private void Update()
    {
        if (!_enable)
            return;

        CheckMotion();
        ResetPosition();
        _camera.LookAt(FocusTarget());
    }

    private void CheckMotion()
    {
        float speed = new Vector3(_controller.velocity.x, 0, _controller.velocity.z).magnitude;
        Debug.Log(_controller.velocity.magnitude);
        if (speed < _toggleSpeed)
            return;
        if (!_controller.isGrounded)
            return;

        PlayMotion(FootStepMotion());
    }

    private void PlayMotion(Vector3 motion)
    {
        _camera.localPosition += motion;
    }

    private Vector3 FootStepMotion()
    {
        Vector3 pos = Vector3.zero;
        pos.y += Mathf.Sin(Time.time * _frequency) * _amplitude;
        pos.x += Mathf.Cos(Time.time * _frequency / 2) * _amplitude * 2;
        return pos;
    }

    private void ResetPosition()
    {
        if (_camera.localPosition == _startPos)
            return;
        _camera.localPosition = Vector3.Lerp(_camera.localPosition, _startPos, 1 * Time.deltaTime);
    }

    private Vector3 FocusTarget()
    {
        Vector3 pos = new Vector3(transform.position.x, transform.position.y + _cameraHolder.localPosition.y, transform.position.z);
        pos += _cameraHolder.forward * 15.0f;
        return pos;
    }
}

解决方案

1. 调整脚本执行顺序

Unity默认按脚本名称排序执行,若HeadBobController在PlayerMotor之前执行,此时controller.Move()还未调用,velocity尚未更新:

  • 打开Edit > Project Settings > Script Execution Order
  • 将HeadBobController拖到PlayerMotor下方,确保PlayerMotor先执行,更新velocity后HeadBobController再读取

2. 直接从PlayerMotor获取移动速度

若调整执行顺序无效,可在PlayerMotor中暴露当前移动速度,让HeadBobController直接读取:
修改PlayerMotor,添加公开属性:

public float CurrentMoveSpeed { get; private set; }

public void ProcessMove(Vector2 input)
{
    Vector3 moveDirection = Vector3.zero;
    moveDirection.x = input.x;
    moveDirection.z = input.y;  
    Vector3 transformedDir = transform.TransformDirection(moveDirection);
    controller.Move(transformedDir * speed * Time.deltaTime);
    // 计算地面移动速度
    CurrentMoveSpeed = new Vector3(transformedDir.x, 0, transformedDir.z).magnitude * speed;
    
    // 原有代码保留...
}

修改HeadBobController:

private PlayerMotor _playerMotor;

private void Awake()
{
    _controller = GetComponent<CharacterController>();
    _playerMotor = GetComponent<PlayerMotor>();
    _startPos = _cameraHolder.localPosition;
}

private void CheckMotion()
{
    float speed = _playerMotor.CurrentMoveSpeed;
    Debug.Log(speed);
    if (speed < _toggleSpeed)
        return;
    if (!_controller.isGrounded)
        return;

    PlayMotion(FootStepMotion());
}

3. 验证CharacterController引用

确认HeadBobController中的_controller正确获取了玩家对象上的组件,可在Awake中添加校验:

private void Awake()
{
    _controller = GetComponent<CharacterController>();
    if(_controller == null)
    {
        Debug.LogError("HeadBobController未找到CharacterController组件!");
    }
    _startPos = _cameraHolder.localPosition;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 13:13:24