スタン状態追加

This commit is contained in:
KawakamiKento
2026-06-03 16:35:15 +09:00
parent 6aaaca800b
commit 69f5891a14
7 changed files with 369 additions and 44 deletions

View File

@@ -12,7 +12,8 @@ public class EnemyAI : MonoBehaviour
{
Wander, // 徘徊
Chase, // 追跡
Attack // 攻撃
Attack, // 攻撃
Stun // 怯み
}
[Header("現在の状態")]
@@ -32,6 +33,7 @@ public class EnemyAI : MonoBehaviour
[SerializeField] private float attackCooldown = 2.0f;
[Tooltip("攻撃の予兆時間 (秒)")]
[SerializeField] private float telegraphDuration = 0.6f;
[SerializeField] private Collider rightHandCollider;
[Header("ループ攻撃の調整")]
[Tooltip("連続攻撃の間に挟む最低限のインターバル (秒)")]
@@ -57,6 +59,8 @@ public class EnemyAI : MonoBehaviour
{
agent = GetComponent<NavMeshAgent>();
anim = GetComponent<Animator>();
SetFistCollider(false);
// AIのメインループコルーチンを開始
StartCoroutine(AILoop());
@@ -87,7 +91,7 @@ public class EnemyAI : MonoBehaviour
}
// 攻撃、のけぞりアニメーションの再生中でない場合のみ、ステートに応じた行動を実行
if (!isAttacking && !isKnockbacking)
if (!isAttacking && !isKnockbacking && currentState != AIState.Stun)
{
switch (currentState)
{
@@ -211,7 +215,7 @@ public class EnemyAI : MonoBehaviour
float targetSpeed = 0f;
// 攻撃中でなく、経路が存在し、かつ実際に一定以上の速度で移動している場合のみSpeedを1にする
if (!isAttacking && !isKnockbacking && agent.hasPath && agent.velocity.magnitude > 0.1f)
if (!isAttacking && !isKnockbacking && currentState != AIState.Stun && agent.hasPath && agent.velocity.magnitude > 0.1f)
{
targetSpeed = 1f;
}
@@ -223,7 +227,7 @@ public class EnemyAI : MonoBehaviour
/// 攻撃ステートの際、プレイヤーの方向へ滑らかに旋回させる
private void UpdateRotationOnAttack()
{
if (currentState == AIState.Attack && playerTransform != null && !isKnockbacking)
if (currentState == AIState.Attack && playerTransform != null && !isKnockbacking && currentState != AIState.Stun)
{
Vector3 direction = (playerTransform.position - transform.position).normalized;
direction.y = 0; // 上下方向の傾き(ピッチ回転)を無視
@@ -242,6 +246,7 @@ public class EnemyAI : MonoBehaviour
{
if(isDead) return;
SetFistCollider(false);
StopAllCoroutines();
isAttacking = false;
isWandering = false;
@@ -255,6 +260,21 @@ public class EnemyAI : MonoBehaviour
StartCoroutine(KnockbackRoutine(duration));
}
public void TriggerParryStun(float duration)
{
if(isDead) return;
Debug.Log($"{gameObject.name}: parry = true Stunnow");
SetFistCollider(false);
StopAllCoroutines();
isAttacking = false;
isWandering = false;
isKnockbacking = false;
StartCoroutine(ParryStunRoutine(duration));
}
public void DisableAIOnDeath()
{
isDead = true;
@@ -266,6 +286,31 @@ public class EnemyAI : MonoBehaviour
}
}
private IEnumerator ParryStunRoutine(float duration)
{
currentState = AIState.Stun;
if(agent.isOnNavMesh)
{
agent.isStopped = true;
agent.ResetPath();
}
anim.SetTrigger("Stun");
yield return new WaitForSeconds(duration);
if(agent.isOnNavMesh)
{
agent.isStopped = false;
}
currentState = AIState.Chase;
isCooldown = false;
StartCoroutine(AILoop());
}
private IEnumerator KnockbackRoutine(float duration)
{
isKnockbacking = true;
@@ -379,21 +424,17 @@ public class EnemyAI : MonoBehaviour
/// 攻撃アニメーションのヒットフレームで実行されるヒット判定Animation Eventから呼び出し
public void OnPunchHit()
{
if (isKnockbacking || playerTransform == null) return;
if (isKnockbacking || currentState == AIState.Stun) return;
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
// ヒット判定の瞬間、プレイヤーが射程内に残っているか判定猶予として0.3m加算)
if (distanceToPlayer <= attackRange + 0.3f)
{
Debug.Log($"💥 敵のパンチがヒット!");
}
SetFistCollider(true);
}
/// Recovery硬直アニメーションの終了時に実行される処理Animation Eventから呼び出し
public void OnRecoveryEnd()
{
if(isKnockbacking) return;
if(isKnockbacking || currentState == AIState.Stun) return;
SetFistCollider(false);
isAttacking = false;
if (playerTransform == null)
@@ -423,6 +464,14 @@ public class EnemyAI : MonoBehaviour
#region
/// 指定された中心座標の範囲内から、NavMesh上の有効なランダム座標を取得する
private void SetFistCollider(bool enabledState)
{
if(rightHandCollider != null)
{
rightHandCollider.enabled = enabledState;
}
}
private Vector3 GetRandomNavMeshPoint(Vector3 center, float radius)
{
Vector3 randomDirection = UnityEngine.Random.insideUnitSphere * radius;

View File

@@ -9,6 +9,8 @@ public class EnemyHealth : MonoBehaviour, IDamageble
[SerializeField] private float knockbackDuration = 0.5f;
private EnemyAI enemyAI;
private bool isDead = false;
void Start()
{
currentHealth = maxHealth; //HPを代入
@@ -41,11 +43,26 @@ public class EnemyHealth : MonoBehaviour, IDamageble
private void Die()
{
if(isDead) return;
isDead = true;
Animator anim = GetComponent<Animator>();
if(anim != null)
{
anim.SetTrigger("Dead");
}
Collider col = GetComponent<Collider>();
if(col != null)
{
col.enabled = false;
}
if(enemyAI != null)
{
enemyAI.DisableAIOnDeath();
}
Destroy(gameObject, 0.5f);
Destroy(gameObject, 3.0f);
}
}

View File

@@ -7,17 +7,25 @@ using UnityEngine.XR; // 振動機能のために必要
public class VRInputPunch : MonoBehaviour
{
[SerializeField] private InputActionProperty velocityAction;
[SerializeField] private XRNode controllerNode = XRNode.RightHand;
[SerializeField] private VRInputPoseDetector poseDetector;
[SerializeField] private float minPunchSpeed = 2.0f; //パンチとして判定する最低速度 (m/s)
[SerializeField] private float damageMultiplier = 10.0f; //ダメージ倍率
[Header("攻撃(パンチ)設定")]
[SerializeField] private float minPunchSpeed = 2.0f; // パンチとして判定する最低速度 (m/s)
[SerializeField] private float damageMultiplier = 10.0f; // ダメージ倍率
[SerializeField] private float hapticAmplitude = 0.5f; // 通常ヒットの振動の強さ
[SerializeField] private float hapticDuration = 0.1f; // 通常ヒットの振動の長さ
[SerializeField] private float hapticAmplitude = 0.5f; //振動の強さ
[SerializeField] private float hapticDuration = 0.1f; //振動の長さ
[Header("パリィ設定")]
[Tooltip("パリィを成立させるために必要な最低限の手の速度 (m/s) ※構え中は速度に関係なく成功します")]
[SerializeField] private float minParrySpeed = 1.0f;
[Tooltip("パリィ成功時に敵がスタン(行動不能)になる時間 (秒)")]
[SerializeField] private float parryStunDuration = 3.0f;
[Tooltip("パリィ成功時の強力な振動の強さ (0.0 1.0)")]
[SerializeField] private float parryHapticAmplitude = 0.9f;
[Tooltip("パリィ成功時の強力な振動の長さ (秒)")]
[SerializeField] private float parryHapticDuration = 0.25f;
// ─────────────────────────────────────────
private void OnEnable()
{
@@ -45,10 +53,42 @@ public class VRInputPunch : MonoBehaviour
private void OnTriggerEnter(Collider other)
{
// コントローラーの現在の移動速度を取得
Vector3 velocity = velocityAction.action.ReadValue<Vector3>();
float speed = velocity.magnitude;
// ─── ★新設:【パリィ判定】当たったオブジェクトが「敵の拳」だった場合 ───
if (other.CompareTag("EnemyFist"))
{
Debug.Log($"🛡️ [パリィチェック] 敵の拳と接触。プレイヤーの手の速度: {speed:F2} m/s / 構え状態: {poseDetector.IsPosing}");
// 条件構え状態IsPosingである、もしくは、弾くための速度が一定以上出ている場合
if (poseDetector.IsPosing || speed >= minParrySpeed)
{
// 敵の親オブジェクトからEnemyAIスクリプトを検索
EnemyAI enemyAI = other.GetComponentInParent<EnemyAI>();
if (enemyAI != null)
{
// 敵の攻撃コンボを強制中断し、スタン状態にする
enemyAI.TriggerParryStun(parryStunDuration);
// パリィ成功用の強力な振動(手応え)をプレイヤーに返す
TriggerHaptics(parryHapticAmplitude, parryHapticDuration);
Debug.Log($"🛡️ 【ジャストパリィ成功!】 敵の体勢を崩しました。スタン時間: {parryStunDuration}秒");
return; // パリィが成立した場合は以降のパンチ処理は行わない
}
}
else
{
Debug.Log("🧱 ガード(または速度不足):敵の攻撃は防いだが、弾き返せなかった。");
return;
}
}
// ────────────────────────────────────────────────────────────────────────
// ─── 以下は従来の【パンチ攻撃】の処理(変更なし・ログのバグ修正のみ) ───
Debug.Log($"[InputSystem接触] {other.name} に触れました。センサー速度: {speed:F2} m/s");
if (speed >= minPunchSpeed)
@@ -58,7 +98,7 @@ public class VRInputPunch : MonoBehaviour
if (damageble != null)
{
DamageInfo info = new DamageInfo();
if(poseDetector.IsPosing)
if (poseDetector.IsPosing)
{
info.amount = (speed * damageMultiplier) * 2f;
info.isPoseBonus = true;
@@ -69,26 +109,33 @@ public class VRInputPunch : MonoBehaviour
info.isPoseBonus = false;
}
info.hitPosition = other.transform.position;
info.punchDirection = velocity.normalized; //パンチの進む向き
damageble.TakeDamage(info);
Debug.Log("パンチ成功 ダメージ: {damage:F1}");
info.punchDirection = velocity.normalized;
TriggerHaptics();
}
else
{
Debug.Log("スピードが足りません。必要: {minPunchSpeed} / 現在: {speed:F2}");
damageble.TakeDamage(info);
// ※元のコードのログ出力の記述バグ($の付け忘れ)を修正しました
Debug.Log($"パンチ成功 ダメージ: {info.amount:F1}");
// 通常ヒットの振動を発生
TriggerHaptics(hapticAmplitude, hapticDuration);
}
}
else
{
// ※元のコードのログ出力の記述バグ($の付け忘れ)を修正しました
Debug.Log($"スピードが足りません。必要: {minPunchSpeed} / 現在: {speed:F2}");
}
}
// コントローラーを振動させる処理
private void TriggerHaptics()
/// <summary>
/// 引数に応じて指定された強さと長さでコントローラーを振動させる
/// </summary>
private void TriggerHaptics(float amplitude, float duration)
{
UnityEngine.XR.InputDevice device = InputDevices.GetDeviceAtXRNode(controllerNode);
if (device.isValid)
{
device.SendHapticImpulse(0, hapticAmplitude, hapticDuration);
device.SendHapticImpulse(0, amplitude, duration);
}
}
}