Files
VRProject/Assets/_Scripts/BossController.cs
2026-07-14 14:14:48 +09:00

238 lines
8.2 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 UnityEngine;
using UnityEngine.AI;
using System.Collections.Generic;
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(Animator))]
public class BossController : MonoBehaviour, IDamageble
{
[SerializeField] private float maxHp = 500f;
[SerializeField] private float currentHp = 500f;
[field: SerializeField] public float ChaseRange { get; private set; } = 15f;
[field: SerializeField] public float BiteRange { get; private set; } = 5f;
[field: SerializeField] public float ChargeSpeed { get; private set; } = 14f;
[field: SerializeField] public float ChargeDuration { get; private set; } = 2f;
[field: SerializeField] public float ChargeMinimumDistance { get; private set; } = 6f;
[field: SerializeField] public float ChargeCooldownDuration { get; private set; } = 4f;
[field: SerializeField] public float ChargeRoarDuration { get; private set; } = 5f;
[SerializeField] public Transform ceilingAnchor;
[SerializeField] public Transform groundAnchor;
[field: SerializeField] public float AttackRadius { get; private set; } = 10f;
[field: SerializeField] public float JumpRoarDuration { get; private set; } = 5f;
[field: SerializeField] public float CeilingAttackDuration { get; private set; } = 8.0f;
[Tooltip("★新設:糸を降らせる時間間隔(秒)。数値を小さくする(例: 0.1)ほど弾数が激増して密度が上がります")]
[field: SerializeField] public float CeilingShotInterval { get; private set; } = 0.2f;
[Tooltip("★新設:地面に赤マーク(予兆)が出てから、本物の糸が降ってくるまでのタイムラグ(秒)")]
[field: SerializeField] public float WebWarningDelay { get; private set; } = 0.8f;
[Tooltip("★新設:地面に生成された糸が消えずに残り続ける時間(秒)")]
[field: SerializeField] public float WebDuration { get; private set; } = 3.0f;
[SerializeField] private GameObject warningMarkerPrefab;
[SerializeField] private GameObject webPrefab;
public NavMeshAgent Agent { get; private set; }
public Animator Anim { get; private set; }
public Transform Player { get; private set; }
public float ChargeCooldownTimer { get; private set; } = 0f;
public float NormalSpeed { get; private set; }
public float NormalAcceleration { get; private set; }
private ISpiderState currentState;
private Dictionary<System.Type, ISpiderState> stateCache;
// フェーズトリガー
private bool triggered50 = false;
private bool triggered25 = false;
public bool PendingUltimate { get; set; } = false;
private void Awake()
{
Agent = GetComponent<NavMeshAgent>();
Anim = GetComponent<Animator>();
GameObject pObj = GameObject.FindGameObjectWithTag("Player");
if (pObj != null) Player = pObj.transform;
InitializeFSM();
}
private void Start()
{
if (Agent != null)
{
NormalSpeed = Agent.speed;
NormalAcceleration = Agent.acceleration;
}
}
private void InitializeFSM()
{
stateCache = new Dictionary<System.Type, ISpiderState>
{
{ typeof(SpiderIdleState), new SpiderIdleState(this) },
{ typeof(SpiderChaseState), new SpiderChaseState(this) },
{ typeof(SpiderBiteState), new SpiderBiteState(this) },
{ typeof(SpiderChargeState), new SpiderChargeState(this) },
{ typeof(SpiderJumpToCeilingState), new SpiderJumpToCeilingState(this) },
{ typeof(SpiderCeilingAttackState), new SpiderCeilingAttackState(this) },
{ typeof(SpiderStunState), new SpiderStunState(this) }
};
ChangeState<SpiderIdleState>();
}
private void Update()
{
currentState?.UpdateState();
if (ChargeCooldownTimer > 0f)
{
ChargeCooldownTimer -= Time.deltaTime;
}
}
public void ChangeState<T>() where T : ISpiderState
{
if (currentState != null)
{
currentState.ExitState();
ResetAllAnimationTriggers();
Debug.Log($"🕷️ BossSpider [Exit]: {currentState.GetType().Name}");
}
currentState = stateCache[typeof(T)];
Debug.Log($"🕷️ BossSpider [Enter]: {currentState.GetType().Name}");
currentState.EnterState();
}
public void TakeDamage(DamageInfo damageInfo)
{
currentHp -= damageInfo.amount;
currentHp = Mathf.Clamp(currentHp, 0, maxHp);
float hpPercentage = (currentHp / maxHp) * 100f;
Debug.Log($"💥 ボスに {damageInfo.amount} ダメージ。残りHP: {hpPercentage:F1}%");
if (hpPercentage <= 25f && !triggered25)
{
triggered25 = true;
PendingUltimate = true;
TriggerPhaseTransition();
}
else if (hpPercentage <= 50f && !triggered50 && hpPercentage > 25f)
{
triggered50 = true;
PendingUltimate = true;
TriggerPhaseTransition();
}
}
private void TriggerPhaseTransition()
{
if (currentState is not SpiderJumpToCeilingState &&
currentState is not SpiderCeilingAttackState &&
currentState is not SpiderStunState)
{
ChangeState<SpiderJumpToCeilingState>();
}
}
public bool TriggerParryStun()
{
if (currentState is SpiderBiteState || currentState is SpiderChargeState)
{
ChangeState<SpiderStunState>();
return true;
}
return false;
}
public void ResetChargeCooldown()
{
ChargeCooldownTimer = ChargeCooldownDuration;
}
public void SpawnWarningAndWeb(Vector3 targetGroundPos, float delay, float webDuration)
{
StartCoroutine(SpawnWebRoutine(targetGroundPos, delay, webDuration));
}
private System.Collections.IEnumerator SpawnWebRoutine(Vector3 pos, float delay, float duration)
{
GameObject marker = null;
if (warningMarkerPrefab != null)
{
marker = Instantiate(warningMarkerPrefab, pos + new Vector3(0, 0.02f, 0), Quaternion.identity);
}
yield return new WaitForSeconds(delay);
if (marker != null) Destroy(marker);
if (webPrefab != null)
{
Quaternion webRotation = Quaternion.Euler(270f, 0f, 0f);
GameObject web = Instantiate(webPrefab, pos + new Vector3(0, 0.5f, 0), webRotation);
Destroy(web, duration);
}
}
public void ResetAllAnimationTriggers()
{
if (Anim == null) return;
Anim.ResetTrigger("Attack_Bite");
Anim.ResetTrigger("Attack_Roar");
Anim.ResetTrigger("ChargeStart");
Anim.ResetTrigger("JumpUp");
Anim.ResetTrigger("Land");
Anim.ResetTrigger("StunStart");
}
#region
/// <summary>
/// Unityインスペクターの右上メニューから実行できるデバッグ用コマンドです
/// </summary>
[ContextMenu("🛠️ デバッグ:強制必殺技(天井ジャンプ)")]
public void DebugTriggerUltimate()
{
if (!Application.isPlaying)
{
Debug.LogWarning("⚠️ ゲームが再生中Playモードの時のみ実行可能です。");
return;
}
Debug.Log("🛠️ 【デバッグ操作】必殺技の予約券を発行し、強制的に天井ジャンプステートへ遷移します。");
// 必殺技の発動権をONにし、現在の行動をキャンセルして天井ジャンプへ移行
PendingUltimate = true;
ChangeState<SpiderJumpToCeilingState>();
}
#endregion
}
/// 共通データboss参照を自動保持し、各子ステートの無駄なコードを極限まで削ります。
public abstract class SpiderStateBase : ISpiderState
{
protected readonly BossController boss;
protected SpiderStateBase(BossController owner)
{
boss = owner;
}
public virtual void EnterState() { }
public abstract void UpdateState();
public virtual void ExitState() { }
}
public interface ISpiderState
{
void EnterState();
void UpdateState();
void ExitState();
}