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

Unity切换探索模式时玩家瞬移后立即复位的问题排查

Unity模式切换时玩家位置复位问题排查与解决

问题描述

我在Unity开发游戏,需求是:按下B键执行switched modes(切换模式)时,切换至探索模式玩家需移动到startPosition,切回建造模式则回到原位置。
涉及两个核心脚本:控制玩家移动的PlayerMotor,负责模式切换的SwitchModes。实际运行时,切换到探索模式玩家仅在1帧内显示在正确位置,随后立即复位到原位置。


核心脚本代码

PlayerMotor 脚本

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

public class PlayerMotor : MonoBehaviour
{
    private CharacterController controller;
    public Vector3 playerVelocity;
    public float speed = 5f;
    public bool isGrounded;
    public float gravity = -9.8f;
    public float jumpHeight = 1.5f;

    private bool lerpCrouch;
    private bool crouching;
    //private bool sprinting;
    private float crouchTimer;
    // Start is called before the first frame update
    void Start()
    {
        controller = GetComponent<CharacterController>();
    }

    // Update is called once per frame
    void Update()
    {
        isGrounded = controller.isGrounded;
        if (lerpCrouch)
        {
            crouchTimer += Time.deltaTime;
            float p = crouchTimer / 1;
            p *= p;
            if (crouching)
                controller.height = Mathf.Lerp(controller.height, 1, p);
            else
                controller.height = Mathf.Lerp(controller.height, 2, p);

            if (p > 1)
            {
                lerpCrouch = false;
                crouchTimer = 0f;
            }
        }
    }
    public void Crouch()
    {
        crouching = !crouching;
        crouchTimer = 0;
        lerpCrouch = true;
    }
    //public void Sprint()
    //{
    //    sprinting = !sprinting;
    //    if (sprinting)
    //        speed = 15;
    //    else
    //        speed = 5;
    //}
    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;
        controller.Move(playerVelocity * Time.deltaTime);

    }
    public void Jump()
    {
        if (isGrounded)
        {
            playerVelocity.y = Mathf.Sqrt(jumpHeight * -3.0f * gravity);
        }
    }
}

SwitchModes 脚本

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

public class SwitchModes : MonoBehaviour
{
    private string mode;
    private InputManager inputManager;

    [Header("Exploring")]
    public GameObject exploringUI;

    [Header("Building")]
    public GameObject buildingUI;

    private Vector3 buildingPosition;

    private string activemode;

    void Start()
    {
        inputManager = GetComponent<InputManager>();
        activemode = "building";
    }

    void Update()
    {
        if (inputManager.onFoot.SwitchModes.triggered)
        {
            SwitchMode();
        }

        if (activemode == "exploring")
        {
            Exploring();
        }
        else if (activemode == "building")
        {
            Building();
        }
        else
        {
            Debug.Log("the code is completely fucked up, give up");
        }
    }

    public void SwitchMode()
    {
        if (activemode == "building")
        {
            buildingPosition = transform.position;
            Vector3 startPosition = GetComponent<PlaceEntriesAndExits>().placedStartPosition;
            transform.position = new Vector3(startPosition.x, transform.position.y, startPosition.z);
            activemode = "exploring";


        }
        else if (activemode == "exploring")
        {
            //transform.position = buildingTransform.position;
            //transform.rotation = buildingTransform.rotation;
            //transform.localScale = buildingTransform.localScale;

            activemode = "building";
        }
    }

    public void Exploring()
    {
        exploringUI.SetActive(true);
        buildingUI.SetActive(false);

        GetComponent<Building>().buildModelList[GetComponent<Building>().currentSelectedBuild].SetActive(false);
        GetComponent<Exploring>().enabled = true;
        GetComponent<Building>().enabled = false;
    }
    public void Building()
    {
        exploringUI.SetActive(false);
        buildingUI.SetActive(true);

        GetComponent<Exploring>().enabled = false;
        if (GetComponent<PlaceEntriesAndExits>().endPlaced)
        {
            GetComponent<Building>().enabled = true;
            GetComponent<PlaceEntriesAndExits>().enabled = false;
        }
        else
        {
            GetComponent<PlaceEntriesAndExits>().enabled = true;

            GetComponent<Building>().enabled = false;
        }
    }
}

问题原因

  1. CharacterController 位置同步冲突:直接修改transform.position时,CharacterController组件会在后续帧的逻辑中覆盖这个位置——因为CharacterController内部维护独立的位置状态,controller.Move调用会强制同步其内部状态到Transform,导致玩家回到原位置。

  2. 移动逻辑未暂停:切换到探索模式后,PlayerMotor的ProcessMove方法可能仍在被InputManager调用。即使输入为零向量,controller.Move(Vector3.zero)也会触发CharacterController的位置同步逻辑,覆盖你手动设置的位置。

  3. 切回建造模式未恢复位置:SwitchMode中探索切建造的分支代码被注释,导致切回时玩家无法回到之前保存的buildingPosition。


解决方案

1. 控制CharacterController的启用状态

切换模式时,暂时禁用CharacterController修改位置,再重新启用:
修改SwitchMode方法:

public void SwitchMode()
{
    var controller = GetComponent<CharacterController>();
    if (activemode == "building")
    {
        buildingPosition = transform.position;
        Vector3 startPosition = GetComponent<PlaceEntriesAndExits>().placedStartPosition;
        
        controller.enabled = false;
        transform.position = new Vector3(startPosition.x, transform.position.y, startPosition.z);
        controller.enabled = true;
        
        activemode = "exploring";
    }
    else if (activemode == "exploring")
    {
        controller.enabled = false;
        transform.position = buildingPosition;
        controller.enabled = true;
        
        activemode = "building";
    }
}

2. 暂停/恢复PlayerMotor的移动逻辑

在模式切换时禁用/启用PlayerMotor脚本,避免ProcessMove持续调用:
在Exploring方法中添加:

GetComponent<PlayerMotor>().enabled = false;

在Building方法中添加:

GetComponent<PlayerMotor>().enabled = true;

3. 拦截探索模式下的输入传递

在InputManager中添加模式判断,仅在建造模式时调用PlayerMotor.ProcessMove,避免探索模式下不必要的移动逻辑触发。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 19:57:35