using System; using System.Collections; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.XR; public class PlayerStatusManager : MonoBehaviour, IDamageable { public static PlayerStatusManager Instance { get; private set; } [Header("HP・ステータス設定")] [SerializeField] private float maxHp = 100f; [SerializeField] private float currentHp = 100f; [Tooltip("被ダメージ後の無敵時間(秒)")] [SerializeField] private float invincibilityDuration = 0.8f; [Header("デバッグ設定")] [SerializeField] private bool ignoreGaugeForDebug = true; [Header("入力設定")] [SerializeField] private InputActionProperty zlButtonAction; [SerializeField] private InputActionProperty zrButtonAction; [SerializeField] private InputActionProperty moveAction; [SerializeField] private InputActionProperty turnAction; [Header("パズル制限時間")] [SerializeField] private float codingLimitDuration = 15f; [Header("ゲージ設定")] [SerializeField] private float maxGauge = 100f; [SerializeField] private float currentGauge = 0f; [Header("UI & エフェクト")] [SerializeField] private GameObject codingModeUI; [SerializeField] private GameObject successEffectPrefab; [SerializeField] private Transform effectSpawnPoint; [Header("バフ機能")] [SerializeField] private BuffHandler buffHandler = new BuffHandler(); private float codingTimer; private bool isInvincible; public bool IsCodingMode { get; private set; } public bool IsDead { get; private set; } public float CurrentHp => currentHp; public float MaxHp => maxHp; public float CurrentGauge => currentGauge; public float MaxGauge => maxGauge; public BuffHandler Buffs => buffHandler; public event Action OnHpChanged; public event Action OnGaugeChanged; public event Action OnDeath; private void Awake() { if (Instance == null) Instance = this; else Destroy(gameObject); currentHp = maxHp; } private void OnEnable() => SetActionsEnabled(true); private void OnDisable() => SetActionsEnabled(false); private void SetActionsEnabled(bool enable) { SetActionState(zlButtonAction, enable); SetActionState(zrButtonAction, enable); SetActionState(moveAction, enable); SetActionState(turnAction, enable); } private void SetActionState(InputActionProperty property, bool enable) { if (property.action == null) return; if (enable) property.action.Enable(); else property.action.Disable(); } private void Start() { if (codingModeUI != null) codingModeUI.SetActive(false); OnHpChanged?.Invoke(currentHp, maxHp); OnGaugeChanged?.Invoke(currentGauge, maxGauge); } private void Update() { if (IsDead) return; CheckComboInput(); if (!IsCodingMode) { buffHandler.UpdateTimers(Time.deltaTime); } else { codingTimer -= Time.deltaTime; if (codingTimer <= 0f) { FailCodingModeByTimeout(); } } } public void AddGauge(float amount) { if (IsCodingMode || IsDead) return; currentGauge = Mathf.Clamp(currentGauge + amount, 0f, maxGauge); OnGaugeChanged?.Invoke(currentGauge, maxGauge); } public void TakeDamage(DamageInfo damageInfo) { if (IsDead || isInvincible || IsCodingMode) return; // 防御バフ適用(倍率を掛けて被ダメージを軽減) float finalDamage = damageInfo.amount * buffHandler.CurrentDefenseMultiplier; currentHp = Mathf.Max(0f, currentHp - finalDamage); OnHpChanged?.Invoke(currentHp, maxHp); Debug.Log($"💥 プレイヤー被ダメージ: {finalDamage:F1} (残HP: {currentHp:F1} / {maxHp})"); // コントローラーへの被弾振動フィードバック TriggerDamageHaptics(0.6f, 0.15f); if (currentHp <= 0f) { Die(); } else { StartCoroutine(InvincibilityRoutine()); } } public void Heal(float amount) { if (IsDead) return; currentHp = Mathf.Min(maxHp, currentHp + amount); OnHpChanged?.Invoke(currentHp, maxHp); Debug.Log($"💚 HP回復: +{amount} (現在HP: {currentHp} / {maxHp})"); } private IEnumerator InvincibilityRoutine() { isInvincible = true; yield return new WaitForSeconds(invincibilityDuration); isInvincible = false; } private void Die() { if (IsDead) return; IsDead = true; Debug.LogError("☠️ プレイヤー死亡:GAME OVER"); OnDeath?.Invoke(); SetActionsEnabled(false); if (WorldPauseManager.Instance != null) { WorldPauseManager.Instance.PauseWorld(); } // 死亡後、2秒待って現在のシーンをリロード(再開) StartCoroutine(GameOverRestartRoutine()); } private IEnumerator GameOverRestartRoutine() { yield return new WaitForSecondsRealtime(2.5f); if (WorldPauseManager.Instance != null) { WorldPauseManager.Instance.ResumeWorld(); } SceneManager.LoadScene(SceneManager.GetActiveScene().name); } private void TriggerDamageHaptics(float amplitude, float duration) { UnityEngine.XR.InputDevice left = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand); if (left.isValid) left.SendHapticImpulse(0, amplitude, duration); UnityEngine.XR.InputDevice right = InputDevices.GetDeviceAtXRNode(XRNode.RightHand); if (right.isValid) right.SendHapticImpulse(0, amplitude, duration); } private void CheckComboInput() { if (zlButtonAction.action == null || zrButtonAction.action == null) return; bool isBothHeld = zlButtonAction.action.ReadValue() > 0.5f && zrButtonAction.action.ReadValue() > 0.5f; bool isAnyTriggered = zlButtonAction.action.triggered || zrButtonAction.action.triggered; if (isBothHeld && isAnyTriggered) { if (!IsCodingMode) { if (ignoreGaugeForDebug || currentGauge >= maxGauge) { StartCodingMode(); } } else { CancelCodingMode(); } } } private void StartCodingMode() { IsCodingMode = true; codingTimer = codingLimitDuration; SetActionState(moveAction, false); SetActionState(turnAction, false); PuzzleBlock.ResetAllBlocksInScene(); if (WorldPauseManager.Instance != null) { WorldPauseManager.Instance.PauseWorld(); } if (codingModeUI != null) codingModeUI.SetActive(true); } // PlayerStatusManager.cs public void ApplyBuffAndResume(BuffType buff) { if (successEffectPrefab != null && effectSpawnPoint != null) { Instantiate(successEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation); } buffHandler.ActivateBuff(buff); ResumeWorldState(consumeGauge: true); } private void FailCodingModeByTimeout() => ResumeWorldState(consumeGauge: true); public void CancelCodingMode() => ResumeWorldState(consumeGauge: false); private void ResumeWorldState(bool consumeGauge) { if (WorldPauseManager.Instance != null) { WorldPauseManager.Instance.ResumeWorld(); } SetActionState(moveAction, true); SetActionState(turnAction, true); PuzzleBlock.ResetAllBlocksInScene(); IsCodingMode = false; if (codingModeUI != null) codingModeUI.SetActive(false); if (consumeGauge) { currentGauge = 0f; OnGaugeChanged?.Invoke(currentGauge, maxGauge); } } }