// ShipMovement.cs // Zweck: Bewegt das Schiff vorwärts/seitlich, kippt visuell, hält Höhe; adaptive Maxspeed nach Antworten. using UnityEngine; public class ShipMovement : MonoBehaviour { [Header("Root that actually moves")] [SerializeField] private Transform moveRoot; // tatsächlicher Mover (Mesh-Root/Cockpit) // Referenzen [SerializeField] private Transform headTransform; [SerializeField] private ThrottleLever throttle; [SerializeField] private float rideHeight = 0f; // Vorwärtsbewegung [SerializeField] private float maxSpeed = 90f; [SerializeField] private float acceleration = 15f; [SerializeField] private float deceleration = 20f; [Header("Adaptive Speed")] [SerializeField] private bool adaptiveSpeedEnabled = false; [SerializeField] private float adaptiveMinMaxSpeed = 60f; [SerializeField] private float adaptiveMaxMaxSpeed = 220f; [SerializeField] private float speedChangeOnCorrect = 10f; [SerializeField] private float speedChangeOnWrong = 10f; private float baseMaxSpeed; // ursprünglicher MaxSpeed (für Reset) // Strafen/Kippung [SerializeField] private float strafeSpeed = 60f; [SerializeField] private float tiltAmount = 20f; [SerializeField] private float tiltSpeed = 5f; [SerializeField] private float headRollForFullInput = 30f; [SerializeField] private float headRollDeadzone = 0.05f; [SerializeField] private float tiltMinSpeed = 2f; [SerializeField] private float tiltFullSpeed = 20f; private float currentSpeed; private bool isActive; [SerializeField] private bool lockToRideHeight = true; [SerializeField] private bool captureHeightOnActivate = true; // Tutorial [Header("Tutorial Controls")] [SerializeField] private bool headTiltEnabled = true; private Vector3 initialPosition; private Quaternion initialRotation; public bool HeadTiltEnabled { get => headTiltEnabled; set => headTiltEnabled = value; } public float CurrentSpeed => currentSpeed; public Transform MoveRoot => moveRoot ? moveRoot : transform; private float capturedY; public void SetHeadTransform(Transform t) => headTransform = t; // Aktiviert/Deaktiviert das System (setzt ggf. Höhe) public void SetActive(bool value) { isActive = value; if (!value) currentSpeed = 0f; else if (captureHeightOnActivate) capturedY = MoveRoot.position.y; } private void Awake() { if (!moveRoot) moveRoot = transform; // Startpose merken (für Tutorial-Reset) initialPosition = MoveRoot.position; initialRotation = MoveRoot.rotation; baseMaxSpeed = maxSpeed; } private void Update() { if (!isActive || headTransform == null) return; // Throttle lesen (mit kleiner Deadzone) float thr = 0f; if (throttle != null) { const float throttleDeadzone = 0.02f; thr = Mathf.Clamp01(throttle.throttle); if (thr < throttleDeadzone) thr = 0f; } // Beschleunigen/Abbremsen if (thr > 0f) currentSpeed += thr * acceleration * Time.deltaTime; else currentSpeed = Mathf.MoveTowards(currentSpeed, 0f, deceleration * Time.deltaTime); // Grenzen currentSpeed = Mathf.Clamp(currentSpeed, 0f, maxSpeed); // Kopf-Roll -> seitlicher Input float headRoll = headTransform.localEulerAngles.z; if (headRoll > 180f) headRoll -= 360f; float horizontalInput = -Mathf.Clamp(headRoll / Mathf.Max(0.0001f, headRollForFullInput), -1f, 1f); if (Mathf.Abs(horizontalInput) < headRollDeadzone) horizontalInput = 0f; float speedFactor = Mathf.InverseLerp(tiltMinSpeed, tiltFullSpeed, Mathf.Max(0f, currentSpeed)); horizontalInput *= speedFactor; if (!headTiltEnabled) horizontalInput = 0f; // Bewegung in Welt (vorwärts + strafe) Vector3 forwardFlat = MoveRoot.forward; forwardFlat.y = 0f; if (forwardFlat.sqrMagnitude < 1e-6f) forwardFlat = Vector3.forward; else forwardFlat.Normalize(); Vector3 rightFlat = Vector3.Cross(Vector3.up, forwardFlat); Vector3 move = forwardFlat * currentSpeed + rightFlat * (horizontalInput * strafeSpeed); MoveRoot.position += move * Time.deltaTime; // Visuelle Roll-Kippung Vector3 e = MoveRoot.eulerAngles; float targetRoll = -horizontalInput * tiltAmount; float newZ = Mathf.LerpAngle(e.z, targetRoll, tiltSpeed * Time.deltaTime); MoveRoot.rotation = Quaternion.Euler(0f, e.y, newZ); // Höhe sperren (Ride Height) if (lockToRideHeight) { var pos = MoveRoot.position; pos.y = captureHeightOnActivate ? capturedY : rideHeight; MoveRoot.position = pos; } } // Adaptive MaxSpeed bei Antwort public void ApplyAnswerResult(bool wasCorrect) { if (!adaptiveSpeedEnabled) return; float delta = wasCorrect ? speedChangeOnCorrect : -speedChangeOnWrong; maxSpeed = Mathf.Clamp(maxSpeed + delta, adaptiveMinMaxSpeed, adaptiveMaxMaxSpeed); if (currentSpeed > maxSpeed) currentSpeed = maxSpeed; } public void ResetAdaptiveSpeed() { maxSpeed = Mathf.Clamp(baseMaxSpeed, adaptiveMinMaxSpeed, adaptiveMaxMaxSpeed); if (currentSpeed > maxSpeed) currentSpeed = maxSpeed; } // Tutorial: Pose zurücksetzen public void ResetToStartPose() { MoveRoot.position = initialPosition; MoveRoot.rotation = initialRotation; currentSpeed = 0f; } }