otamesi
This commit is contained in:
@@ -1,20 +1,12 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
[RequireComponent(typeof(NavMeshAgent))]
|
||||
[RequireComponent(typeof(Animator))]
|
||||
public class EnemyAI : MonoBehaviour
|
||||
public class EnemyAI : MonoBehaviour, IPauseable
|
||||
{
|
||||
/// 敵AIの状態を表すステート
|
||||
public enum AIState
|
||||
{
|
||||
Wander, // 徘徊
|
||||
Chase, // 追跡
|
||||
Attack, // 攻撃
|
||||
Stun // 怯み
|
||||
}
|
||||
public enum AIState { Wander, Chase, Attack, Stun }
|
||||
|
||||
[Header("現在の状態")]
|
||||
[SerializeField] private AIState currentState = AIState.Wander;
|
||||
@@ -25,21 +17,14 @@ public class EnemyAI : MonoBehaviour
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
|
||||
[Header("攻撃設定")]
|
||||
[Tooltip("この距離に近づいたら攻撃 (メートル)")]
|
||||
[SerializeField] private float attackRange = 1.5f;
|
||||
[Tooltip("パンチの攻撃力")]
|
||||
[SerializeField] private float attackDamage = 10f;
|
||||
[Tooltip("一度攻撃が終了してから、次の攻撃を開始するまでの待ち時間 (秒)")]
|
||||
[SerializeField] private float attackCooldown = 2.0f;
|
||||
[Tooltip("攻撃の予兆時間 (秒)")]
|
||||
[SerializeField] private float telegraphDuration = 0.6f;
|
||||
[SerializeField] private Collider rightHandCollider;
|
||||
|
||||
[Header("ループ攻撃の調整")]
|
||||
[Tooltip("連続攻撃の間に挟む最低限のインターバル (秒)")]
|
||||
[SerializeField] private float comboInterval = 0.2f;
|
||||
|
||||
[Header("徘徊(Wander)設定")]
|
||||
[Header("徘徊設定")]
|
||||
[SerializeField] private float wanderRadius = 8f;
|
||||
[SerializeField] private float minWaitTime = 2f;
|
||||
[SerializeField] private float maxWaitTime = 4f;
|
||||
@@ -49,223 +34,220 @@ public class EnemyAI : MonoBehaviour
|
||||
private Animator anim;
|
||||
private Transform playerTransform;
|
||||
|
||||
private bool isCooldown = false;
|
||||
private bool isAttacking = false;
|
||||
private bool isWandering = false;
|
||||
private bool isKnockbacking = false;
|
||||
private bool isDead = false;
|
||||
private bool isCooldown;
|
||||
private bool isAttacking;
|
||||
private bool isWandering;
|
||||
private bool isKnockbacking;
|
||||
private bool isDead;
|
||||
private bool isPaused;
|
||||
|
||||
private void Start()
|
||||
private static readonly int SpeedHash = Animator.StringToHash("Speed");
|
||||
private static readonly int StunHash = Animator.StringToHash("Stun");
|
||||
private static readonly int HitHash = Animator.StringToHash("Hit");
|
||||
private static readonly int TelegraphHash = Animator.StringToHash("Telegraph");
|
||||
private static readonly int AttackHash = Animator.StringToHash("Attack");
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
agent = GetComponent<NavMeshAgent>();
|
||||
anim = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetFistCollider(false);
|
||||
|
||||
// AIのメインループコルーチンを開始
|
||||
if (WorldPauseManager.Instance != null)
|
||||
{
|
||||
WorldPauseManager.Instance.Register(this);
|
||||
}
|
||||
StartCoroutine(AILoop());
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (WorldPauseManager.Instance != null)
|
||||
{
|
||||
WorldPauseManager.Instance.Unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
isPaused = true;
|
||||
if (anim != null) anim.speed = 0f;
|
||||
if (agent != null && agent.enabled && agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
isPaused = false;
|
||||
if (anim != null) anim.speed = 1f;
|
||||
if (agent != null && agent.enabled && agent.isOnNavMesh && !isDead && !isKnockbacking && currentState != AIState.Stun)
|
||||
{
|
||||
agent.isStopped = false;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator WaitForUnpausedSeconds(float duration)
|
||||
{
|
||||
float timer = 0f;
|
||||
while (timer < duration)
|
||||
{
|
||||
if (!isPaused)
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(isDead) return;
|
||||
if (isDead || isPaused) return;
|
||||
|
||||
// 毎フレームのアニメーションパラメータ更新処理
|
||||
UpdateAnimation();
|
||||
|
||||
// 攻撃ステートにおけるプレイヤーへの方向転換処理
|
||||
UpdateRotationOnAttack();
|
||||
}
|
||||
|
||||
#region AI メインループ (思考ロジック)
|
||||
private IEnumerator AILoop()
|
||||
{
|
||||
while (true)
|
||||
while (!isDead)
|
||||
{
|
||||
if(isDead) yield break;
|
||||
|
||||
// プレイヤーが未検出の場合はタグから検索
|
||||
if (playerTransform == null)
|
||||
if (isPaused)
|
||||
{
|
||||
FindPlayer();
|
||||
yield return null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 攻撃、のけぞりアニメーションの再生中でない場合のみ、ステートに応じた行動を実行
|
||||
if (playerTransform == null) FindPlayer();
|
||||
|
||||
if (!isAttacking && !isKnockbacking && currentState != AIState.Stun)
|
||||
{
|
||||
switch (currentState)
|
||||
{
|
||||
case AIState.Wander:
|
||||
WanderBehavior();
|
||||
break;
|
||||
case AIState.Chase:
|
||||
ChaseBehavior();
|
||||
break;
|
||||
case AIState.Attack:
|
||||
AttackBehavior();
|
||||
break;
|
||||
case AIState.Wander: WanderBehavior(); break;
|
||||
case AIState.Chase: ChaseBehavior(); break;
|
||||
case AIState.Attack: AttackBehavior(); break;
|
||||
}
|
||||
}
|
||||
|
||||
// 毎フレーム実行を避け、0.2秒待機することでCPU負荷を軽減
|
||||
yield return new WaitForSeconds(0.2f);
|
||||
yield return WaitForUnpausedSeconds(0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
private void FindPlayer()
|
||||
{
|
||||
GameObject playerObj = GameObject.FindGameObjectWithTag(playerTag);
|
||||
if (playerObj != null)
|
||||
{
|
||||
playerTransform = playerObj.transform;
|
||||
}
|
||||
if (playerObj != null) playerTransform = playerObj.transform;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 各ステートの固有行動
|
||||
/// 【徘徊状態】の思考ロジック
|
||||
private void WanderBehavior()
|
||||
{
|
||||
if (playerTransform != null)
|
||||
{
|
||||
// プレイヤーとの距離を測定し、索敵範囲内なら追跡ステートへ遷移
|
||||
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
|
||||
if (distanceToPlayer <= detectionRange)
|
||||
{
|
||||
currentState = AIState.Chase;
|
||||
isWandering = false;
|
||||
agent.ResetPath();
|
||||
Debug.Log($"👁️ {gameObject.name}: プレイヤーを発見! 追跡します。");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// すでに徘徊移動中でなければ、新しい目的地への移動コルーチンを開始
|
||||
if (!isWandering)
|
||||
if (!isWandering)
|
||||
{
|
||||
StartCoroutine(WanderMoveRoutine());
|
||||
}
|
||||
}
|
||||
|
||||
/// 【追跡状態】の思考ロジック
|
||||
private void ChaseBehavior()
|
||||
{
|
||||
if (playerTransform == null)
|
||||
{
|
||||
currentState = AIState.Wander;
|
||||
return;
|
||||
}
|
||||
if (playerTransform == null) { currentState = AIState.Wander; return; }
|
||||
|
||||
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
|
||||
|
||||
// 攻撃射程内に入ったら攻撃ステートへ遷移し、その場に停止
|
||||
if (distanceToPlayer <= attackRange)
|
||||
{
|
||||
currentState = AIState.Attack;
|
||||
agent.ResetPath();
|
||||
agent.ResetPath();
|
||||
return;
|
||||
}
|
||||
|
||||
// プレイヤーが見失う範囲を超えたら徘徊ステートにリセット
|
||||
if (distanceToPlayer > loseRange)
|
||||
{
|
||||
currentState = AIState.Wander;
|
||||
agent.ResetPath();
|
||||
Debug.Log($"❓ {gameObject.name}: プレイヤーを見失った。");
|
||||
return;
|
||||
}
|
||||
|
||||
// のけぞり中でない場合、追跡の目的地を更新する
|
||||
if(!isKnockbacking)
|
||||
if (!isKnockbacking)
|
||||
{
|
||||
agent.SetDestination(playerTransform.position);
|
||||
}
|
||||
}
|
||||
|
||||
/// 【攻撃状態】の思考ロジック
|
||||
private void AttackBehavior()
|
||||
{
|
||||
if (playerTransform == null)
|
||||
{
|
||||
currentState = AIState.Wander;
|
||||
return;
|
||||
}
|
||||
if (playerTransform == null) { currentState = AIState.Wander; return; }
|
||||
|
||||
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
|
||||
|
||||
// 攻撃射程から外れていたら、再び追跡ステートに戻る
|
||||
if (distanceToPlayer > attackRange)
|
||||
{
|
||||
currentState = AIState.Chase;
|
||||
return;
|
||||
}
|
||||
|
||||
// クールダウン中でなければ攻撃コルーチンを実行(クールダウン中の場合は、Update内の処理によりプレイヤーの方向を向きながら待機します)
|
||||
if (!isCooldown)
|
||||
{
|
||||
StartCoroutine(PerformPunchAttack());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region アニメーション・回転の制御メソッド
|
||||
/// エージェントの移動状態に応じてAnimatorのSpeedパラメータを更新する
|
||||
private void UpdateAnimation()
|
||||
{
|
||||
float targetSpeed = 0f;
|
||||
|
||||
// 攻撃中でなく、経路が存在し、かつ実際に一定以上の速度で移動している場合のみSpeedを1にする
|
||||
if (!isAttacking && !isKnockbacking && currentState != AIState.Stun && agent.hasPath && agent.velocity.magnitude > 0.1f)
|
||||
{
|
||||
targetSpeed = 1f;
|
||||
}
|
||||
|
||||
// パラメータをなめらかに変化させて渡す
|
||||
anim.SetFloat("Speed", targetSpeed, 0.1f, Time.deltaTime);
|
||||
anim.SetFloat(SpeedHash, targetSpeed, 0.1f, Time.deltaTime);
|
||||
}
|
||||
|
||||
/// 攻撃ステートの際、プレイヤーの方向へ滑らかに旋回させる
|
||||
private void UpdateRotationOnAttack()
|
||||
{
|
||||
if (currentState == AIState.Attack && playerTransform != null && !isKnockbacking && currentState != AIState.Stun)
|
||||
{
|
||||
Vector3 direction = (playerTransform.position - transform.position).normalized;
|
||||
direction.y = 0; // 上下方向の傾き(ピッチ回転)を無視
|
||||
direction.y = 0;
|
||||
|
||||
if (direction != Vector3.zero)
|
||||
{
|
||||
// Y軸の回転のみを滑らかに補間して適用
|
||||
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * 10f);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
// ダメージを受けた際に、EnemyhHealthから呼び出されるのけぞり開始メソッド
|
||||
public void TriggerKnockback(float duration)
|
||||
{
|
||||
if(isDead) return;
|
||||
|
||||
if(currentState == AIState.Stun) return;
|
||||
if (isDead || currentState == AIState.Stun) return;
|
||||
|
||||
SetFistCollider(false);
|
||||
StopAllCoroutines();
|
||||
isAttacking = false;
|
||||
isWandering = false;
|
||||
|
||||
if(agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = false;
|
||||
}
|
||||
|
||||
if (agent.isOnNavMesh) agent.isStopped = false;
|
||||
StartCoroutine(KnockbackRoutine(duration));
|
||||
}
|
||||
|
||||
public void TriggerParryStun(float duration)
|
||||
{
|
||||
if(isDead) return;
|
||||
|
||||
Debug.Log($"{gameObject.name}: parry = true Stunnow");
|
||||
if (isDead) return;
|
||||
|
||||
SetFistCollider(false);
|
||||
StopAllCoroutines();
|
||||
@@ -280,7 +262,7 @@ public class EnemyAI : MonoBehaviour
|
||||
{
|
||||
isDead = true;
|
||||
StopAllCoroutines();
|
||||
if(agent != null && agent.isOnNavMesh)
|
||||
if (agent != null && agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = true;
|
||||
agent.ResetPath();
|
||||
@@ -291,24 +273,19 @@ public class EnemyAI : MonoBehaviour
|
||||
{
|
||||
currentState = AIState.Stun;
|
||||
|
||||
if(agent.isOnNavMesh)
|
||||
if (agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = true;
|
||||
agent.ResetPath();
|
||||
}
|
||||
|
||||
anim.SetTrigger("Stun");
|
||||
anim.SetTrigger(StunHash);
|
||||
yield return WaitForUnpausedSeconds(duration);
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
|
||||
if(agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = false;
|
||||
}
|
||||
if (agent.isOnNavMesh) agent.isStopped = false;
|
||||
|
||||
currentState = AIState.Chase;
|
||||
isCooldown = false;
|
||||
|
||||
StartCoroutine(AILoop());
|
||||
}
|
||||
|
||||
@@ -316,65 +293,48 @@ public class EnemyAI : MonoBehaviour
|
||||
{
|
||||
isKnockbacking = true;
|
||||
|
||||
if(agent.isOnNavMesh)
|
||||
if (agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = true;
|
||||
agent.ResetPath();
|
||||
}
|
||||
|
||||
anim.SetTrigger("Hit");
|
||||
anim.SetTrigger(HitHash);
|
||||
yield return WaitForUnpausedSeconds(duration);
|
||||
|
||||
yield return new WaitForSeconds(duration);
|
||||
|
||||
if(agent.isOnNavMesh)
|
||||
{
|
||||
agent.isStopped = false;
|
||||
}
|
||||
if (agent.isOnNavMesh) agent.isStopped = false;
|
||||
|
||||
isKnockbacking = false;
|
||||
isCooldown = false;
|
||||
|
||||
StartCoroutine(AILoop());
|
||||
}
|
||||
|
||||
#region 攻撃・移動のコルーチン処理
|
||||
/// 徘徊時のランダム移動を制御するコルーチン
|
||||
private IEnumerator WanderMoveRoutine()
|
||||
{
|
||||
isWandering = true;
|
||||
Vector3 newPos = Vector3.zero;
|
||||
bool isPathValid = false;
|
||||
int maxAttempts = 10;
|
||||
|
||||
// 有効な経路(壁などで分断されていないルート)が見つかるまで試行
|
||||
for (int i = 0; i < maxAttempts; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
newPos = GetRandomNavMeshPoint(transform.position, wanderRadius);
|
||||
NavMeshPath path = new NavMeshPath();
|
||||
|
||||
if (agent.CalculatePath(newPos, path))
|
||||
if (agent.CalculatePath(newPos, path) && path.status == NavMeshPathStatus.PathComplete)
|
||||
{
|
||||
if (path.status == NavMeshPathStatus.PathComplete)
|
||||
{
|
||||
isPathValid = true;
|
||||
break;
|
||||
}
|
||||
isPathValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 有効な目的地が設定できた場合の移動処理
|
||||
if (isPathValid)
|
||||
{
|
||||
agent.SetDestination(newPos);
|
||||
float elapsedTime = 0f;
|
||||
|
||||
// 目的地に到達するか、タイムアウト時間を迎えるまで待機
|
||||
while (agent.remainingDistance > agent.stoppingDistance)
|
||||
{
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
yield return WaitForUnpausedSeconds(0.5f);
|
||||
elapsedTime += 0.5f;
|
||||
|
||||
// スタック対策としてのタイムアウト判定
|
||||
if (elapsedTime >= wanderTimeout)
|
||||
{
|
||||
agent.ResetPath();
|
||||
@@ -383,57 +343,36 @@ public class EnemyAI : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// 到着または諦めた後、ランダムな時間その場で待機
|
||||
yield return new WaitForSeconds(UnityEngine.Random.Range(minWaitTime, maxWaitTime));
|
||||
yield return WaitForUnpausedSeconds(Random.Range(minWaitTime, maxWaitTime));
|
||||
isWandering = false;
|
||||
}
|
||||
|
||||
/// 予兆から攻撃アニメーションのトリガーまでを制御するコルーチン
|
||||
private IEnumerator PerformPunchAttack()
|
||||
{
|
||||
isAttacking = true;
|
||||
isCooldown = true; // ここで攻撃フラグをロックします
|
||||
isCooldown = true;
|
||||
|
||||
anim.SetTrigger("Telegraph");
|
||||
Debug.Log($"⚠️ {gameObject.name}: 攻撃の予兆(溜め)開始");
|
||||
anim.SetTrigger(TelegraphHash);
|
||||
yield return WaitForUnpausedSeconds(telegraphDuration);
|
||||
|
||||
yield return new WaitForSeconds(telegraphDuration);
|
||||
if (isKnockbacking || isDead)
|
||||
{
|
||||
isAttacking = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
if(isKnockbacking) yield break;
|
||||
|
||||
anim.SetTrigger("Attack");
|
||||
anim.SetTrigger(AttackHash);
|
||||
}
|
||||
|
||||
/// 連続攻撃成功時の、クイックなクールダウン解除処理
|
||||
private IEnumerator ResetCooldownQuickly()
|
||||
{
|
||||
yield return new WaitForSeconds(comboInterval);
|
||||
isCooldown = false;
|
||||
}
|
||||
|
||||
/// コンボが途切れた際の、通常の攻撃クールダウン解除処理
|
||||
private IEnumerator ResetCooldown()
|
||||
{
|
||||
// 指定された秒数待機する
|
||||
yield return new WaitForSeconds(attackCooldown);
|
||||
isCooldown = false;
|
||||
Debug.Log($"✅ {gameObject.name}: 攻撃クールダウンが終了しました。再攻撃可能です。");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region アニメーションイベント(外部からのコールバック)
|
||||
/// 攻撃アニメーションのヒットフレームで実行されるヒット判定(Animation Eventから呼び出し)
|
||||
public void OnPunchHit()
|
||||
{
|
||||
if (isKnockbacking || currentState == AIState.Stun) return;
|
||||
|
||||
if (isKnockbacking || currentState == AIState.Stun || isPaused) return;
|
||||
SetFistCollider(true);
|
||||
}
|
||||
|
||||
/// Recovery(硬直)アニメーションの終了時に実行される処理(Animation Eventから呼び出し)
|
||||
public void OnRecoveryEnd()
|
||||
{
|
||||
if(isKnockbacking || currentState == AIState.Stun) return;
|
||||
if (isKnockbacking || currentState == AIState.Stun) return;
|
||||
|
||||
SetFistCollider(false);
|
||||
isAttacking = false;
|
||||
@@ -447,59 +386,51 @@ public class EnemyAI : MonoBehaviour
|
||||
|
||||
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
|
||||
|
||||
// ループ判定:まだプレイヤーが射程内にいる場合はクイックな連続攻撃へ移行
|
||||
if (distanceToPlayer <= attackRange)
|
||||
{
|
||||
StartCoroutine(ResetCooldownQuickly());
|
||||
Debug.Log($"🔄 {gameObject.name}: プレイヤーがまだ射程内です。連続攻撃(コンボ)を実行します。");
|
||||
}
|
||||
else
|
||||
{
|
||||
// プレイヤーが離れていた場合はコンボを抜け、追跡状態に戻ると同時に、通常のクールダウン(長めの待機)を開始
|
||||
currentState = AIState.Chase;
|
||||
StartCoroutine(ResetCooldown());
|
||||
Debug.Log($"🏃 {gameObject.name}: プレイヤーが離れたため追跡に戻ります。({attackCooldown}秒の攻撃クールダウンを適用)");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ヘルパー・デバッグ用メソッド
|
||||
/// 指定された中心座標の範囲内から、NavMesh上の有効なランダム座標を取得する
|
||||
|
||||
private IEnumerator ResetCooldownQuickly()
|
||||
{
|
||||
yield return WaitForUnpausedSeconds(comboInterval);
|
||||
isCooldown = false;
|
||||
}
|
||||
|
||||
private IEnumerator ResetCooldown()
|
||||
{
|
||||
yield return WaitForUnpausedSeconds(attackCooldown);
|
||||
isCooldown = false;
|
||||
}
|
||||
|
||||
private void SetFistCollider(bool enabledState)
|
||||
{
|
||||
if(rightHandCollider != null)
|
||||
{
|
||||
rightHandCollider.enabled = enabledState;
|
||||
}
|
||||
if (rightHandCollider != null) rightHandCollider.enabled = enabledState;
|
||||
}
|
||||
|
||||
private Vector3 GetRandomNavMeshPoint(Vector3 center, float radius)
|
||||
{
|
||||
Vector3 randomDirection = UnityEngine.Random.insideUnitSphere * radius;
|
||||
randomDirection += center;
|
||||
NavMeshHit hit;
|
||||
|
||||
if (NavMesh.SamplePosition(randomDirection, out hit, radius, NavMesh.AllAreas))
|
||||
Vector3 randomDirection = Random.insideUnitSphere * radius + center;
|
||||
if (NavMesh.SamplePosition(randomDirection, out NavMeshHit hit, radius, NavMesh.AllAreas))
|
||||
{
|
||||
return hit.position;
|
||||
}
|
||||
return center;
|
||||
}
|
||||
|
||||
/// エディタのSceneビューに索敵・攻撃範囲をギズモとして描画する
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
// 索敵範囲(赤)
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireSphere(transform.position, detectionRange);
|
||||
|
||||
// 見失う範囲(黄)
|
||||
Gizmos.color = Color.yellow;
|
||||
Gizmos.DrawWireSphere(transform.position, loseRange);
|
||||
|
||||
// 攻撃範囲(青)
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireSphere(transform.position, attackRange);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user