Files
VRProject/Assets/_Scripts/PlayerStatusManager.cs
ohgushiyuga 9864b95a91 HPbarzissou
2026-08-27 17:59:06 +09:00

269 lines
8.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<float, float> OnHpChanged;
public event Action<float, float> 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<float>() > 0.5f && zrButtonAction.action.ReadValue<float>() > 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);
}
}
}