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; } } }
问题原因
CharacterController 位置同步冲突:直接修改
transform.position时,CharacterController组件会在后续帧的逻辑中覆盖这个位置——因为CharacterController内部维护独立的位置状态,controller.Move调用会强制同步其内部状态到Transform,导致玩家回到原位置。移动逻辑未暂停:切换到探索模式后,
PlayerMotor的ProcessMove方法可能仍在被InputManager调用。即使输入为零向量,controller.Move(Vector3.zero)也会触发CharacterController的位置同步逻辑,覆盖你手动设置的位置。切回建造模式未恢复位置:
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

