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

Unity3D中如何实现角色随摄像机指向同步调整朝向?

Unity角色移动与摄像机同步问题

问题现象

移动摄像机时,处于移动状态的角色会原地旋转,且角色的前进方向仅在左摇杆输入更新时才会变动,不符合预期。
自行尝试修复均未成功:

  • 在Update生命周期中更新方向,会导致角色原地转圈
  • 将更新逻辑绑定到右摇杆(控制摄像机)的触发事件中,会出现角色旋转和移动方向与预期不符的问题,仅在推动左摇杆更新角色移动时问题会临时修复

现有控制方案

  • 左摇杆:移动玩家
  • 右摇杆:控制摄像机
  • 东侧按键:跳跃
  • 北侧按键:奔跑

预期需求

移动摄像机时,角色可以同步更新自身朝向与前进方向,而非仅在左摇杆操作时才更新方向。

相关依赖

项目中影响该逻辑的Unity依赖包为:Cinemachine、Input System

受影响的移动代码

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;
using UnityEngine.AI;
using UnityEngine.InputSystem;

public class PlayerMovement : MonoBehaviour
{
    private DefaultControls controls;

    [Header("Unity General")]
    [SerializeField]
    private CharacterController controller;
    public Transform cameraTransform;
    public InputActionReference cameraControl;

    [Header("General Settings")]//Player movement.
    public bool canMovePlayer;
    private Vector2 currentMovementInput;
    private Vector3 currentMovement;
    private Vector3 currentRunMovement;
    private bool isMovementPressed;
    private bool isRunPressed;
    [Space]//Animator stuff.
    public Animator characterAnimator;
    private int isWalkingHash;
    private int isRunningHash;
    [Space]//Player running speed & how fast the player will turn when going left or right.
    public float rotationFactorPerFrame = 15.0f;
    public float runMultiplier = 3.0f;
    [Space]//Default gravity for when the player is falling and gravity for when the player is grounded.
    public float gravity = -9.81f;
    public float groundedGravity = -0.05f;
    [Space]//Playing jumping.
    public float initialJumpVelocity;
    private bool isJumpPressed = false;
    private float maxJumpHeight = 1f;
    private float maxJumpTime = 0.5f;
    private bool isJumping = false;
    private int isJumpingHash;
    private bool isJumpAnimating = false;

    private void Start()
    {
        Cursor.lockState = CursorLockMode.Locked;

        npcInteraction = GetComponent<NPCInteraction>();
    }

    private void Awake()
    {
        controls = new DefaultControls();
        controller = GetComponent<CharacterController>();

        isWalkingHash = Animator.StringToHash("isWalking");
        isRunningHash = Animator.StringToHash("isRunning");
        isJumpingHash = Animator.StringToHash("isJumping");

        controls.Movement.Walking.started += OnMovementInput;
        controls.Movement.Walking.canceled += OnMovementInput;
        controls.Movement.Walking.performed += OnMovementInput;
        controls.Movement.Run.started += OnRun;
        controls.Movement.Run.canceled += OnRun;
        controls.Movement.Jump.started += OnJump;
        controls.Movement.Jump.canceled += OnJump;

        SetupJumpVariables();
    }

    private void SetupJumpVariables()
    {
        float timeToApex = maxJumpTime / 2;
        gravity = (-2 * maxJumpHeight) / Mathf.Pow(timeToApex, 2);
        initialJumpVelocity = (2 * maxJumpHeight) / timeToApex;
    }

    private void HandleJump()
    {
        if (!isJumping && controller.isGrounded && isJumpPressed)
        {
            characterAnimator.SetBool(isJumpingHash, true);
            isJumpAnimating = true;
            isJumping = true;
            currentMovement.y = initialJumpVelocity * 0.5f;
            currentRunMovement.y = (initialJumpVelocity + 0.5f) * 0.5f;
        }
        else if (!isJumpPressed && isJumping && controller.isGrounded)
        {
            isJumping = false;
        }
    }

    private void OnJump(InputAction.CallbackContext context)
    {
        isJumpPressed = context.ReadValueAsButton();
    }

    private void OnRun(InputAction.CallbackContext context)
    {
        isRunPressed = context.ReadValueAsButton();
    }

    private void HandleRotation()
    {
        Vector3 positionToLookAt;

        //Change in position our character should point to.
        positionToLookAt.x = currentMovement.x;
        positionToLookAt.y = 0.0f;
        positionToLookAt.z = currentMovement.z;

        //Current rotation of our character.
        Quaternion currentRotation = transform.rotation;

        if (currentMovementInput != Vector2.zero)
        {
            //Creates a new rotation based on where the player is currently pressing.
            float targetAngle = Mathf.Atan2(currentMovementInput.x, currentMovementInput.y) * Mathf.Rad2Deg + cameraTransform.eulerAngles.y;
            Quaternion targetRotation = Quaternion.Euler(0f, targetAngle, 0f);
            transform.rotation = Quaternion.Lerp(currentRotation, targetRotation, rotationFactorPerFrame * Time.deltaTime);
        }
    }

    private void OnMovementInput(InputAction.CallbackContext context)
    {
        currentMovementInput = context.ReadValue<Vector2>();
        currentMovement = new Vector3(currentMovementInput.x, 0f, currentMovementInput.y);
        currentRunMovement.x = currentMovementInput.x * runMultiplier;
        currentRunMovement.z = currentMovementInput.y * runMultiplier;
        MovementDirection();
        isMovementPressed = currentMovementInput.x != 0 || currentMovementInput.y != 0;
    }

    private void MovementDirection()
    {
        currentMovement = cameraTransform.forward * currentMovement.z + cameraTransform.right * currentMovement.x;
        currentMovement.y = 0f;

        currentRunMovement = cameraTransform.forward * currentRunMovement.z + cameraTransform.right * currentRunMovement.x;
        currentRunMovement.y = 0f;
    }

    private void HandleAnimation()
    {
        bool isWalking = characterAnimator.GetBool(isWalkingHash);
        bool isRunning = characterAnimator.GetBool(isRunningHash);

        if (isMovementPressed && !isWalking)
        {
            characterAnimator.SetBool(isWalkingHash, true);
        }
        else if (!isMovementPressed && isWalking)
        {
            characterAnimator.SetBool(isWalkingHash, false);
        }

        if ((isMovementPressed && isRunPressed) && !isRunning)
        {
            characterAnimator.SetBool(isRunningHash, true);
        }
        else if ((!isMovementPressed || !isRunPressed) && isRunning)
        {
            characterAnimator.SetBool(isRunningHash, false);
        }
    }

    private void HandleGravity()
    {
        bool isFalling = currentMovement.y <= 0.0f;
        float fallMultiplier = 1.5f;

        if (controller.isGrounded)
        {
            characterAnimator.SetBool(isJumpingHash, false);
            isJumpAnimating = false;
            currentMovement.y = groundedGravity;
            currentRunMovement.y = groundedGravity;
        }
        else if (isFalling)
        {
            float previousYVelocity = currentMovement.y;
            float newYVelocity = currentMovement.y + (gravity * fallMultiplier * Time.deltaTime);
            float nextYVelocity = (previousYVelocity + newYVelocity) * 0.5f;
            currentMovement.y = nextYVelocity;
            currentRunMovement.y = nextYVelocity;
        }
        else
        {
            float previousYVelocity = currentMovement.y;
            float newYVelocity = currentMovement.y + (gravity * Time.deltaTime);
            float nextYVelocity = (previousYVelocity + newYVelocity) * 0.5f;
            currentMovement.y = nextYVelocity;
            currentRunMovement.y = nextYVelocity;
        }
    }

    // Update is called once per frame
    public void Update()
    {
        HandleRotation();
        HandleAnimation();

        controller.Move(currentMovement * Time.deltaTime);

        characterAnimator.SetFloat("Speed", controls.Movement.Walking.ReadValue<Vector2>().magnitude);

        if (isRunPressed)
        {
            controller.Move(currentRunMovement * Time.deltaTime);
        }
        else
        {
            controller.Move(currentMovement * Time.deltaTime);
        }

        HandleGravity();
        HandleJump();

        if (cameraControl.action.triggered)
        {
            MovementDirection();
        }

        LockOnTarget();
        Interaction();
    }

    private void OnEnable()
    {
        controls.Movement.Enable();
    }

    private void OnDisable()
    {
        controls.Movement.Disable();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 09:42:03