SpaceQuestions_MarioKart/Assets/Scripts/CockpitTransitionController.cs
2025-12-11 13:14:43 +01:00

291 lines
9.0 KiB
C#

// 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;
}
}
}