// ShipFeedbackController.cs // Zweck: Visuelles/Haptisches Feedback bei richtigen/falschen Antworten (Partikel, Shake, Haptik). using System.Collections; using UnityEngine; using UnityEngine.XR; public class ShipFeedbackController : MonoBehaviour { [Header("Shake target")] [SerializeField] private Transform shakeRoot; // zu schüttelndes Transform [Header("Particles")] [SerializeField] private ParticleSystem correctParticles; // Partikel bei richtig [SerializeField] private ParticleSystem wrongParticles; // Partikel bei falsch [Header("Haptics (XR Nodes)")] [SerializeField] private bool useLeftHand = true; [SerializeField] private bool useRightHand = true; [SerializeField] private XRNode leftHandNode = XRNode.LeftHand; [SerializeField] private XRNode rightHandNode = XRNode.RightHand; [Header("Shake Settings (wrong answer)")] [SerializeField] private float shakeDuration = 0.25f; [SerializeField] private float positionIntensity = 0.25f; [SerializeField] private float rotationIntensity = 6f; [Header("Haptic Settings")] [Range(0f, 1f)] [SerializeField] private float correctAmplitude = 0.3f; [SerializeField] private float correctDuration = 0.12f; [Range(0f, 1f)] [SerializeField] private float wrongAmplitude = 0.8f; [SerializeField] private float wrongDuration = 0.25f; private Coroutine shakeRoutine; private void Awake() { // Fallback: eigenes Transform if (!shakeRoot) shakeRoot = transform; } // Korrektes Tor public void PlayCorrectFeedback() { if (correctParticles) correctParticles.Play(); PlayHaptics(correctAmplitude, correctDuration); } // Falsches Tor public void PlayWrongFeedback() { if (wrongParticles) wrongParticles.Play(); if (shakeRoutine != null) StopCoroutine(shakeRoutine); shakeRoutine = StartCoroutine(Co_Shake()); PlayHaptics(wrongAmplitude, wrongDuration); } // Haptik an beide Hände (optional) private void PlayHaptics(float amplitude, float duration) { if (amplitude <= 0f || duration <= 0f) return; if (useLeftHand) SendHapticToNode(leftHandNode, amplitude, duration); if (useRightHand) SendHapticToNode(rightHandNode, amplitude, duration); } private void SendHapticToNode(XRNode node, float amplitude, float duration) { var device = InputDevices.GetDeviceAtXRNode(node); if (!device.isValid) return; if (device.TryGetHapticCapabilities(out var caps) && caps.supportsImpulse) { device.SendHapticImpulse(0u, amplitude, duration); // Kanal 0 = Standard } } // Einfacher Shake mit Dämpfung private IEnumerator Co_Shake() { if (!shakeRoot) yield break; Vector3 originalPos = shakeRoot.localPosition; Quaternion originalRot = shakeRoot.localRotation; float t = 0f; while (t < shakeDuration) { t += Time.deltaTime; float n = Mathf.Clamp01(t / shakeDuration); float damper = 1f - n; float posMag = positionIntensity * damper; float rotMag = rotationIntensity * damper; Vector3 posOffset = new Vector3( (Random.value - 0.5f) * 2f * posMag, (Random.value - 0.5f) * 2f * posMag, (Random.value - 0.5f) * 2f * posMag ); Vector3 rotOffset = new Vector3( (Random.value - 0.5f) * 2f * rotMag, (Random.value - 0.5f) * 2f * rotMag, (Random.value - 0.5f) * 2f * rotMag ); shakeRoot.localPosition = originalPos + posOffset; shakeRoot.localRotation = originalRot * Quaternion.Euler(rotOffset); yield return null; } shakeRoot.localPosition = originalPos; shakeRoot.localRotation = originalRot; shakeRoutine = null; } }