2025-12-11 13:14:43 +01:00

156 lines
5.5 KiB
C#

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