From 52341100edb1282da2eaf165162cc683dc8064f9 Mon Sep 17 00:00:00 2001 From: ohgushiyuga Date: Thu, 27 Aug 2026 12:51:15 +0900 Subject: [PATCH] otamesi --- Assets/_Objects/AoePrefab.prefab | 2 +- Assets/_Scenes/BossStage.unity | 21 +- Assets/_Scenes/GameScene.unity | 52 ++- Assets/_Scripts/BossController.cs | 174 ++++------ Assets/_Scripts/BossWebSpawner.cs | 81 +++++ Assets/_Scripts/BossWebSpawner.cs.meta | 2 + Assets/_Scripts/BuffHandler.cs | 54 +++ Assets/_Scripts/BuffHandler.cs.meta | 2 + Assets/_Scripts/ElevatorSceneTransition.cs | 41 +-- Assets/_Scripts/EnemyAI.cs | 327 +++++++----------- Assets/_Scripts/EnemyHealth.cs | 80 +++-- Assets/_Scripts/IDamageble.cs | 11 +- Assets/_Scripts/IPauseable.cs | 5 + Assets/_Scripts/IPauseable.cs.meta | 2 + Assets/_Scripts/InputLR.cs | 28 -- Assets/_Scripts/InputLR.cs.meta | 2 - Assets/_Scripts/InputManagerLR.cs | 81 ++--- Assets/_Scripts/NewMonoBehaviourScript.cs | 74 ---- .../_Scripts/NewMonoBehaviourScript.cs.meta | 2 - Assets/_Scripts/PlayerStatusManager.cs | 187 ++++------ Assets/_Scripts/PuzzleBlock.cs | 113 ++++-- Assets/_Scripts/SlowWebArea.cs | 23 +- Assets/_Scripts/SpiderIdleState.cs | 88 +++-- Assets/_Scripts/TitleManager.cs | 11 +- Assets/_Scripts/VRDoorController.cs | 44 +-- Assets/_Scripts/VRInputPoseDetector.cs | 68 ++-- Assets/_Scripts/VRLaserPointer.cs | 92 ----- Assets/_Scripts/VRLaserPointer.cs.meta | 2 - Assets/_Scripts/VRPunchAttack.cs | 174 ++++------ Assets/_Scripts/VRUIDoorFaceFollow.cs | 24 +- Assets/_Scripts/WorldPauseService.cs | 47 +++ Assets/_Scripts/WorldPauseService.cs.meta | 2 + 32 files changed, 840 insertions(+), 1076 deletions(-) create mode 100644 Assets/_Scripts/BossWebSpawner.cs create mode 100644 Assets/_Scripts/BossWebSpawner.cs.meta create mode 100644 Assets/_Scripts/BuffHandler.cs create mode 100644 Assets/_Scripts/BuffHandler.cs.meta create mode 100644 Assets/_Scripts/IPauseable.cs create mode 100644 Assets/_Scripts/IPauseable.cs.meta delete mode 100644 Assets/_Scripts/InputLR.cs delete mode 100644 Assets/_Scripts/InputLR.cs.meta delete mode 100644 Assets/_Scripts/NewMonoBehaviourScript.cs delete mode 100644 Assets/_Scripts/NewMonoBehaviourScript.cs.meta delete mode 100644 Assets/_Scripts/VRLaserPointer.cs delete mode 100644 Assets/_Scripts/VRLaserPointer.cs.meta create mode 100644 Assets/_Scripts/WorldPauseService.cs create mode 100644 Assets/_Scripts/WorldPauseService.cs.meta diff --git a/Assets/_Objects/AoePrefab.prefab b/Assets/_Objects/AoePrefab.prefab index d06f90b..3978477 100644 --- a/Assets/_Objects/AoePrefab.prefab +++ b/Assets/_Objects/AoePrefab.prefab @@ -84,7 +84,7 @@ MonoBehaviour: m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 m_DynamicPixelsPerUnit: 1 - m_PresetInfoIsWorld: 0 + m_PresetInfoIsWorld: 1 --- !u!114 &4150137515110638401 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/_Scenes/BossStage.unity b/Assets/_Scenes/BossStage.unity index cd7ae10..8d06c48 100644 --- a/Assets/_Scenes/BossStage.unity +++ b/Assets/_Scenes/BossStage.unity @@ -732,9 +732,6 @@ MonoBehaviour: k__BackingField: 0.2 k__BackingField: 0.8 k__BackingField: 10 - k__BackingField: 2.5 - warningMarkerPrefab: {fileID: 780381182997121746, guid: ad8909b4e4ba9424794a9e9054ea9e43, type: 3} - webPrefab: {fileID: 7127456385706922585, guid: 6cee7cb816e34c1478e31e98e258a22a, type: 3} --- !u!195 &139010308 NavMeshAgent: m_ObjectHideFlags: 0 @@ -817,6 +814,21 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d2cd755b0c100204e833df85acb412e8, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::BossDebugController +--- !u!114 &139010321 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 139010306} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: aef8ddafc1c1d9e448ca7631fa034d60, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::BossWebSpawner + warningMarkerPrefab: {fileID: 780381182997121746, guid: ad8909b4e4ba9424794a9e9054ea9e43, type: 3} + webPrefab: {fileID: 7127456385706922585, guid: 6cee7cb816e34c1478e31e98e258a22a, type: 3} + k__BackingField: 2.5 --- !u!1 &142265989 GameObject: m_ObjectHideFlags: 0 @@ -4447,6 +4459,9 @@ PrefabInstance: - targetCorrespondingSourceObject: {fileID: 1987720806421918, guid: a2dfe588a42016e40892b72d31fd1cf6, type: 3} insertIndex: -1 addedObject: {fileID: 139010320} + - targetCorrespondingSourceObject: {fileID: 1987720806421918, guid: a2dfe588a42016e40892b72d31fd1cf6, type: 3} + insertIndex: -1 + addedObject: {fileID: 139010321} - targetCorrespondingSourceObject: {fileID: 1388461240955602, guid: a2dfe588a42016e40892b72d31fd1cf6, type: 3} insertIndex: -1 addedObject: {fileID: 50675083} diff --git a/Assets/_Scenes/GameScene.unity b/Assets/_Scenes/GameScene.unity index 83f3315..955d4fa 100644 --- a/Assets/_Scenes/GameScene.unity +++ b/Assets/_Scenes/GameScene.unity @@ -756,7 +756,7 @@ GameObject: - component: {fileID: 119118690} - component: {fileID: 119118689} - component: {fileID: 119118688} - - component: {fileID: 119118691} + - component: {fileID: 119118692} m_Layer: 5 m_Name: Button (Legacy) m_TagString: Untagged @@ -866,18 +866,27 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 119118686} m_CullTransparentMesh: 1 ---- !u!114 &119118691 -MonoBehaviour: +--- !u!65 &119118692 +BoxCollider: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 119118686} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bca5237dce7df3543ba5a9c686d545d6, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::VRButtonTest + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} + m_Center: {x: 0, y: 0, z: 0} --- !u!1001 &130511407 PrefabInstance: m_ObjectHideFlags: 0 @@ -14741,7 +14750,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!224 &591090780 RectTransform: m_ObjectHideFlags: 0 @@ -646636,6 +646645,7 @@ GameObject: m_Component: - component: {fileID: 1455781481} - component: {fileID: 1455781480} + - component: {fileID: 1455781482} m_Layer: 0 m_Name: PlayerManager m_TagString: Untagged @@ -646655,7 +646665,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 0215a75a0f67e1b49a1b823cfc7b8503, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::PlayerStatusManager - ignoreGaugeForDebug: 1 + ignoreGaugeForDebug: 0 zlButtonAction: m_UseReference: 1 m_Action: @@ -646716,12 +646726,13 @@ MonoBehaviour: maxGauge: 10 currentGauge: 0 codingModeUI: {fileID: 797096988} - buffDuration: 10 - attackBuffMultiplier: 1.5 - defenseBuffMultiplier: 0.5 - hpHealAmount: 30 successEffectPrefab: {fileID: 3075638675573572061, guid: 59334a8a54b4cbc48996b04618ef137c, type: 3} effectSpawnPoint: {fileID: 797096992} + buffHandler: + buffDuration: 10 + attackBuffMultiplier: 1.5 + defenseBuffMultiplier: 0.5 + hpHealAmount: 30 --- !u!4 &1455781481 Transform: m_ObjectHideFlags: 0 @@ -646737,6 +646748,18 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1455781482 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1455781479} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6f00095a3e63cc440be35394ad49b505, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::WorldPauseManager --- !u!1001 &1455907610 PrefabInstance: m_ObjectHideFlags: 0 @@ -656775,7 +656798,8 @@ PrefabInstance: propertyPath: m_LocalEulerAnglesHint.z value: 0 objectReference: {fileID: 0} - m_RemovedComponents: [] + m_RemovedComponents: + - {fileID: 3150725272858075539, guid: 469b8fdf008b844589dcc60b237284c5, type: 3} m_RemovedGameObjects: [] m_AddedGameObjects: [] m_AddedComponents: [] diff --git a/Assets/_Scripts/BossController.cs b/Assets/_Scripts/BossController.cs index d8bd126..72a2741 100644 --- a/Assets/_Scripts/BossController.cs +++ b/Assets/_Scripts/BossController.cs @@ -1,24 +1,29 @@ +using System; +using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; -using System.Collections.Generic; [RequireComponent(typeof(NavMeshAgent))] [RequireComponent(typeof(Animator))] -public class BossController : MonoBehaviour, IDamageble +[RequireComponent(typeof(BossWebSpawner))] +public class BossController : MonoBehaviour, IDamageable, IPauseable { + [Header("ステータス")] [SerializeField] private float maxHp = 500f; [SerializeField] private float currentHp = 500f; + + [field: Header("移動・攻撃パラメータ")] [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; + [Header("アンカー・天井技設定")] + public Transform ceilingAnchor; + 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; @@ -26,33 +31,28 @@ public class BossController : MonoBehaviour, IDamageble [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 ActiveWebPositions { get; private set; } = new List(); - [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 BossWebSpawner WebSpawner { get; private set; } + + public float ChargeCooldownTimer { get; private set; } public float NormalSpeed { get; private set; } public float NormalAcceleration { get; private set; } + public bool PendingUltimate { get; set; } private ISpiderState currentState; - private Dictionary stateCache; + private Dictionary stateCache; - // フェーズトリガー - private bool triggered50 = false; - private bool triggered25 = false; - public bool PendingUltimate { get; set; } = false; + private bool triggered50; + private bool triggered25; + private bool isPaused; private void Awake() { Agent = GetComponent(); Anim = GetComponent(); + WebSpawner = GetComponent(); GameObject pObj = GameObject.FindGameObjectWithTag("Player"); if (pObj != null) Player = pObj.transform; @@ -67,11 +67,44 @@ public class BossController : MonoBehaviour, IDamageble NormalSpeed = Agent.speed; NormalAcceleration = Agent.acceleration; } + + if (WorldPauseManager.Instance != null) + { + WorldPauseManager.Instance.Register(this); + } + } + + 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) + { + Agent.isStopped = false; + } } private void InitializeFSM() { - stateCache = new Dictionary + stateCache = new Dictionary { { typeof(SpiderIdleState), new SpiderIdleState(this) }, { typeof(SpiderChaseState), new SpiderChaseState(this) }, @@ -87,6 +120,8 @@ public class BossController : MonoBehaviour, IDamageble private void Update() { + if (isPaused) return; + currentState?.UpdateState(); if (ChargeCooldownTimer > 0f) @@ -97,26 +132,18 @@ public class BossController : MonoBehaviour, IDamageble public void ChangeState() where T : ISpiderState { - if (currentState != null) - { - currentState.ExitState(); - ResetAllAnimationTriggers(); - Debug.Log($"🕷️ BossSpider [Exit]: {currentState.GetType().Name}"); - } + currentState?.ExitState(); + ResetAllAnimationTriggers(); 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); + currentHp = Mathf.Clamp(currentHp - damageInfo.amount, 0, maxHp); float hpPercentage = (currentHp / maxHp) * 100f; - Debug.Log($"💥 ボスに {damageInfo.amount} ダメージ。残りHP: {hpPercentage:F1}%"); - if (hpPercentage <= 25f && !triggered25) { triggered25 = true; @@ -143,7 +170,7 @@ public class BossController : MonoBehaviour, IDamageble public bool TriggerParryStun() { - if (currentState is SpiderBiteState || currentState is SpiderChargeState) + if (currentState is SpiderBiteState or SpiderChargeState) { ChangeState(); return true; @@ -151,49 +178,7 @@ public class BossController : MonoBehaviour, IDamageble 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 ResetChargeCooldown() => ChargeCooldownTimer = ChargeCooldownDuration; public void ResetAllAnimationTriggers() { @@ -206,46 +191,11 @@ public class BossController : MonoBehaviour, IDamageble Anim.ResetTrigger("StunStart"); } - #region デバッグ用機能 - /// - /// Unityインスペクターの右上メニューから実行できるデバッグ用コマンドです - /// - [ContextMenu("🛠️ デバッグ:強制必殺技(天井ジャンプ)")] + [ContextMenu("🛠️ デバッグ:強制必殺技")] public void DebugTriggerUltimate() { - if (!Application.isPlaying) - { - Debug.LogWarning("⚠️ ゲームが再生中(Playモード)の時のみ実行可能です。"); - return; - } - - Debug.Log("🛠️ 【デバッグ操作】必殺技の予約券を発行し、強制的に天井ジャンプステートへ遷移します。"); - - // 必殺技の発動権をONにし、現在の行動をキャンセルして天井ジャンプへ移行 + if (!Application.isPlaying) return; PendingUltimate = true; ChangeState(); } - #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(); } \ No newline at end of file diff --git a/Assets/_Scripts/BossWebSpawner.cs b/Assets/_Scripts/BossWebSpawner.cs new file mode 100644 index 0000000..1e91879 --- /dev/null +++ b/Assets/_Scripts/BossWebSpawner.cs @@ -0,0 +1,81 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +public class BossWebSpawner : MonoBehaviour, IPauseable +{ + [SerializeField] private GameObject warningMarkerPrefab; + [SerializeField] private GameObject webPrefab; + [field: SerializeField] public float WebAvoidanceRadius { get; private set; } = 2.5f; + + public List ActiveWebPositions { get; private set; } = new List(); + private bool isPaused; + + private void Start() + { + if (WorldPauseManager.Instance != null) + { + WorldPauseManager.Instance.Register(this); + } + } + + private void OnDestroy() + { + if (WorldPauseManager.Instance != null) + { + WorldPauseManager.Instance.Unregister(this); + } + } + + public void Pause() => isPaused = true; + public void Resume() => isPaused = false; + + private IEnumerator WaitForUnpausedSeconds(float duration) + { + float timer = 0f; + while (timer < duration) + { + if (!isPaused) + { + timer += Time.deltaTime; + } + yield return null; + } + } + + public void SpawnWarningAndWeb(Vector3 targetGroundPos, float delay, float webDuration) + { + StartCoroutine(SpawnWebRoutine(targetGroundPos, delay, webDuration)); + } + + private IEnumerator SpawnWebRoutine(Vector3 pos, float delay, float duration) + { + ActiveWebPositions.Add(pos); + + GameObject marker = null; + if (warningMarkerPrefab != null) + { + marker = Instantiate(warningMarkerPrefab, pos + new Vector3(0, 0.5f, 0), Quaternion.identity); + } + + yield return WaitForUnpausedSeconds(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); + + yield return WaitForUnpausedSeconds(duration); + + if (web != null) Destroy(web); + } + else + { + yield return WaitForUnpausedSeconds(duration); + } + + ActiveWebPositions.Remove(pos); + } +} \ No newline at end of file diff --git a/Assets/_Scripts/BossWebSpawner.cs.meta b/Assets/_Scripts/BossWebSpawner.cs.meta new file mode 100644 index 0000000..ec58b54 --- /dev/null +++ b/Assets/_Scripts/BossWebSpawner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: aef8ddafc1c1d9e448ca7631fa034d60 \ No newline at end of file diff --git a/Assets/_Scripts/BuffHandler.cs b/Assets/_Scripts/BuffHandler.cs new file mode 100644 index 0000000..45eb183 --- /dev/null +++ b/Assets/_Scripts/BuffHandler.cs @@ -0,0 +1,54 @@ +using System; +using UnityEngine; + +public enum BuffType +{ + None, + AttackUp, + DefenseUp, + Heal +} + +[Serializable] +public class BuffHandler +{ + [SerializeField] private float buffDuration = 10f; + [SerializeField] private float attackBuffMultiplier = 1.5f; + [SerializeField] private float defenseBuffMultiplier = 0.5f; + [SerializeField] private float hpHealAmount = 30f; + + private float attackBuffTimer; + private float defenseBuffTimer; + + public bool IsAttackBuffActive => attackBuffTimer > 0f; + public bool IsDefenseBuffActive => defenseBuffTimer > 0f; + public float CurrentAttackMultiplier => IsAttackBuffActive ? attackBuffMultiplier : 1.0f; + public float CurrentDefenseMultiplier => IsDefenseBuffActive ? defenseBuffMultiplier : 1.0f; + + public void UpdateTimers(float deltaTime) + { + if (attackBuffTimer > 0f) attackBuffTimer -= deltaTime; + if (defenseBuffTimer > 0f) defenseBuffTimer -= deltaTime; + } + + public void ActivateBuff(BuffType type) + { + switch (type) + { + case BuffType.AttackUp: + attackBuffTimer = buffDuration; + break; + case BuffType.DefenseUp: + defenseBuffTimer = buffDuration; + break; + case BuffType.Heal: + ExecuteHeal(); + break; + } + } + + private void ExecuteHeal() + { + Debug.Log($"💚 HP回復バフ発動:回復量 {hpHealAmount}"); + } +} \ No newline at end of file diff --git a/Assets/_Scripts/BuffHandler.cs.meta b/Assets/_Scripts/BuffHandler.cs.meta new file mode 100644 index 0000000..54a7e87 --- /dev/null +++ b/Assets/_Scripts/BuffHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3db55a74838ece4418651e3d28d097cf \ No newline at end of file diff --git a/Assets/_Scripts/ElevatorSceneTransition.cs b/Assets/_Scripts/ElevatorSceneTransition.cs index d97ecdb..4d9cc0c 100644 --- a/Assets/_Scripts/ElevatorSceneTransition.cs +++ b/Assets/_Scripts/ElevatorSceneTransition.cs @@ -1,38 +1,28 @@ +using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.XR; -using System.Collections; public class ElevatorSceneTransition : MonoBehaviour { - [Header("遷移先の設定")] + [Header("シーン設定")] [SerializeField] private string bossSceneName = "BossStage"; [SerializeField] private string playerTag = "Player"; - [Header("ローディングUI設定")] + [Header("UI設定")] [SerializeField] private GameObject loadingCanvas; [SerializeField] private Text progressText; [SerializeField] private Slider progressSlider; - [Header("エレベーター下降演出設定")] - [Tooltip("エレベーターの部屋(壁や床、UIすべて)をまとめているオブジェクトを指定してください(※プレイヤーは外しておきます)")] + [Header("演出設定")] [SerializeField] private Transform elevatorCageTransform; - - [Tooltip("ガタガタ揺れる激しさ(0.01~0.03あたりが自然です)")] [SerializeField] private float shakeMagnitude = 0.015f; - - [Tooltip("下降中のコントローラーの振動の強さ(0.0 ~ 1.0)")] [SerializeField] private float hapticAmplitude = 0.15f; - - // ─── ★新設:親子関係にしないためのプレイヤー登録枠 ─── - [Header("プレイヤーの同期設定")] - [Tooltip("XR Origin(またはXR Rig)のTransformを直接ドラッグ&ドロップしてください")] [SerializeField] private Transform playerTransform; - // ───────────────────────────────────────────────────── - private bool isLoadingStarted = false; - private Vector3 originalCagePosition; + private bool isLoadingStarted; + private Vector3 originalCagePosition; private void Start() { @@ -40,7 +30,6 @@ public class ElevatorSceneTransition : MonoBehaviour if (elevatorCageTransform != null) { - // ワールド空間での初期位置を記憶(localPositionからpositionに変更し、より確実に) originalCagePosition = elevatorCageTransform.position; } } @@ -52,14 +41,12 @@ public class ElevatorSceneTransition : MonoBehaviour if (other.CompareTag(playerTag)) { isLoadingStarted = true; - - // ★安全対策:接触したプレイヤーのTransformを自動でキャッシュする + if (playerTransform == null) { playerTransform = other.transform; } - Debug.Log("🛗 プレイヤー乗車:親子関係なしのデルタ同期で下降演出を開始します。"); StartCoroutine(LoadSceneAsyncRoutine()); } } @@ -79,31 +66,24 @@ public class ElevatorSceneTransition : MonoBehaviour if (progressText != null) progressText.text = $"LOADING: {(currentProgress * 100f):F0}%"; if (progressSlider != null) progressSlider.value = currentProgress; - // ─── ★【超重要】親子関係なしでプレイヤーを完全同期させるロジック ─── - - // 1. 動かす前の、このフレームにおけるエレベーターの現在位置を記録 Vector3 previousCagePosition = elevatorCageTransform.position; - // 2. エレベーターの部屋だけをランダムにガタガタ動かす if (elevatorCageTransform != null) { Vector3 randomOffset = Random.insideUnitSphere * shakeMagnitude; - randomOffset.y = 0; // 横揺れのみ + randomOffset.y = 0; elevatorCageTransform.position = originalCagePosition + randomOffset; } - // 3. 「この1フレームでエレベーターが実際に動いた移動差分(デルタ)」を計算 Vector3 cageDelta = elevatorCageTransform.position - previousCagePosition; - // 4. その移動差分を、プレイヤーの座標にもそのまま上乗せして足し算する if (playerTransform != null && cageDelta != Vector3.zero) { playerTransform.position += cageDelta; } - // ─────────────────────────────────────────────────────────────────── hapticTimer += Time.deltaTime; - if (hapticTimer >= 0.1f) + if (hapticTimer >= 0.1f) { TriggerBothHandHaptics(hapticAmplitude, 0.08f); hapticTimer = 0f; @@ -112,10 +92,8 @@ public class ElevatorSceneTransition : MonoBehaviour yield return null; } - // 到着後、エレベーターの位置を元の位置に綺麗に戻す if (elevatorCageTransform != null) { - // 最後の移動差分を計算してプレイヤーを戻す Vector3 previousCagePosition = elevatorCageTransform.position; elevatorCageTransform.position = originalCagePosition; Vector3 cageDelta = elevatorCageTransform.position - previousCagePosition; @@ -127,7 +105,6 @@ public class ElevatorSceneTransition : MonoBehaviour yield return new WaitForSeconds(1.0f); - Debug.Log("🎉 ボスステージに到着。シーンを切り替えます。"); asyncLoad.allowSceneActivation = true; } diff --git a/Assets/_Scripts/EnemyAI.cs b/Assets/_Scripts/EnemyAI.cs index 8cbcf7e..cfa1c21 100644 --- a/Assets/_Scripts/EnemyAI.cs +++ b/Assets/_Scripts/EnemyAI.cs @@ -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(); anim = GetComponent(); + } + 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 } \ No newline at end of file diff --git a/Assets/_Scripts/EnemyHealth.cs b/Assets/_Scripts/EnemyHealth.cs index 05b15be..d08939c 100644 --- a/Assets/_Scripts/EnemyHealth.cs +++ b/Assets/_Scripts/EnemyHealth.cs @@ -1,68 +1,74 @@ using System; using UnityEngine; -public class EnemyHealth : MonoBehaviour, IDamageble +public class EnemyHealth : MonoBehaviour, IDamageable { [SerializeField] private float maxHealth = 100f; - private float currentHealth; - [SerializeField] private float knockbackDuration = 0.5f; + + public float CurrentHealth { get; private set; } + public bool IsDead { get; private set; } + + public event Action OnKnockbackRequested; + public event Action OnDeath; + private EnemyAI enemyAI; + private Animator anim; + private Collider col; - private bool isDead = false; + private static readonly int DeadHash = Animator.StringToHash("Dead"); - void Start() + private void Awake() { - currentHealth = maxHealth; //HPを代入 + CurrentHealth = maxHealth; enemyAI = GetComponent(); + anim = GetComponent(); + col = GetComponent(); + } + + private void OnEnable() + { + if (enemyAI != null) + { + OnKnockbackRequested += enemyAI.TriggerKnockback; + OnDeath += enemyAI.DisableAIOnDeath; + } + } + + private void OnDisable() + { + if (enemyAI != null) + { + OnKnockbackRequested -= enemyAI.TriggerKnockback; + OnDeath -= enemyAI.DisableAIOnDeath; + } } public void TakeDamage(DamageInfo damageInfo) { - if(currentHealth <= 0) return; + if (IsDead) return; - currentHealth -= damageInfo.amount; //ダメージ計算 + CurrentHealth = Mathf.Max(0f, CurrentHealth - damageInfo.amount); - if(damageInfo.isPoseBonus) - { - Debug.Log("構えボーナス"); - } - - if(currentHealth <= 0) + if (CurrentHealth <= 0f) { Die(); } else { - if(enemyAI != null) - { - enemyAI.TriggerKnockback(knockbackDuration); - } + OnKnockbackRequested?.Invoke(knockbackDuration); } } private void Die() { - if(isDead) return; - isDead = true; + if (IsDead) return; + IsDead = true; - Animator anim = GetComponent(); - if(anim != null) - { - anim.SetTrigger("Dead"); - } + if (anim != null) anim.SetTrigger(DeadHash); + if (col != null) col.enabled = false; - Collider col = GetComponent(); - if(col != null) - { - col.enabled = false; - } - - if(enemyAI != null) - { - enemyAI.DisableAIOnDeath(); - } - + OnDeath?.Invoke(); Destroy(gameObject, 3.0f); } -} +} \ No newline at end of file diff --git a/Assets/_Scripts/IDamageble.cs b/Assets/_Scripts/IDamageble.cs index afd5423..efd6438 100644 --- a/Assets/_Scripts/IDamageble.cs +++ b/Assets/_Scripts/IDamageble.cs @@ -2,12 +2,13 @@ using UnityEngine; public struct DamageInfo { - public float amount; //ダメージ量 - public Vector3 hitPosition; //殴られた位置 - public Vector3 punchDirection; //パンチが飛んできた方向(ノックバック用) - public bool isPoseBonus; //構えから攻撃かどうか + public float amount; + public Vector3 hitPosition; + public Vector3 punchDirection; + public bool isPoseBonus; } -public interface IDamageble + +public interface IDamageable { void TakeDamage(DamageInfo damageInfo); } \ No newline at end of file diff --git a/Assets/_Scripts/IPauseable.cs b/Assets/_Scripts/IPauseable.cs new file mode 100644 index 0000000..11901e9 --- /dev/null +++ b/Assets/_Scripts/IPauseable.cs @@ -0,0 +1,5 @@ +public interface IPauseable +{ + void Pause(); + void Resume(); +} \ No newline at end of file diff --git a/Assets/_Scripts/IPauseable.cs.meta b/Assets/_Scripts/IPauseable.cs.meta new file mode 100644 index 0000000..7c7a724 --- /dev/null +++ b/Assets/_Scripts/IPauseable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4b7c4298a74840743a4bb184041b53f9 \ No newline at end of file diff --git a/Assets/_Scripts/InputLR.cs b/Assets/_Scripts/InputLR.cs deleted file mode 100644 index dbdd07e..0000000 --- a/Assets/_Scripts/InputLR.cs +++ /dev/null @@ -1,28 +0,0 @@ -using UnityEngine; -using Unity.VRTemplate; - -public class InputLR : MonoBehaviour -{ - void Update() - { - if(InputManagerLR.PrimaryButtonR()) - { - OnPrimaryButtonR(); - } - - if(InputManagerLR.PrimaryButtonR_OnPress()) - { - OnPressPrimaryButtonR(); - } - } - - void OnPrimaryButtonR() - { - //邪魔になるためなし - } - - void OnPressPrimaryButtonR() - { - Debug.Log("押した瞬間"); - } -} diff --git a/Assets/_Scripts/InputLR.cs.meta b/Assets/_Scripts/InputLR.cs.meta deleted file mode 100644 index 230244d..0000000 --- a/Assets/_Scripts/InputLR.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: ae6f0d5ca1f0f42b6a38e38170777c16 \ No newline at end of file diff --git a/Assets/_Scripts/InputManagerLR.cs b/Assets/_Scripts/InputManagerLR.cs index cab8495..1959ce1 100644 --- a/Assets/_Scripts/InputManagerLR.cs +++ b/Assets/_Scripts/InputManagerLR.cs @@ -3,55 +3,56 @@ using UnityEngine.InputSystem; public class InputManagerLR : MonoBehaviour { - static InputManagerLR instance; + private static InputManagerLR instance; - [SerializeField] - InputActionAsset ActionAsset; + [SerializeField] private InputActionAsset actionAsset; - InputActionMap ActionMap; - InputAction m_PrimaryButtonR; - InputAction m_SecondaryButtonR; - InputAction m_PrimaryButtonL; - InputAction m_SecondaryButtonL; + private InputActionMap actionMap; + private InputAction primaryButtonR; + private InputAction secondaryButtonR; + private InputAction primaryButtonL; + private InputAction secondaryButtonL; private void Awake() { + if (instance != null && instance != this) + { + Destroy(gameObject); + return; + } + instance = this; - GameObject.DontDestroyOnLoad(gameObject); - ActionMap = ActionAsset.FindActionMap("Test"); - m_PrimaryButtonR = ActionMap.FindAction("XR_PrimaryButtonR", throwIfNotFound: true); - m_PrimaryButtonL = ActionMap.FindAction("XR_PrimaryButtonL", throwIfNotFound: true); - m_SecondaryButtonR = ActionMap.FindAction("XR_SecondaryButtonR", throwIfNotFound: true); - m_SecondaryButtonL = ActionMap.FindAction("XR_SecondaryButtonL", throwIfNotFound: true); + DontDestroyOnLoad(gameObject); + + if (actionAsset != null) + { + actionMap = actionAsset.FindActionMap("Test"); + if (actionMap != null) + { + primaryButtonR = actionMap.FindAction("XR_PrimaryButtonR", throwIfNotFound: false); + primaryButtonL = actionMap.FindAction("XR_PrimaryButtonL", throwIfNotFound: false); + secondaryButtonR = actionMap.FindAction("XR_SecondaryButtonR", throwIfNotFound: false); + secondaryButtonL = actionMap.FindAction("XR_SecondaryButtonL", throwIfNotFound: false); + } + } } - private void OnEnable() - { - ActionMap?.Enable(); - } + private void OnEnable() => actionMap?.Enable(); + private void OnDisable() => actionMap?.Disable(); - private void OnDisable() - { - ActionMap?.Disable(); - } + public static bool PrimaryButtonR() => instance?.primaryButtonR?.IsPressed() ?? false; + public static bool PrimaryButtonR_OnPress() => instance?.primaryButtonR?.WasPressedThisFrame() ?? false; + public static bool PrimaryButtonR_OnRelease() => instance?.primaryButtonR?.WasReleasedThisFrame() ?? false; - // Aボタン - public static bool PrimaryButtonR() => instance.m_PrimaryButtonR.IsPressed(); - public static bool PrimaryButtonR_OnPress() => instance.m_PrimaryButtonR.WasPressedThisFrame(); - public static bool PrimaryButtonR_OnRelease() => instance.m_PrimaryButtonR.WasReleasedThisFrame(); + public static bool PrimaryButtonL() => instance?.primaryButtonL?.IsPressed() ?? false; + public static bool PrimaryButtonL_OnPress() => instance?.primaryButtonL?.WasPressedThisFrame() ?? false; + public static bool PrimaryButtonL_OnRelease() => instance?.primaryButtonL?.WasReleasedThisFrame() ?? false; - // Xボタン - public static bool PrimaryButtonL() => instance.m_PrimaryButtonL.IsPressed(); - public static bool PrimaryButtonL_OnPress() => instance.m_PrimaryButtonL.WasPressedThisFrame(); - public static bool PrimaryButtonL_OnRelease() => instance.m_PrimaryButtonL.WasReleasedThisFrame(); + public static bool SecondaryButtonR() => instance?.secondaryButtonR?.IsPressed() ?? false; + public static bool SecondaryButtonR_OnPress() => instance?.secondaryButtonR?.WasPressedThisFrame() ?? false; + public static bool SecondaryButtonR_OnRelease() => instance?.secondaryButtonR?.WasReleasedThisFrame() ?? false; - // Bボタン - public static bool SecondaryButtonR() => instance.m_SecondaryButtonR.IsPressed(); - public static bool SecondaryButtonR_OnPress() => instance.m_SecondaryButtonR.WasPressedThisFrame(); - public static bool SecondaryButtonR_OnRelease() => instance.m_SecondaryButtonR.WasReleasedThisFrame(); - - // Yボタン - public static bool SecondaryButtonL() => instance.m_SecondaryButtonL.IsPressed(); - public static bool SecondaryButtonL_OnPress() => instance.m_SecondaryButtonL.WasPressedThisFrame(); - public static bool SecondaryButtonL_OnRelease() => instance.m_SecondaryButtonL.WasReleasedThisFrame(); -} + public static bool SecondaryButtonL() => instance?.secondaryButtonL?.IsPressed() ?? false; + public static bool SecondaryButtonL_OnPress() => instance?.secondaryButtonL?.WasPressedThisFrame() ?? false; + public static bool SecondaryButtonL_OnRelease() => instance?.secondaryButtonL?.WasReleasedThisFrame() ?? false; +} \ No newline at end of file diff --git a/Assets/_Scripts/NewMonoBehaviourScript.cs b/Assets/_Scripts/NewMonoBehaviourScript.cs deleted file mode 100644 index 23d35f3..0000000 --- a/Assets/_Scripts/NewMonoBehaviourScript.cs +++ /dev/null @@ -1,74 +0,0 @@ -using UnityEngine; -using UnityEngine.EventSystems; -using UnityEngine.UI; - -/// -/// XR UIが反応しない原因を自動判定する超安全な診断スクリプト -/// -public class XRUIDiagnoser : MonoBehaviour -{ - private void Start() - { - Debug.Log("============== 🔍 XR UI 自動診断スタート =============="); - - // 1. EventSystem の重複・存在チェック - var eventSystems = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); - if (eventSystems.Length == 0) - { - Debug.LogError("🚨【原因】シーンに EventSystem が存在しません!"); - } - else if (eventSystems.Length > 1) - { - Debug.LogError($"🚨【原因】EventSystem がシーンに {eventSystems.Length} 個あります!これが最大の原因になりやすいです。不要な方を削除してください。"); - foreach (var es in eventSystems) Debug.Log($" ・配置場所: {es.gameObject.name}", es.gameObject); - } - else - { - var module = eventSystems[0].GetComponent(); - Debug.Log($"⭕ EventSystem: OK [{eventSystems[0].name}] | Module: {(module != null ? module.GetType().Name : "なし")}"); - } - - // 2. Ray Interactor (ビーム発射体) の存在チェック (名前で動的探索するためバージョンエラーになりません) - var allComponents = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); - int rayCount = 0; - foreach (var c in allComponents) - { - if (c != null && c.GetType().Name.Contains("RayInteractor")) - { - rayCount++; - bool active = c.gameObject.activeInHierarchy; - if (!active) - Debug.LogError($"🚨【原因】[{c.gameObject.name}] の RayInteractor オブジェクトが非アクティブ(OFF)になっています!", c.gameObject); - else - Debug.Log($"⭕ Ray Interactor 発見: [{c.gameObject.name}] ({c.GetType().Name}) | Active: OK"); - } - } - - if (rayCount == 0) - { - Debug.LogError("🚨【原因】コントローラーの手のオブジェクトに RayInteractor コンポーネントが見つかりません!"); - } - - // 3. Canvas の設定チェック - var canvases = FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); - if (canvases.Length == 0) - { - Debug.LogError("🚨【原因】アクティブな Canvas がシーン内に存在しません!"); - } - - foreach (var canvas in canvases) - { - var raycaster = canvas.GetComponent(); - if (raycaster == null) - { - Debug.LogError($"🚨【原因】Canvas [{canvas.name}] に Raycaster コンポーネントが付いていません!", canvas.gameObject); - } - else - { - Debug.Log($"⭕ Canvas: OK [{canvas.name}] | RenderMode: {canvas.renderMode} | Raycaster: {raycaster.GetType().Name}"); - } - } - - Debug.Log("========================================================"); - } -} \ No newline at end of file diff --git a/Assets/_Scripts/NewMonoBehaviourScript.cs.meta b/Assets/_Scripts/NewMonoBehaviourScript.cs.meta deleted file mode 100644 index da26504..0000000 --- a/Assets/_Scripts/NewMonoBehaviourScript.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: bca5237dce7df3543ba5a9c686d545d6 \ No newline at end of file diff --git a/Assets/_Scripts/PlayerStatusManager.cs b/Assets/_Scripts/PlayerStatusManager.cs index d43b28f..241473d 100644 --- a/Assets/_Scripts/PlayerStatusManager.cs +++ b/Assets/_Scripts/PlayerStatusManager.cs @@ -1,92 +1,73 @@ using UnityEngine; using UnityEngine.InputSystem; -using UnityEngine.AI; -using System.Collections.Generic; public class PlayerStatusManager : MonoBehaviour { public static PlayerStatusManager Instance { get; private set; } [Header("デバッグ設定")] - [SerializeField] private bool ignoreGaugeForDebug = true; + [SerializeField] private bool ignoreGaugeForDebug = true; - [Header("Codingモード用 入力設定")] + [Header("入力設定")] [SerializeField] private InputActionProperty zlButtonAction; [SerializeField] private InputActionProperty zrButtonAction; - - [Header("プレイヤー移動・操作制御用 入力設定")] [SerializeField] private InputActionProperty moveAction; [SerializeField] private InputActionProperty turnAction; - [Header("パズル制限時間設定")] + [Header("パズル制限時間")] [SerializeField] private float codingLimitDuration = 15f; - private float codingTimer = 0f; [Header("ゲージ設定")] [SerializeField] private float maxGauge = 100f; [SerializeField] private float currentGauge = 0f; - [Header("表示UI設定")] + [Header("UI & エフェクト")] [SerializeField] private GameObject codingModeUI; + [SerializeField] private GameObject successEffectPrefab; + [SerializeField] private Transform effectSpawnPoint; - [Header("バフパラメータ設定")] - [Tooltip("攻撃力UP・防御力UPの持続時間 (秒)")] - [SerializeField] private float buffDuration = 10f; - [Tooltip("攻撃力UP時のダメージ倍率")] - [SerializeField] private float attackBuffMultiplier = 1.5f; - [Tooltip("防御力UP時の被ダメージ軽減率")] - [SerializeField] private float defenseBuffMultiplier = 0.5f; - [Tooltip("HP回復バフ発動時の回復量")] - [SerializeField] private float hpHealAmount = 30f; + [Header("バフ機能")] + [SerializeField] private BuffHandler buffHandler = new BuffHandler(); - private float attackBuffTimer = 0f; - private float defenseBuffTimer = 0f; - private bool isCodingMode = false; - - public GameObject successEffectPrefab; - // エフェクトを出す位置(合体したブロックの少し上など) - public Transform effectSpawnPoint; - - private List pausedAnimators = new List(); - private List pausedAgents = new List(); - - #region プロパティ - public bool IsAttackBuffActive => attackBuffTimer > 0f; - public bool IsDefenseBuffActive => defenseBuffTimer > 0f; - public float CurrentAttackMultiplier => IsAttackBuffActive ? attackBuffMultiplier : 1.0f; - public float CurrentDefenseMultiplier => IsDefenseBuffActive ? defenseBuffMultiplier : 1.0f; - #endregion + private float codingTimer; + public bool IsCodingMode { get; private set; } + public BuffHandler Buffs => buffHandler; private void Awake() { - if (Instance == null) { Instance = this; } - else { Destroy(gameObject); } + if (Instance == null) + { + Instance = this; + } + else + { + Destroy(gameObject); + } } private void OnEnable() { - EnableAction(zlButtonAction); - EnableAction(zrButtonAction); - EnableAction(moveAction); - EnableAction(turnAction); + SetActionsEnabled(true); } private void OnDisable() { - DisableAction(zlButtonAction); - DisableAction(zrButtonAction); - DisableAction(moveAction); - DisableAction(turnAction); + SetActionsEnabled(false); } - private void EnableAction(InputActionProperty property) + private void SetActionsEnabled(bool enable) { - if (property.action != null) property.action.Enable(); + SetActionState(zlButtonAction, enable); + SetActionState(zrButtonAction, enable); + SetActionState(moveAction, enable); + SetActionState(turnAction, enable); } - private void DisableAction(InputActionProperty property) + private void SetActionState(InputActionProperty property, bool enable) { - if (property.action != null) property.action.Disable(); + if (property.action == null) return; + if (enable) property.action.Enable(); + else property.action.Disable(); } private void Start() @@ -98,13 +79,12 @@ public class PlayerStatusManager : MonoBehaviour { CheckComboInput(); - if (!isCodingMode) + if (!IsCodingMode) { - UpdateBuffTimers(); + buffHandler.UpdateTimers(Time.deltaTime); } else { - // CodingMode中の制限時間カウントダウン codingTimer -= Time.deltaTime; if (codingTimer <= 0f) { @@ -115,33 +95,25 @@ public class PlayerStatusManager : MonoBehaviour public void AddGauge(float amount) { - if (isCodingMode) return; - currentGauge += amount; - currentGauge = Mathf.Clamp(currentGauge, 0f, maxGauge); + if (IsCodingMode) return; + currentGauge = Mathf.Clamp(currentGauge + amount, 0f, maxGauge); } private void CheckComboInput() { if (zlButtonAction.action == null || zrButtonAction.action == null) return; - float zlVal = zlButtonAction.action.ReadValue(); - float zrVal = zrButtonAction.action.ReadValue(); - - bool isBothHeld = zlVal > 0.5f && zrVal > 0.5f; + bool isBothHeld = zlButtonAction.action.ReadValue() > 0.5f && zrButtonAction.action.ReadValue() > 0.5f; bool isAnyTriggered = zlButtonAction.action.triggered || zrButtonAction.action.triggered; if (isBothHeld && isAnyTriggered) { - if (!isCodingMode) + if (!IsCodingMode) { if (ignoreGaugeForDebug || currentGauge >= maxGauge) { StartCodingMode(); } - else - { - Debug.LogWarning($"⚠️ ゲージ不足のためCodingModeを開始できません。現在: {currentGauge} / 必要: {maxGauge}"); - } } else { @@ -152,90 +124,49 @@ public class PlayerStatusManager : MonoBehaviour private void StartCodingMode() { - isCodingMode = true; + IsCodingMode = true; codingTimer = codingLimitDuration; - // 1. プレイヤーの移動制御をロック - DisableAction(moveAction); - DisableAction(turnAction); + SetActionState(moveAction, false); + SetActionState(turnAction, false); - // 2. パズルブロックの状態・位置を全リセット PuzzleBlock.ResetAllBlocksInScene(); - - // 3. 敵のアニメーション・NavMeshを停止 - foreach (var anim in FindObjectsByType(FindObjectsSortMode.None)) + + if (WorldPauseManager.Instance != null) { - if (anim == null || anim.gameObject == this.gameObject || anim.CompareTag("Player")) continue; - anim.speed = 0f; - pausedAnimators.Add(anim); - } - - foreach (var agent in FindObjectsByType(FindObjectsSortMode.None)) - { - if (agent == null || !agent.enabled || !agent.gameObject.activeInHierarchy || agent.CompareTag("Player")) continue; - if (!agent.isStopped) { agent.isStopped = true; pausedAgents.Add(agent); } + WorldPauseManager.Instance.PauseWorld(); } if (codingModeUI != null) codingModeUI.SetActive(true); - Debug.Log($"⏳ Codingモード開始(制限時間: {codingLimitDuration}秒)"); } - public void ApplyBuffsAndResume(string buff1, string buff2) + public void ApplyBuffsAndResume(BuffType buff1, BuffType buff2) { - Instantiate(successEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation); - ActivateBuffByName(buff1); - ActivateBuffByName(buff2); + if (successEffectPrefab != null && effectSpawnPoint != null) + { + Instantiate(successEffectPrefab, effectSpawnPoint.position, effectSpawnPoint.rotation); + } + + buffHandler.ActivateBuff(buff1); + buffHandler.ActivateBuff(buff2); ResumeWorldState(consumeGauge: true); - Debug.Log("🎉 パズル成功!バフを適用してゲームを再開しました。"); } - private void FailCodingModeByTimeout() - { - ResumeWorldState(consumeGauge: true); - Debug.Log("💀 制限時間切れ! Codingモード失敗(バフなし・ゲージ消費)。"); - } - - public void CancelCodingMode() - { - ResumeWorldState(consumeGauge: false); - Debug.Log("↩️ Codingモードを手動キャンセルしました。"); - } + private void FailCodingModeByTimeout() => ResumeWorldState(consumeGauge: true); + public void CancelCodingMode() => ResumeWorldState(consumeGauge: false); private void ResumeWorldState(bool consumeGauge) { - foreach (var anim in pausedAnimators) { if (anim != null) anim.speed = 1f; } - pausedAnimators.Clear(); + if (WorldPauseManager.Instance != null) + { + WorldPauseManager.Instance.ResumeWorld(); + } - foreach (var agent in pausedAgents) { if (agent != null && agent.enabled) agent.isStopped = false; } - pausedAgents.Clear(); + SetActionState(moveAction, true); + SetActionState(turnAction, true); - EnableAction(moveAction); - EnableAction(turnAction); - - isCodingMode = false; + IsCodingMode = false; if (codingModeUI != null) codingModeUI.SetActive(false); - if (consumeGauge) currentGauge = 0f; } - - private void ActivateBuffByName(string buffName) - { - switch (buffName) - { - case "AttackUp": attackBuffTimer = buffDuration; break; - case "DefenseUp": defenseBuffTimer = buffDuration; break; - case "Heal": ExecuteHeal(); break; - } - } - - private void ExecuteHeal() - { - Debug.Log($"💚 HP回復バフ発動! 回復量: {hpHealAmount}"); - } - - private void UpdateBuffTimers() - { - if (attackBuffTimer > 0f) attackBuffTimer -= Time.deltaTime; - if (defenseBuffTimer > 0f) defenseBuffTimer -= Time.deltaTime; - } } \ No newline at end of file diff --git a/Assets/_Scripts/PuzzleBlock.cs b/Assets/_Scripts/PuzzleBlock.cs index 1a0bf6a..8fc23d2 100644 --- a/Assets/_Scripts/PuzzleBlock.cs +++ b/Assets/_Scripts/PuzzleBlock.cs @@ -1,24 +1,32 @@ +using System; +using System.Collections.Generic; using UnityEngine; -using UnityEngine.XR.Interaction.Toolkit.Interactables; // XR Toolkit用 +using UnityEngine.XR.Interaction.Toolkit; +using UnityEngine.XR.Interaction.Toolkit.Interactables; public enum BlockColor { Red, Blue, Green } -public enum BlockShapeType { TShape, CShape } // ト字, コ字 +public enum BlockShapeType { TShape, CShape } public class PuzzleBlock : MonoBehaviour { [field: SerializeField] public BlockColor Color { get; private set; } [field: SerializeField] public BlockShapeType ShapeType { get; private set; } - [Header("スナップ(合体)位置の設定")] - [Tooltip("相手と合体する基準位置(空のGameObject)を指定。未設定でも中心位置でスナップします")] - [SerializeField] private Transform snapPoint; + [SerializeField] private Transform snapPoint; - private PuzzleBlock nearbyPartner; // 近くにある結合対象のブロック - private bool isConnected = false; // 結合済みフラグ + public static event Action OnPuzzleCompleted; + + private static readonly HashSet allBlocks = new HashSet(); + + private PuzzleBlock nearbyPartner; + private bool isConnected; + + // ★ワールド情報ではなく「親階層」と「ローカル情報」を記憶 + private Transform initialParent; + private Vector3 initialLocalPosition; + private Quaternion initialLocalRotation; + private Vector3 initialLocalScale; - // リセット用の初期座標・回転保持用 - private Vector3 initialPosition; - private Quaternion initialRotation; private Rigidbody rb; private XRGrabInteractable grabInteractable; @@ -27,9 +35,21 @@ public class PuzzleBlock : MonoBehaviour rb = GetComponent(); grabInteractable = GetComponent(); - // 初期スポーン位置・回転を記憶しておく - initialPosition = transform.position; - initialRotation = transform.rotation; + // ★開始時の「親」と「ローカル座標・回転・スケール」を保持 + initialParent = transform.parent; + initialLocalPosition = transform.localPosition; + initialLocalRotation = transform.localRotation; + initialLocalScale = transform.localScale; + } + + private void OnEnable() + { + allBlocks.Add(this); + } + + private void OnDisable() + { + allBlocks.Remove(this); } private void OnTriggerEnter(Collider other) @@ -40,7 +60,6 @@ public class PuzzleBlock : MonoBehaviour if (partner != null && IsValidPartner(partner)) { nearbyPartner = partner; - Debug.Log($"🧩 パートナー検知: {partner.Color} の {partner.ShapeType}"); } } @@ -56,8 +75,7 @@ public class PuzzleBlock : MonoBehaviour private bool IsValidPartner(PuzzleBlock partner) { if (partner == null || partner.isConnected) return false; - // 同一色 & 異なる形状(ト字 + コ字)のみ許可 - return partner.Color == this.Color && partner.ShapeType != this.ShapeType; + return partner.Color == Color && partner.ShapeType != ShapeType; } public void OnReleased() @@ -75,7 +93,6 @@ public class PuzzleBlock : MonoBehaviour isConnected = true; partner.isConnected = true; - // 1. 位置・回転のスナップ補正 if (snapPoint != null && partner.snapPoint != null) { transform.position = partner.snapPoint.position; @@ -87,15 +104,16 @@ public class PuzzleBlock : MonoBehaviour transform.rotation = partner.transform.rotation; } - // 2. 物理&VR掴み動作の完全ロック(完成後に崩れないようにする) LockBlockState(); partner.LockBlockState(); - Debug.Log($"🎉 マグネット合体成功! 色: {Color}"); + OnPuzzleCompleted?.Invoke(Color); - // 3. バフ適用してCodingMode完了へ - string buffName = ConvertColorToBuffName(Color); - PlayerStatusManager.Instance.ApplyBuffsAndResume(buffName, buffName); + BuffType buff = ConvertColorToBuffType(Color); + if (PlayerStatusManager.Instance != null) + { + PlayerStatusManager.Instance.ApplyBuffsAndResume(buff, buff); + } } private void LockBlockState() @@ -106,43 +124,72 @@ public class PuzzleBlock : MonoBehaviour rb.angularVelocity = Vector3.zero; rb.isKinematic = true; } - // 掴みコンポーネントをオフにして手から切り離す if (grabInteractable != null) grabInteractable.enabled = false; } public void ResetBlock() { + // 1. 手で掴んでいる場合は手離し処理 + ForceDrop(); + isConnected = false; nearbyPartner = null; - transform.position = initialPosition; - transform.rotation = initialRotation; + // 2. ★親オブジェクト(Canvas)の子へ戻す + if (initialParent != null && transform.parent != initialParent) + { + transform.SetParent(initialParent, false); + } + // 3. ★Canvas内のローカル位置・回転・スケールを完全復元 + transform.localPosition = initialLocalPosition; + transform.localRotation = initialLocalRotation; + transform.localScale = initialLocalScale; + + // 4. 物理状態の再初期化 if (rb != null) { rb.isKinematic = false; rb.linearVelocity = Vector3.zero; rb.angularVelocity = Vector3.zero; } - if (grabInteractable != null) grabInteractable.enabled = true; + + // 5. 掴み機能の再有効化 + if (grabInteractable != null) + { + grabInteractable.enabled = true; + } + } + + private void ForceDrop() + { + if (grabInteractable == null || !grabInteractable.isSelected) return; + + if (grabInteractable.interactionManager != null && grabInteractable.firstInteractorSelecting != null) + { + grabInteractable.interactionManager.SelectExit(grabInteractable.firstInteractorSelecting, grabInteractable); + } } public static void ResetAllBlocksInScene() { - foreach (var block in FindObjectsByType(FindObjectsSortMode.None)) + foreach (var block in allBlocks) { - if (block != null) block.ResetBlock(); + if (block != null) + { + block.ResetBlock(); + } } } - private string ConvertColorToBuffName(BlockColor color) + private BuffType ConvertColorToBuffType(BlockColor color) { return color switch { - BlockColor.Red => "AttackUp", - BlockColor.Blue => "DefenseUp", - BlockColor.Green => "Heal", - _ => "AttackUp" + BlockColor.Red => BuffType.AttackUp, + BlockColor.Blue => BuffType.DefenseUp, + BlockColor.Green => BuffType.Heal, + _ => BuffType.AttackUp }; } } \ No newline at end of file diff --git a/Assets/_Scripts/SlowWebArea.cs b/Assets/_Scripts/SlowWebArea.cs index 9f659ae..c730036 100644 --- a/Assets/_Scripts/SlowWebArea.cs +++ b/Assets/_Scripts/SlowWebArea.cs @@ -3,24 +3,25 @@ using UnityEngine; public class SlowWebArea : MonoBehaviour { [SerializeField] private float damagePerSecond = 5f; - private float damageTimer = 0f; + private float damageTimer; private void OnTriggerStay(Collider other) { if (other.CompareTag("Player")) { - // ★鈍足効果の適用演出 - // プレイヤーの移動スクリプトに対し、一時的な速度デバフ(例: Speed * 0.3f)を掛けます - // ここでは設計の切り離しのため、ログ出力のみとしています - Debug.LogWarning("🕸️ プレイヤーが蜘蛛の糸に囚われている! 【鈍足化デバフ発動中】"); - - // スリップダメージ(1秒ごとに継続ダメージ) damageTimer += Time.deltaTime; if (damageTimer >= 1.0f) { - // プレイヤーのHealthコンポーネントにダメージを通知 - // other.GetComponent()?.TakeDamage(damagePerSecond); - Debug.Log($"🕸️ 糸によるスリップダメージ: {damagePerSecond}"); + IDamageable damageable = other.GetComponentInParent(); + if (damageable != null) + { + DamageInfo info = new DamageInfo + { + amount = damagePerSecond, + hitPosition = other.transform.position + }; + damageable.TakeDamage(info); + } damageTimer = 0f; } } @@ -30,7 +31,7 @@ public class SlowWebArea : MonoBehaviour { if (other.CompareTag("Player")) { - Debug.Log("🏃 プレイヤーが糸の範囲から脱出。 鈍足効果解除。"); + damageTimer = 0f; } } } \ No newline at end of file diff --git a/Assets/_Scripts/SpiderIdleState.cs b/Assets/_Scripts/SpiderIdleState.cs index ae6ee18..f6e29c3 100644 --- a/Assets/_Scripts/SpiderIdleState.cs +++ b/Assets/_Scripts/SpiderIdleState.cs @@ -1,5 +1,26 @@ using UnityEngine; +public interface ISpiderState +{ + void EnterState(); + void UpdateState(); + void ExitState(); +} + +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 class SpiderIdleState : SpiderStateBase { public SpiderIdleState(BossController owner) : base(owner) { } @@ -29,14 +50,12 @@ public class SpiderChaseState : SpiderStateBase float distance = Vector3.Distance(boss.transform.position, boss.Player.position); - // 遠距離突進判定(カプセル化されたプロパティを使用) if (distance >= boss.ChargeMinimumDistance && boss.ChargeCooldownTimer <= 0f) { boss.ChangeState(); return; } - // 至近距離噛みつき判定 if (distance <= boss.BiteRange) { boss.ChangeState(); @@ -58,14 +77,12 @@ public class SpiderBiteState : SpiderStateBase public override void EnterState() { stateTimer = 1.5f; - boss.Agent.ResetPath(); - boss.Agent.isStopped = true; - boss.Agent.velocity = Vector3.zero; + boss.Agent.ResetPath(); + boss.Agent.isStopped = true; + boss.Agent.velocity = Vector3.zero; boss.Anim.SetFloat("Speed", 0f); - boss.Anim.SetTrigger("Attack_Bite"); - Debug.Log("🎯 蜘蛛ボス:通常噛みつき開始。攻撃モーション中にゆっくり向き直ります。"); } public override void ExitState() @@ -81,17 +98,17 @@ public class SpiderBiteState : SpiderStateBase { Vector3 lookDir = (boss.Player.position - boss.transform.position).normalized; lookDir.y = 0; - + if (lookDir != Vector3.zero) { boss.transform.rotation = Quaternion.Slerp( - boss.transform.rotation, - Quaternion.LookRotation(lookDir), + boss.transform.rotation, + Quaternion.LookRotation(lookDir), Time.deltaTime * TurnSpeed ); } } - + stateTimer -= Time.deltaTime; if (stateTimer <= 0f) { @@ -121,7 +138,6 @@ public class SpiderChargeState : SpiderStateBase boss.ResetChargeCooldown(); boss.Anim.SetTrigger("Attack_Roar"); - Debug.Log($"🔊 蜘蛛ボス:遠距離を検知。その場でピタッと完全停止。咆哮(溜め {roarTimer}秒)を開始!"); } public override void ExitState() @@ -134,11 +150,10 @@ public class SpiderChargeState : SpiderStateBase public override void UpdateState() { - // フェーズ1:咆哮・溜めロックオン if (!isChargingPhase) { roarTimer -= Time.deltaTime; - boss.Agent.velocity = Vector3.zero; // 完全固定 + boss.Agent.velocity = Vector3.zero; if (boss.Player != null) { @@ -168,13 +183,10 @@ public class SpiderChargeState : SpiderStateBase boss.Agent.speed = boss.ChargeSpeed; boss.Agent.acceleration = 120f; boss.Agent.SetDestination(targetDestination); - - Debug.Log("🚀 蜘蛛ボス:ロックオン完了。全速力で突進ダッシュを発射!"); } return; } - // フェーズ2:突進移動中 chargeTimer -= Time.deltaTime; if (chargeTimer <= 0f || (boss.Agent.velocity.magnitude < 0.2f && chargeTimer < (boss.ChargeDuration - 0.3f))) @@ -202,12 +214,10 @@ public class SpiderJumpToCeilingState : SpiderStateBase boss.Agent.enabled = false; boss.Anim.SetTrigger("Attack_Roar"); - Debug.Log($"🚨 蜘蛛ボス:必殺技前の一大威嚇(咆哮 {roarTimer}秒)!"); } public override void UpdateState() { - // フェーズ1:飛び立つ前の威嚇 if (!isJumpingPhase) { roarTimer -= Time.deltaTime; @@ -219,7 +229,6 @@ public class SpiderJumpToCeilingState : SpiderStateBase return; } - // フェーズ2:ジャンプ空中移動 if (boss.ceilingAnchor == null) return; jumpProgress += Time.unscaledDeltaTime * 1.5f; @@ -237,25 +246,20 @@ public class SpiderJumpToCeilingState : SpiderStateBase } } -// ========================================== -// ■ STATE: CEILING ATTACK (インスペクター無段階調整版) -// ========================================== public class SpiderCeilingAttackState : SpiderStateBase { - private float timer = 0f; - private float shotTimer = 0f; + private float timer; + private float shotTimer; public SpiderCeilingAttackState(BossController owner) : base(owner) { } public override void EnterState() { - // ★リファクタリング:インスペクターで設定した必殺技の長さを適用 timer = boss.CeilingAttackDuration; shotTimer = 0f; boss.Anim.SetBool("IsOnCeiling", true); boss.PendingUltimate = false; - Debug.Log($"🕸️ 蜘蛛ボス:天井からの乱射開始! 持続時間: {timer}秒 / 発射間隔: {boss.CeilingShotInterval}秒"); } public override void ExitState() @@ -271,12 +275,9 @@ public class SpiderCeilingAttackState : SpiderStateBase timer -= Time.deltaTime; shotTimer -= Time.deltaTime; - // タイマーがゼロになったら発射 if (shotTimer <= 0f) { ExecuteRandomWebAttack(); - - // ★リファクタリング:インスペクターで設定した間隔(秒)でリセットして弾数を制御 shotTimer = boss.CeilingShotInterval; } @@ -286,14 +287,13 @@ public class SpiderCeilingAttackState : SpiderStateBase } } - private void ExecuteRandomWebAttack() + private void ExecuteRandomWebAttack() { Vector3 targetGroundPos = Vector3.zero; - int maxAttempts = 15; // 無限ループ(フリーズ)を防止するための最大再抽選回数 - + int maxAttempts = 15; + for (int i = 0; i < maxAttempts; i++) { - // 1. 一旦ランダムな座標を算出 Vector2 randomCircle = Random.insideUnitCircle * boss.AttackRadius; targetGroundPos = new Vector3( boss.groundAnchor.position.x + randomCircle.x, @@ -301,29 +301,23 @@ public class SpiderCeilingAttackState : SpiderStateBase boss.groundAnchor.position.z + randomCircle.y ); - // 2. 現在アクティブなすべての糸との距離を計測する bool isTooClose = false; - foreach (Vector3 existingPos in boss.ActiveWebPositions) + foreach (Vector3 existingPos in boss.WebSpawner.ActiveWebPositions) { - // 設定された回避半径(WebAvoidanceRadius)より近い既存の糸があるか? - if (Vector3.Distance(targetGroundPos, existingPos) < boss.WebAvoidanceRadius) + if (Vector3.Distance(targetGroundPos, existingPos) < boss.WebSpawner.WebAvoidanceRadius) { isTooClose = true; - break; // 被り検知、この座標は使えないのでチェックを抜けて再抽選へ + break; } } - // 3. 誰とも被っていなければ、このクリーンな座標で確定 - if (!isTooClose) - { - break; - } + if (!isTooClose) break; } - // 確定した(または上限に達して安全が担保された)座標へ向けて発射命令 - boss.SpawnWarningAndWeb(targetGroundPos, boss.WebWarningDelay, boss.WebDuration); + boss.WebSpawner.SpawnWarningAndWeb(targetGroundPos, boss.WebWarningDelay, boss.WebDuration); } } + public class SpiderStunState : SpiderStateBase { private float stunTimer; @@ -336,8 +330,6 @@ public class SpiderStunState : SpiderStateBase boss.Anim.SetTrigger("StunStart"); boss.Anim.SetBool("IsStunned", true); if (boss.Agent.enabled) boss.Agent.isStopped = true; - Debug.Log("🛡️ パリィ成功! チャンスタイム!"); - } public override void ExitState() diff --git a/Assets/_Scripts/TitleManager.cs b/Assets/_Scripts/TitleManager.cs index ff2f211..4da7cfc 100644 --- a/Assets/_Scripts/TitleManager.cs +++ b/Assets/_Scripts/TitleManager.cs @@ -1,9 +1,9 @@ using System.Collections; using UnityEngine; -using UnityEngine.UI; using UnityEngine.SceneManagement; +using UnityEngine.UI; -public class SimpleTitleManager : MonoBehaviour +public class TitleManager : MonoBehaviour { [Header("UI Buttons")] [SerializeField] private Button startButton; @@ -14,14 +14,13 @@ public class SimpleTitleManager : MonoBehaviour private void Awake() { - // テンプレートの手でボタンを押した時に実行される処理 - startButton.onClick.AddListener(OnStartButtonClicked); - exitButton.onClick.AddListener(OnExitButtonClicked); + if (startButton != null) startButton.onClick.AddListener(OnStartButtonClicked); + if (exitButton != null) exitButton.onClick.AddListener(OnExitButtonClicked); } private void OnStartButtonClicked() { - startButton.interactable = false; // 二重押し防止 + if (startButton != null) startButton.interactable = false; StartCoroutine(LoadSceneRoutine()); } diff --git a/Assets/_Scripts/VRDoorController.cs b/Assets/_Scripts/VRDoorController.cs index e7bbd6d..9db6258 100644 --- a/Assets/_Scripts/VRDoorController.cs +++ b/Assets/_Scripts/VRDoorController.cs @@ -1,72 +1,48 @@ -using UnityEngine; using System.Collections; +using UnityEngine; -/// -/// 1つのトリガーで左右のドアを反対方向にスライド開閉させ、 -/// 一定時間後に自動で閉じるクラス -/// public class VRDoorController : MonoBehaviour { - [Header("ドアのオブジェクト登録")] - [Tooltip("左側にスライドさせたいドアのTransformを指定してください")] + [Header("ドアオブジェクト")] [SerializeField] private Transform leftDoor; - [Tooltip("右側にスライドさせたいドアのTransformを指定してください")] [SerializeField] private Transform rightDoor; - [Header("スライド量と速度の設定")] - [Tooltip("ドアが横に何メートルずれるか(移動量)")] + [Header("動作設定")] [SerializeField] private float slideDistance = 1.0f; - [Tooltip("ドアが完全に開ききる(閉じきる)までにかかる時間(秒)")] [SerializeField] private float openSpeed = 1.2f; - [Tooltip("ドアが開ききった後、自動で閉まり始めるまでの待ち時間(秒)")] [SerializeField] private float autoCloseDelay = 4.0f; - // ドアの「閉じた位置」と「開いた位置」の座標を記憶する変数 private Vector3 leftDoorClosedPos; private Vector3 rightDoorClosedPos; private Vector3 leftDoorOpenPos; private Vector3 rightDoorOpenPos; - // ドアの状態を管理するフラグ(バグ連打の防止用) - private bool isOpen = false; - private bool isMoving = false; + private bool isOpen; + private bool isMoving; private void Start() { - // 1. ゲーム開始時の位置を「閉じた状態」として記憶 if (leftDoor != null) { leftDoorClosedPos = leftDoor.localPosition; - // 左ドアはローカルのX軸のマイナス方向へスライドさせる leftDoorOpenPos = leftDoorClosedPos + new Vector3(-slideDistance, 0, 0); } if (rightDoor != null) { rightDoorClosedPos = rightDoor.localPosition; - // 右ドアはローカルのX軸のプラス方向へスライドさせる rightDoorOpenPos = rightDoorClosedPos + new Vector3(slideDistance, 0, 0); } } - /// - /// 【外部連携用】VRのボタンが押されたときに呼び出す公開メソッド - /// public void OnButtonPress() { - // すでに移動中、またはすでに開いている場合はボタンの入力を完全に無視する if (isMoving || isOpen) return; - - // 開いてから自動で閉まるまでの一連のコルーチンを始動 StartCoroutine(OpenAndAutoCloseRoutine()); } - /// - /// ドアを開け、待機し、自動で閉める一連の流れを管理するコルーチン - /// private IEnumerator OpenAndAutoCloseRoutine() { - // ─── 1. ドアを開ける処理 ─── isMoving = true; float elapsedTime = 0f; @@ -74,28 +50,22 @@ public class VRDoorController : MonoBehaviour { elapsedTime += Time.deltaTime; float t = elapsedTime / openSpeed; - - // ★VR酔い・チープさ対策:数値を滑らかに補間して、リアルな加減速(イージング)をかける float easedT = Mathf.SmoothStep(0f, 1f, t); if (leftDoor != null) leftDoor.localPosition = Vector3.Lerp(leftDoorClosedPos, leftDoorOpenPos, easedT); if (rightDoor != null) rightDoor.localPosition = Vector3.Lerp(rightDoorClosedPos, rightDoorOpenPos, easedT); - yield return null; // 1フレーム待機 + yield return null; } - // 完全に開ききった座標で固定 if (leftDoor != null) leftDoor.localPosition = leftDoorOpenPos; if (rightDoor != null) rightDoor.localPosition = rightDoorOpenPos; isOpen = true; isMoving = false; - Debug.Log("🚪 ドアが完全に開きました。"); - // ─── 2. 開いた状態での待機(自動クローズのカウントダウン) ─── yield return new WaitForSeconds(autoCloseDelay); - // ─── 3. ドアを閉じる処理 ─── isMoving = true; isOpen = false; elapsedTime = 0f; @@ -112,11 +82,9 @@ public class VRDoorController : MonoBehaviour yield return null; } - // 完全に閉じた座標で固定 if (leftDoor != null) leftDoor.localPosition = leftDoorClosedPos; if (rightDoor != null) rightDoor.localPosition = rightDoorClosedPos; isMoving = false; - Debug.Log("🔒 ドアが自動で閉じました。"); } } \ No newline at end of file diff --git a/Assets/_Scripts/VRInputPoseDetector.cs b/Assets/_Scripts/VRInputPoseDetector.cs index b567a4a..8f7b689 100644 --- a/Assets/_Scripts/VRInputPoseDetector.cs +++ b/Assets/_Scripts/VRInputPoseDetector.cs @@ -2,79 +2,65 @@ using UnityEngine; public class VRInputPoseDetector : MonoBehaviour { - [Header("必須コンポーネント登録")] + [Header("参照")] [SerializeField] private Transform headTransform; [SerializeField] private Transform rightHandTransform; [SerializeField] private Transform leftHandTransform; - [Header("構えの判定範囲設定")] + [Header("判定パラメータ")] [SerializeField] private float minForwardDistance = 0.15f; - [SerializeField] private float maxForwardDistance = 0.5f; + [SerializeField] private float maxForwardDistance = 0.5f; [SerializeField] private float minHeightOffset = -0.35f; [SerializeField] private float maxHeightOffset = -0.1f; - [Header("タイマー・バッファ設定")] + [Header("タイマーパラメータ")] [SerializeField] private float requiredPoseTime = 0.3f; - - [SerializeField] private float poseExitDelay = 0.15f; + [SerializeField] private float poseExitDelay = 0.15f; - [Header("視覚フィードバック(ガイドUI設定)")] + [Header("視覚フィードバック")] [SerializeField] private Renderer guideRenderer; - [SerializeField] private Color defaultColor = new Color(0.5f, 0.5f, 0.5f, 0.05f); // 普段(超薄いグレー) - [SerializeField] private Color chargingColor = new Color(1f, 0.8f, 0f, 0.2f); // 溜め中(うっすら黄色) - [SerializeField] private Color successColor = new Color(0f, 0.6f, 1f, 0.4f); // 構え完了(綺麗な青シールド) + [SerializeField] private Color defaultColor = new Color(0.5f, 0.5f, 0.5f, 0.05f); + [SerializeField] private Color chargingColor = new Color(1f, 0.8f, 0f, 0.2f); + [SerializeField] private Color successColor = new Color(0f, 0.6f, 1f, 0.4f); - private float poseTimer = 0f; - private float exitTimer = 0f; - private bool isPoseMaintained = false; + private float poseTimer; + private float exitTimer; - public bool IsPosing => isPoseMaintained; + public bool IsPosing { get; private set; } - void Update() + private void Update() { if (headTransform == null || rightHandTransform == null || leftHandTransform == null) return; - // 両手の世界座標を頭を中心としたローカル座標に変換 - Vector3 localRightHand = headTransform.InverseTransformPoint(rightHandTransform.position); + Vector3 localRightHand = headTransform.InverseTransformPoint(rightHandTransform.position); Vector3 localLeftHand = headTransform.InverseTransformPoint(leftHandTransform.position); - // 両手が構え範囲に入っているかチェック - bool isRightHandGuarding = CheckHandPosition(localRightHand); - bool isLeftHandGuarding = CheckHandPosition(localLeftHand); + bool isBothHandsGuarding = CheckHandPosition(localRightHand) && CheckHandPosition(localLeftHand); - bool isBothHandsGuarding = isRightHandGuarding && isLeftHandGuarding; - - // ─── 🛡️ チャタリング防止付き・構え判定ロジック ─── if (isBothHandsGuarding) { - // 範囲内に手が戻ったので、解除カウントダウンを即座にリセット exitTimer = 0f; - if (!isPoseMaintained) + if (!IsPosing) { poseTimer += Time.deltaTime; if (poseTimer >= requiredPoseTime) { - isPoseMaintained = true; - Debug.Log("🛡️ 【構え成功】 パリィ受付がアクティブになりました。"); + IsPosing = true; } } } else { - // 範囲外に出た場合 - poseTimer = 0f; // まだ構えが完了していない段階(溜め中)なら即リセット + poseTimer = 0f; - if (isPoseMaintained) + if (IsPosing) { - // すでに構えが成功している状態なら、猶予タイマーを開始(ここで即解除しない!) exitTimer += Time.deltaTime; - if (exitTimer >= poseExitDelay) { - isPoseMaintained = false; + IsPosing = false; exitTimer = 0f; - Debug.Log("❌ 【構え解除】 猶予時間を超えたためガードが解けました。"); } } } @@ -84,9 +70,8 @@ public class VRInputPoseDetector : MonoBehaviour private bool CheckHandPosition(Vector3 localHandPos) { - bool inForwardRange = localHandPos.z >= minForwardDistance && localHandPos.z <= maxForwardDistance; // Z軸:頭より前に手があるか - bool inHeightRange = localHandPos.y >= minHeightOffset && localHandPos.y <= maxHeightOffset; // Y軸:手の高さが適切か - + bool inForwardRange = localHandPos.z >= minForwardDistance && localHandPos.z <= maxForwardDistance; + bool inHeightRange = localHandPos.y >= minHeightOffset && localHandPos.y <= maxHeightOffset; return inForwardRange && inHeightRange; } @@ -94,18 +79,17 @@ public class VRInputPoseDetector : MonoBehaviour { if (guideRenderer == null) return; - // ボスの攻撃を見やすくするため、現在の状態に応じてガイドシールドのカラーを3段階可変させる - if (isPoseMaintained) + if (IsPosing) { - guideRenderer.material.color = successColor; // 成功:青シールド + guideRenderer.material.color = successColor; } else if (isBothHandsGuarding) { - guideRenderer.material.color = chargingColor; // 溜め中:黄色 + guideRenderer.material.color = chargingColor; } else { - guideRenderer.material.color = defaultColor; // 構えなし:目立たない透明グレー + guideRenderer.material.color = defaultColor; } } } \ No newline at end of file diff --git a/Assets/_Scripts/VRLaserPointer.cs b/Assets/_Scripts/VRLaserPointer.cs deleted file mode 100644 index e29efec..0000000 --- a/Assets/_Scripts/VRLaserPointer.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.EventSystems; - -namespace VRTitle.Input -{ - /// - /// VRコントローラーからのポインター入力とUI操作を仲介するコンポーネント - /// - [RequireComponent(typeof(LineRenderer))] - public class VRLaserPointer : MonoBehaviour - { - [Header("Ray Settings")] - [SerializeField] private float maxDistance = 10f; - [SerializeField] private LayerMask uiLayerMask = ~0; - - [Header("Visual")] - [SerializeField] private LineRenderer lineRenderer; - - private GameObject currentHoveredObject; - - private void Reset() - { - lineRenderer = GetComponent(); - lineRenderer.positionCount = 2; - lineRenderer.startWidth = 0.005f; - lineRenderer.endWidth = 0.001f; - } - - private void Update() - { - ProcessPointer(); - } - - private void ProcessPointer() - { - Ray ray = new Ray(transform.position, transform.forward); - PointerEventData pointerData = new PointerEventData(EventSystem.current) - { - position = new Vector2(Screen.width / 2f, Screen.height / 2f) - }; - - List results = new List(); - EventSystem.current?.RaycastAll(pointerData, results); - - Vector3 targetPosition = transform.position + transform.forward * maxDistance; - - if (results.Count > 0) - { - RaycastResult hit = results[0]; - targetPosition = hit.worldPosition; - UpdateHoverState(hit.gameObject, pointerData); - } - else - { - UpdateHoverState(null, pointerData); - } - - // ポインター描画の更新 - lineRenderer.SetPosition(0, transform.position); - lineRenderer.SetPosition(1, targetPosition); - } - - private void UpdateHoverState(GameObject newHover, PointerEventData pointerData) - { - if (currentHoveredObject == newHover) return; - - if (currentHoveredObject != null) - { - ExecuteEvents.Execute(currentHoveredObject, pointerData, ExecuteEvents.pointerExitHandler); - } - - currentHoveredObject = newHover; - - if (currentHoveredObject != null) - { - ExecuteEvents.Execute(currentHoveredObject, pointerData, ExecuteEvents.pointerEnterHandler); - } - } - - /// - /// トリガーボタンが押された時に呼び出す(Input System側から呼び出し可能) - /// - public void OnPrimaryTriggerDown() - { - if (currentHoveredObject == null) return; - - PointerEventData pointerData = new PointerEventData(EventSystem.current); - ExecuteEvents.Execute(currentHoveredObject, pointerData, ExecuteEvents.pointerClickHandler); - } - } -} \ No newline at end of file diff --git a/Assets/_Scripts/VRLaserPointer.cs.meta b/Assets/_Scripts/VRLaserPointer.cs.meta deleted file mode 100644 index 588617c..0000000 --- a/Assets/_Scripts/VRLaserPointer.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 6159a60476d605c4494622c0790e9b02 \ No newline at end of file diff --git a/Assets/_Scripts/VRPunchAttack.cs b/Assets/_Scripts/VRPunchAttack.cs index 9af493b..694ebe5 100644 --- a/Assets/_Scripts/VRPunchAttack.cs +++ b/Assets/_Scripts/VRPunchAttack.cs @@ -1,6 +1,6 @@ using UnityEngine; -using UnityEngine.InputSystem; // Input Systemを使うために必要 -using UnityEngine.XR; // 振動機能のために必要 +using UnityEngine.InputSystem; +using UnityEngine.XR; [RequireComponent(typeof(Collider))] [RequireComponent(typeof(Rigidbody))] @@ -10,37 +10,30 @@ public class VRInputPunch : MonoBehaviour [SerializeField] private XRNode controllerNode = XRNode.RightHand; [SerializeField] private VRInputPoseDetector poseDetector; - [Header("攻撃(パンチ)設定")] - [SerializeField] private float minPunchSpeed = 1.0f; // パンチとして判定する最低速度 (m/s) - [SerializeField] private float damageMultiplier = 10.0f; // ダメージ倍率 - [SerializeField] private float hapticAmplitude = 0.5f; // 通常ヒットの振動の強さ - [SerializeField] private float hapticDuration = 0.1f; // 通常ヒットの振動の長さ + [Header("パンチパラメータ")] + [SerializeField] private float minPunchSpeed = 1.0f; + [SerializeField] private float damageMultiplier = 10.0f; + [SerializeField] private float hapticAmplitude = 0.5f; + [SerializeField] private float hapticDuration = 0.1f; - [Header("パリィ設定")] - [SerializeField] private float parryStunDuration = 3.0f; // スタン時間(秒) - [SerializeField] private float parryHapticAmplitude = 0.9f; // パリィ成功時の振動の強さ - [SerializeField] private float parryHapticDuration = 0.25f; // パリィ成功時の振動の長さ - // ─── 多段ヒット・連続暴発防止用タイマー ─── - private float punchHitCooldown = 0f; // 次のパンチが当たるまでのインターバル - private float parryTriggerCooldown = 0f; // 次のパリィが成功するまでのインターバル + [Header("パリィパラメータ")] + [SerializeField] private float parryStunDuration = 3.0f; + [SerializeField] private float parryHapticAmplitude = 0.9f; + [SerializeField] private float parryHapticDuration = 0.25f; + + private const string EnemyFistTag = "EnemyFist"; private void OnEnable() { - if (velocityAction.action != null) - { - velocityAction.action.Enable(); - } + if (velocityAction.action != null) velocityAction.action.Enable(); } private void OnDisable() { - if (velocityAction.action != null) - { - velocityAction.action.Disable(); - } + if (velocityAction.action != null) velocityAction.action.Disable(); } - void Start() + private void Start() { Rigidbody rb = GetComponent(); rb.isKinematic = true; @@ -50,104 +43,71 @@ public class VRInputPunch : MonoBehaviour private void OnTriggerEnter(Collider other) { - // コントローラーの現在の移動速度を取得 - Vector3 velocity = velocityAction.action.ReadValue(); + Vector3 velocity = velocityAction.action != null ? velocityAction.action.ReadValue() : Vector3.zero; float speed = velocity.magnitude; - // ======================================================================== - // 【パリィ判定】当たったオブジェクトが敵の拳だった場合 - // ======================================================================== - if (other.CompareTag("EnemyFist")) + if (other.CompareTag(EnemyFistTag)) { - Debug.Log($"🛡️ [パリィチェック] 敵の拳と接触。構え状態: {poseDetector.IsPosing}"); - - // 自分が「構え状態」のときだけパリィの成立チェックを行う - if (poseDetector.IsPosing) + if (poseDetector != null && poseDetector.IsPosing) { - // 1. ザコ敵(EnemyAI)へのパリィ判定 - EnemyAI enemyAI = other.GetComponentInParent(); - if (enemyAI != null) - { - enemyAI.TriggerParryStun(parryStunDuration); - TriggerHaptics(parryHapticAmplitude, parryHapticDuration); - Debug.Log($"🛡️ 【ジャストパリィ成功!】 敵の体勢を崩しました。"); - return; // ★本当のパリィ成功時のみ、ここで処理を終了する - } - - // 2. ボス(BossController)へのパリィ判定 - BossController boss = other.GetComponentInParent(); - if (boss != null) - { - // ★リファクタリング:ボスが本当に攻撃中(Bite/Charge)で、パリィが成功した時だけ終了する - if (boss.TriggerParryStun()) - { - TriggerHaptics(parryHapticAmplitude, parryHapticDuration); - Debug.Log("🛡️ 【ジャストパリィ成功!】 ボスの体勢を崩しました!"); - return; // ★本当のパリィ成功時のみ、ここで処理を終了する - } - } + if (TryProcessParry(other)) return; } - - // ─── ★ここが今回の核心(超重要リファクタリング) ─── - // 構え状態ではなかった場合、またはボスが攻撃中でなくパリィが不成立だった場合は、 - // 「プレイヤーが隙を狙って殴りに行った」とみなして、returnせずにそのまま下のパンチ処理へ流す! - Debug.Log("🥊 パリィ不成立、または攻撃チャンス:通常のパンチダメージ処理へ移行します。"); } - // ──────────────────────────────────────────────────────────────────────── - - - // ======================================================================== - // 【パンチ攻撃の処理】 - // ======================================================================== - Debug.Log($"[InputSystem接触] {other.name} に触れました。センサー速度: {speed:F2} m/s"); if (speed >= minPunchSpeed) { - IDamageble damageble = other.GetComponentInParent(); - - if (damageble != null) - { - DamageInfo info = new DamageInfo(); - // 構えからパンチするとダメージ2倍、ゲージ取得度2倍 - if (poseDetector.IsPosing) - { - info.amount = (speed * damageMultiplier) * 2f; - info.isPoseBonus = true; - - if (PlayerStatusManager.Instance != null) PlayerStatusManager.Instance.AddGauge(20f); - } - else - { - info.amount = speed * damageMultiplier; - info.isPoseBonus = false; - - if (PlayerStatusManager.Instance != null) PlayerStatusManager.Instance.AddGauge(10f); - } - info.hitPosition = other.transform.position; - info.punchDirection = velocity.normalized; - - if (PlayerStatusManager.Instance != null) - { - info.amount *= PlayerStatusManager.Instance.CurrentAttackMultiplier; - } - - damageble.TakeDamage(info); - - Debug.Log($"パンチ成功 ダメージ: {info.amount:F1}"); - - // 通常ヒットの振動を発生 - TriggerHaptics(hapticAmplitude, hapticDuration); - } - } - else - { - Debug.Log($"スピードが足りません。必要: {minPunchSpeed} / 現在: {speed:F2}"); + ProcessPunchAttack(other, speed, velocity); } } - /// 引数に応じて指定された強さと長さでコントローラーを振動させる + private bool TryProcessParry(Collider other) + { + EnemyAI enemyAI = other.GetComponentInParent(); + if (enemyAI != null) + { + enemyAI.TriggerParryStun(parryStunDuration); + TriggerHaptics(parryHapticAmplitude, parryHapticDuration); + return true; + } + + BossController boss = other.GetComponentInParent(); + if (boss != null && boss.TriggerParryStun()) + { + TriggerHaptics(parryHapticAmplitude, parryHapticDuration); + return true; + } + + return false; + } + + private void ProcessPunchAttack(Collider other, float speed, Vector3 velocity) + { + IDamageable damageable = other.GetComponentInParent(); + if (damageable == null) return; + + bool isPosing = poseDetector != null && poseDetector.IsPosing; + + DamageInfo info = new DamageInfo + { + amount = speed * damageMultiplier * (isPosing ? 2f : 1f), + isPoseBonus = isPosing, + hitPosition = other.transform.position, + punchDirection = velocity.normalized + }; + + if (PlayerStatusManager.Instance != null) + { + PlayerStatusManager.Instance.AddGauge(isPosing ? 20f : 10f); + info.amount *= PlayerStatusManager.Instance.Buffs.CurrentAttackMultiplier; + } + + damageable.TakeDamage(info); + TriggerHaptics(hapticAmplitude, hapticDuration); + } + private void TriggerHaptics(float amplitude, float duration) { + // 明示的に UnityEngine.XR.InputDevice と指定して型衝突を回避 UnityEngine.XR.InputDevice device = InputDevices.GetDeviceAtXRNode(controllerNode); if (device.isValid) { diff --git a/Assets/_Scripts/VRUIDoorFaceFollow.cs b/Assets/_Scripts/VRUIDoorFaceFollow.cs index 7f21cc0..bcc6e2b 100644 --- a/Assets/_Scripts/VRUIDoorFaceFollow.cs +++ b/Assets/_Scripts/VRUIDoorFaceFollow.cs @@ -1,78 +1,60 @@ using UnityEngine; -// World Space Canvas をプレイヤーの真正面に移動させ、開いている間はプレイヤーの視点に滑らかに追従させるスクリプト public class VRUIDoorFaceFollow : MonoBehaviour { [SerializeField] private Transform vrCameraTransform; - [Header("配置・追従のパラメータ")] + [Header("配置・追従パラメータ")] [SerializeField] private float forwardDistance = 0.5f; [SerializeField] private float heightOffset = -0.1f; [SerializeField] private float followSpeed = 5.0f; - // 画面がアクティブ(表示)になった瞬間を検知するためのフラグ - private bool wasActiveLastFrame = false; + private bool wasActiveLastFrame; private void Start() { - // カメラが未設定の場合は、シーン内からメインカメラを自動取得 if (vrCameraTransform == null && Camera.main != null) { vrCameraTransform = Camera.main.transform; } } - // 全体の時間が止まっている(Time.timeScale = 0)間でも動作させるため、カメラ移動の後に実行される LateUpdate を使用します private void LateUpdate() { if (vrCameraTransform == null) return; - // このオブジェクト(UI画面)が有効化された「最初のフレーム」の処理 if (!wasActiveLastFrame) { - // 開いた瞬間は、プレイヤーの今の向きの「完全な正面」に瞬間移動させる(遅れてついてくるのを防ぐため) SetInitialPosition(); wasActiveLastFrame = true; return; } - // 毎フレームの滑らかな追従処理 SmoothFollowPlayer(); } private void OnDisable() { - // 画面が閉じられたらフラグをリセットする wasActiveLastFrame = false; } - /// 画面が開いた瞬間に、プレイヤーの正面の正しい位置・向きに強制配置する private void SetInitialPosition() { - // カメラの位置から、カメラが向いている正面方向に一定距離進んだ座標を計算 Vector3 targetPosition = vrCameraTransform.position + (vrCameraTransform.forward * forwardDistance); - targetPosition.y += heightOffset; // 高さを微調整 + targetPosition.y += heightOffset; transform.position = targetPosition; - - // 画面の向きをプレイヤー(カメラ)の方向に向ける(背を向けないようにする) - // UIパネルがプレイヤー側を正面にするよう、LookAtの後に180度回転させて裏返りを防ぎます transform.LookAt(vrCameraTransform.position); transform.Rotate(0, 180, 0); } - /// プレイヤーが首を動かしたり移動したりした際、画面を滑らかについてこさせる private void SmoothFollowPlayer() { - // 目的地(目標座標)の計算 Vector3 targetPosition = vrCameraTransform.position + (vrCameraTransform.forward * forwardDistance); targetPosition.y += heightOffset; - // Time.timeScale = 0 でも動かすため、Time.unscaledDeltaTime を使用 - // 現在位置から目標位置まで滑らかに補間移動(Lerp)させる transform.position = Vector3.Lerp(transform.position, targetPosition, Time.unscaledDeltaTime * followSpeed); - // 向きも滑らかに補間(Slerp)してプレイヤーの方を向かせ続ける Quaternion targetRotation = Quaternion.LookRotation(transform.position - vrCameraTransform.position); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, Time.unscaledDeltaTime * followSpeed); } diff --git a/Assets/_Scripts/WorldPauseService.cs b/Assets/_Scripts/WorldPauseService.cs new file mode 100644 index 0000000..8deb52b --- /dev/null +++ b/Assets/_Scripts/WorldPauseService.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using UnityEngine; + +public class WorldPauseManager : MonoBehaviour +{ + public static WorldPauseManager Instance { get; private set; } + + private readonly HashSet pauseables = new HashSet(); + + private void Awake() + { + if (Instance == null) + { + Instance = this; + } + else + { + Destroy(gameObject); + } + } + + public void Register(IPauseable pauseable) + { + if (pauseable != null) pauseables.Add(pauseable); + } + + public void Unregister(IPauseable pauseable) + { + if (pauseable != null) pauseables.Remove(pauseable); + } + + public void PauseWorld() + { + foreach (var p in pauseables) + { + p?.Pause(); + } + } + + public void ResumeWorld() + { + foreach (var p in pauseables) + { + p?.Resume(); + } + } +} \ No newline at end of file diff --git a/Assets/_Scripts/WorldPauseService.cs.meta b/Assets/_Scripts/WorldPauseService.cs.meta new file mode 100644 index 0000000..7fc0f74 --- /dev/null +++ b/Assets/_Scripts/WorldPauseService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f00095a3e63cc440be35394ad49b505 \ No newline at end of file