// CockpitTransitionController.cs // Zweck: Übergang Hangar <-> Cockpit (Fade, Rig umhängen), Modus/Interfaces schalten, Gameplay/Tutorial starten. using System.Collections; using Unity.XR.CoreUtils; using UnityEngine; using UnityEngine.XR.Interaction.Toolkit; using UnityEngine.XR.Interaction.Toolkit.Locomotion; using UnityEngine.XR.Interaction.Toolkit.Locomotion.Movement; public class CockpitTransitionController : MonoBehaviour { // Rig [SerializeField] private XROrigin xrOrigin; [SerializeField] private Transform cameraOffset; [SerializeField] private ContinuousMoveProvider moveProvider; [SerializeField] private CharacterController playerCC; [SerializeField] private GameObject xrDeviceSimulator; [SerializeField] private XRBodyTransformer bodyTransformer; [SerializeField] private UnityEngine.XR.Interaction.Toolkit.Interactors.XRBaseInputInteractor rightHandNearFar; // Schiff [SerializeField] private Transform shipRoot; [SerializeField] private ShipMovement shipMovement; // Welt-Anker [SerializeField] private Transform hangarSpawnAnchor; [SerializeField] private Transform cockpitSeatAnchor; // Fade [SerializeField] private CanvasGroup fadeCanvas; [SerializeField] private float fadeDuration = 0.6f; // UI/Modus [SerializeField] private GameObject rightHandRay; [SerializeField] private GameObject hangarUI; [SerializeField] private GameObject cockpitUI; [SerializeField] private CockpitTutorialUI tutorialUI; [SerializeField] private CockpitMenuController menuController; // Tutorial [SerializeField] private TutorialFlightController flightTutorial; // Gates [SerializeField] private GateSpawner gateSpawner; private bool inCockpit; private bool gameplayActive; private bool isTransitioning; private void Awake() { if (fadeCanvas) fadeCanvas.alpha = 0f; gameplayActive = false; // Schiffssysteme anfangs aus if (shipMovement) shipMovement.SetActive(false); UpdateMode(true); } // -> Cockpit public void EnterCockpit() { if (inCockpit || isTransitioning) return; StartCoroutine(DoTransition(true)); } // <- Hangar (optional) public void ExitCockpit() { if (!inCockpit || isTransitioning) return; StartCoroutine(DoTransition(false)); } private IEnumerator DoTransition(bool enter) { isTransitioning = true; // Pre UpdateMode(false); // Fade + Input blocken yield return FadeTo(1f, blockInput: true); // CC/Body kurz deaktivieren (Verkanten vermeiden) SetCharacterControllerActive(false); if (enter) AttachRigToShip(); else { DetachRigFromShip(); gameplayActive = false; } inCockpit = enter; // CC/Body nur außerhalb Cockpit SetCharacterControllerActive(!inCockpit); // Post UpdateMode(true); // Fade zurück yield return FadeTo(0f, blockInput: false); isTransitioning = false; } private void SetCharacterControllerActive(bool active) { if (playerCC) playerCC.enabled = active; if (bodyTransformer) bodyTransformer.enabled = active; } private IEnumerator FadeTo(float targetAlpha, bool blockInput) { if (!fadeCanvas) yield break; if (!fadeCanvas.gameObject.activeSelf) fadeCanvas.gameObject.SetActive(true); fadeCanvas.blocksRaycasts = blockInput; fadeCanvas.interactable = blockInput; float start = fadeCanvas.alpha; float elapsed = 0f; while (elapsed < fadeDuration) { elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / fadeDuration); fadeCanvas.alpha = Mathf.Lerp(start, targetAlpha, t); yield return null; } fadeCanvas.alpha = targetAlpha; if (Mathf.Approximately(targetAlpha, 0f)) { fadeCanvas.blocksRaycasts = false; fadeCanvas.interactable = false; fadeCanvas.gameObject.SetActive(false); } } // UI/Steuerungsmodi schalten private void UpdateMode(bool afterTransition) { bool enableLocomotion = !inCockpit; bool enableShipSystems = inCockpit && gameplayActive; if (moveProvider) moveProvider.enabled = enableLocomotion; if (shipMovement) shipMovement.SetActive(enableShipSystems); if (rightHandRay) rightHandRay.SetActive(inCockpit && !gameplayActive); if (hangarUI) hangarUI.SetActive(!inCockpit); if (cockpitUI) cockpitUI.SetActive(inCockpit); if (tutorialUI) { if (inCockpit && !gameplayActive && afterTransition) tutorialUI.BeginTutorial(); else tutorialUI.CloseTutorial(); } if (menuController) { if (!inCockpit || gameplayActive) menuController.HideMenu(); } } public void OnTutorialFinished() { StartCoroutine(ShowMenuNextFrame()); } private IEnumerator ShowMenuNextFrame() { // Einen Frame warten, damit Schließen/Fade fertig ist yield return null; menuController?.ShowMenu(); } // Gameplay nach Moduswahl starten private void BeginGameplay() { UpdateMode(true); gateSpawner?.BeginGame(); gameplayActive = true; menuController?.HideMenu(); if (rightHandRay) rightHandRay.SetActive(false); UpdateMode(true); } // Buttons (Passthroughs) public void StartSequentialMode() { Debug.Log("Mode A selected"); BeginGameplay(); } public void StartParallelMode() { Debug.Log("Mode B selected"); BeginGameplay(); } public void StartTutorialMode() { Debug.Log("Tutorial practice selected"); // Tutorial als Gameplay behandeln (Bewegung an) gameplayActive = true; gateSpawner?.ClearAll(); menuController?.HideMenu(); UpdateMode(true); if (flightTutorial != null && shipMovement != null && gateSpawner != null) flightTutorial.BeginTutorial(this, shipMovement, gateSpawner); else Debug.LogWarning("[CockpitTransitionController] TutorialFlightController oder Abhängigkeiten fehlen."); } // Rig ans Schiff hängen, Sitzposition ausrichten private void AttachRigToShip() { if (!xrOrigin || !shipMovement) return; var mover = shipMovement.MoveRoot; if (!mover) return; // 0) Parent unter den Mover (Pose beibehalten) xrOrigin.transform.SetParent(mover, worldPositionStays: true); // 1) Tracking-Origin "Device" (seated) #if UNITY_XR_CORE_UTILS_2_4_OR_NEWER xrOrigin.RequestedTrackingOriginMode = UnityEngine.XR.TrackingOriginModeFlags.Device; xrOrigin.CameraYOffset = 0f; #else xrOrigin.RequestedTrackingOriginMode = XROrigin.TrackingOriginMode.Device; if (xrOrigin.CameraFloorOffsetObject) { var off = xrOrigin.CameraFloorOffsetObject.transform; off.localPosition = Vector3.zero; off.localRotation = Quaternion.identity; } #endif // 2) Offset zurücksetzen if (cameraOffset) { cameraOffset.localPosition = Vector3.zero; cameraOffset.localRotation = Quaternion.identity; cameraOffset.localScale = Vector3.one; } // 3) Kamera exakt zur Sitz-Augenposition verschieben var cam = xrOrigin.Camera; if (!cam || !cockpitSeatAnchor) return; const float eyeLift = 0.05f; Vector3 targetCamPos = cockpitSeatAnchor.position + cockpitSeatAnchor.up * eyeLift; xrOrigin.MoveCameraToWorldLocation(targetCamPos); // 4) Yaw an Sitz ausrichten (um die Kameraposition rotieren) Vector3 camF = cam.transform.forward; camF.y = 0f; Vector3 seatF = cockpitSeatAnchor.forward; seatF.y = 0f; if (camF.sqrMagnitude > 1e-4f && seatF.sqrMagnitude > 1e-4f) { camF.Normalize(); seatF.Normalize(); float deltaYaw = Vector3.SignedAngle(camF, seatF, Vector3.up); xrOrigin.transform.RotateAround(cam.transform.position, Vector3.up, deltaYaw); } } public void EndTutorialReturnToMenu() { // Tutorial vorbei: Schiff stoppen, Menü anzeigen gameplayActive = false; if (shipMovement) shipMovement.SetActive(false); if (rightHandRay) rightHandRay.SetActive(true); if (hangarUI) hangarUI.SetActive(false); if (cockpitUI) cockpitUI.SetActive(true); menuController?.ShowMenu(); } // Rig vom Schiff lösen, in Hangar stellen private void DetachRigFromShip() { if (!xrOrigin) return; xrOrigin.transform.SetParent(null, true); if (hangarSpawnAnchor) { xrOrigin.MoveCameraToWorldLocation(hangarSpawnAnchor.position); var e = xrOrigin.transform.eulerAngles; e.y = hangarSpawnAnchor.eulerAngles.y; xrOrigin.transform.eulerAngles = e; } } }