251 lines
8.9 KiB
C#
251 lines
8.9 KiB
C#
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; } = 15f;
|
||
[field: SerializeField] public float JumpRoarDuration { get; private set; } = 2f;
|
||
[field: SerializeField] public float CeilingAttackDuration { get; private set; } = 8.0f;
|
||
[field: SerializeField] public float CeilingShotInterval { get; private set; } = 0.2f;
|
||
[field: SerializeField] public float WebWarningDelay { get; private set; } = 0.8f;
|
||
[field: SerializeField] public float WebDuration { get; private set; } = 3.0f;
|
||
|
||
[Tooltip("★新設:すでに糸がある場所から、最低何メートル離すか(被り防止の半径)")]
|
||
[field: SerializeField] public float WebAvoidanceRadius { get; private set; } = 2.5f;
|
||
|
||
// ★新設:現在ステージ上に存在している(または予兆がある)糸の座標リスト
|
||
public List<Vector3> ActiveWebPositions { get; private set; } = new List<Vector3>();
|
||
[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)
|
||
{
|
||
// 1. 生成する座標をアクティブリストに登録(この場所への他の割り込みを禁止する)
|
||
ActiveWebPositions.Add(pos);
|
||
|
||
// 2. 地面に予兆マークを生成
|
||
GameObject marker = null;
|
||
if (warningMarkerPrefab != null)
|
||
{
|
||
marker = Instantiate(warningMarkerPrefab, pos + new Vector3(0, 0.5f, 0), Quaternion.identity);
|
||
}
|
||
|
||
yield return new WaitForSeconds(delay);
|
||
if (marker != null) Destroy(marker);
|
||
|
||
// 3. 本物の糸オブジェクトを生成(X軸90度回転)
|
||
if (webPrefab != null)
|
||
{
|
||
Quaternion webRotation = Quaternion.Euler(270f, 0f, 0f);
|
||
GameObject web = Instantiate(webPrefab, pos + new Vector3(0, 0.5f, 0), webRotation);
|
||
|
||
// ★持続時間(duration)が終わるまで待機してからDestroyする形に変更
|
||
yield return new WaitForSeconds(duration);
|
||
if (web != null) Destroy(web);
|
||
}
|
||
else
|
||
{
|
||
yield return new WaitForSeconds(duration);
|
||
}
|
||
|
||
// 4. 糸が消滅したので、リストから座標を削除してスペースを解放する
|
||
ActiveWebPositions.Remove(pos);
|
||
}
|
||
|
||
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();
|
||
} |