Unity中玩家无法随移动平台移动的问题排查请求
问题分析:玩家重置后无法随移动平台移动
问题现象
- 游戏Build模式下,玩家初始状态跳上移动平台可正常跟随移动;
- 玩家死亡失活、重置位置并重新激活后,**常规移动模式(movemode=0)下无法随平台移动,但攻击模式(movemode=3)**下功能正常。
相关脚本
移动平台逻辑脚本(PlatformMovement.cs)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlatformMovement : MonoBehaviour { public int PlatformDirection = 0; public int MoveSpeed; void Update() { if (PauseMenu.Paused == 0) { if (PlatformDirection == 0) { transform.Translate(Vector3.forward * MoveSpeed * Time.deltaTime); } if (PlatformDirection == 1) { transform.Translate(Vector3.back * MoveSpeed * Time.deltaTime); } } } void OnCollisionEnter (Collision col) { if(col.gameObject.name == "Wall") { if (PlatformDirection == 0) { PlatformDirection = 1; } else { PlatformDirection = 0; } } } }
平台触发器脚本(PlatformDetector.cs)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlatformDetector : MonoBehaviour { public GameObject Player; private void OnTriggerEnter(Collider other) { if (other.gameObject == Player) { Player.transform.parent = transform; } } private void OnTriggerExit(Collider other) { if (other.gameObject == Player) { Player.transform.parent = null; } } }
玩家控制脚本(PlayerController.cs)
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public float _moveSpeed; public float _gravity; public float _jumpSpeed; public float _bounceSpeed; public static int movemode; public GameObject Camera; public Animator animator; public AudioSource audioSource; public AudioClip JumpSound; public AudioClip BoingSound; public AudioClip HitSound; public static int OnIce; private CharacterController _controller; private Rigidbody rb; private float horizontalInput; private float verticalInput; public static float _directionY; // Start is called before the first frame update void Start() { _controller = GetComponent<CharacterController>(); audioSource = gameObject.GetComponent<AudioSource>(); movemode = 0; PauseMenuController.ExitingLevel = 0; } // Update is called once per frame void Update() { if (movemode == 0) { _moveSpeed = 5f; _gravity = 9.81f; _jumpSpeed = 4f; if (OnIce == 0) { horizontalInput = Input.GetAxis("Horizontal"); verticalInput = Input.GetAxis("Vertical"); } if (OnIce == 1) { horizontalInput = Input.GetAxis("HorizontalIce"); verticalInput = Input.GetAxis("VerticalIce"); } Vector3 direction = new Vector3(horizontalInput, 0, verticalInput); direction = Camera.transform.TransformDirection(direction); if (_controller.isGrounded) { if(Input.GetButtonDown("Jump")) { audioSource.PlayOneShot(JumpSound, 1); _directionY = _jumpSpeed; } } if (!_controller.isGrounded) { _directionY -= _gravity * Time.deltaTime; direction.y = _directionY; } else { _directionY -= 0 * Time.deltaTime; direction.y = _directionY; } _controller.Move(direction * _moveSpeed * Time.deltaTime); animator.SetBool("isWalking", verticalInput != 0 || horizontalInput != 0); animator.SetBool("isGrounded", _controller.isGrounded == true); animator.SetBool("Attacking", movemode == 3); animator.SetBool("Swimming", movemode == 1); animator.SetBool("LevelComplete", movemode == 4); } if (movemode == 1) { _moveSpeed = 3f; _gravity = 4f; _jumpSpeed = 1.5f; float horizontalInput = Input.GetAxis("Horizontal"); float verticalInput = Input.GetAxis("Vertical"); Vector3 direction = new Vector3(horizontalInput, 0, verticalInput); direction = Camera.transform.TransformDirection(direction); if(Input.GetButtonDown("Jump")) { _directionY = _jumpSpeed; } _directionY -= _gravity * Time.deltaTime; direction.y = _directionY; _controller.Move(direction * _moveSpeed * Time.deltaTime); animator.SetBool("Swimming", movemode == 1); animator.SetBool("Attacking", movemode == 3); } if (movemode == 3) { animator.SetBool("Attacking", movemode == 3); } if (movemode == 4) { animator.SetBool("LevelComplete", movemode == 4); } } void OnTriggerEnter (Collider col) { if(col.gameObject.name == "Spike") { if (Pos_reset.invincibilityframe > 2) { HealthCounter.Health -= 1; if (HealthCounter.Health > 0) { audioSource.PlayOneShot(HitSound, 1); _directionY = _jumpSpeed; } Pos_reset.invincibilityframe = 0; } } if(col.gameObject.name == "Trampoline") { audioSource.PlayOneShot(BoingSound, 1); _directionY = _bounceSpeed; } } }
核心原因
- CharacterController的Move方法覆盖父物体移动:常规移动模式(movemode=0)下,玩家持续调用
_controller.Move(),该方法直接修改玩家的世界空间位置,会忽略父物体(平台)的位移影响;而攻击模式(movemode=3)下没有调用Move方法,所以平台的父物体移动能正常生效。 - 重置后触发器未正确触发:玩家死亡失活再激活时,若激活位置已经在平台的触发器范围内,
OnTriggerEnter事件不会触发,导致玩家没有被设置为平台的子物体,自然无法跟随移动。 - 重置时父物体状态未清理:玩家重置过程中,如果没有将父物体重置为
null,可能导致后续触发器逻辑出现异常,无法正确绑定平台父物体。
修复方案
方案1:兼容CharacterController与平台移动(推荐)
修改玩家移动逻辑,主动叠加平台的移动速度,避免Move方法覆盖父物体位移:
- 在
PlatformMovement.cs中添加获取平台速度的方法:
public Vector3 GetPlatformVelocity() { if (PauseMenu.Paused == 0) { return PlatformDirection == 0 ? Vector3.forward * MoveSpeed : Vector3.back * MoveSpeed; } return Vector3.zero; }
- 在
PlayerController.cs的movemode=0逻辑中,修改Move调用:
// 计算移动方向后,添加平台速度 Vector3 platformVelocity = Vector3.zero; if (transform.parent != null) { PlatformMovement platform = transform.parent.GetComponent<PlatformMovement>(); if (platform != null) { platformVelocity = platform.GetPlatformVelocity(); } } // 叠加平台速度后再移动 _controller.Move((direction * _moveSpeed + platformVelocity) * Time.deltaTime);
方案2:重置时主动检测平台触发器
在玩家重置激活的脚本(如Pos_reset)中,添加主动检测逻辑,确保玩家在平台上时正确设置父物体:
// 玩家激活后执行 Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, 0.2f); foreach (var collider in nearbyColliders) { PlatformDetector detector = collider.GetComponent<PlatformDetector>(); if (detector != null && detector.Player == gameObject) { transform.parent = detector.transform; break; } }
方案3:替换CharacterController为Rigidbody
如果项目逻辑允许,将玩家的CharacterController替换为运动学Rigidbody(勾选Is Kinematic),通过物理引擎处理平台跟随,避免手动移动与父物体位移的冲突。
内容的提问来源于stack exchange,提问作者user25986742
相关产品推荐
相关产品推荐

