156 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|