using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.XR; using System.Collections; public class ElevatorSceneTransition : MonoBehaviour { [Header("遷移先の設定")] [SerializeField] private string bossSceneName = "BossStage"; [SerializeField] private string playerTag = "Player"; [Header("ローディングUI設定")] [SerializeField] private GameObject loadingCanvas; [SerializeField] private Text progressText; [SerializeField] private Slider progressSlider; [Header("エレベーター下降演出設定")] [Tooltip("エレベーターの部屋(壁や床、UIすべて)をまとめているオブジェクトを指定してください(※プレイヤーは外しておきます)")] [SerializeField] private Transform elevatorCageTransform; [Tooltip("ガタガタ揺れる激しさ(0.01~0.03あたりが自然です)")] [SerializeField] private float shakeMagnitude = 0.015f; [Tooltip("下降中のコントローラーの振動の強さ(0.0 ~ 1.0)")] [SerializeField] private float hapticAmplitude = 0.15f; // ─── ★新設:親子関係にしないためのプレイヤー登録枠 ─── [Header("プレイヤーの同期設定")] [Tooltip("XR Origin(またはXR Rig)のTransformを直接ドラッグ&ドロップしてください")] [SerializeField] private Transform playerTransform; // ───────────────────────────────────────────────────── private bool isLoadingStarted = false; private Vector3 originalCagePosition; private void Start() { if (loadingCanvas != null) loadingCanvas.SetActive(false); if (elevatorCageTransform != null) { // ワールド空間での初期位置を記憶(localPositionからpositionに変更し、より確実に) originalCagePosition = elevatorCageTransform.position; } } private void OnTriggerEnter(Collider other) { if (isLoadingStarted) return; if (other.CompareTag(playerTag)) { isLoadingStarted = true; // ★安全対策:接触したプレイヤーのTransformを自動でキャッシュする if (playerTransform == null) { playerTransform = other.transform; } Debug.Log("🛗 プレイヤー乗車:親子関係なしのデルタ同期で下降演出を開始します。"); StartCoroutine(LoadSceneAsyncRoutine()); } } private IEnumerator LoadSceneAsyncRoutine() { if (loadingCanvas != null) loadingCanvas.SetActive(true); AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(bossSceneName); asyncLoad.allowSceneActivation = false; float hapticTimer = 0f; while (asyncLoad.progress < 0.9f) { float currentProgress = Mathf.Clamp01(asyncLoad.progress / 0.9f); if (progressText != null) progressText.text = $"LOADING: {(currentProgress * 100f):F0}%"; if (progressSlider != null) progressSlider.value = currentProgress; // ─── ★【超重要】親子関係なしでプレイヤーを完全同期させるロジック ─── // 1. 動かす前の、このフレームにおけるエレベーターの現在位置を記録 Vector3 previousCagePosition = elevatorCageTransform.position; // 2. エレベーターの部屋だけをランダムにガタガタ動かす if (elevatorCageTransform != null) { Vector3 randomOffset = Random.insideUnitSphere * shakeMagnitude; randomOffset.y = 0; // 横揺れのみ elevatorCageTransform.position = originalCagePosition + randomOffset; } // 3. 「この1フレームでエレベーターが実際に動いた移動差分(デルタ)」を計算 Vector3 cageDelta = elevatorCageTransform.position - previousCagePosition; // 4. その移動差分を、プレイヤーの座標にもそのまま上乗せして足し算する if (playerTransform != null && cageDelta != Vector3.zero) { playerTransform.position += cageDelta; } // ─────────────────────────────────────────────────────────────────── hapticTimer += Time.deltaTime; if (hapticTimer >= 0.1f) { TriggerBothHandHaptics(hapticAmplitude, 0.08f); hapticTimer = 0f; } yield return null; } // 到着後、エレベーターの位置を元の位置に綺麗に戻す if (elevatorCageTransform != null) { // 最後の移動差分を計算してプレイヤーを戻す Vector3 previousCagePosition = elevatorCageTransform.position; elevatorCageTransform.position = originalCagePosition; Vector3 cageDelta = elevatorCageTransform.position - previousCagePosition; if (playerTransform != null) playerTransform.position += cageDelta; } if (progressText != null) progressText.text = "ARRIVED"; if (progressSlider != null) progressSlider.value = 1f; yield return new WaitForSeconds(1.0f); Debug.Log("🎉 ボスステージに到着。シーンを切り替えます。"); asyncLoad.allowSceneActivation = true; } private void TriggerBothHandHaptics(float amplitude, float duration) { InputDevice leftDevice = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand); if (leftDevice.isValid) leftDevice.SendHapticImpulse(0, amplitude, duration); InputDevice rightDevice = InputDevices.GetDeviceAtXRNode(XRNode.RightHand); if (rightDevice.isValid) rightDevice.SendHapticImpulse(0, amplitude, duration); } }