diff --git a/Directory.Packages.props b/Directory.Packages.props
index a44e08fe6..044bc6ef6 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -9,7 +9,7 @@
-
+
@@ -38,7 +38,7 @@
-
+
diff --git a/docker/unity-ci/Dockerfile b/docker/unity-ci/Dockerfile
index 183634a6e..09c35b473 100644
--- a/docker/unity-ci/Dockerfile
+++ b/docker/unity-ci/Dockerfile
@@ -20,7 +20,7 @@
# 設定方法: .env ファイルで UNITY_MODULE を変更
# ビルド引数でプラットフォームを指定可能
-ARG UNITY_VERSION=6000.3.8f1
+ARG UNITY_VERSION=6000.3.20f1
ARG UNITY_MODULE=windows-mono
ARG IMAGE_VERSION=3
diff --git a/docker/unity-ci/docker-compose.yml b/docker/unity-ci/docker-compose.yml
index 21c6b5810..fe1ec4255 100644
--- a/docker/unity-ci/docker-compose.yml
+++ b/docker/unity-ci/docker-compose.yml
@@ -28,10 +28,10 @@ services:
context: .
dockerfile: Dockerfile
args:
- UNITY_VERSION: ${UNITY_VERSION:-6000.3.8f1}
+ UNITY_VERSION: ${UNITY_VERSION:-6000.3.20f1}
UNITY_MODULE: ${UNITY_MODULE:-windows-mono}
IMAGE_VERSION: ${IMAGE_VERSION:-3}
- image: unity-ci-windows-mono:${UNITY_VERSION:-6000.3.8f1}
+ image: unity-ci-windows-mono:${UNITY_VERSION:-6000.3.20f1}
container_name: unity-ci-windows-mono
restart: unless-stopped
@@ -102,10 +102,10 @@ services:
context: .
dockerfile: Dockerfile
args:
- UNITY_VERSION: ${UNITY_VERSION:-6000.3.8f1}
+ UNITY_VERSION: ${UNITY_VERSION:-6000.3.20f1}
UNITY_MODULE: linux-il2cpp
IMAGE_VERSION: ${IMAGE_VERSION:-3}
- image: unity-ci-linux-il2cpp:${UNITY_VERSION:-6000.3.8f1}
+ image: unity-ci-linux-il2cpp:${UNITY_VERSION:-6000.3.20f1}
container_name: unity-ci-linux-il2cpp
restart: unless-stopped
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset b/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset
index c8fa677d9..529644c73 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetGroupSortSettings.asset
@@ -38,4 +38,4 @@ MonoBehaviour:
- 0f8a457f8195a184e885754247412e9d
- b6cbf1b9a8d40d842b769f6982d86d29
- 56927c97116190547950555aaeff8bfa
- - 317cfc82a4a899d439fa6c78d3f55f7a
+ - b5733cba5107f704b885f59e2d1e9a28
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetSettings.asset
index ce335894a..54b25cd06 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetSettings.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AddressableAssetSettings.asset
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: ab956b5b82bfa614baabbac2e24e7add
m_currentHash:
serializedVersion: 2
- Hash: 00000000000000000000000000000000
+ Hash: e5dc310166583843a3cfa81bb860fbd4
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0
@@ -42,6 +42,7 @@ MonoBehaviour:
m_BundleRedirectLimit: -1
m_SharedBundleSettings: 0
m_SharedBundleSettingsCustomGroupIndex: 0
+ m_simulatedLoadDelay: 0.1
m_ContiguousBundles: 1
m_StripUnityVersionFromBundleBuild: 0
m_DisableVisibleSubAssetRepresentations: 0
@@ -69,7 +70,6 @@ MonoBehaviour:
- {fileID: 11400000, guid: eb36b54ff6dd7fd46a42e6c4f36d346d, type: 2}
- {fileID: 11400000, guid: f691c583cb7f18c4c831bed78c4743b3, type: 2}
- {fileID: 11400000, guid: f48c279515b20a848bbe7305354ba2a4, type: 2}
- - {fileID: 11400000, guid: 5a7e4d6d68dba0f46a19de454a4fc884, type: 2}
- {fileID: 11400000, guid: 27f7a719e4e0eec4a9c3e60758573df5, type: 2}
- {fileID: 11400000, guid: f29fc9061d6231b40853f02e3f248050, type: 2}
- {fileID: 11400000, guid: 54e78d3d38c666e42978d3b01e7a0e21, type: 2}
@@ -81,6 +81,7 @@ MonoBehaviour:
- {fileID: 11400000, guid: 5e2ddb4ec7c1867408af5eecbd693ae2, type: 2}
- {fileID: 11400000, guid: 22802c6482d74aa4da9dbcf4deaa6caa, type: 2}
- {fileID: 11400000, guid: 3f6fd4c48a94ca74991f0e8752f953fc, type: 2}
+ - {fileID: 11400000, guid: 1a97009704561af4faebfbd1a46b5e71, type: 2}
- {fileID: 11400000, guid: 5c5187892da6d3f4a98b4b3b18c15866, type: 2}
- {fileID: 11400000, guid: 11d78004797733640ac015be7cacaaa7, type: 2}
- {fileID: 11400000, guid: c7261704b46dcac4698a4c6ce106fb98, type: 2}
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorPrefabs.asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorPrefabs.asset
index 7326508ec..fc977815b 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorPrefabs.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorPrefabs.asset
@@ -20,13 +20,18 @@ MonoBehaviour:
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: 6dc4cf4496348884d8ee27743ae38562
+ m_Address: HorrorZombie
+ m_ReadOnly: 0
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: b1611d7d32b3037468188d2f5c8ff025
m_Address: HorrorEnemy
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: c0b020a3757cd34408a570b82a0c43bf
- m_Address: HorrorGameRootController
+ m_Address: HorrorGameRootContainer
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorScenes.asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorScenes.asset
index e56611213..8ed308f2e 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorScenes.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/HorrorScenes.asset
@@ -15,6 +15,11 @@ MonoBehaviour:
m_GroupName: HorrorScenes
m_GUID: 1e0ce9587ad9ff24b901b56d8bb3da6f
m_SerializeEntries:
+ - m_GUID: 094364c160b114e4dba809befc69171a
+ m_Address: HorrorGameOverDialog
+ m_ReadOnly: 0
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 25545f51a6c8d10439ed6f34f00651b3
m_Address: HorrorEquipmentShortcutDialog
m_ReadOnly: 0
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/CleanVectorIcons.asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/InputIcons.asset
similarity index 64%
rename from src/Game.Client/Assets/AddressableAssetsData/AssetGroups/CleanVectorIcons.asset
rename to src/Game.Client/Assets/AddressableAssetsData/AssetGroups/InputIcons.asset
index d80fb2e24..ee982b0c0 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/CleanVectorIcons.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/InputIcons.asset
@@ -10,23 +10,23 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bbb281ee3bf0b054c82ac2347e9e782c, type: 3}
- m_Name: CleanVectorIcons
+ m_Name: InputIcons
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.AddressableAssetGroup
- m_GroupName: CleanVectorIcons
- m_GUID: 317cfc82a4a899d439fa6c78d3f55f7a
+ m_GroupName: InputIcons
+ m_GUID: b5733cba5107f704b885f59e2d1e9a28
m_SerializeEntries:
- - m_GUID: 062eeb9d5e8ba8f45a9ddfea52518f82
- m_Address: T_1_fire_
+ - m_GUID: 30a61f33c53c49d47b3a9e577ee33753
+ m_Address: keyboard-&-mouse_sheet_default
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- - m_GUID: 71c2aae20a433fc498da71248e974a9c
- m_Address: T_14_key_
+ - m_GUID: 8fc2e8359006c0d43af5e16143646b22
+ m_Address: playstation-series_sheet_default
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
- - m_GUID: d3590afc6b555c44d8fcd6c5241012ab
- m_Address: T_8_bottle_magic_
+ - m_GUID: c9a22fff10df8784f86f655f115f0965
+ m_Address: xbox-series_sheet_default
m_ReadOnly: 0
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
@@ -34,5 +34,5 @@ MonoBehaviour:
m_Settings: {fileID: 11400000, guid: be8c7bfaed8f7eb428b95bd26a52c525, type: 2}
m_SchemaSet:
m_Schemas:
- - {fileID: 11400000, guid: ffcf6222cb4867c4b95c0ce0e9cfc3a9, type: 2}
- - {fileID: 11400000, guid: f41a9c230ca9c8b4b900aaeaf919fbe6, type: 2}
+ - {fileID: 11400000, guid: cb6dd28bc88e53d4caa34dbc54f1113f, type: 2}
+ - {fileID: 11400000, guid: 14c91cd24bdc9ca4c806658f226be448, type: 2}
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_ContentUpdateGroupSchema.asset.meta b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/InputIcons.asset.meta
similarity index 79%
rename from src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_ContentUpdateGroupSchema.asset.meta
rename to src/Game.Client/Assets/AddressableAssetsData/AssetGroups/InputIcons.asset.meta
index c87a1e7c7..eb93ae7d0 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_ContentUpdateGroupSchema.asset.meta
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/InputIcons.asset.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: f41a9c230ca9c8b4b900aaeaf919fbe6
+guid: 1a97009704561af4faebfbd1a46b5e71
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-Assets-Shared.asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-Assets-Shared.asset
index 5f8a60bfb..2c9c2fc02 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-Assets-Shared.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-Assets-Shared.asset
@@ -16,7 +16,7 @@ MonoBehaviour:
m_GUID: 451666f5d0e2643409edecbc7af33bb7
m_SerializeEntries:
- m_GUID: 0252908f50cdd1b4699d01422aa45d9b
- m_Address: Assets/Localization/Tables/String/InteractionMessages Shared Data.asset
+ m_Address: Assets/Localization/Tables/String/Messages Shared Data.asset
m_ReadOnly: 1
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
@@ -25,6 +25,11 @@ MonoBehaviour:
m_ReadOnly: 1
m_SerializedLabels: []
FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: 0c4ffabf8dd85254d84805227319e77b
+ m_Address: Assets/Localization/Tables/String/PropTexts Shared Data.asset
+ m_ReadOnly: 1
+ m_SerializedLabels: []
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 257b1f2889daa8142959891c0f690b60
m_Address: Assets/Localization/Tables/String/InputControls Shared Data.asset
m_ReadOnly: 1
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-English (en).asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-English (en).asset
index 32560d543..521480528 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-English (en).asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-English (en).asset
@@ -15,6 +15,12 @@ MonoBehaviour:
m_GroupName: Localization-String-Tables-English (en)
m_GUID: 1ca163cf09ed1b34d8f4544789927cb0
m_SerializeEntries:
+ - m_GUID: 2f75852d94bfb23428214abec2db295b
+ m_Address: PropTexts_en
+ m_ReadOnly: 1
+ m_SerializedLabels:
+ - Locale-en
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 4fa203c0eb4932b49b2b9655b528fc06
m_Address: InputActions_en
m_ReadOnly: 1
@@ -41,7 +47,7 @@ MonoBehaviour:
- Locale-en
FlaggedDuringContentUpdateRestriction: 0
- m_GUID: a9aa40b0a66e2b747a851c060038d943
- m_Address: InteractionMessages_en
+ m_Address: Messages_en
m_ReadOnly: 1
m_SerializedLabels:
- Locale-en
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Japanese (ja).asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Japanese (ja).asset
index 6f7cbd99e..fc2b00a74 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Japanese (ja).asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Localization-String-Tables-Japanese (ja).asset
@@ -21,8 +21,14 @@ MonoBehaviour:
m_SerializedLabels:
- Locale-ja
FlaggedDuringContentUpdateRestriction: 0
+ - m_GUID: 52cacc5ca28e3e0418264c67aacbab31
+ m_Address: PropTexts_ja
+ m_ReadOnly: 1
+ m_SerializedLabels:
+ - Locale-ja
+ FlaggedDuringContentUpdateRestriction: 0
- m_GUID: 5ee0655136a28074b9b2f60a229d32c2
- m_Address: InteractionMessages_ja
+ m_Address: Messages_ja
m_ReadOnly: 1
m_SerializedLabels:
- Locale-ja
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_BundledAssetGroupSchema.asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_BundledAssetGroupSchema.asset
similarity index 92%
rename from src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_BundledAssetGroupSchema.asset
rename to src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_BundledAssetGroupSchema.asset
index 8db4669a4..757bd72b0 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_BundledAssetGroupSchema.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_BundledAssetGroupSchema.asset
@@ -10,9 +10,9 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e5d17a21594effb4e9591490b009e7aa, type: 3}
- m_Name: CleanVectorIcons_BundledAssetGroupSchema
+ m_Name: InputIcons_BundledAssetGroupSchema
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema
- m_Group: {fileID: 11400000, guid: 5a7e4d6d68dba0f46a19de454a4fc884, type: 2}
+ m_Group: {fileID: 11400000, guid: 1a97009704561af4faebfbd1a46b5e71, type: 2}
m_InternalBundleIdMode: 1
m_Compression: 1
m_IncludeAddressInCatalog: 1
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/CleanVectorIcons.asset.meta b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_BundledAssetGroupSchema.asset.meta
similarity index 79%
rename from src/Game.Client/Assets/AddressableAssetsData/AssetGroups/CleanVectorIcons.asset.meta
rename to src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_BundledAssetGroupSchema.asset.meta
index abad42493..0ef57a679 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/CleanVectorIcons.asset.meta
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_BundledAssetGroupSchema.asset.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 5a7e4d6d68dba0f46a19de454a4fc884
+guid: cb6dd28bc88e53d4caa34dbc54f1113f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_ContentUpdateGroupSchema.asset b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_ContentUpdateGroupSchema.asset
similarity index 79%
rename from src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_ContentUpdateGroupSchema.asset
rename to src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_ContentUpdateGroupSchema.asset
index efcf6b7e4..9fdf74892 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_ContentUpdateGroupSchema.asset
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_ContentUpdateGroupSchema.asset
@@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5834b5087d578d24c926ce20cd31e6d6, type: 3}
- m_Name: CleanVectorIcons_ContentUpdateGroupSchema
+ m_Name: InputIcons_ContentUpdateGroupSchema
m_EditorClassIdentifier: Unity.Addressables.Editor::UnityEditor.AddressableAssets.Settings.GroupSchemas.ContentUpdateGroupSchema
- m_Group: {fileID: 11400000, guid: 5a7e4d6d68dba0f46a19de454a4fc884, type: 2}
+ m_Group: {fileID: 11400000, guid: 1a97009704561af4faebfbd1a46b5e71, type: 2}
m_StaticContent: 0
diff --git a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_BundledAssetGroupSchema.asset.meta b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_ContentUpdateGroupSchema.asset.meta
similarity index 79%
rename from src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_BundledAssetGroupSchema.asset.meta
rename to src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_ContentUpdateGroupSchema.asset.meta
index 96a8c24e6..8002e6492 100644
--- a/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/CleanVectorIcons_BundledAssetGroupSchema.asset.meta
+++ b/src/Game.Client/Assets/AddressableAssetsData/AssetGroups/Schemas/InputIcons_ContentUpdateGroupSchema.asset.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: ffcf6222cb4867c4b95c0ce0e9cfc3a9
+guid: 14c91cd24bdc9ca4c806658f226be448
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
diff --git a/src/Game.Client/Assets/Localization/CSV/InputControls.csv b/src/Game.Client/Assets/Localization/CSV/InputControls.csv
index bc5e95ac3..8f89ef8ab 100644
--- a/src/Game.Client/Assets/Localization/CSV/InputControls.csv
+++ b/src/Game.Client/Assets/Localization/CSV/InputControls.csv
@@ -42,108 +42,109 @@ Key,Id,English(en),Japanese(ja)
"switch/select",196230207512615,"Minus","-ボタン"
"switch/capture",196230207512616,"Capture","キャプチャー"
"switch/home",196230207512617,"Home","ホーム"
-"a",76630990848,"A","A"
-"b",76668739584,"B","B"
-"c",76668739585,"C","C"
-"d",76668739586,"D","D"
-"e",76668739587,"E","E"
-"f",76668739588,"F","F"
-"g",76668739589,"G","G"
-"h",76668739590,"H","H"
-"i",76668739591,"I","I"
-"j",76668739592,"J","J"
-"k",76668739593,"K","K"
-"l",76668739594,"L","L"
-"m",76668739595,"M","M"
-"n",76668739596,"N","N"
-"o",76668739597,"O","O"
-"p",76668739598,"P","P"
-"q",76668739599,"Q","Q"
-"r",76668739600,"R","R"
-"s",76668739601,"S","S"
-"t",76668739602,"T","T"
-"u",76668739603,"U","U"
-"v",76668739604,"V","V"
-"w",76668739605,"W","W"
-"x",76668739606,"X","X"
-"y",76668739607,"Y","Y"
-"z",76668739608,"Z","Z"
-"1",76668739609,"1","1"
-"2",76668739610,"2","2"
-"3",76668739611,"3","3"
-"4",76672933888,"4","4"
-"5",76672933889,"5","5"
-"6",76672933890,"6","6"
-"7",76672933891,"7","7"
-"8",76672933892,"8","8"
-"9",76672933893,"9","9"
-"0",76672933894,"0","0"
-"space",76672933895,"Space","Space"
-"enter",76672933896,"Enter","Enter"
-"escape",76672933897,"Escape","Esc"
-"tab",76672933898,"Tab","Tab"
-"backspace",76672933899,"Backspace","Backspace"
-"leftShift",76672933900,"Left Shift","Left Shift"
-"rightShift",76672933901,"Right Shift","Right Shift"
-"leftCtrl",76672933902,"Left Ctrl","Left Ctrl"
-"rightCtrl",76672933903,"Right Ctrl","Right Ctrl"
-"leftAlt",76672933904,"Left Alt","Left Alt"
-"rightAlt",76672933905,"Right Alt","Right Alt"
-"upArrow",76672933906,"Up Arrow","↑"
-"downArrow",76672933907,"Down Arrow","↓"
-"leftArrow",76672933908,"Left Arrow","←"
-"rightArrow",76672933909,"Right Arrow","→"
-"backquote",76672933910,"`","`"
-"minus",76672933911,"-","-"
-"equals",76672933912,"=","="
-"leftBracket",76672933913,"[","["
-"rightBracket",76672933914,"]","]"
-"semicolon",76672933915,";",";"
-"quote",76672933916,"'","'"
-"backslash",76672933917,"\","\"
-"comma",76672933918,",",","
-"period",76672933919,".","."
-"slash",76672933920,"/","/"
-"home",76672933921,"Home","Home"
-"end",76672933922,"End","End"
-"insert",76672933923,"Insert","Insert"
-"delete",76672933924,"Delete","Delete"
-"pageUp",76672933925,"Page Up","PageUp"
-"pageDown",76672933926,"Page Down","PageDown"
-"capsLock",76672933927,"Caps Lock","CapsLock"
-"f1",76672933928,"F1","F1"
-"f2",76672933929,"F2","F2"
-"f3",76672933930,"F3","F3"
-"f4",76672933931,"F4","F4"
-"f5",76672933932,"F5","F5"
-"f6",76672933933,"F6","F6"
-"f7",76672933934,"F7","F7"
-"f8",76672933935,"F8","F8"
-"f9",76672933936,"F9","F9"
-"f10",76672933937,"F10","F10"
-"f11",76672933938,"F11","F11"
-"f12",76672933939,"F12","F12"
-"numpad0",76672933940,"Numpad 0","テンキー0"
-"numpad1",76672933941,"Numpad 1","テンキー1"
-"numpad2",76672933942,"Numpad 2","テンキー2"
-"numpad3",76672933943,"Numpad 3","テンキー3"
-"numpad4",76672933944,"Numpad 4","テンキー4"
-"numpad5",76672933945,"Numpad 5","テンキー5"
-"numpad6",76672933946,"Numpad 6","テンキー6"
-"numpad7",76672933947,"Numpad 7","テンキー7"
-"numpad8",76672933948,"Numpad 8","テンキー8"
-"numpad9",76672933949,"Numpad 9","テンキー9"
-"numpadEnter",76672933950,"Numpad Enter","テンキーEnter"
-"numpadPlus",76672933951,"Numpad +","テンキー+"
-"numpadMinus",76672933952,"Numpad -","テンキー-"
-"numpadMultiply",76672933953,"Numpad *","テンキー*"
-"numpadDivide",76672933954,"Numpad /","テンキー/"
-"numpadPeriod",76672933955,"Numpad .","テンキー."
-"leftButton",76672933956,"Left Click","左クリック"
-"rightButton",76672933957,"Right Click","右クリック"
-"middleButton",76672933958,"Middle Click","中クリック"
-"forwardButton",76672933959,"Forward","進む"
-"backButton",76672933960,"Back","戻る"
+"keyboard/a",76630990848,"A","A"
+"keyboard/b",76668739584,"B","B"
+"keyboard/c",76668739585,"C","C"
+"keyboard/d",76668739586,"D","D"
+"keyboard/e",76668739587,"E","E"
+"keyboard/f",76668739588,"F","F"
+"keyboard/g",76668739589,"G","G"
+"keyboard/h",76668739590,"H","H"
+"keyboard/i",76668739591,"I","I"
+"keyboard/j",76668739592,"J","J"
+"keyboard/k",76668739593,"K","K"
+"keyboard/l",76668739594,"L","L"
+"keyboard/m",76668739595,"M","M"
+"keyboard/n",76668739596,"N","N"
+"keyboard/o",76668739597,"O","O"
+"keyboard/p",76668739598,"P","P"
+"keyboard/q",76668739599,"Q","Q"
+"keyboard/r",76668739600,"R","R"
+"keyboard/s",76668739601,"S","S"
+"keyboard/t",76668739602,"T","T"
+"keyboard/u",76668739603,"U","U"
+"keyboard/v",76668739604,"V","V"
+"keyboard/w",76668739605,"W","W"
+"keyboard/x",76668739606,"X","X"
+"keyboard/y",76668739607,"Y","Y"
+"keyboard/z",76668739608,"Z","Z"
+"keyboard/1",76668739609,"1","1"
+"keyboard/2",76668739610,"2","2"
+"keyboard/3",76668739611,"3","3"
+"keyboard/4",76672933888,"4","4"
+"keyboard/5",76672933889,"5","5"
+"keyboard/6",76672933890,"6","6"
+"keyboard/7",76672933891,"7","7"
+"keyboard/8",76672933892,"8","8"
+"keyboard/9",76672933893,"9","9"
+"keyboard/0",76672933894,"0","0"
+"keyboard/space",76672933895,"Space","Space"
+"keyboard/enter",76672933896,"Enter","Enter"
+"keyboard/escape",76672933897,"Escape","Esc"
+"keyboard/tab",76672933898,"Tab","Tab"
+"keyboard/backspace",76672933899,"Backspace","Backspace"
+"keyboard/leftShift",76672933900,"Left Shift","Left Shift"
+"keyboard/rightShift",76672933901,"Right Shift","Right Shift"
+"keyboard/leftCtrl",76672933902,"Left Ctrl","Left Ctrl"
+"keyboard/rightCtrl",76672933903,"Right Ctrl","Right Ctrl"
+"keyboard/leftAlt",76672933904,"Left Alt","Left Alt"
+"keyboard/rightAlt",76672933905,"Right Alt","Right Alt"
+"keyboard/upArrow",76672933906,"Up Arrow","↑"
+"keyboard/downArrow",76672933907,"Down Arrow","↓"
+"keyboard/leftArrow",76672933908,"Left Arrow","←"
+"keyboard/rightArrow",76672933909,"Right Arrow","→"
+"keyboard/backquote",76672933910,"`","`"
+"keyboard/minus",76672933911,"-","-"
+"keyboard/equals",76672933912,"=","="
+"keyboard/leftBracket",76672933913,"[","["
+"keyboard/rightBracket",76672933914,"]","]"
+"keyboard/semicolon",76672933915,";",";"
+"keyboard/quote",76672933916,"'","'"
+"keyboard/backslash",76672933917,"\","\"
+"keyboard/comma",76672933918,",",","
+"keyboard/period",76672933919,".","."
+"keyboard/slash",76672933920,"/","/"
+"keyboard/home",76672933921,"Home","Home"
+"keyboard/end",76672933922,"End","End"
+"keyboard/insert",76672933923,"Insert","Insert"
+"keyboard/delete",76672933924,"Delete","Delete"
+"keyboard/pageUp",76672933925,"Page Up","PageUp"
+"keyboard/pageDown",76672933926,"Page Down","PageDown"
+"keyboard/capsLock",76672933927,"Caps Lock","CapsLock"
+"keyboard/f1",76672933928,"F1","F1"
+"keyboard/f2",76672933929,"F2","F2"
+"keyboard/f3",76672933930,"F3","F3"
+"keyboard/f4",76672933931,"F4","F4"
+"keyboard/f5",76672933932,"F5","F5"
+"keyboard/f6",76672933933,"F6","F6"
+"keyboard/f7",76672933934,"F7","F7"
+"keyboard/f8",76672933935,"F8","F8"
+"keyboard/f9",76672933936,"F9","F9"
+"keyboard/f10",76672933937,"F10","F10"
+"keyboard/f11",76672933938,"F11","F11"
+"keyboard/f12",76672933939,"F12","F12"
+"keyboard/numpad0",76672933940,"Numpad 0","テンキー0"
+"keyboard/numpad1",76672933941,"Numpad 1","テンキー1"
+"keyboard/numpad2",76672933942,"Numpad 2","テンキー2"
+"keyboard/numpad3",76672933943,"Numpad 3","テンキー3"
+"keyboard/numpad4",76672933944,"Numpad 4","テンキー4"
+"keyboard/numpad5",76672933945,"Numpad 5","テンキー5"
+"keyboard/numpad6",76672933946,"Numpad 6","テンキー6"
+"keyboard/numpad7",76672933947,"Numpad 7","テンキー7"
+"keyboard/numpad8",76672933948,"Numpad 8","テンキー8"
+"keyboard/numpad9",76672933949,"Numpad 9","テンキー9"
+"keyboard/numpadEnter",76672933950,"Numpad Enter","テンキーEnter"
+"keyboard/numpadPlus",76672933951,"Numpad +","テンキー+"
+"keyboard/numpadMinus",76672933952,"Numpad -","テンキー-"
+"keyboard/numpadMultiply",76672933953,"Numpad *","テンキー*"
+"keyboard/numpadDivide",76672933954,"Numpad /","テンキー/"
+"keyboard/numpadPeriod",76672933955,"Numpad .","テンキー."
+"mouse/leftButton",76672933956,"Left Click","左クリック"
+"mouse/rightButton",76672933957,"Right Click","右クリック"
+"mouse/middleButton",76672933958,"Middle Click","中クリック"
+"mouse/forwardButton",76672933959,"Forward","進む"
+"mouse/backButton",76672933960,"Back","戻る"
+"mouse/delta",147288283422720,"Mouse XY 2D-Axis","マウス XY 2D軸"
"buttonSouth",76672933961,"A","Aボタン"
"buttonEast",76672933962,"B","Bボタン"
"buttonWest",76672933963,"X","Xボタン"
@@ -156,10 +157,9 @@ Key,Id,English(en),Japanese(ja)
"rightStick",76672933970,"Right Stick","右スティック"
"leftStickPress",76672933971,"Left Stick Press","左スティック押込"
"rightStickPress",76672933972,"Right Stick Press","右スティック押込"
-"leftShoulder",76672933973,"Left Shoulder","Lボタン"
-"rightShoulder",76672933974,"Right Shoulder","Rボタン"
-"leftTrigger",76672933975,"Left Trigger","LT"
-"rightTrigger",76672933976,"Right Trigger","RT"
+"leftShoulder",76672933973,"LB","LB"
+"rightShoulder",76672933974,"RB","RB"
+"leftTrigger",76672933975,"LT","LT"
+"rightTrigger",76672933976,"RT","RT"
"start",76672933977,"Start","Start"
-"select",76672933978,"Select","Select"
-"delta",147288283422720,"Mouse XY 2D-Axis","マウス XY 2D軸"
\ No newline at end of file
+"select",76672933978,"Select","Select"
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Localization/Tables/String/ContextActions Shared Data.asset b/src/Game.Client/Assets/Localization/Tables/String/ContextActions Shared Data.asset
index 039af298d..6abbe0325 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/ContextActions Shared Data.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/ContextActions Shared Data.asset
@@ -59,6 +59,30 @@ MonoBehaviour:
m_Key: Save
m_Metadata:
m_Items: []
+ - m_Id: 8704920440537088
+ m_Key: Submit
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8705142377938944
+ m_Key: Back
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8705362595676160
+ m_Key: Reset
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8716247565336576
+ m_Key: Remove
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8720956409774080
+ m_Key: "Close\t"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8724977921032192
+ m_Key: Cancel
+ m_Metadata:
+ m_Items: []
m_Metadata:
m_Items: []
m_KeyGenerator:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/ContextActions_en.asset b/src/Game.Client/Assets/Localization/Tables/String/ContextActions_en.asset
index 640d706ad..2b0ed27de 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/ContextActions_en.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/ContextActions_en.asset
@@ -62,6 +62,30 @@ MonoBehaviour:
m_Localized: Save
m_Metadata:
m_Items: []
+ - m_Id: 8704920440537088
+ m_Localized: Submit
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8705142377938944
+ m_Localized: Back
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8705362595676160
+ m_Localized: Reset
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8716247565336576
+ m_Localized: Remove
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8724977921032192
+ m_Localized: Cancel
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8720956409774080
+ m_Localized: Close
+ m_Metadata:
+ m_Items: []
references:
version: 2
RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/ContextActions_ja.asset b/src/Game.Client/Assets/Localization/Tables/String/ContextActions_ja.asset
index b1dc87739..55299946c 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/ContextActions_ja.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/ContextActions_ja.asset
@@ -62,6 +62,30 @@ MonoBehaviour:
m_Localized: "\u30BB\u30FC\u30D6\u3059\u308B"
m_Metadata:
m_Items: []
+ - m_Id: 8704920440537088
+ m_Localized: "\u6C7A\u5B9A"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8705142377938944
+ m_Localized: "\u623B\u308B"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8705362595676160
+ m_Localized: "\u30EA\u30BB\u30C3\u30C8"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8716247565336576
+ m_Localized: "\u524A\u9664\u3059\u308B"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8724977921032192
+ m_Localized: "\u30AD\u30E3\u30F3\u30BB\u30EB"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8720956409774080
+ m_Localized: "\u9589\u3058\u308B"
+ m_Metadata:
+ m_Items: []
references:
version: 2
RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InputActions Shared Data.asset b/src/Game.Client/Assets/Localization/Tables/String/InputActions Shared Data.asset
index bdeeaffb8..142c279a2 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InputActions Shared Data.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/InputActions Shared Data.asset
@@ -59,6 +59,46 @@ MonoBehaviour:
m_Key: Move
m_Metadata:
m_Items: []
+ - m_Id: 11710666294435840
+ m_Key: Fire
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11710856673894400
+ m_Key: Aim
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11711954906259456
+ m_Key: EquipSlot1
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11712643908771840
+ m_Key: Reload
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11712901485174784
+ m_Key: EquipSlot2
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11713160324063232
+ m_Key: EquipSlot3
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11713418957430784
+ m_Key: EquipSlot4
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11718485202706432
+ m_Key: EquipSlot
+ m_Metadata:
+ m_Items: []
+ - m_Id: 13609069693640704
+ m_Key: PauseMenu
+ m_Metadata:
+ m_Items: []
+ - m_Id: 13609530941251584
+ m_Key: Inventory
+ m_Metadata:
+ m_Items: []
m_Metadata:
m_Items: []
m_KeyGenerator:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InputActions_en.asset b/src/Game.Client/Assets/Localization/Tables/String/InputActions_en.asset
index d1e06c92a..0952b140b 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InputActions_en.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/InputActions_en.asset
@@ -62,6 +62,46 @@ MonoBehaviour:
m_Localized: Move
m_Metadata:
m_Items: []
+ - m_Id: 11710666294435840
+ m_Localized: Fire
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11710856673894400
+ m_Localized: Aim
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11711954906259456
+ m_Localized: Equip Slot 1
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11712643908771840
+ m_Localized: Reload
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11712901485174784
+ m_Localized: Equip Slot 2
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11713160324063232
+ m_Localized: Equip Slot 3
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11713418957430784
+ m_Localized: Equip Slot 4
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11718485202706432
+ m_Localized: Equip Slot
+ m_Metadata:
+ m_Items: []
+ - m_Id: 13609069693640704
+ m_Localized: Pause Menu
+ m_Metadata:
+ m_Items: []
+ - m_Id: 13609530941251584
+ m_Localized: Inventory
+ m_Metadata:
+ m_Items: []
references:
version: 2
RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InputActions_ja.asset b/src/Game.Client/Assets/Localization/Tables/String/InputActions_ja.asset
index afda50336..2ef8f8db9 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InputActions_ja.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/InputActions_ja.asset
@@ -55,13 +55,53 @@ MonoBehaviour:
m_Metadata:
m_Items: []
- m_Id: 2596711440384
- m_Localized: "\u30B9\u30D7\u30EA\u30F3\u30C8"
+ m_Localized: "\u8D70\u308B"
m_Metadata:
m_Items: []
- m_Id: 301517983719424
m_Localized: "\u79FB\u52D5"
m_Metadata:
m_Items: []
+ - m_Id: 11710666294435840
+ m_Localized: "\u5C04\u6483"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11710856673894400
+ m_Localized: "\u30A8\u30A4\u30E0"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11711954906259456
+ m_Localized: "\u88C5\u5099\u30B9\u30ED\u30C3\u30C81"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11712643908771840
+ m_Localized: "\u30EA\u30ED\u30FC\u30C9"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11712901485174784
+ m_Localized: "\u88C5\u5099\u30B9\u30ED\u30C3\u30C82"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11713160324063232
+ m_Localized: "\u88C5\u5099\u30B9\u30ED\u30C3\u30C83"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11713418957430784
+ m_Localized: "\u88C5\u5099\u30B9\u30ED\u30C3\u30C84"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 11718485202706432
+ m_Localized: "\u88C5\u5099\u30B9\u30ED\u30C3\u30C8"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 13609069693640704
+ m_Localized: "\u30DD\u30FC\u30BA\u30E1\u30CB\u30E5\u30FC"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 13609530941251584
+ m_Localized: "\u30A4\u30F3\u30D9\u30F3\u30C8\u30EA"
+ m_Metadata:
+ m_Items: []
references:
version: 2
RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InputControls Shared Data.asset b/src/Game.Client/Assets/Localization/Tables/String/InputControls Shared Data.asset
index bff7622c2..578c6987a 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InputControls Shared Data.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/InputControls Shared Data.asset
@@ -188,411 +188,415 @@ MonoBehaviour:
m_Metadata:
m_Items: []
- m_Id: 76630990848
- m_Key: a
+ m_Key: keyboard/a
m_Metadata:
m_Items: []
- m_Id: 76668739584
- m_Key: b
+ m_Key: keyboard/b
m_Metadata:
m_Items: []
- m_Id: 76668739585
- m_Key: c
+ m_Key: keyboard/c
m_Metadata:
m_Items: []
- m_Id: 76668739586
- m_Key: d
+ m_Key: keyboard/d
m_Metadata:
m_Items: []
- m_Id: 76668739587
- m_Key: e
+ m_Key: keyboard/e
m_Metadata:
m_Items: []
- m_Id: 76668739588
- m_Key: f
+ m_Key: keyboard/f
m_Metadata:
m_Items: []
- m_Id: 76668739589
- m_Key: g
+ m_Key: keyboard/g
m_Metadata:
m_Items: []
- m_Id: 76668739590
- m_Key: h
+ m_Key: keyboard/h
m_Metadata:
m_Items: []
- m_Id: 76668739591
- m_Key: i
+ m_Key: keyboard/i
m_Metadata:
m_Items: []
- m_Id: 76668739592
- m_Key: j
+ m_Key: keyboard/j
m_Metadata:
m_Items: []
- m_Id: 76668739593
- m_Key: k
+ m_Key: keyboard/k
m_Metadata:
m_Items: []
- m_Id: 76668739594
- m_Key: l
+ m_Key: keyboard/l
m_Metadata:
m_Items: []
- m_Id: 76668739595
- m_Key: m
+ m_Key: keyboard/m
m_Metadata:
m_Items: []
- m_Id: 76668739596
- m_Key: n
+ m_Key: keyboard/n
m_Metadata:
m_Items: []
- m_Id: 76668739597
- m_Key: o
+ m_Key: keyboard/o
m_Metadata:
m_Items: []
- m_Id: 76668739598
- m_Key: p
+ m_Key: keyboard/p
m_Metadata:
m_Items: []
- m_Id: 76668739599
- m_Key: q
+ m_Key: keyboard/q
m_Metadata:
m_Items: []
- m_Id: 76668739600
- m_Key: r
+ m_Key: keyboard/r
m_Metadata:
m_Items: []
- m_Id: 76668739601
- m_Key: s
+ m_Key: keyboard/s
m_Metadata:
m_Items: []
- m_Id: 76668739602
- m_Key: t
+ m_Key: keyboard/t
m_Metadata:
m_Items: []
- m_Id: 76668739603
- m_Key: u
+ m_Key: keyboard/u
m_Metadata:
m_Items: []
- m_Id: 76668739604
- m_Key: v
+ m_Key: keyboard/v
m_Metadata:
m_Items: []
- m_Id: 76668739605
- m_Key: w
+ m_Key: keyboard/w
m_Metadata:
m_Items: []
- m_Id: 76668739606
- m_Key: x
+ m_Key: keyboard/x
m_Metadata:
m_Items: []
- m_Id: 76668739607
- m_Key: y
+ m_Key: keyboard/y
m_Metadata:
m_Items: []
- m_Id: 76668739608
- m_Key: z
+ m_Key: keyboard/z
m_Metadata:
m_Items: []
- m_Id: 76668739609
- m_Key: 1
+ m_Key: keyboard/1
m_Metadata:
m_Items: []
- m_Id: 76668739610
- m_Key: 2
+ m_Key: keyboard/2
m_Metadata:
m_Items: []
- m_Id: 76668739611
- m_Key: 3
+ m_Key: keyboard/3
m_Metadata:
m_Items: []
- m_Id: 76672933888
- m_Key: 4
+ m_Key: keyboard/4
m_Metadata:
m_Items: []
- m_Id: 76672933889
- m_Key: 5
+ m_Key: keyboard/5
m_Metadata:
m_Items: []
- m_Id: 76672933890
- m_Key: 6
+ m_Key: keyboard/6
m_Metadata:
m_Items: []
- m_Id: 76672933891
- m_Key: 7
+ m_Key: keyboard/7
m_Metadata:
m_Items: []
- m_Id: 76672933892
- m_Key: 8
+ m_Key: keyboard/8
m_Metadata:
m_Items: []
- m_Id: 76672933893
- m_Key: 9
+ m_Key: keyboard/9
m_Metadata:
m_Items: []
- m_Id: 76672933894
- m_Key: 0
+ m_Key: keyboard/0
m_Metadata:
m_Items: []
- m_Id: 76672933895
- m_Key: space
+ m_Key: keyboard/space
m_Metadata:
m_Items: []
- m_Id: 76672933896
- m_Key: enter
+ m_Key: keyboard/enter
m_Metadata:
m_Items: []
- m_Id: 76672933897
- m_Key: escape
+ m_Key: keyboard/escape
m_Metadata:
m_Items: []
- m_Id: 76672933898
- m_Key: tab
+ m_Key: keyboard/tab
m_Metadata:
m_Items: []
- m_Id: 76672933899
- m_Key: backspace
+ m_Key: keyboard/backspace
m_Metadata:
m_Items: []
- m_Id: 76672933900
- m_Key: leftShift
+ m_Key: keyboard/leftShift
m_Metadata:
m_Items: []
- m_Id: 76672933901
- m_Key: rightShift
+ m_Key: keyboard/rightShift
m_Metadata:
m_Items: []
- m_Id: 76672933902
- m_Key: leftCtrl
+ m_Key: keyboard/leftCtrl
m_Metadata:
m_Items: []
- m_Id: 76672933903
- m_Key: rightCtrl
+ m_Key: keyboard/rightCtrl
m_Metadata:
m_Items: []
- m_Id: 76672933904
- m_Key: leftAlt
+ m_Key: keyboard/leftAlt
m_Metadata:
m_Items: []
- m_Id: 76672933905
- m_Key: rightAlt
+ m_Key: keyboard/rightAlt
m_Metadata:
m_Items: []
- m_Id: 76672933906
- m_Key: upArrow
+ m_Key: keyboard/upArrow
m_Metadata:
m_Items: []
- m_Id: 76672933907
- m_Key: downArrow
+ m_Key: keyboard/downArrow
m_Metadata:
m_Items: []
- m_Id: 76672933908
- m_Key: leftArrow
+ m_Key: keyboard/leftArrow
m_Metadata:
m_Items: []
- m_Id: 76672933909
- m_Key: rightArrow
+ m_Key: keyboard/rightArrow
m_Metadata:
m_Items: []
- m_Id: 76672933910
- m_Key: backquote
+ m_Key: keyboard/backquote
m_Metadata:
m_Items: []
- m_Id: 76672933911
- m_Key: minus
+ m_Key: keyboard/minus
m_Metadata:
m_Items: []
- m_Id: 76672933912
- m_Key: equals
+ m_Key: keyboard/equals
m_Metadata:
m_Items: []
- m_Id: 76672933913
- m_Key: leftBracket
+ m_Key: keyboard/leftBracket
m_Metadata:
m_Items: []
- m_Id: 76672933914
- m_Key: rightBracket
+ m_Key: keyboard/rightBracket
m_Metadata:
m_Items: []
- m_Id: 76672933915
- m_Key: semicolon
+ m_Key: keyboard/semicolon
m_Metadata:
m_Items: []
- m_Id: 76672933916
- m_Key: quote
+ m_Key: keyboard/quote
m_Metadata:
m_Items: []
- m_Id: 76672933917
- m_Key: backslash
+ m_Key: keyboard/backslash
m_Metadata:
m_Items: []
- m_Id: 76672933918
- m_Key: comma
+ m_Key: keyboard/comma
m_Metadata:
m_Items: []
- m_Id: 76672933919
- m_Key: period
+ m_Key: keyboard/period
m_Metadata:
m_Items: []
- m_Id: 76672933920
- m_Key: slash
+ m_Key: keyboard/slash
m_Metadata:
m_Items: []
- m_Id: 76672933921
- m_Key: home
+ m_Key: keyboard/home
m_Metadata:
m_Items: []
- m_Id: 76672933922
- m_Key: end
+ m_Key: keyboard/end
m_Metadata:
m_Items: []
- m_Id: 76672933923
- m_Key: insert
+ m_Key: keyboard/insert
m_Metadata:
m_Items: []
- m_Id: 76672933924
- m_Key: delete
+ m_Key: keyboard/delete
m_Metadata:
m_Items: []
- m_Id: 76672933925
- m_Key: pageUp
+ m_Key: keyboard/pageUp
m_Metadata:
m_Items: []
- m_Id: 76672933926
- m_Key: pageDown
+ m_Key: keyboard/pageDown
m_Metadata:
m_Items: []
- m_Id: 76672933927
- m_Key: capsLock
+ m_Key: keyboard/capsLock
m_Metadata:
m_Items: []
- m_Id: 76672933928
- m_Key: f1
+ m_Key: keyboard/f1
m_Metadata:
m_Items: []
- m_Id: 76672933929
- m_Key: f2
+ m_Key: keyboard/f2
m_Metadata:
m_Items: []
- m_Id: 76672933930
- m_Key: f3
+ m_Key: keyboard/f3
m_Metadata:
m_Items: []
- m_Id: 76672933931
- m_Key: f4
+ m_Key: keyboard/f4
m_Metadata:
m_Items: []
- m_Id: 76672933932
- m_Key: f5
+ m_Key: keyboard/f5
m_Metadata:
m_Items: []
- m_Id: 76672933933
- m_Key: f6
+ m_Key: keyboard/f6
m_Metadata:
m_Items: []
- m_Id: 76672933934
- m_Key: f7
+ m_Key: keyboard/f7
m_Metadata:
m_Items: []
- m_Id: 76672933935
- m_Key: f8
+ m_Key: keyboard/f8
m_Metadata:
m_Items: []
- m_Id: 76672933936
- m_Key: f9
+ m_Key: keyboard/f9
m_Metadata:
m_Items: []
- m_Id: 76672933937
- m_Key: f10
+ m_Key: keyboard/f10
m_Metadata:
m_Items: []
- m_Id: 76672933938
- m_Key: f11
+ m_Key: keyboard/f11
m_Metadata:
m_Items: []
- m_Id: 76672933939
- m_Key: f12
+ m_Key: keyboard/f12
m_Metadata:
m_Items: []
- m_Id: 76672933940
- m_Key: numpad0
+ m_Key: keyboard/numpad0
m_Metadata:
m_Items: []
- m_Id: 76672933941
- m_Key: numpad1
+ m_Key: keyboard/numpad1
m_Metadata:
m_Items: []
- m_Id: 76672933942
- m_Key: numpad2
+ m_Key: keyboard/numpad2
m_Metadata:
m_Items: []
- m_Id: 76672933943
- m_Key: numpad3
+ m_Key: keyboard/numpad3
m_Metadata:
m_Items: []
- m_Id: 76672933944
- m_Key: numpad4
+ m_Key: keyboard/numpad4
m_Metadata:
m_Items: []
- m_Id: 76672933945
- m_Key: numpad5
+ m_Key: keyboard/numpad5
m_Metadata:
m_Items: []
- m_Id: 76672933946
- m_Key: numpad6
+ m_Key: keyboard/numpad6
m_Metadata:
m_Items: []
- m_Id: 76672933947
- m_Key: numpad7
+ m_Key: keyboard/numpad7
m_Metadata:
m_Items: []
- m_Id: 76672933948
- m_Key: numpad8
+ m_Key: keyboard/numpad8
m_Metadata:
m_Items: []
- m_Id: 76672933949
- m_Key: numpad9
+ m_Key: keyboard/numpad9
m_Metadata:
m_Items: []
- m_Id: 76672933950
- m_Key: numpadEnter
+ m_Key: keyboard/numpadEnter
m_Metadata:
m_Items: []
- m_Id: 76672933951
- m_Key: numpadPlus
+ m_Key: keyboard/numpadPlus
m_Metadata:
m_Items: []
- m_Id: 76672933952
- m_Key: numpadMinus
+ m_Key: keyboard/numpadMinus
m_Metadata:
m_Items: []
- m_Id: 76672933953
- m_Key: numpadMultiply
+ m_Key: keyboard/numpadMultiply
m_Metadata:
m_Items: []
- m_Id: 76672933954
- m_Key: numpadDivide
+ m_Key: keyboard/numpadDivide
m_Metadata:
m_Items: []
- m_Id: 76672933955
- m_Key: numpadPeriod
+ m_Key: keyboard/numpadPeriod
m_Metadata:
m_Items: []
- m_Id: 76672933956
- m_Key: leftButton
+ m_Key: mouse/leftButton
m_Metadata:
m_Items: []
- m_Id: 76672933957
- m_Key: rightButton
+ m_Key: mouse/rightButton
m_Metadata:
m_Items: []
- m_Id: 76672933958
- m_Key: middleButton
+ m_Key: mouse/middleButton
m_Metadata:
m_Items: []
- m_Id: 76672933959
- m_Key: forwardButton
+ m_Key: mouse/forwardButton
m_Metadata:
m_Items: []
- m_Id: 76672933960
- m_Key: backButton
+ m_Key: mouse/backButton
+ m_Metadata:
+ m_Items: []
+ - m_Id: 147288283422720
+ m_Key: mouse/delta
m_Metadata:
m_Items: []
- m_Id: 76672933961
@@ -667,10 +671,6 @@ MonoBehaviour:
m_Key: select
m_Metadata:
m_Items: []
- - m_Id: 147288283422720
- m_Key: delta
- m_Metadata:
- m_Items: []
m_Metadata:
m_Items: []
m_KeyGenerator:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages Shared Data.asset b/src/Game.Client/Assets/Localization/Tables/String/Messages Shared Data.asset
similarity index 91%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages Shared Data.asset
rename to src/Game.Client/Assets/Localization/Tables/String/Messages Shared Data.asset
index 3c46a6234..167ec5abf 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages Shared Data.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/Messages Shared Data.asset
@@ -10,9 +10,9 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5b11a58205ec3474ca216360e9fa74a8, type: 3}
- m_Name: InteractionMessages Shared Data
+ m_Name: Messages Shared Data
m_EditorClassIdentifier: Unity.Localization::UnityEngine.Localization.Tables.SharedTableData
- m_TableCollectionName: InteractionMessages
+ m_TableCollectionName: Messages
m_TableCollectionNameGuidString: 0252908f50cdd1b4699d01422aa45d9b
m_Entries:
- m_Id: 2000202125312
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages Shared Data.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/Messages Shared Data.asset.meta
similarity index 100%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages Shared Data.asset.meta
rename to src/Game.Client/Assets/Localization/Tables/String/Messages Shared Data.asset.meta
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages.asset b/src/Game.Client/Assets/Localization/Tables/String/Messages.asset
similarity index 96%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages.asset
rename to src/Game.Client/Assets/Localization/Tables/String/Messages.asset
index 4b1464569..f5275a78d 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/Messages.asset
@@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5be51871efa6c3e4eae1703925c8f5ac, type: 3}
- m_Name: InteractionMessages
+ m_Name: Messages
m_EditorClassIdentifier: Unity.Localization.Editor::UnityEditor.Localization.StringTableCollection
m_SharedTableData: {fileID: 11400000, guid: 0252908f50cdd1b4699d01422aa45d9b, type: 2}
m_Tables:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/Messages.asset.meta
similarity index 100%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages.asset.meta
rename to src/Game.Client/Assets/Localization/Tables/String/Messages.asset.meta
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_en.asset b/src/Game.Client/Assets/Localization/Tables/String/Messages_en.asset
similarity index 95%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_en.asset
rename to src/Game.Client/Assets/Localization/Tables/String/Messages_en.asset
index 4d43fc987..bcf4a7e6e 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_en.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/Messages_en.asset
@@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e9620f8c34305754d8cc9a7e49e852d9, type: 3}
- m_Name: InteractionMessages_en
+ m_Name: Messages_en
m_EditorClassIdentifier: Unity.Localization::UnityEngine.Localization.Tables.StringTable
m_LocaleId:
m_Code: en
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_en.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/Messages_en.asset.meta
similarity index 100%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_en.asset.meta
rename to src/Game.Client/Assets/Localization/Tables/String/Messages_en.asset.meta
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_ja.asset b/src/Game.Client/Assets/Localization/Tables/String/Messages_ja.asset
similarity index 95%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_ja.asset
rename to src/Game.Client/Assets/Localization/Tables/String/Messages_ja.asset
index f542b59b8..b5e09f95e 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_ja.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/Messages_ja.asset
@@ -10,7 +10,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e9620f8c34305754d8cc9a7e49e852d9, type: 3}
- m_Name: InteractionMessages_ja
+ m_Name: Messages_ja
m_EditorClassIdentifier: Unity.Localization::UnityEngine.Localization.Tables.StringTable
m_LocaleId:
m_Code: ja
diff --git a/src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_ja.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/Messages_ja.asset.meta
similarity index 100%
rename from src/Game.Client/Assets/Localization/Tables/String/InteractionMessages_ja.asset.meta
rename to src/Game.Client/Assets/Localization/Tables/String/Messages_ja.asset.meta
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts Shared Data.asset b/src/Game.Client/Assets/Localization/Tables/String/PropTexts Shared Data.asset
new file mode 100644
index 000000000..ad559e614
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts Shared Data.asset
@@ -0,0 +1,84 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5b11a58205ec3474ca216360e9fa74a8, type: 3}
+ m_Name: PropTexts Shared Data
+ m_EditorClassIdentifier: Unity.Localization::UnityEngine.Localization.Tables.SharedTableData
+ m_TableCollectionName: PropTexts
+ m_TableCollectionNameGuidString: 0c4ffabf8dd85254d84805227319e77b
+ m_Entries:
+ - m_Id: 20063977472
+ m_Key: item_1_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1965512196096
+ m_Key: item_1_desc
+ m_Metadata:
+ m_Items: []
+ - m_Id: 909218676736
+ m_Key: item_2_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 2936522932224
+ m_Key: item_2_desc
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1618366431232
+ m_Key: item_3_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 3004315467776
+ m_Key: item_3_desc
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1685894725632
+ m_Key: item_4_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 3082140778496
+ m_Key: item_4_desc
+ m_Metadata:
+ m_Items: []
+ - m_Id: 6184436760576
+ m_Key: weapon_1_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 6330654392320
+ m_Key: weapon_1_desc
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8018622676992
+ m_Key: weapon_2_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8022145892352
+ m_Key: weapon_2_desc
+ m_Metadata:
+ m_Items: []
+ - m_Id: 759911493926912
+ m_Key: item_5_name
+ m_Metadata:
+ m_Items: []
+ - m_Id: 759920394240000
+ m_Key: item_5_desc
+ m_Metadata:
+ m_Items: []
+ m_Metadata:
+ m_Items: []
+ m_KeyGenerator:
+ rid: 8935594006025863599
+ references:
+ version: 2
+ RefIds:
+ - rid: 8935594006025863599
+ type: {class: DistributedUIDGenerator, ns: UnityEngine.Localization.Tables, asm: Unity.Localization}
+ data:
+ m_CustomEpoch: 1784649567701
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts Shared Data.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/PropTexts Shared Data.asset.meta
new file mode 100644
index 000000000..5a70502ff
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts Shared Data.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0c4ffabf8dd85254d84805227319e77b
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts.asset b/src/Game.Client/Assets/Localization/Tables/String/PropTexts.asset
new file mode 100644
index 000000000..5bfe8d9d4
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts.asset
@@ -0,0 +1,23 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5be51871efa6c3e4eae1703925c8f5ac, type: 3}
+ m_Name: PropTexts
+ m_EditorClassIdentifier: Unity.Localization.Editor::UnityEditor.Localization.StringTableCollection
+ m_SharedTableData: {fileID: 11400000, guid: 0c4ffabf8dd85254d84805227319e77b, type: 2}
+ m_Tables:
+ - {fileID: 11400000, guid: 2f75852d94bfb23428214abec2db295b, type: 2}
+ - {fileID: 11400000, guid: 52cacc5ca28e3e0418264c67aacbab31, type: 2}
+ m_Extensions: []
+ m_Group: String Table
+ references:
+ version: 2
+ RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/PropTexts.asset.meta
new file mode 100644
index 000000000..6f58bb49c
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0f05c7fc842c2d045adba939aee2533f
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts_en.asset b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_en.asset
new file mode 100644
index 000000000..ad640f61b
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_en.asset
@@ -0,0 +1,83 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e9620f8c34305754d8cc9a7e49e852d9, type: 3}
+ m_Name: PropTexts_en
+ m_EditorClassIdentifier: Unity.Localization::UnityEngine.Localization.Tables.StringTable
+ m_LocaleId:
+ m_Code: en
+ m_SharedData: {fileID: 11400000, guid: 0c4ffabf8dd85254d84805227319e77b, type: 2}
+ m_Metadata:
+ m_Items: []
+ m_TableData:
+ - m_Id: 20063977472
+ m_Localized: An Old Key 1
+ m_Metadata:
+ m_Items: []
+ - m_Id: 909218676736
+ m_Localized: An Old Key 2
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1618366431232
+ m_Localized: An Old Key 3
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1685894725632
+ m_Localized: Handgun Ammo
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1965512196096
+ m_Localized: "A well-worn key. I wonder if it\u2019ll work anywhere."
+ m_Metadata:
+ m_Items: []
+ - m_Id: 2936522932224
+ m_Localized: "A well-worn key. I wonder if it\u2019ll work anywhere."
+ m_Metadata:
+ m_Items: []
+ - m_Id: 3004315467776
+ m_Localized: "A well-worn key. I wonder if it\u2019ll work anywhere."
+ m_Metadata:
+ m_Items: []
+ - m_Id: 3082140778496
+ m_Localized: 'Handgun ammunition.
+
+ It has low recoil and tends to have
+ stable ballistics.'
+ m_Metadata:
+ m_Items: []
+ - m_Id: 6184436760576
+ m_Localized: M1911
+ m_Metadata:
+ m_Items: []
+ - m_Id: 6330654392320
+ m_Localized: An Easy-to-Handle Automatic Pistol.
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8018622676992
+ m_Localized: Smoke Grenade
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8022145892352
+ m_Localized: A grenade that releases smoke into the surrounding area when the
+ pin is pulled and it is thrown
+ m_Metadata:
+ m_Items: []
+ - m_Id: 759920394240000
+ m_Localized: Restores a small amount of Health.
+ m_Metadata:
+ m_Items: []
+ - m_Id: 759911493926912
+ m_Localized: Medical First Aid Bottle
+ m_Metadata:
+ m_Items: []
+ references:
+ version: 2
+ RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts_en.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_en.asset.meta
new file mode 100644
index 000000000..a6ab46ed4
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_en.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2f75852d94bfb23428214abec2db295b
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts_ja.asset b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_ja.asset
new file mode 100644
index 000000000..497d0ca83
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_ja.asset
@@ -0,0 +1,79 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: e9620f8c34305754d8cc9a7e49e852d9, type: 3}
+ m_Name: PropTexts_ja
+ m_EditorClassIdentifier: Unity.Localization::UnityEngine.Localization.Tables.StringTable
+ m_LocaleId:
+ m_Code: ja
+ m_SharedData: {fileID: 11400000, guid: 0c4ffabf8dd85254d84805227319e77b, type: 2}
+ m_Metadata:
+ m_Items: []
+ m_TableData:
+ - m_Id: 20063977472
+ m_Localized: "\u53E4\u3073\u305F\u30AB\u30AE1"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 909218676736
+ m_Localized: "\u53E4\u3073\u305F\u30AB\u30AE2"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1618366431232
+ m_Localized: "\u53E4\u3073\u305F\u30AB\u30AE3"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1685894725632
+ m_Localized: "\u30CF\u30F3\u30C9\u30AC\u30F3\u306E\u5F3E\u85AC"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 1965512196096
+ m_Localized: "\u5E74\u5B63\u306E\u5165\u3063\u305F\u30AB\u30AE\u3002\u3069\u3053\u304B\u3067\u4F7F\u3048\u308B\u3060\u308D\u3046\u304B\u3002"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 2936522932224
+ m_Localized: "\u5E74\u5B63\u306E\u5165\u3063\u305F\u30AB\u30AE\u3002\u3069\u3053\u304B\u3067\u4F7F\u3048\u308B\u3060\u308D\u3046\u304B\u3002"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 3004315467776
+ m_Localized: "\u5E74\u5B63\u306E\u5165\u3063\u305F\u30AB\u30AE\u3002\u3069\u3053\u304B\u3067\u4F7F\u3048\u308B\u3060\u308D\u3046\u304B\u3002"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 3082140778496
+ m_Localized: "\u30CF\u30F3\u30C9\u30AC\u30F3\u306E\u5F3E\u85AC\u3002\n\u53CD\u52D5\u304C\u5F31\u304F\u5F3E\u9053\u3082\u5B89\u5B9A\u3057\u3084\u3059\u3044\u3002"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 6184436760576
+ m_Localized: M1911
+ m_Metadata:
+ m_Items: []
+ - m_Id: 6330654392320
+ m_Localized: "\u6271\u3044\u3084\u3059\u3044\u81EA\u52D5\u62F3\u9283"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8018622676992
+ m_Localized: "\u30B9\u30E2\u30FC\u30AF\u30B0\u30EC\u30CD\u30FC\u30C9"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 8022145892352
+ m_Localized: "\u30D4\u30F3\u3092\u629C\u3044\u3066\u6295\u64F2\u3059\u308B\u3068\u5468\u56F2\u306B\u7159\u3092\u307E\u304F\u30B0\u30EC\u30CD\u30FC\u30C9"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 759911493926912
+ m_Localized: "\u56DE\u5FA9\u30DC\u30C8\u30EB"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 759920394240000
+ m_Localized: "\u4F53\u529B\u3092\u5C11\u3057\u56DE\u5FA9\u3067\u304D\u308B"
+ m_Metadata:
+ m_Items: []
+ references:
+ version: 2
+ RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/PropTexts_ja.asset.meta b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_ja.asset.meta
new file mode 100644
index 000000000..5904c24e4
--- /dev/null
+++ b/src/Game.Client/Assets/Localization/Tables/String/PropTexts_ja.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 52cacc5ca28e3e0418264c67aacbab31
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/UITexts Shared Data.asset b/src/Game.Client/Assets/Localization/Tables/String/UITexts Shared Data.asset
index 5c5b74c4f..665eac1ec 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/UITexts Shared Data.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/UITexts Shared Data.asset
@@ -158,7 +158,7 @@ MonoBehaviour:
m_Metadata:
m_Items: []
- m_Id: 2232632818540544
- m_Key: FrameRateLimit
+ m_Key: FrameRate
m_Metadata:
m_Items: []
- m_Id: 2234188787884032
@@ -178,7 +178,7 @@ MonoBehaviour:
m_Metadata:
m_Items: []
- m_Id: 2313625302736896
- m_Key: UncappedFrameRateLimit
+ m_Key: UncappedFrameRate
m_Metadata:
m_Items: []
- m_Id: 5849428867948544
@@ -233,6 +233,14 @@ MonoBehaviour:
m_Key: Maps
m_Metadata:
m_Items: []
+ - m_Id: 17248116852924416
+ m_Key: ShowFrameRate
+ m_Metadata:
+ m_Items: []
+ - m_Id: 17270524204818432
+ m_Key: BasicSettings
+ m_Metadata:
+ m_Items: []
m_Metadata:
m_Items: []
m_KeyGenerator:
diff --git a/src/Game.Client/Assets/Localization/Tables/String/UITexts_en.asset b/src/Game.Client/Assets/Localization/Tables/String/UITexts_en.asset
index e9fc6ae4c..49d78dd43 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/UITexts_en.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/UITexts_en.asset
@@ -159,7 +159,7 @@ MonoBehaviour:
m_Metadata:
m_Items: []
- m_Id: 2232632818540544
- m_Localized: Frame Rate Limit
+ m_Localized: Frame Rate
m_Metadata:
m_Items: []
- m_Id: 2234188787884032
@@ -179,7 +179,7 @@ MonoBehaviour:
m_Metadata:
m_Items: []
- m_Id: 2313625302736896
- m_Localized: Uncapped Frame RateLimit
+ m_Localized: Uncapped Frame Rate
m_Metadata:
m_Items: []
- m_Id: 5849428867948544
@@ -234,6 +234,14 @@ MonoBehaviour:
m_Localized: Maps
m_Metadata:
m_Items: []
+ - m_Id: 17248116852924416
+ m_Localized: Show Frame Rate
+ m_Metadata:
+ m_Items: []
+ - m_Id: 17270524204818432
+ m_Localized: Basic Settings
+ m_Metadata:
+ m_Items: []
references:
version: 2
RefIds: []
diff --git a/src/Game.Client/Assets/Localization/Tables/String/UITexts_ja.asset b/src/Game.Client/Assets/Localization/Tables/String/UITexts_ja.asset
index cecc95c42..dd73519ff 100644
--- a/src/Game.Client/Assets/Localization/Tables/String/UITexts_ja.asset
+++ b/src/Game.Client/Assets/Localization/Tables/String/UITexts_ja.asset
@@ -234,6 +234,14 @@ MonoBehaviour:
m_Localized: "\u30DE\u30C3\u30D7"
m_Metadata:
m_Items: []
+ - m_Id: 17248116852924416
+ m_Localized: "\u30D5\u30EC\u30FC\u30E0\u30EC\u30FC\u30C8\u8868\u793A"
+ m_Metadata:
+ m_Items: []
+ - m_Id: 17270524204818432
+ m_Localized: "\u57FA\u672C\u8A2D\u5B9A"
+ m_Metadata:
+ m_Items: []
references:
version: 2
RefIds: []
diff --git a/src/Game.Client/Assets/Programs/Editor/MenuCommands/WrapAroundNavigationBuilder.cs b/src/Game.Client/Assets/Programs/Editor/MenuCommands/WrapAroundNavigationBuilder.cs
new file mode 100644
index 000000000..679e4001f
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/MenuCommands/WrapAroundNavigationBuilder.cs
@@ -0,0 +1,89 @@
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace Game.Editor.MenuCommands
+{
+ ///
+ /// 選択した GameObject の子階層にある Selectable に対して、 WrapAround Navigationを一括設定する Editor ツール。
+ ///
+ public static class WrapAroundNavigationBuilder
+ {
+ private const string MenuPathRoot = "GameObject/Navigation/WrapAround/";
+
+ [MenuItem(MenuPathRoot + "Build Navigation(Horizontal)", false, 0)]
+ private static void BuildHorizontal(MenuCommand command)
+ {
+ var target = command.context as GameObject;
+ if (target == null) return;
+
+ var selectables = CollectSelectables(target);
+ if (selectables.Length == 0) return;
+
+ Undo.RecordObjects(selectables, "Build Horizontal Navigation");
+
+ for (int i = 0; i < selectables.Length; i++)
+ {
+ Apply(selectables[i], Navigation.Mode.Horizontal);
+ }
+
+ Debug.Log($"[NavigationBuilder] Built WrapAround Left/Right navigation for {selectables.Length} Selectables under '{target.name}'");
+ }
+
+ [MenuItem(MenuPathRoot + "Build Navigation(Vertical)", false, 0)]
+ private static void BuildVertical(MenuCommand command)
+ {
+ var target = command.context as GameObject;
+ if (target == null) return;
+
+ var selectables = CollectSelectables(target);
+ if (selectables.Length == 0) return;
+
+ Undo.RecordObjects(selectables, "Build Vertical Navigation");
+
+ for (int i = 0; i < selectables.Length; i++)
+ {
+ Apply(selectables[i], Navigation.Mode.Vertical);
+ }
+
+ Debug.Log($"[ExplicitNavigationBuilder] Built WrapAround Up/Down navigation for {selectables.Length} Selectables under '{target.name}'");
+ }
+
+ [MenuItem(MenuPathRoot, true)]
+ private static bool Validate()
+ {
+ return Selection.activeGameObject != null;
+ }
+
+ ///
+ /// 対象配下から Explicit 設定対象となる Selectable を収集する。Navigation.Mode.None は除外。
+ /// 見つからなければ警告を出して空配列を返す。
+ ///
+ private static Selectable[] CollectSelectables(GameObject target)
+ {
+ var selectables = target.GetComponentsInChildren(includeInactive: false)
+ .Where(x => x.navigation.mode != Navigation.Mode.None)
+ .ToArray();
+ if (selectables.Length == 0)
+ {
+ Debug.LogWarning($"[ExplicitNavigationBuilder] No Selectable found under '{target.name}'");
+ }
+
+ return selectables;
+ }
+
+ private static void Apply(Selectable selectable, Navigation.Mode mode)
+ {
+ var nav = selectable.navigation;
+ nav.mode = mode;
+ nav.selectOnUp = null;
+ nav.selectOnDown = null;
+ nav.selectOnLeft = null;
+ nav.selectOnRight = null;
+ nav.wrapAround = true;
+ selectable.navigation = nav;
+ EditorUtility.SetDirty(selectable);
+ }
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/MenuCommands/WrapAroundNavigationBuilder.cs.meta b/src/Game.Client/Assets/Programs/Editor/MenuCommands/WrapAroundNavigationBuilder.cs.meta
new file mode 100644
index 000000000..20ec7e677
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/MenuCommands/WrapAroundNavigationBuilder.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 85f3ca703af7466e950291c93e1e10f7
+timeCreated: 1784324856
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceDialogTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceDialogTests.cs
new file mode 100644
index 000000000..07169cd9e
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceDialogTests.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading.Tasks;
+using Cysharp.Threading.Tasks;
+using Game.Core.Services;
+using Game.MVC.Core.Enums;
+using Game.MVC.Core.Scenes;
+using NUnit.Framework;
+using R3;
+
+namespace Game.Editor.Tests
+{
+ ///
+ /// GameSceneService のダイアログライフサイクル保証のテスト。
+ /// TransitionDialogAsync の complete-or-cancel 保証(TerminateCore がサービス側で
+ /// ResultTcs をキャンセルする)と、ダイアログ完了継続が次シーン起動より先に
+ /// 同期実行されることを固定する。
+ /// この保証は InputSystemService の入力ブロック(using スコープの確実な Dispose)の前提。
+ ///
+ [TestFixture]
+ public class GameSceneServiceDialogTests
+ {
+ private GameSceneService _service;
+ private List _gameScenes;
+ private static List s_eventLog;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _service = new GameSceneService();
+ _gameScenes = (List)typeof(GameSceneService)
+ .GetField("_gameScenes", BindingFlags.NonPublic | BindingFlags.Instance)
+ .GetValue(_service);
+ s_eventLog = new List();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _gameScenes?.Clear();
+ _service = null;
+ s_eventLog = null;
+ }
+
+ [Test]
+ public async Task TransitionDialog_NormalClose_ReturnsResult()
+ {
+ var dialogTask = _service.TransitionDialogAsync().Preserve();
+ var dialog = (FakeDialogScene)_gameScenes[^1];
+
+ Assert.IsFalse(dialogTask.Status.IsCompleted(), "リザルト確定前は未完了のはず");
+
+ dialog.TrySetResult(true);
+
+ var result = await dialogTask;
+ Assert.IsTrue(result);
+ Assert.IsFalse(_gameScenes.Contains(dialog), "クローズ後は履歴から除去されるはず");
+ }
+
+ [Test]
+ public async Task TransitionDialog_ExternalTransition_CompletesWithDefault()
+ {
+ // シーン側に cancel 実装が無くても、TerminateCore がサービス側でキャンセルし
+ // ダイアログ Task が放棄されない(= 呼び出し元 using が必ず Dispose される)ことの検証
+ var dialogTask = _service.TransitionDialogAsync().Preserve();
+ var dialog = (FakeDialogScene)_gameScenes[^1];
+
+ await _service.TransitionAsync();
+
+ Assert.IsTrue(dialogTask.Status.IsCompleted(), "外部遷移後にダイアログ Task が放棄されず完了するはず");
+ var result = await dialogTask;
+ Assert.IsFalse(result, "キャンセル時は default が返るはず");
+ Assert.IsTrue(dialog.TerminateCalled, "外部遷移時に Terminate が呼ばれるはず");
+ Assert.IsFalse(_gameScenes.Contains(dialog), "外部遷移後は履歴から除去されるはず");
+ }
+
+ [Test]
+ public async Task TransitionDialog_ContinuationRunsBeforeNextSceneStartup()
+ {
+ // ダイアログ完了継続(実利用では using スコープの Dispose)が
+ // 次シーンの Startup より前に同期実行されることを固定する
+ var dialogTask = _service.TransitionDialogAsync().Preserve();
+ var tracked = dialogTask.ContinueWith(_ => s_eventLog.Add("dialog:continuation"));
+
+ await _service.TransitionAsync();
+ await tracked;
+
+ int continuationIndex = s_eventLog.IndexOf("dialog:continuation");
+ int startupIndex = s_eventLog.IndexOf("next:startup");
+ Assert.GreaterOrEqual(continuationIndex, 0, "ダイアログ継続が実行されているはず");
+ Assert.GreaterOrEqual(startupIndex, 0, "次シーンの Startup が実行されているはず");
+ Assert.Less(continuationIndex, startupIndex, "ダイアログ継続は次シーン Startup より先に実行されるはず");
+ }
+
+ [Test]
+ public async Task TerminateLastAsync_CancelsResultTcs()
+ {
+ var scene = new FakeDialogScene
+ {
+ State = GameSceneState.Processing,
+ ResultTcs = new UniTaskCompletionSource(),
+ };
+ _gameScenes.Add(scene);
+
+ await _service.TerminateLastAsync(clearHistory: true);
+
+ Assert.IsTrue(scene.TerminateCalled);
+ Assert.AreEqual(UniTaskStatus.Canceled, scene.ResultTcs.Task.Status, "TerminateCore がサービス側で ResultTcs をキャンセルするはず");
+ }
+
+ #region Fake Classes
+
+ ///
+ /// 自前の cancel 処理を持たないリザルトシーン。
+ /// complete-or-cancel 保証がシーン実装ではなくサービス側にあることを検証するため、
+ /// 意図的に GameDialogScene を継承しない。
+ ///
+ private class FakeDialogScene : IGameScene, IGameSceneResult
+ {
+ public GameSceneState State { get; set; }
+ public Func ArgHandler { get; set; }
+ public CompositeDisposable Disposables { get; } = new();
+ public bool Result { get; set; }
+ public UniTaskCompletionSource ResultTcs { get; set; }
+
+ public bool TerminateCalled { get; private set; }
+
+ public UniTask Terminate()
+ {
+ TerminateCalled = true;
+ return UniTask.CompletedTask;
+ }
+
+ public bool TrySetResult(bool result)
+ {
+ Result = result;
+ return ResultTcs.TrySetResult(result);
+ }
+
+ public bool TrySetCanceled() => ResultTcs.TrySetCanceled();
+
+ public bool TrySetException(Exception e) => ResultTcs.TrySetException(e);
+ }
+
+ /// 外部遷移先。Startup の実行を共有ログへ記録する。
+ private class RecordingScene : IGameScene
+ {
+ public GameSceneState State { get; set; }
+ public Func ArgHandler { get; set; }
+ public CompositeDisposable Disposables { get; } = new();
+
+ public UniTask Startup()
+ {
+ s_eventLog.Add("next:startup");
+ return UniTask.CompletedTask;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceDialogTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceDialogTests.cs.meta
new file mode 100644
index 000000000..85127770d
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceDialogTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: dcb839977415be54dbf6b993418e7abb
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceTests.cs
index 0158cadc4..2389ec0f4 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameSceneServiceTests.cs
@@ -1095,6 +1095,16 @@ private class MockGameSceneWithResult : IGameScene, IGameSceneResult
public UniTask Sleep() => UniTask.CompletedTask;
public UniTask Restart() => UniTask.CompletedTask;
public UniTask Terminate() => UniTask.CompletedTask;
+
+ public bool TrySetResult(int result)
+ {
+ Result = result;
+ return ResultTcs.TrySetResult(result);
+ }
+
+ public bool TrySetCanceled() => ResultTcs.TrySetCanceled();
+
+ public bool TrySetException(Exception e) => ResultTcs.TrySetException(e);
}
private class MockGameSceneWithArg : IGameScene, IGameSceneArg
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameServiceManagerTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameServiceManagerTests.cs
index 82c1dd4b1..2974f6107 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameServiceManagerTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/GameServiceManagerTests.cs
@@ -1,5 +1,6 @@
using System;
using Game.Core.Services;
+using Game.Shared.Services.Interfaces;
using NUnit.Framework;
namespace Game.Tests.MVC
@@ -9,7 +10,7 @@ public class GameServiceManagerTests
{
#region Test Service Classes
- private interface ITestService
+ private interface ITestService : IGameService
{
public bool IsStarted { get;}
public bool IsShutdown { get;}
@@ -17,7 +18,7 @@ private interface ITestService
public int ShutdownCallCount { get; }
}
- private class TestService : ITestService, IGameService
+ private class TestService : ITestService
{
public bool IsStarted { get; private set; }
public bool IsShutdown { get; private set; }
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Game.Tests.MVC.Horror.asmdef b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Game.Tests.MVC.Horror.asmdef
index 07c83b783..d3d1b9163 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Game.Tests.MVC.Horror.asmdef
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Game.Tests.MVC.Horror.asmdef
@@ -9,7 +9,10 @@
"UniTask",
"R3.Unity",
"MessagePipe",
- "MemoryPack.Unity"
+ "MemoryPack.Unity",
+ "Unity.InputSystem",
+ "Unity.TextMeshPro",
+ "Unity.Localization"
],
"includePlatforms": [
"Editor"
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorAmmoViewTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorAmmoViewTests.cs
index 37afc5d23..539ed916c 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorAmmoViewTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorAmmoViewTests.cs
@@ -11,33 +11,33 @@ public class HorrorAmmoViewTests
[Test]
public void ResolveDisplayMode_NoWeapon_IsNone()
- => Assert.That(HorrorAmmoView.ResolveDisplayMode(false, 4), Is.EqualTo(HorrorAmmoView.DisplayMode.None));
+ => Assert.That(HorrorAmmoView.ResolveViewMode(false, 4), Is.EqualTo(HorrorAmmoViewMode.Hidden));
[Test]
public void ResolveDisplayMode_WeaponWithAmmoItem_IsMagazineAndReserve()
- => Assert.That(HorrorAmmoView.ResolveDisplayMode(true, 4), Is.EqualTo(HorrorAmmoView.DisplayMode.MagazineAndReserve));
+ => Assert.That(HorrorAmmoView.ResolveViewMode(true, 4), Is.EqualTo(HorrorAmmoViewMode.MagazineAndReserve));
[Test]
public void ResolveDisplayMode_WeaponWithoutAmmoItem_IsCountOnly()
- => Assert.That(HorrorAmmoView.ResolveDisplayMode(true, 0), Is.EqualTo(HorrorAmmoView.DisplayMode.CountOnly));
+ => Assert.That(HorrorAmmoView.ResolveViewMode(true, 0), Is.EqualTo(HorrorAmmoViewMode.CountOnly));
// 目標アルファ:None は常に 0。表示維持中または保持時間内は 1、保持時間超過で 0。
[Test]
public void CalculateTargetAlpha_None_IsZeroEvenIfKeepVisible()
- => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoView.DisplayMode.None, true, 0f, 2f), Is.EqualTo(0f));
+ => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoViewMode.Hidden, true, 0f, 2f), Is.EqualTo(0f));
[Test]
public void CalculateTargetAlpha_KeepVisible_IsOne()
- => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoView.DisplayMode.CountOnly, true, 999f, 2f), Is.EqualTo(1f));
+ => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoViewMode.CountOnly, true, 999f, 2f), Is.EqualTo(1f));
[Test]
public void CalculateTargetAlpha_WithinHoldDuration_IsOne()
- => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoView.DisplayMode.CountOnly, false, 1f, 2f), Is.EqualTo(1f));
+ => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoViewMode.CountOnly, false, 1f, 2f), Is.EqualTo(1f));
[Test]
public void CalculateTargetAlpha_PastHoldDuration_IsZero()
- => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoView.DisplayMode.CountOnly, false, 2f, 2f), Is.EqualTo(0f));
+ => Assert.That(HorrorAmmoView.CalculateTargetAlpha(HorrorAmmoViewMode.CountOnly, false, 2f, 2f), Is.EqualTo(0f));
// 弾倉側文字色:MagazineAndReserve かつ満タンのみ強調色。それ以外(CountOnly を含む)は通常色。
@@ -47,19 +47,19 @@ public void CalculateTargetAlpha_PastHoldDuration_IsZero()
[Test]
public void CalculateMagazineColor_FullMagazine_IsFull()
- => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoView.DisplayMode.MagazineAndReserve, 10, 10, Full, Normal), Is.EqualTo(Full));
+ => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoViewMode.MagazineAndReserve, 10, 10, Full, Normal), Is.EqualTo(Full));
[Test]
public void CalculateMagazineColor_NotFullMagazine_IsNormal()
- => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoView.DisplayMode.MagazineAndReserve, 5, 10, Full, Normal), Is.EqualTo(Normal));
+ => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoViewMode.MagazineAndReserve, 5, 10, Full, Normal), Is.EqualTo(Normal));
[Test]
public void CalculateMagazineColor_ZeroMagazineSize_IsNormal()
- => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoView.DisplayMode.MagazineAndReserve, 0, 0, Full, Normal), Is.EqualTo(Normal));
+ => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoViewMode.MagazineAndReserve, 0, 0, Full, Normal), Is.EqualTo(Normal));
[Test]
public void CalculateMagazineColor_CountOnly_IsAlwaysNormal()
- => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoView.DisplayMode.CountOnly, 10, 10, Full, Normal), Is.EqualTo(Normal));
+ => Assert.That(HorrorAmmoView.CalculateMagazineColor(HorrorAmmoViewMode.CountOnly, 10, 10, Full, Normal), Is.EqualTo(Normal));
// 予備側文字色:0 以下は警告色、正数は通常色。
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorDamageViewTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorDamageViewTests.cs
new file mode 100644
index 000000000..4a2458497
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorDamageViewTests.cs
@@ -0,0 +1,43 @@
+using Game.Horror.Player;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace Game.Tests.MVC.Horror
+{
+ [TestFixture]
+ public class HorrorDamageViewTests
+ {
+ // スクリーン位置算出:WorldToScreenPoint の結果と上昇オフセットから表示位置を導く。
+ // screenPoint.z < 0(カメラ背後)のみ非表示(false)とする。
+
+ [Test]
+ public void TryCalculateScreenPosition_BehindCamera_ReturnsFalse()
+ => Assert.That(HorrorDamageView.TryCalculateScreenPosition(new Vector3(100f, 200f, -0.1f), 0f, out _), Is.False);
+
+ [Test]
+ public void TryCalculateScreenPosition_AtZeroDepth_ReturnsTrue()
+ => Assert.That(HorrorDamageView.TryCalculateScreenPosition(new Vector3(100f, 200f, 0f), 0f, out _), Is.True);
+
+ [Test]
+ public void TryCalculateScreenPosition_WithRiseOffset_AddsToY()
+ {
+ HorrorDamageView.TryCalculateScreenPosition(new Vector3(100f, 200f, 10f), 60f, out var position);
+ Assert.That(position.y, Is.EqualTo(260f));
+ }
+
+ [Test]
+ public void TryCalculateScreenPosition_ZeroRiseOffset_KeepsXAndY()
+ {
+ HorrorDamageView.TryCalculateScreenPosition(new Vector3(100f, 200f, 10f), 0f, out var position);
+ Assert.That(position.x, Is.EqualTo(100f));
+ Assert.That(position.y, Is.EqualTo(200f));
+ }
+
+ [Test]
+ public void TryCalculateScreenPosition_AlwaysReturnsZeroDepth()
+ {
+ HorrorDamageView.TryCalculateScreenPosition(new Vector3(100f, 200f, 10f), 60f, out var position);
+ Assert.That(position.z, Is.EqualTo(0f));
+ }
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorDamageViewTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorDamageViewTests.cs.meta
new file mode 100644
index 000000000..814e9b7a9
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorDamageViewTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: df7b63cfd4f85e0438f09a0880526a2c
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyControllerTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyControllerTests.cs
new file mode 100644
index 000000000..261fc8f92
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyControllerTests.cs
@@ -0,0 +1,63 @@
+using Game.Horror.Enemy;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace Game.Tests.MVC.Horror
+{
+ [TestFixture]
+ public class HorrorEnemyControllerTests
+ {
+ // アニメーター Speed 平滑化:指数補間(1-exp(-response・dt))で target へ追従する。
+ // 目標一致・dt=0 は不動、途中値はオーバーシュートなし、反復適用で収束し、dt に依らず結果が一致する。
+
+ [Test]
+ public void CalculateSmoothedAnimSpeed_TargetReached_ReturnsTarget()
+ => Assert.That(HorrorEnemyController.CalculateSmoothedAnimSpeed(0.5f, 0.5f, 8f, 1f / 60f), Is.EqualTo(0.5f).Within(1e-4f));
+
+ [Test]
+ public void CalculateSmoothedAnimSpeed_ZeroDeltaTime_ReturnsCurrent()
+ => Assert.That(HorrorEnemyController.CalculateSmoothedAnimSpeed(0.3f, 0.8f, 8f, 0f), Is.EqualTo(0.3f).Within(1e-4f));
+
+ [Test]
+ public void CalculateSmoothedAnimSpeed_RisingStep_StaysWithinRange()
+ {
+ var result = HorrorEnemyController.CalculateSmoothedAnimSpeed(0f, 0.5f, 8f, 1f / 60f);
+ Assert.That(result, Is.InRange(0f, 0.5f));
+ }
+
+ [Test]
+ public void CalculateSmoothedAnimSpeed_RepeatedApplication_ConvergesToTarget()
+ {
+ var current = 0f;
+ const float target = 0.5f;
+ for (var i = 0; i < 60; i++)
+ current = HorrorEnemyController.CalculateSmoothedAnimSpeed(current, target, 8f, 1f / 60f);
+
+ Assert.That(target - current, Is.LessThan(0.01f));
+ }
+
+ [Test]
+ public void CalculateSmoothedAnimSpeed_FrameRateIndependent_MatchesAcrossStepSizes()
+ {
+ var oneStep = HorrorEnemyController.CalculateSmoothedAnimSpeed(0f, 1f, 8f, 0.2f);
+ var twoStep = HorrorEnemyController.CalculateSmoothedAnimSpeed(0f, 1f, 8f, 0.1f);
+ twoStep = HorrorEnemyController.CalculateSmoothedAnimSpeed(twoStep, 1f, 8f, 0.1f);
+
+ Assert.That(twoStep, Is.EqualTo(oneStep).Within(1e-4f));
+ }
+
+ [Test]
+ public void CalculateSmoothedAnimSpeed_Decaying_StaysWithinRangeAndConverges()
+ {
+ var result = HorrorEnemyController.CalculateSmoothedAnimSpeed(0.5f, 0f, 8f, 1f / 60f);
+ Assert.That(result, Is.InRange(0f, 0.5f));
+
+ var current = 0.5f;
+ const float target = 0f;
+ for (var i = 0; i < 60; i++)
+ current = HorrorEnemyController.CalculateSmoothedAnimSpeed(current, target, 8f, 1f / 60f);
+
+ Assert.That(current - target, Is.LessThan(0.01f));
+ }
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyControllerTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyControllerTests.cs.meta
new file mode 100644
index 000000000..eb1b32cde
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyControllerTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: bb0edc9114d79914ba6b14528affe2b9
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyPerceptionTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyPerceptionTests.cs
new file mode 100644
index 000000000..e98092405
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyPerceptionTests.cs
@@ -0,0 +1,115 @@
+using Game.Horror.Enemy;
+using Game.Horror.Signals;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace Game.Tests.MVC.Horror
+{
+ [TestFixture]
+ public class HorrorEnemyPerceptionTests
+ {
+ // 視野錐判定:Dot と cos 閾値の比較(>=)。正面・視野縁の等号境界・視野外・真後ろを検証する。
+ // 境界値は独立オラクルとして Mathf.Cos/Sin(angle * Deg2Rad) から toTarget を構築し、
+ // Dot(forward, toTarget) が cosHalfAngle とビット一致するようにして境界の >= を確実に検証する。
+
+ [Test]
+ public void IsInSightCone_DirectlyAhead_ReturnsTrue()
+ {
+ var forward = Vector3.forward;
+ var cosHalfAngle = Mathf.Cos(60f * Mathf.Deg2Rad);
+ Assert.That(HorrorEnemyPerception.IsInSightCone(forward, forward, cosHalfAngle), Is.True);
+ }
+
+ [Test]
+ public void IsInSightCone_AtConeEdgeBoundary_ReturnsTrue()
+ {
+ const float halfAngle = 45f;
+ var cosHalfAngle = Mathf.Cos(halfAngle * Mathf.Deg2Rad);
+ var sinHalfAngle = Mathf.Sin(halfAngle * Mathf.Deg2Rad);
+ var forward = Vector3.forward;
+
+ // forward=(0,0,1) との Dot が cosHalfAngle とビット一致するよう構築した境界方向
+ var toTargetAtEdge = new Vector3(sinHalfAngle, 0f, cosHalfAngle);
+
+ Assert.That(HorrorEnemyPerception.IsInSightCone(forward, toTargetAtEdge, cosHalfAngle), Is.True);
+ }
+
+ [Test]
+ public void IsInSightCone_OutsideCone_ReturnsFalse()
+ {
+ var forward = Vector3.forward;
+ var toTargetSide = Vector3.right; // forward との Dot = 0(真横)
+ var cosHalfAngle = Mathf.Cos(45f * Mathf.Deg2Rad); // 約 0.707 > 0
+ Assert.That(HorrorEnemyPerception.IsInSightCone(forward, toTargetSide, cosHalfAngle), Is.False);
+ }
+
+ [Test]
+ public void IsInSightCone_DirectlyBehind_ReturnsFalse()
+ {
+ var forward = Vector3.forward;
+ var toTargetBehind = Vector3.back; // Dot = -1
+ var cosHalfAngle = Mathf.Cos(60f * Mathf.Deg2Rad);
+ Assert.That(HorrorEnemyPerception.IsInSightCone(forward, toTargetBehind, cosHalfAngle), Is.False);
+ }
+
+ // 警戒度ゲージ更新:視認中は近いほど速く充填(distance01=0 で 2x、=1 で 1x)、非視認は減衰、
+ // 0〜1 にクランプ、dt=0 では不動。
+
+ [Test]
+ public void UpdateAwareness_HasSightAtClosestDistance_FillsAtDoubleRate()
+ {
+ var result = HorrorEnemyPerception.UpdateAwareness(0f, true, 0f, 0.1f, 0.05f, 1f);
+ Assert.That(result, Is.EqualTo(0.2f).Within(1e-4f));
+ }
+
+ [Test]
+ public void UpdateAwareness_HasSightAtFarthestDistance_FillsAtBaseRate()
+ {
+ var result = HorrorEnemyPerception.UpdateAwareness(0f, true, 1f, 0.1f, 0.05f, 1f);
+ Assert.That(result, Is.EqualTo(0.1f).Within(1e-4f));
+ }
+
+ [Test]
+ public void UpdateAwareness_NoSight_Decays()
+ {
+ var result = HorrorEnemyPerception.UpdateAwareness(0.5f, false, 0f, 0.1f, 0.2f, 1f);
+ Assert.That(result, Is.EqualTo(0.3f).Within(1e-4f));
+ }
+
+ [Test]
+ public void UpdateAwareness_ClampsToUnitRange()
+ {
+ var upperClamped = HorrorEnemyPerception.UpdateAwareness(0.95f, true, 0f, 1f, 0f, 1f);
+ Assert.That(upperClamped, Is.EqualTo(1f));
+
+ var lowerClamped = HorrorEnemyPerception.UpdateAwareness(0.05f, false, 0f, 0f, 1f, 1f);
+ Assert.That(lowerClamped, Is.EqualTo(0f));
+ }
+
+ [Test]
+ public void UpdateAwareness_ZeroDeltaTime_IsUnchanged()
+ {
+ var result = HorrorEnemyPerception.UpdateAwareness(0.5f, true, 0f, 0.1f, 0.05f, 0f);
+ Assert.That(result, Is.EqualTo(0.5f).Within(1e-4f));
+ }
+
+ // 音種分類:Footstep/Gunshot はプレイヤー実位置に相関(プレイヤー知覚位置を更新)、
+ // Object(着弾=デコイ可能)/Scream(敵自身の発声)は注意対象位置のみ。
+
+ [Test]
+ public void IsPlayerLocatedNoise_Footstep_IsTrue()
+ => Assert.That(HorrorEnemyPerception.IsPlayerLocatedNoise(NoiseType.Footstep), Is.True);
+
+ [Test]
+ public void IsPlayerLocatedNoise_Gunshot_IsTrue()
+ => Assert.That(HorrorEnemyPerception.IsPlayerLocatedNoise(NoiseType.Gunshot), Is.True);
+
+ [Test]
+ public void IsPlayerLocatedNoise_Object_IsFalse()
+ => Assert.That(HorrorEnemyPerception.IsPlayerLocatedNoise(NoiseType.Object), Is.False);
+
+ [Test]
+ public void IsPlayerLocatedNoise_Scream_IsFalse()
+ => Assert.That(HorrorEnemyPerception.IsPlayerLocatedNoise(NoiseType.Scream), Is.False);
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyPerceptionTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyPerceptionTests.cs.meta
new file mode 100644
index 000000000..6a9c3a863
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEnemyPerceptionTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d7810f2946ad0b84fb1ac6397ae9eb9a
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEquipmentServiceTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEquipmentServiceTests.cs
index f1131cb2c..f31168e27 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEquipmentServiceTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorEquipmentServiceTests.cs
@@ -41,11 +41,11 @@ private async Task LoadDefaultData()
await _repository.LoadAsync();
}
- private int CountOf(InventorySlotType type, int id)
+ private int CountOf(ObjectCategory type, int id)
{
int count = 0;
foreach (var s in _repository.Data.Equipment.Slots)
- if (s.SlotType == type && s.Id == id)
+ if (s.ObjectCategory == type && s.Id == id)
count++;
return count;
}
@@ -54,13 +54,13 @@ private int CountOf(InventorySlotType type, int id)
public async Task TryEquip_WeaponPossessed_SucceedsAndMarksDirty()
{
await LoadDefaultData();
- _mockInventory.HasItem(InventorySlotType.Weapon, 5).Returns(true);
+ _mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(true);
- var ok = _service.TryEquip(InventorySlotType.Weapon, 5);
+ var ok = _service.TryEquip(ObjectCategory.Weapon, 5);
Assert.That(ok, Is.True);
Assert.That(_service.TryGetEquipped(out var type, out var id), Is.True);
- Assert.That(type, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(type, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(id, Is.EqualTo(5));
Assert.That(_repository.IsDirty, Is.True);
}
@@ -69,9 +69,9 @@ public async Task TryEquip_WeaponPossessed_SucceedsAndMarksDirty()
public async Task TryEquip_Item_ReturnsFalse()
{
await LoadDefaultData();
- _mockInventory.HasItem(InventorySlotType.Item, 3).Returns(true);
+ _mockInventory.HasObject(ObjectCategory.Item, 3).Returns(true);
- var ok = _service.TryEquip(InventorySlotType.Item, 3);
+ var ok = _service.TryEquip(ObjectCategory.Item, 3);
Assert.That(ok, Is.False);
Assert.That(_service.TryGetEquipped(out _, out _), Is.False);
@@ -82,9 +82,9 @@ public async Task TryEquip_Item_ReturnsFalse()
public async Task TryEquip_NotPossessed_ReturnsFalse()
{
await LoadDefaultData();
- _mockInventory.HasItem(InventorySlotType.Weapon, 5).Returns(false);
+ _mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(false);
- var ok = _service.TryEquip(InventorySlotType.Weapon, 5);
+ var ok = _service.TryEquip(ObjectCategory.Weapon, 5);
Assert.That(ok, Is.False);
Assert.That(_service.TryGetEquipped(out _, out _), Is.False);
@@ -95,14 +95,14 @@ public async Task TryEquip_NotPossessed_ReturnsFalse()
public async Task TryEquip_SameWeaponAgain_ReturnsTrueIdempotently()
{
await LoadDefaultData();
- _mockInventory.HasItem(InventorySlotType.Weapon, 5).Returns(true);
- _service.TryEquip(InventorySlotType.Weapon, 5);
+ _mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(true);
+ _service.TryEquip(ObjectCategory.Weapon, 5);
- var ok = _service.TryEquip(InventorySlotType.Weapon, 5);
+ var ok = _service.TryEquip(ObjectCategory.Weapon, 5);
Assert.That(ok, Is.True);
Assert.That(_service.TryGetEquipped(out var type, out var id), Is.True);
- Assert.That(type, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(type, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(id, Is.EqualTo(5));
}
@@ -112,7 +112,7 @@ public async Task TryGetEquipped_Unequipped_ReturnsFalse()
await LoadDefaultData();
Assert.That(_service.TryGetEquipped(out var type, out var id), Is.False);
- Assert.That(type, Is.EqualTo(InventorySlotType.None));
+ Assert.That(type, Is.EqualTo(ObjectCategory.None));
Assert.That(id, Is.EqualTo(0));
}
@@ -122,7 +122,7 @@ public void Mutators_WhenDataNull_DoNotThrowAndReturnFalse()
// LoadAsync 未実行 = Data は null
Assert.DoesNotThrow(() =>
{
- Assert.That(_service.TryEquip(InventorySlotType.Weapon, 1), Is.False);
+ Assert.That(_service.TryEquip(ObjectCategory.Weapon, 1), Is.False);
Assert.That(_service.TryGetEquipped(out _, out _), Is.False);
});
}
@@ -132,11 +132,11 @@ public async Task Set_RegistersAndMarksDirty()
{
await LoadDefaultData();
- var ok = _service.TrySetSlot(1, InventorySlotType.Weapon, 5);
+ var ok = _service.TrySetSlot(1, ObjectCategory.Weapon, 5);
Assert.That(ok, Is.True);
Assert.That(_service.TryGetSlot(1, out var slot), Is.True);
- Assert.That(slot.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(slot.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(slot.Id, Is.EqualTo(5));
Assert.That(_repository.IsDirty, Is.True);
}
@@ -145,7 +145,7 @@ public async Task Set_RegistersAndMarksDirty()
public async Task Clear_RemovesRegistrationAndMarksDirty()
{
await LoadDefaultData();
- _service.TrySetSlot(2, InventorySlotType.Item, 3);
+ _service.TrySetSlot(2, ObjectCategory.Item, 3);
var ok = _service.ClearSlot(2);
@@ -159,21 +159,21 @@ public async Task Set_OutOfRange_ReturnsFalse()
{
await LoadDefaultData();
- Assert.That(_service.TrySetSlot(-1, InventorySlotType.Item, 1), Is.False);
- Assert.That(_service.TrySetSlot(HorrorEquipmentConstants.MaxEquipmentSlotCount, InventorySlotType.Item, 1), Is.False);
+ Assert.That(_service.TrySetSlot(-1, ObjectCategory.Item, 1), Is.False);
+ Assert.That(_service.TrySetSlot(HorrorEquipmentConstants.MaxEquipmentSlotCount, ObjectCategory.Item, 1), Is.False);
}
[Test]
public async Task Assign_RegisteredElsewhere_ToEmptySlot_Moves()
{
await LoadDefaultData();
- _service.TrySetSlot(0, InventorySlotType.Weapon, 5);
+ _service.TrySetSlot(0, ObjectCategory.Weapon, 5);
- var ok = _service.TryAssignSlot(1, InventorySlotType.Weapon, 5);
+ var ok = _service.TryAssignSlot(1, ObjectCategory.Weapon, 5);
Assert.That(ok, Is.True);
Assert.That(_service.TryGetSlot(1, out var dest), Is.True);
- Assert.That(dest.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(dest.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(dest.Id, Is.EqualTo(5));
Assert.That(_service.TryGetSlot(0, out _), Is.False, "旧スロットは空になる(移動)");
}
@@ -182,17 +182,17 @@ public async Task Assign_RegisteredElsewhere_ToEmptySlot_Moves()
public async Task Assign_RegisteredElsewhere_ToOccupiedSlot_Swaps()
{
await LoadDefaultData();
- _service.TrySetSlot(0, InventorySlotType.Weapon, 5);
- _service.TrySetSlot(1, InventorySlotType.Item, 3);
+ _service.TrySetSlot(0, ObjectCategory.Weapon, 5);
+ _service.TrySetSlot(1, ObjectCategory.Item, 3);
- var ok = _service.TryAssignSlot(1, InventorySlotType.Weapon, 5);
+ var ok = _service.TryAssignSlot(1, ObjectCategory.Weapon, 5);
Assert.That(ok, Is.True);
Assert.That(_service.TryGetSlot(1, out var dest), Is.True);
- Assert.That(dest.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(dest.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(dest.Id, Is.EqualTo(5));
Assert.That(_service.TryGetSlot(0, out var src), Is.True, "元 dest の item が旧スロットへ入替");
- Assert.That(src.SlotType, Is.EqualTo(InventorySlotType.Item));
+ Assert.That(src.ObjectCategory, Is.EqualTo(ObjectCategory.Item));
Assert.That(src.Id, Is.EqualTo(3));
}
@@ -200,28 +200,28 @@ public async Task Assign_RegisteredElsewhere_ToOccupiedSlot_Swaps()
public async Task Assign_Unregistered_ToOccupiedSlot_Overwrites()
{
await LoadDefaultData();
- _service.TrySetSlot(1, InventorySlotType.Item, 3);
+ _service.TrySetSlot(1, ObjectCategory.Item, 3);
- var ok = _service.TryAssignSlot(1, InventorySlotType.Weapon, 5);
+ var ok = _service.TryAssignSlot(1, ObjectCategory.Weapon, 5);
Assert.That(ok, Is.True);
Assert.That(_service.TryGetSlot(1, out var dest), Is.True);
- Assert.That(dest.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(dest.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(dest.Id, Is.EqualTo(5));
- Assert.That(CountOf(InventorySlotType.Item, 3), Is.EqualTo(0), "元の item は上書きで消える");
+ Assert.That(CountOf(ObjectCategory.Item, 3), Is.EqualTo(0), "元の item は上書きで消える");
}
[Test]
public async Task Assign_ToSameSlot_ReturnsFalseAndNoChange()
{
await LoadDefaultData();
- _service.TrySetSlot(2, InventorySlotType.Weapon, 5);
+ _service.TrySetSlot(2, ObjectCategory.Weapon, 5);
- var ok = _service.TryAssignSlot(2, InventorySlotType.Weapon, 5);
+ var ok = _service.TryAssignSlot(2, ObjectCategory.Weapon, 5);
Assert.That(ok, Is.False);
Assert.That(_service.TryGetSlot(2, out var slot), Is.True);
- Assert.That(slot.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(slot.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(slot.Id, Is.EqualTo(5));
}
@@ -229,11 +229,11 @@ public async Task Assign_ToSameSlot_ReturnsFalseAndNoChange()
public async Task Assign_KeepsSingleRegistration()
{
await LoadDefaultData();
- _service.TrySetSlot(0, InventorySlotType.Weapon, 5);
+ _service.TrySetSlot(0, ObjectCategory.Weapon, 5);
- _service.TryAssignSlot(2, InventorySlotType.Weapon, 5);
+ _service.TryAssignSlot(2, ObjectCategory.Weapon, 5);
- Assert.That(CountOf(InventorySlotType.Weapon, 5), Is.EqualTo(1), "同一装備は常に1スロットのみ");
+ Assert.That(CountOf(ObjectCategory.Weapon, 5), Is.EqualTo(1), "同一装備は常に1スロットのみ");
Assert.That(_service.TryGetSlot(2, out _), Is.True);
Assert.That(_service.TryGetSlot(0, out _), Is.False);
}
@@ -244,7 +244,7 @@ public void SlotMutators_WhenDataNull_DoNotThrowAndReturnFalse()
// LoadAsync 未実行 = Data は null
Assert.DoesNotThrow(() =>
{
- Assert.That(_service.TrySetSlot(0, InventorySlotType.Item, 1), Is.False);
+ Assert.That(_service.TrySetSlot(0, ObjectCategory.Item, 1), Is.False);
Assert.That(_service.ClearSlot(0), Is.False);
Assert.That(_service.TryGetSlot(0, out _), Is.False);
});
@@ -254,13 +254,13 @@ public void SlotMutators_WhenDataNull_DoNotThrowAndReturnFalse()
public async Task TryEquip_DoesNotAffectSlots()
{
await LoadDefaultData();
- _service.TrySetSlot(0, InventorySlotType.Weapon, 5);
- _mockInventory.HasItem(InventorySlotType.Weapon, 7).Returns(true);
+ _service.TrySetSlot(0, ObjectCategory.Weapon, 5);
+ _mockInventory.HasObject(ObjectCategory.Weapon, 7).Returns(true);
- _service.TryEquip(InventorySlotType.Weapon, 7);
+ _service.TryEquip(ObjectCategory.Weapon, 7);
Assert.That(_service.TryGetSlot(0, out var slot), Is.True);
- Assert.That(slot.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(slot.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(slot.Id, Is.EqualTo(5));
}
@@ -268,13 +268,13 @@ public async Task TryEquip_DoesNotAffectSlots()
public async Task Assign_DoesNotAffectEquipped()
{
await LoadDefaultData();
- _mockInventory.HasItem(InventorySlotType.Weapon, 5).Returns(true);
- _service.TryEquip(InventorySlotType.Weapon, 5);
+ _mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(true);
+ _service.TryEquip(ObjectCategory.Weapon, 5);
- _service.TryAssignSlot(0, InventorySlotType.Item, 3);
+ _service.TryAssignSlot(0, ObjectCategory.Item, 3);
Assert.That(_service.TryGetEquipped(out var type, out var id), Is.True);
- Assert.That(type, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(type, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(id, Is.EqualTo(5));
}
@@ -282,15 +282,15 @@ public async Task Assign_DoesNotAffectEquipped()
public async Task Clear_DoesNotAffectEquipped()
{
await LoadDefaultData();
- _mockInventory.HasItem(InventorySlotType.Weapon, 5).Returns(true);
- _service.TrySetSlot(0, InventorySlotType.Weapon, 5);
- _service.TryEquip(InventorySlotType.Weapon, 5);
+ _mockInventory.HasObject(ObjectCategory.Weapon, 5).Returns(true);
+ _service.TrySetSlot(0, ObjectCategory.Weapon, 5);
+ _service.TryEquip(ObjectCategory.Weapon, 5);
_service.ClearSlot(0);
Assert.That(_service.TryGetSlot(0, out _), Is.False);
Assert.That(_service.TryGetEquipped(out var type, out var id), Is.True);
- Assert.That(type, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(type, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(id, Is.EqualTo(5));
}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorHealthViewTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorHealthViewTests.cs
new file mode 100644
index 000000000..dfa33c165
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorHealthViewTests.cs
@@ -0,0 +1,49 @@
+using Game.Horror.Player;
+using NUnit.Framework;
+
+namespace Game.Tests.MVC.Horror
+{
+ [TestFixture]
+ public class HorrorHealthViewTests
+ {
+ // HP ゲージ値(0〜1):通常は current/max、max 0 以下はゼロ除算ガードで 0、範囲外はクランプ。
+
+ [Test]
+ public void CalculateGaugeValue_Full_IsOne()
+ => Assert.That(HorrorHealthView.CalculateGaugeValue(100, 100), Is.EqualTo(1f));
+
+ [Test]
+ public void CalculateGaugeValue_Half_IsHalf()
+ => Assert.That(HorrorHealthView.CalculateGaugeValue(50, 100), Is.EqualTo(0.5f).Within(1e-4f));
+
+ [Test]
+ public void CalculateGaugeValue_Zero_IsZero()
+ => Assert.That(HorrorHealthView.CalculateGaugeValue(0, 100), Is.EqualTo(0f));
+
+ [Test]
+ public void CalculateGaugeValue_OverMax_ClampsToOne()
+ => Assert.That(HorrorHealthView.CalculateGaugeValue(150, 100), Is.EqualTo(1f));
+
+ [Test]
+ public void CalculateGaugeValue_ZeroMax_IsZero()
+ => Assert.That(HorrorHealthView.CalculateGaugeValue(50, 0), Is.EqualTo(0f));
+
+ [Test]
+ public void CalculateGaugeValue_NegativeCurrent_IsZero()
+ => Assert.That(HorrorHealthView.CalculateGaugeValue(-10, 100), Is.EqualTo(0f));
+
+ // 目標アルファ:表示維持中または保持時間内は 1、保持時間超過で 0(境界は厳密 < で無敵終了と同じ契約)。
+
+ [Test]
+ public void CalculateTargetAlpha_KeepVisible_IsOne()
+ => Assert.That(HorrorHealthView.CalculateTargetAlpha(true, 999f, 2f), Is.EqualTo(1f));
+
+ [Test]
+ public void CalculateTargetAlpha_WithinHoldDuration_IsOne()
+ => Assert.That(HorrorHealthView.CalculateTargetAlpha(false, 1f, 2f), Is.EqualTo(1f));
+
+ [Test]
+ public void CalculateTargetAlpha_PastHoldDuration_IsZero()
+ => Assert.That(HorrorHealthView.CalculateTargetAlpha(false, 2f, 2f), Is.EqualTo(0f));
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorHealthViewTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorHealthViewTests.cs.meta
new file mode 100644
index 000000000..3158f2c7a
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorHealthViewTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: fecfd26d9cff2a74a94fa25a4b03ac02
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryResultTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryResultTests.cs
new file mode 100644
index 000000000..1d19774bd
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryResultTests.cs
@@ -0,0 +1,67 @@
+using Game.Horror.Dialogs;
+using Game.Shared.Enums;
+using NUnit.Framework;
+
+namespace Game.Tests.MVC.Horror
+{
+ [TestFixture]
+ public class HorrorInventoryResultTests
+ {
+ // HasEquipRequest は EquipCategory の番兵(None)で判定する。
+ // default 返却(多重要求時・強制クローズ時)で誤発火しないことが最重要契約。
+
+ [Test]
+ public void HasEquipRequest_Default_IsFalse()
+ => Assert.That(default(HorrorInventoryResult).HasEquipRequest, Is.False);
+
+ [Test]
+ public void HasEquipRequest_WeaponCategory_IsTrue()
+ {
+ var result = new HorrorInventoryResult { EquipCategory = ObjectCategory.Weapon, EquipId = 1 };
+ Assert.That(result.HasEquipRequest, Is.True);
+ }
+
+ [Test]
+ public void HasEquipRequest_NoneCategory_IsFalse()
+ {
+ var result = new HorrorInventoryResult { EquipCategory = ObjectCategory.None, EquipId = 1 };
+ Assert.That(result.HasEquipRequest, Is.False);
+ }
+
+ // HasUseRequest も UseCategory の番兵(None)で判定する。Equip 予約との併存を許容する。
+
+ [Test]
+ public void HasUseRequest_Default_IsFalse()
+ => Assert.That(default(HorrorInventoryResult).HasUseRequest, Is.False);
+
+ [Test]
+ public void HasUseRequest_ItemCategory_IsTrue()
+ {
+ var result = new HorrorInventoryResult { UseCategory = ObjectCategory.Item, UseId = 5 };
+ Assert.That(result.HasUseRequest, Is.True);
+ }
+
+ [Test]
+ public void HasUseRequest_NoneCategory_IsFalse()
+ {
+ var result = new HorrorInventoryResult { UseCategory = ObjectCategory.None, UseId = 5 };
+ Assert.That(result.HasUseRequest, Is.False);
+ }
+
+ [Test]
+ public void HasUseRequest_WithEquipRequest_BothTrue()
+ {
+ var result = new HorrorInventoryResult
+ {
+ EquipCategory = ObjectCategory.Weapon,
+ EquipId = 1,
+ UseCategory = ObjectCategory.Item,
+ UseId = 5,
+ };
+
+ Assert.That(result.HasEquipRequest, Is.True);
+ Assert.That(result.HasUseRequest, Is.True);
+ }
+
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryResultTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryResultTests.cs.meta
new file mode 100644
index 000000000..1656b2ea0
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryResultTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: e8dc556e1b32b0c4db35ee1720f6e617
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryServiceTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryServiceTests.cs
index 9f5dcc637..ddc1366c6 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryServiceTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorInventoryServiceTests.cs
@@ -4,7 +4,6 @@
using Game.Horror.Services;
using Game.Horror.Services.Interfaces;
using Game.Shared.Enums;
-using Game.Shared.Interfaces;
using Game.Shared.SaveData;
using Game.Shared.Services;
using NSubstitute;
@@ -39,84 +38,69 @@ private async Task LoadDefaultData()
await _repository.LoadAsync();
}
- private static IHorrorInventorySlotInfo MakeInfo(InventorySlotType type, int id, int maxCount)
- {
- var info = Substitute.For();
- info.SlotType.Returns(type);
- info.Id.Returns(id);
- info.MaxCount.Returns(maxCount);
- return info;
- }
-
[Test]
public async Task GetCount_NotPossessed_ReturnsZero()
{
await LoadDefaultData();
- Assert.That(_service.GetCount(InventorySlotType.Item, 3), Is.EqualTo(0));
+ Assert.That(_service.GetCount(ObjectCategory.Item, 3), Is.EqualTo(0));
}
[Test]
public async Task GetCount_AfterTryAdd_ReturnsAddedCount()
{
await LoadDefaultData();
- var info = MakeInfo(InventorySlotType.Item, 3, 10);
-
- _service.TryAdd(info, 4);
+ _service.TryAdd(ObjectCategory.Item, 3, 4, 10);
- Assert.That(_service.GetCount(InventorySlotType.Item, 3), Is.EqualTo(4));
+ Assert.That(_service.GetCount(ObjectCategory.Item, 3), Is.EqualTo(4));
}
[Test]
public async Task TryConsume_FullAmount_RemovesSlotAndReturnsTrue()
{
await LoadDefaultData();
- var info = MakeInfo(InventorySlotType.Item, 3, 10);
- _service.TryAdd(info, 4);
+ _service.TryAdd(ObjectCategory.Item, 3, 4, 10);
- var ok = _service.TryConsume(InventorySlotType.Item, 3, 4);
+ var ok = _service.TryConsume(ObjectCategory.Item, 3, 4);
Assert.That(ok, Is.True);
- Assert.That(_service.HasItem(InventorySlotType.Item, 3), Is.False);
- Assert.That(_service.GetCount(InventorySlotType.Item, 3), Is.EqualTo(0));
+ Assert.That(_service.HasObject(ObjectCategory.Item, 3), Is.False);
+ Assert.That(_service.GetCount(ObjectCategory.Item, 3), Is.EqualTo(0));
}
[Test]
public async Task TryConsume_PartialAmount_ReturnsTrueAndLeavesRemainder()
{
await LoadDefaultData();
- var info = MakeInfo(InventorySlotType.Item, 3, 10);
- _service.TryAdd(info, 4);
+ _service.TryAdd(ObjectCategory.Item, 3, 4, 10);
- var ok = _service.TryConsume(InventorySlotType.Item, 3, 1);
+ var ok = _service.TryConsume(ObjectCategory.Item, 3, 1);
Assert.That(ok, Is.True);
- Assert.That(_service.GetCount(InventorySlotType.Item, 3), Is.EqualTo(3));
+ Assert.That(_service.GetCount(ObjectCategory.Item, 3), Is.EqualTo(3));
}
[Test]
public async Task TryConsume_InsufficientCount_ReturnsFalseAndLeavesUnchanged()
{
await LoadDefaultData();
- var info = MakeInfo(InventorySlotType.Item, 3, 10);
- _service.TryAdd(info, 2);
+ _service.TryAdd(ObjectCategory.Item, 3, 2, 10);
- var ok = _service.TryConsume(InventorySlotType.Item, 3, 5);
+ var ok = _service.TryConsume(ObjectCategory.Item, 3, 5);
Assert.That(ok, Is.False);
- Assert.That(_service.GetCount(InventorySlotType.Item, 3), Is.EqualTo(2));
+ Assert.That(_service.GetCount(ObjectCategory.Item, 3), Is.EqualTo(2));
}
[Test]
public async Task TryConsume_ZeroOrNegativeCount_ReturnsFalse()
{
await LoadDefaultData();
- var info = MakeInfo(InventorySlotType.Item, 3, 10);
- _service.TryAdd(info, 2);
+ _service.TryAdd(ObjectCategory.Item, 3, 2, 10);
- Assert.That(_service.TryConsume(InventorySlotType.Item, 3, 0), Is.False);
- Assert.That(_service.TryConsume(InventorySlotType.Item, 3, -1), Is.False);
- Assert.That(_service.GetCount(InventorySlotType.Item, 3), Is.EqualTo(2));
+ Assert.That(_service.TryConsume(ObjectCategory.Item, 3, 0), Is.False);
+ Assert.That(_service.TryConsume(ObjectCategory.Item, 3, -1), Is.False);
+ Assert.That(_service.GetCount(ObjectCategory.Item, 3), Is.EqualTo(2));
}
[Test]
@@ -124,9 +108,9 @@ public async Task TryConsume_Success_MarksDirty()
{
await LoadDefaultData();
// TryAdd 経由だと既に Dirty になるため、直接 Slots へ登録して Dirty を汚さず前提を作る。
- _repository.Data.Inventory.Slots.Add(new HorrorInventorySlotData { SlotType = InventorySlotType.Item, Id = 3, Count = 4 });
+ _repository.Data.Inventory.Slots.Add(new HorrorInventorySlotData { ObjectCategory = ObjectCategory.Item, Id = 3, Count = 4 });
- var ok = _service.TryConsume(InventorySlotType.Item, 3, 1);
+ var ok = _service.TryConsume(ObjectCategory.Item, 3, 1);
Assert.That(ok, Is.True);
Assert.That(_repository.IsDirty, Is.True);
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerControllerTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerControllerTests.cs
index d39aef6b0..e0af5d524 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerControllerTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerControllerTests.cs
@@ -156,5 +156,200 @@ public void CalculateRecoiledPitch_BeyondUpperLimit_ClampsToNegative89()
[Test]
public void CalculateRecoiledPitch_BeyondLowerLimit_ClampsTo89()
=> Assert.That(HorrorPlayerController.CalculateRecoiledPitch(88f, -5f, 1f), Is.EqualTo(89f).Within(1e-4f));
+
+ // 足音の歩幅積算ステップ:stride 到達で発火し超過分のみ持ち越す。複数歩分を1フレームで
+ // 移動しても発火は1回・剰余は [0, stride) に収まる。stride 0 以下は無限発火防止で発火せず 0 固定。
+
+ [Test]
+ public void StepFootstep_BelowStride_DoesNotFireAndAccumulates()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(0.5f, 0.25f, 1.25f);
+ Assert.That(fired, Is.False);
+ Assert.That(next, Is.EqualTo(0.75f).Within(1e-4f));
+ }
+
+ [Test]
+ public void StepFootstep_ExactlyAtStride_FiresAndResetsToZero()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(0.75f, 0.5f, 1.25f);
+ Assert.That(fired, Is.True);
+ Assert.That(next, Is.EqualTo(0f).Within(1e-4f));
+ }
+
+ [Test]
+ public void StepFootstep_ExceedsStride_FiresAndKeepsRemainder()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(1.0f, 0.5f, 1.25f);
+ Assert.That(fired, Is.True);
+ Assert.That(next, Is.EqualTo(0.25f).Within(1e-4f));
+ }
+
+ [Test]
+ public void StepFootstep_MultipleStridesInOneFrame_FiresOnceAndRemainderStaysBelowStride()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(0f, 2.6f, 1.25f);
+ Assert.That(fired, Is.True);
+ Assert.That(next, Is.EqualTo(0.1f).Within(1e-4f));
+ }
+
+ [Test]
+ public void StepFootstep_NoMovement_DoesNotFireAndUnchanged()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(0.6f, 0f, 1.25f);
+ Assert.That(fired, Is.False);
+ Assert.That(next, Is.EqualTo(0.6f).Within(1e-4f));
+ }
+
+ [Test]
+ public void StepFootstep_ZeroStride_DoesNotFireAndReturnsZero()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(0.5f, 10f, 0f);
+ Assert.That(fired, Is.False);
+ Assert.That(next, Is.EqualTo(0f));
+ }
+
+ [Test]
+ public void StepFootstep_NegativeStride_DoesNotFireAndReturnsZero()
+ {
+ var (fired, next) = HorrorPlayerController.StepFootstep(0.5f, 10f, -1f);
+ Assert.That(fired, Is.False);
+ Assert.That(next, Is.EqualTo(0f));
+ }
+
+ // 足音 Loudness:走りは走り値、歩き(エイム歩行含む)は歩き値。しゃがみ無音は UpdateFootstep 側のガードで保証。
+
+ [Test]
+ public void CalculateFootstepLoudness_Running_ReturnsRunLoudness()
+ => Assert.That(HorrorPlayerController.CalculateFootstepLoudness(true, 0.5f, 1f), Is.EqualTo(1f));
+
+ [Test]
+ public void CalculateFootstepLoudness_Walking_ReturnsWalkLoudness()
+ => Assert.That(HorrorPlayerController.CalculateFootstepLoudness(false, 0.5f, 1f), Is.EqualTo(0.5f));
+
+ // 被弾後の無敵時間判定:窓内は無敵、境界(経過==秒数)で無敵終了。未被弾(負の無限大)と秒数 0 以下は常に無敵なし。
+
+ [Test]
+ public void IsInvincible_WithinWindow_IsTrue()
+ => Assert.That(HorrorPlayerController.IsInvincible(1.0f, 0.7f, 0.5f), Is.True);
+
+ [Test]
+ public void IsInvincible_AtExactBoundary_IsFalse()
+ => Assert.That(HorrorPlayerController.IsInvincible(1.2f, 0.7f, 0.5f), Is.False);
+
+ [Test]
+ public void IsInvincible_AfterWindow_IsFalse()
+ => Assert.That(HorrorPlayerController.IsInvincible(2.0f, 0.7f, 0.5f), Is.False);
+
+ [Test]
+ public void IsInvincible_NeverDamaged_NegativeInfinity_IsFalse()
+ => Assert.That(HorrorPlayerController.IsInvincible(0f, float.NegativeInfinity, 0.5f), Is.False);
+
+ [Test]
+ public void IsInvincible_SameFrameAsDamage_IsTrue()
+ => Assert.That(HorrorPlayerController.IsInvincible(1.0f, 1.0f, 0.5f), Is.True);
+
+ [Test]
+ public void IsInvincible_ZeroSeconds_IsFalse()
+ => Assert.That(HorrorPlayerController.IsInvincible(1.0f, 1.0f, 0f), Is.False);
+
+ // 被弾後の残 HP:通常減算、致死ちょうど・過剰ダメージは 0 で止まる(負にならない)。
+
+ [Test]
+ public void CalculateDamagedHealth_Normal_Subtracts()
+ => Assert.That(HorrorPlayerController.CalculateDamagedHealth(100, 10), Is.EqualTo(90));
+
+ [Test]
+ public void CalculateDamagedHealth_ExactKill_IsZero()
+ => Assert.That(HorrorPlayerController.CalculateDamagedHealth(10, 10), Is.EqualTo(0));
+
+ [Test]
+ public void CalculateDamagedHealth_Overkill_ClampsToZero()
+ => Assert.That(HorrorPlayerController.CalculateDamagedHealth(5, 999), Is.EqualTo(0));
+
+ [Test]
+ public void CalculateDamagedHealth_ZeroDamage_Unchanged()
+ => Assert.That(HorrorPlayerController.CalculateDamagedHealth(50, 0), Is.EqualTo(50));
+
+ // ロード HP の正規化:0 以下(旧セーブ既定値・新規・不正値)は Max へ、Max 超はクランプ、正常値はそのまま。
+
+ [Test]
+ public void NormalizeLoadedHealth_Zero_ReturnsMax()
+ => Assert.That(HorrorPlayerController.NormalizeLoadedHealth(0, 100), Is.EqualTo(100));
+
+ [Test]
+ public void NormalizeLoadedHealth_Negative_ReturnsMax()
+ => Assert.That(HorrorPlayerController.NormalizeLoadedHealth(-5, 100), Is.EqualTo(100));
+
+ [Test]
+ public void NormalizeLoadedHealth_Normal_ReturnsSaved()
+ => Assert.That(HorrorPlayerController.NormalizeLoadedHealth(40, 100), Is.EqualTo(40));
+
+ [Test]
+ public void NormalizeLoadedHealth_AboveMax_ClampsToMax()
+ => Assert.That(HorrorPlayerController.NormalizeLoadedHealth(150, 100), Is.EqualTo(100));
+
+ [Test]
+ public void NormalizeLoadedHealth_AtMax_ReturnsMax()
+ => Assert.That(HorrorPlayerController.NormalizeLoadedHealth(100, 100), Is.EqualTo(100));
+
+ // 回復後の残 HP:通常加算、最大値ちょうど・超過はクランプ(超えない)。
+
+ [Test]
+ public void CalculateHealedHealth_Normal_Adds()
+ => Assert.That(HorrorPlayerController.CalculateHealedHealth(50, 10, 100), Is.EqualTo(60));
+
+ [Test]
+ public void CalculateHealedHealth_AtMax_StaysMax()
+ => Assert.That(HorrorPlayerController.CalculateHealedHealth(100, 10, 100), Is.EqualTo(100));
+
+ [Test]
+ public void CalculateHealedHealth_OverMax_ClampsToMax()
+ => Assert.That(HorrorPlayerController.CalculateHealedHealth(95, 25, 100), Is.EqualTo(100));
+
+ [Test]
+ public void CalculateHealedHealth_ZeroAmount_Unchanged()
+ => Assert.That(HorrorPlayerController.CalculateHealedHealth(50, 0, 100), Is.EqualTo(50));
+
+ // アイテム使用の適用済み回復総量:経過比率からの再計算。開始 0 → 全量へ単調に増え、
+ // 期間超過はクランプ。duration 0 以下は即全量(ゼロ除算ガード)。丸め境界 .5 は避けて検証。
+
+ [Test]
+ public void CalculateAppliedHeal_AtStart_IsZero()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, 0f, 2f), Is.EqualTo(0));
+
+ [Test]
+ public void CalculateAppliedHeal_Midway_IsProportional()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, 0.8f, 2f), Is.EqualTo(10));
+
+ [Test]
+ public void CalculateAppliedHeal_AtDuration_IsFullEffect()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, 2f, 2f), Is.EqualTo(25));
+
+ [Test]
+ public void CalculateAppliedHeal_PastDuration_ClampsToEffect()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, 5f, 2f), Is.EqualTo(25));
+
+ [Test]
+ public void CalculateAppliedHeal_ZeroDuration_IsFullEffect()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, 0f, 0f), Is.EqualTo(25));
+
+ [Test]
+ public void CalculateAppliedHeal_NegativeDuration_IsFullEffect()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, 0f, -1f), Is.EqualTo(25));
+
+ [Test]
+ public void CalculateAppliedHeal_NegativeElapsed_IsZero()
+ => Assert.That(HorrorPlayerController.CalculateAppliedHeal(25, -0.5f, 2f), Is.EqualTo(0));
+
+ [Test]
+ public void CalculateAppliedHeal_MonotonicOverTime()
+ {
+ var early = HorrorPlayerController.CalculateAppliedHeal(25, 0.4f, 2f);
+ var middle = HorrorPlayerController.CalculateAppliedHeal(25, 1.2f, 2f);
+ var late = HorrorPlayerController.CalculateAppliedHeal(25, 1.9f, 2f);
+
+ Assert.That(middle, Is.GreaterThanOrEqualTo(early));
+ Assert.That(late, Is.GreaterThanOrEqualTo(middle));
+ }
}
}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerServiceTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerServiceTests.cs
index 2ead953e9..e5a9a4d23 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerServiceTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorPlayerServiceTests.cs
@@ -83,5 +83,117 @@ public void LastSavepointId_WhenDataNull_ReturnsZero()
{
Assert.That(_service.LastSavepointId, Is.EqualTo(0));
}
+
+ // 残 HP の永続化:記録+Dirty 化、同値は Dirty にしない、未ロードは安全に no-op。
+ // 新規データの既定 0 は「未記録」を意味し、復元側(NormalizeLoadedHealth)が Max へ正規化する前提。
+
+ [Test]
+ public async Task SetCurrentHealth_RecordsAndMarksDirty()
+ {
+ await LoadDefaultData();
+
+ _service.SetCurrentHealth(40);
+
+ Assert.That(_service.CurrentHealth, Is.EqualTo(40));
+ Assert.That(_repository.IsDirty, Is.True);
+ }
+
+ [Test]
+ public async Task SetCurrentHealth_SameValue_DoesNotMarkDirty()
+ {
+ await LoadDefaultData();
+ _service.SetCurrentHealth(40);
+ await _repository.SaveBySlotAsync(0);
+ Assert.That(_repository.IsDirty, Is.False);
+
+ _service.SetCurrentHealth(40);
+
+ Assert.That(_repository.IsDirty, Is.False);
+ }
+
+ [Test]
+ public void SetCurrentHealth_WhenDataNull_DoesNotThrow()
+ {
+ Assert.DoesNotThrow(() => _service.SetCurrentHealth(40));
+ }
+
+ [Test]
+ public void CurrentHealth_WhenDataNull_ReturnsZero()
+ {
+ Assert.That(_service.CurrentHealth, Is.EqualTo(0));
+ }
+
+ [Test]
+ public async Task CurrentHealth_NewData_IsZero()
+ {
+ await LoadDefaultData();
+
+ Assert.That(_service.CurrentHealth, Is.EqualTo(0));
+ }
+
+ // 最大 HP:マスタ由来のランタイム値。セーブリポジトリ非経由のためロード不要・Dirty 化しない。
+
+ [Test]
+ public void MaxHealth_Initial_IsZero()
+ {
+ Assert.That(_service.MaxHealth, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void SetMaxHealth_RecordsValue()
+ {
+ _service.SetMaxHealth(100);
+
+ Assert.That(_service.MaxHealth, Is.EqualTo(100));
+ }
+
+ [Test]
+ public void SetMaxHealth_DoesNotMarkDirty()
+ {
+ _service.SetMaxHealth(100);
+
+ Assert.That(_repository.IsDirty, Is.False);
+ }
+
+ // 満タン判定:MaxHealth 未設定(0)は満タン扱いにしない(誤って使用不能にならない)。
+
+ [Test]
+ public async Task IsHealthFull_AtMax_IsTrue()
+ {
+ await LoadDefaultData();
+ _service.SetMaxHealth(100);
+ _service.SetCurrentHealth(100);
+
+ Assert.That(_service.IsHealthFull, Is.True);
+ }
+
+ [Test]
+ public async Task IsHealthFull_BelowMax_IsFalse()
+ {
+ await LoadDefaultData();
+ _service.SetMaxHealth(100);
+ _service.SetCurrentHealth(99);
+
+ Assert.That(_service.IsHealthFull, Is.False);
+ }
+
+ [Test]
+ public async Task IsHealthFull_OverMax_IsTrue()
+ {
+ await LoadDefaultData();
+ _service.SetMaxHealth(100);
+ _service.SetCurrentHealth(150);
+
+ Assert.That(_service.IsHealthFull, Is.True);
+ }
+
+ [Test]
+ public async Task IsHealthFull_MaxNotSet_IsFalse()
+ {
+ await LoadDefaultData();
+ _service.SetCurrentHealth(40);
+
+ Assert.That(_service.IsHealthFull, Is.False);
+ }
}
}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorSaveRepositoryTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorSaveRepositoryTests.cs
index 1cf028c80..06f18daa3 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorSaveRepositoryTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/HorrorSaveRepositoryTests.cs
@@ -53,11 +53,11 @@ public async Task Load_WhenNoFile_CreatesNewDataWithEmptySectionsAndFourEquipmen
Assert.That(_repository.Data.Player.LastSavepointId, Is.EqualTo(0));
Assert.That(_repository.Data.Inventory.Slots, Is.Empty);
Assert.That(_repository.Data.Interaction.InteractionIds, Is.Empty);
- Assert.That(_repository.Data.Equipment.SlotType, Is.EqualTo(InventorySlotType.None));
+ Assert.That(_repository.Data.Equipment.ObjectCategory, Is.EqualTo(ObjectCategory.None));
Assert.That(_repository.Data.Equipment.Id, Is.EqualTo(0));
Assert.That(_repository.Data.Equipment.Slots.Count, Is.EqualTo(HorrorEquipmentConstants.MaxEquipmentSlotCount));
foreach (var slot in _repository.Data.Equipment.Slots)
- Assert.That(slot.SlotType, Is.EqualTo(InventorySlotType.None));
+ Assert.That(slot.ObjectCategory, Is.EqualTo(ObjectCategory.None));
Assert.That(_repository.IsDirty, Is.False);
}
@@ -108,19 +108,19 @@ public void Serialization_RoundTrip_PreservesAllSections()
{
Slots = new List
{
- new() { SlotType = InventorySlotType.Item, Id = 3, Count = 4 },
+ new() { ObjectCategory = ObjectCategory.Item, Id = 3, Count = 4 },
},
},
Equipment = new HorrorEquipmentSaveData
{
- SlotType = InventorySlotType.Weapon,
+ ObjectCategory = ObjectCategory.Weapon,
Id = 5,
Slots = new List
{
- new() { SlotType = InventorySlotType.Weapon, Id = 5 },
- new() { SlotType = InventorySlotType.None, Id = 0 },
- new() { SlotType = InventorySlotType.Item, Id = 3 },
- new() { SlotType = InventorySlotType.None, Id = 0 },
+ new() { ObjectCategory = ObjectCategory.Weapon, Id = 5 },
+ new() { ObjectCategory = ObjectCategory.None, Id = 0 },
+ new() { ObjectCategory = ObjectCategory.Item, Id = 3 },
+ new() { ObjectCategory = ObjectCategory.None, Id = 0 },
},
Magazines = new List
{
@@ -131,6 +131,13 @@ public void Serialization_RoundTrip_PreservesAllSections()
{
InteractionIds = new List { 1, 2, 3 },
},
+ KeyItem = new HorrorKeyItemSaveData
+ {
+ KeyItems = new List
+ {
+ new() { ObjectCategory = ObjectCategory.Item, Id = 3 },
+ },
+ },
};
var bytes = MemoryPackSerializer.Serialize(original);
@@ -140,20 +147,23 @@ public void Serialization_RoundTrip_PreservesAllSections()
Assert.That(restored.Version, Is.EqualTo(1));
Assert.That(restored.Player.LastSavepointId, Is.EqualTo(42));
Assert.That(restored.Inventory.Slots.Count, Is.EqualTo(1));
- Assert.That(restored.Inventory.Slots[0].SlotType, Is.EqualTo(InventorySlotType.Item));
+ Assert.That(restored.Inventory.Slots[0].ObjectCategory, Is.EqualTo(ObjectCategory.Item));
Assert.That(restored.Inventory.Slots[0].Id, Is.EqualTo(3));
Assert.That(restored.Inventory.Slots[0].Count, Is.EqualTo(4));
- Assert.That(restored.Equipment.SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(restored.Equipment.ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(restored.Equipment.Id, Is.EqualTo(5));
Assert.That(restored.Equipment.Slots.Count, Is.EqualTo(4));
- Assert.That(restored.Equipment.Slots[0].SlotType, Is.EqualTo(InventorySlotType.Weapon));
+ Assert.That(restored.Equipment.Slots[0].ObjectCategory, Is.EqualTo(ObjectCategory.Weapon));
Assert.That(restored.Equipment.Slots[0].Id, Is.EqualTo(5));
- Assert.That(restored.Equipment.Slots[2].SlotType, Is.EqualTo(InventorySlotType.Item));
+ Assert.That(restored.Equipment.Slots[2].ObjectCategory, Is.EqualTo(ObjectCategory.Item));
Assert.That(restored.Equipment.Slots[2].Id, Is.EqualTo(3));
Assert.That(restored.Equipment.Magazines.Count, Is.EqualTo(1));
Assert.That(restored.Equipment.Magazines[0].WeaponId, Is.EqualTo(5));
Assert.That(restored.Equipment.Magazines[0].Count, Is.EqualTo(12));
Assert.That(restored.Interaction.InteractionIds, Is.EquivalentTo(new[] { 1, 2, 3 }));
+ Assert.That(restored.KeyItem.KeyItems.Count, Is.EqualTo(1));
+ Assert.That(restored.KeyItem.KeyItems[0].ObjectCategory, Is.EqualTo(ObjectCategory.Item));
+ Assert.That(restored.KeyItem.KeyItems[0].Id, Is.EqualTo(3));
}
[Test]
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionDetectorOcclusionTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionDetectorOcclusionTests.cs
new file mode 100644
index 000000000..72fcaf8ac
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionDetectorOcclusionTests.cs
@@ -0,0 +1,153 @@
+using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
+using Game.Horror.Interaction;
+using Game.Shared.Constants;
+using Game.Shared.Enums;
+using NUnit.Framework;
+using UnityEngine;
+
+namespace Game.Tests.MVC.Horror.Interaction
+{
+ ///
+ /// の遮蔽判定検証。
+ /// 特に「Interactable 化された家具(ソファ等)が他の Interactable の遮蔽物として機能せず、
+ /// 背後のアイテムのプロンプトが貫通表示された不具合」の回帰を防止する。
+ /// 実コライダーへの Raycast を使うため、生成後に で
+ /// ブロードフェーズを同期する(Raycast はシミュレーション不要)。エディタで開いている
+ /// シーンのコライダーとの干渉を避けるため、テストリグは遠隔座標に配置する。
+ ///
+ [TestFixture]
+ public class InteractionDetectorOcclusionTests
+ {
+ // 開いているシーンのレベルジオメトリと交差しない遠隔座標をレイの始点(カメラ相当)とする
+ private static readonly Vector3 CameraPos = new(5000f, 5000f, 5000f);
+
+ // 対象(遮蔽判定のレイ終点)。レイが対象自身の前面(z=+4.5)を必ず貫く距離に置く
+ private static readonly Vector3 TargetPos = CameraPos + new Vector3(0f, 0f, 5f);
+
+ // カメラと対象の中間の遮蔽物位置
+ private static readonly Vector3 BetweenPos = CameraPos + new Vector3(0f, 0f, 2.5f);
+
+ // HorrorPlayer.prefab の設定相当(_occluderMask | _interactableMask)を定数で再現
+ private static readonly int OcclusionMask =
+ LayerMaskConstants.Default | LayerMaskConstants.Ground |
+ LayerMaskConstants.Structure | LayerMaskConstants.Interactable;
+
+ private readonly List _spawned = new();
+ private RaycastHit[] _hitBuffer;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _hitBuffer = new RaycastHit[16];
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ foreach (var go in _spawned)
+ {
+ if (go != null) Object.DestroyImmediate(go);
+ }
+
+ _spawned.Clear();
+ }
+
+ // 間に Structure レイヤーの壁 → 遮蔽(従来動作の回帰確認)
+ [Test]
+ public void IsOccluded_StructureWallBetween_ReturnsTrue()
+ {
+ var target = CreateInteractable("Target", TargetPos);
+ CreateWall(BetweenPos, LayerConstants.Structure);
+ Physics.SyncTransforms();
+
+ bool occluded = InteractionDetector.IsOccluded(CameraPos, TargetPos, target, OcclusionMask, _hitBuffer);
+
+ Assert.IsTrue(occluded, "Structure レイヤーの壁が遮蔽物として扱われていません");
+ }
+
+ // 間に別 Interactable のソリッドコライダー → 遮蔽(プロンプト貫通バグの回帰防止の本命)
+ [Test]
+ public void IsOccluded_OtherInteractableBetween_ReturnsTrue()
+ {
+ var target = CreateInteractable("Target", TargetPos);
+ CreateInteractable("Sofa", BetweenPos);
+ Physics.SyncTransforms();
+
+ bool occluded = InteractionDetector.IsOccluded(CameraPos, TargetPos, target, OcclusionMask, _hitBuffer);
+
+ Assert.IsTrue(occluded, "Interactable レイヤーの他対象が遮蔽物として扱われていません(プロンプト貫通バグの回帰)");
+ }
+
+ // 対象自身のコライダーのみ → 非遮蔽(自己ヒットの参照一致除外)
+ [Test]
+ public void IsOccluded_OnlyTargetOwnCollider_ReturnsFalse()
+ {
+ var target = CreateInteractable("Target", TargetPos);
+ Physics.SyncTransforms();
+
+ bool occluded = InteractionDetector.IsOccluded(CameraPos, TargetPos, target, OcclusionMask, _hitBuffer);
+
+ Assert.IsFalse(occluded, "対象自身のコライダーが自己遮蔽になっています(自分のプロンプトが消える退行)");
+ }
+
+ // 間にトリガーコライダー → 非遮蔽(拾得用に膨らませたトリガーは遮蔽物にしない)
+ [Test]
+ public void IsOccluded_TriggerColliderBetween_ReturnsFalse()
+ {
+ var target = CreateInteractable("Target", TargetPos);
+ CreateWall(BetweenPos, LayerConstants.Structure, isTrigger: true);
+ Physics.SyncTransforms();
+
+ bool occluded = InteractionDetector.IsOccluded(CameraPos, TargetPos, target, OcclusionMask, _hitBuffer);
+
+ Assert.IsFalse(occluded, "トリガーコライダーが遮蔽物として扱われています");
+ }
+
+ ///
+ /// ルート()+子コライダー(Interactable レイヤー・1m 立方)の
+ /// 対象を生成する。コライダーを子に置くことで、自己ヒット除外の親方向探索
+ /// (GetComponentInParent)も同時に検証する。
+ ///
+ private FakeInteractable CreateInteractable(string name, Vector3 position)
+ {
+ var root = new GameObject(name);
+ _spawned.Add(root);
+ root.transform.position = position;
+ var interactable = root.AddComponent();
+
+ var colliderGo = new GameObject(name + "Collider");
+ colliderGo.transform.SetParent(root.transform, worldPositionStays: false);
+ colliderGo.layer = LayerConstants.Interactable;
+ colliderGo.AddComponent();
+
+ return interactable;
+ }
+
+ private void CreateWall(Vector3 position, int layer, bool isTrigger = false)
+ {
+ var wall = new GameObject("Wall");
+ _spawned.Add(wall);
+ wall.transform.position = position;
+ wall.layer = layer;
+ var collider = wall.AddComponent();
+ collider.size = new Vector3(3f, 3f, 0.2f);
+ collider.isTrigger = isTrigger;
+ }
+
+ // GetComponentInParent の参照一致判定には階層上の実コンポーネントが必要なため、
+ // モックではなく IInteractable を直接実装する最小スタブを使う
+ private sealed class FakeInteractable : MonoBehaviour, IInteractable
+ {
+ public Vector3 CenterPosition => transform.position;
+ public Bounds WorldBounds => new(transform.position, Vector3.one);
+ public InteractionInputType InputType => InteractionInputType.Instant;
+ public float HoldSeconds => 0f;
+ public bool CanInteract() => true;
+ public void Interact() { }
+ public void SetInteractionState(InteractionState state, Camera viewCamera) { }
+ public void SetHoldProgress(float progress01) { }
+ public UniTask TryShowRejectionMessage() => UniTask.FromResult(false);
+ }
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionDetectorOcclusionTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionDetectorOcclusionTests.cs.meta
new file mode 100644
index 000000000..474edac6e
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionDetectorOcclusionTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: ade6281623d19e747adad245d549776a
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptPoolTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptPoolTests.cs
new file mode 100644
index 000000000..74b405291
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptPoolTests.cs
@@ -0,0 +1,270 @@
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Game.Core.Services;
+using Game.Horror.Interaction;
+using Game.Horror.Services.Interfaces;
+using Game.Shared.Enums;
+using Game.Shared.Input;
+using Game.Shared.Scriptable.Database.Tables;
+using Game.Shared.Services.Interfaces;
+using NSubstitute;
+using NUnit.Framework;
+using R3;
+using TMPro;
+using UnityEngine;
+using UnityEngine.InputSystem;
+using UnityEngine.Localization;
+using UnityEngine.TestTools;
+using UnityEngine.UI;
+using Object = UnityEngine.Object;
+
+namespace Game.Tests.MVC.Horror.Interaction
+{
+ [TestFixture]
+ public class InteractionPromptPoolTests
+ {
+ private InteractionPromptPool _pool;
+ private GameObject _poolGo;
+ private InteractionPromptView _prefab;
+ private GameObject _anchorGo;
+
+ // Construct の購読が実 InputAction(Player.Interact)を要求するため、実アセットを生成して保持する。
+ // 生成された ProjectInputActions.Dispose() は Object.Destroy を呼ぶため EditMode では使えず、
+ // TearDown で内部アセットを DestroyImmediate する。
+ private ProjectInputActions _inputActions;
+
+ [SetUp]
+ public void TearUp()
+ {
+ GameServiceManager.StartUp();
+ GameServiceManager.Register(CreateLocalizationService());
+ GameServiceManager.Register(CreateInputSystemService());
+ GameServiceManager.Register(CreateInputActionIconService());
+ GameServiceManager.Register(CreateHorrorIconService());
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ if (_anchorGo != null) Object.DestroyImmediate(_anchorGo);
+ if (_poolGo != null) Object.DestroyImmediate(_poolGo);
+ if (_prefab != null) Object.DestroyImmediate(_prefab.gameObject);
+ if (_inputActions != null)
+ {
+ Object.DestroyImmediate(_inputActions.asset);
+ _inputActions = null;
+ }
+ }
+
+ // ---- フェイクサービス(NSubstitute。アセンブリ標準の手法に合わせる) ----
+
+ // GetStringByContextActions はキーをそのまま返すため、Bind 時の動詞キー選択(toggle 反映)を
+ // テキストの一致で直接検証できる
+ private static ILocalizationService CreateLocalizationService()
+ {
+ var localization = Substitute.For();
+ localization.OnLocaleChanged.Returns(new Subject());
+ localization.GetStringByContextActions(Arg.Any()).Returns(ci => ci.Arg());
+ return localization;
+ }
+
+ // Construct が購読する Observable 群と Player.Interact 参照のみ意味を持たせる
+ private IInputSystemService CreateInputSystemService()
+ {
+ _inputActions = new ProjectInputActions();
+
+ var input = Substitute.For();
+ input.Player.Returns(_inputActions.Player);
+ input.OnControlSchemeChanged.Returns(new Subject());
+ input.OnDeviceChanged.Returns(new Subject());
+ input.OnBindingChanged.Returns(new Subject());
+ input.GetBindingInfo(Arg.Any(), Arg.Any()).Returns(new InputBindingInfo());
+ return input;
+ }
+
+ private IInputActionIconService CreateInputActionIconService()
+ {
+ var icon = Substitute.For();
+ // Texture2D texture = new Texture2D(128, 128);
+ // Rect rect = new Rect(0, 0, 128, 128);
+ // Vector2 pivot = new Vector2(0.5f, 0.5f);
+ // Sprite dummySprite = Sprite.Create(texture, rect, pivot);
+ // icon.GetSprite(new InputBindingInfo()).Returns(dummySprite);
+ icon.GetSprite(new InputBindingInfo()).Returns(_ => null);
+ return icon;
+ }
+
+ private IHorrorIconService CreateHorrorIconService()
+ {
+ var icon = Substitute.For();
+ icon.GetSprite(Arg.Any()).Returns(_ => null);
+ return icon;
+ }
+
+ // ---- ヘルパー ----
+
+ // 貸出・Bind に必要な参照(RectTransform ルート・動詞テキスト・入力表示テキスト)を最小限だけ配線したプレハブ雛形。
+ // InteractionPromptView.Awake が transform を RectTransform へキャストするため RectTransform は必須。
+ private static InteractionPromptView CreatePromptPrefab()
+ {
+ var go = new GameObject("PromptViewPrefab", typeof(RectTransform));
+ var view = go.AddComponent();
+
+ var interactionTextGo = new GameObject("InteractionText", typeof(RectTransform));
+ interactionTextGo.transform.SetParent(go.transform);
+ var interactionText = interactionTextGo.AddComponent();
+
+ var inputBindingTextGo = new GameObject("InputBindingText", typeof(RectTransform));
+ inputBindingTextGo.transform.SetParent(go.transform);
+ var inputBindingText = inputBindingTextGo.AddComponent();
+
+ var inputActionIconGo = new GameObject("InputActionIcon", typeof(RectTransform));
+ inputActionIconGo.transform.SetParent(go.transform);
+ var inputActionIcon = inputActionIconGo.AddComponent();
+
+ var holdGaugeGo = new GameObject("HoldGauge");
+ holdGaugeGo.transform.SetParent(go.transform);
+ var holdGauge = holdGaugeGo.AddComponent();
+
+ SetPrivateField(view, "_interactionText", interactionText);
+ SetPrivateField(view, "_inputBindingText", inputBindingText);
+ SetPrivateField(view, "_inputActionIcon", inputActionIcon);
+ SetPrivateField(view, "_holdGauge", holdGauge);
+
+ return view;
+ }
+
+ // プールを生成し、プレハブ・prewarm 数(reflection 注入)・フェイクサービスで初期化してフィールドへ保持する
+ private InteractionPromptPool CreateInitializedPool(int prewarmCount)
+ {
+ _poolGo = new GameObject("Pool");
+ _pool = _poolGo.AddComponent();
+ _prefab = CreatePromptPrefab();
+
+ SetPrivateField(_pool, "_promptPrefab", _prefab);
+ SetPrivateField(_pool, "_prewarmCount", prewarmCount);
+
+ // _pool.Initialize(CreateLocalizationSubstitute(), CreateInputServiceSubstitute());
+ _pool.Initialize();
+ return _pool;
+ }
+
+ private static void SetPrivateField(object target, string fieldName, object value)
+ {
+ target.GetType()
+ .GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance)
+ .SetValue(target, value);
+ }
+
+ private static T GetPrivateField(object target, string fieldName)
+ {
+ return (T)target.GetType()
+ .GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance)
+ .GetValue(target);
+ }
+
+ private static string GetInteractionText(InteractionPromptView view)
+ => GetPrivateField(view, "_interactionText").text;
+
+ private static HorrorInteractionMaster CreateMaster() => new()
+ {
+ Id = 1,
+ InteractionVerb = "VerbOpen",
+ ReinteractionVerb = "VerbClose",
+ InputType = InteractionInputType.Instant,
+ };
+
+ // ---- Rent / Return ----
+
+ // Return したインスタンスは、次の Rent で再利用される(同一参照)。
+ // 待機列は FIFO のため、同一参照を保証できるのは待機が返却分のみになる prewarm=1 の構成
+ [Test]
+ public void Rent_AfterReturn_ReusesSameInstance()
+ {
+ var pool = CreateInitializedPool(prewarmCount: 1);
+
+ var first = pool.Rent();
+ pool.Return(first);
+ var second = pool.Rent();
+
+ Assert.That(second, Is.SameAs(first));
+ }
+
+ // 同一インスタンスを2回返却すると、二重返却として LogError で顕在化し(無音で握りつぶさない)、待機列も汚染しない。
+ // 待機列の重複不在を「再 Rent が同一参照になる」ことで検証するため prewarm=1 の構成にする
+ [Test]
+ public void Return_CalledTwice_LogsErrorAndKeepsQueueConsistent()
+ {
+ var pool = CreateInitializedPool(prewarmCount: 1);
+
+ var view = pool.Rent();
+ pool.Return(view);
+
+ LogAssert.Expect(LogType.Error, new Regex("二重返却"));
+ pool.Return(view);
+
+ // 待機列は1件のまま(二重返却で重複登録されていないこと)を、再 Rent が新規生成にならないことで確認する
+ var rentedAgain = pool.Rent();
+ Assert.That(rentedAgain, Is.SameAs(view));
+ }
+
+ // prewarm 数を超えて Rent すると Warning ログで顕在化しつつ、追加生成して動作を継続する
+ [Test]
+ public void Rent_BeyondPrewarmCount_LogsWarningAndCreatesAdditionalInstance()
+ {
+ var pool = CreateInitializedPool(prewarmCount: 1);
+
+ var first = pool.Rent();
+
+ LogAssert.Expect(LogType.Warning, new Regex("prewarm"));
+ var second = pool.Rent();
+
+ Assert.That(second, Is.Not.SameAs(first));
+ Assert.That(second, Is.Not.Null);
+ }
+
+ // null の返却は呼び出し側の貸出参照管理の欠陥として LogError で顕在化する
+ [Test]
+ public void Return_Null_LogsError()
+ {
+ var pool = CreateInitializedPool(prewarmCount: 1);
+
+ LogAssert.Expect(LogType.Error, new Regex("null"));
+ pool.Return(null);
+ }
+
+ // ---- Bind / Unbind ----
+
+ // Bind 時、interactionToggle=false なら通常動詞キー、true なら再インタラクト動詞キーがテキストへ反映される
+ [Test]
+ public void Bind_ReflectsInteractionToggle_IntoVerbText()
+ {
+ var pool = CreateInitializedPool(prewarmCount: 2);
+ _anchorGo = new GameObject("Anchor");
+
+ var view = pool.Rent();
+ var master = CreateMaster();
+
+ view.Bind(master, _anchorGo.transform, interactionToggle: false);
+ Assert.That(GetInteractionText(view), Is.EqualTo("VerbOpen"));
+
+ view.Bind(master, _anchorGo.transform, interactionToggle: true);
+ Assert.That(GetInteractionText(view), Is.EqualTo("VerbClose"));
+ }
+
+ // Unbind 後も SetHoldProgress は例外を投げず安全に動作する(InteractableBase は _rentedView != null の場合のみ
+ // 転送するため通常到達しないが、View 単体としての不変条件として固定する)
+ [Test]
+ public void SetHoldProgress_AfterUnbind_DoesNotThrow()
+ {
+ var pool = CreateInitializedPool(prewarmCount: 2);
+ _anchorGo = new GameObject("Anchor");
+
+ var view = pool.Rent();
+ view.Bind(CreateMaster(), _anchorGo.transform, interactionToggle: false);
+ pool.Return(view);
+
+ Assert.DoesNotThrow(() => view.SetHoldProgress(0.5f));
+ }
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptPoolTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptPoolTests.cs.meta
new file mode 100644
index 000000000..648b2c1ab
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptPoolTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 950b46bbe0218d5458b341fd7af86415
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptViewTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptViewTests.cs
index 1fd3e3859..d8cb07cc8 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptViewTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/Horror/Interaction/InteractionPromptViewTests.cs
@@ -10,11 +10,12 @@ namespace Game.Tests.MVC.Horror.Interaction
public class InteractionPromptViewTests
{
// 深度が2倍ならスケールも2倍(距離比例で見かけサイズが相殺される)
+ // スケール計算はワールド空間実装(バックアップ)側にのみ残る。ScreenSpace 版は距離非依存のため対象外。
[Test]
public void Scale_IsProportionalToDepth()
{
- float near = InteractionPromptView.CalculateUniformLocalScale(2f, 60f, 0.05f, 1f);
- float far = InteractionPromptView.CalculateUniformLocalScale(4f, 60f, 0.05f, 1f);
+ float near = WorldSpaceInteractionPromptView.CalculateUniformLocalScale(2f, 60f, 0.05f, 1f);
+ float far = WorldSpaceInteractionPromptView.CalculateUniformLocalScale(4f, 60f, 0.05f, 1f);
Assert.AreEqual(near * 2f, far, 1e-4f);
}
@@ -22,8 +23,8 @@ public void Scale_IsProportionalToDepth()
[Test]
public void Scale_CancelsParentLossyScale()
{
- float unit = InteractionPromptView.CalculateUniformLocalScale(3f, 60f, 0.05f, 1f);
- float scaled = InteractionPromptView.CalculateUniformLocalScale(3f, 60f, 0.05f, 2f);
+ float unit = WorldSpaceInteractionPromptView.CalculateUniformLocalScale(3f, 60f, 0.05f, 1f);
+ float scaled = WorldSpaceInteractionPromptView.CalculateUniformLocalScale(3f, 60f, 0.05f, 2f);
Assert.AreEqual(unit / 2f, scaled, 1e-4f);
}
@@ -31,14 +32,36 @@ public void Scale_CancelsParentLossyScale()
[Test]
public void Scale_MatchesExpectedValue()
{
- float scale = InteractionPromptView.CalculateUniformLocalScale(1f, 90f, 0.1f, 1f);
+ float scale = WorldSpaceInteractionPromptView.CalculateUniformLocalScale(1f, 90f, 0.1f, 1f);
Assert.AreEqual(0.2f, scale, 1e-4f);
}
+ // z > 0(カメラ前方への射影深度が正)は表示可能
+ [Test]
+ public void IsInFrontOfCamera_PositiveZ_ReturnsTrue()
+ {
+ Assert.That(InteractionPromptView.IsInFrontOfCamera(new Vector3(100f, 200f, 5f)), Is.True);
+ }
+
+ // z == 0 はカメラ平面上=背後扱い(非表示)
+ [Test]
+ public void IsInFrontOfCamera_ZeroZ_ReturnsFalse()
+ {
+ Assert.That(InteractionPromptView.IsInFrontOfCamera(new Vector3(100f, 200f, 0f)), Is.False);
+ }
+
+ // z < 0 はカメラ背後(非表示)
+ [Test]
+ public void IsInFrontOfCamera_NegativeZ_ReturnsFalse()
+ {
+ Assert.That(InteractionPromptView.IsInFrontOfCamera(new Vector3(100f, 200f, -5f)), Is.False);
+ }
+
// _holdGauge(private)を注入し、SetHoldProgress の表示/非表示・fillAmount を検証する
+ // ルートは RectTransform 必須(Awake で transform を RectTransform へキャストするため)
private static (InteractionPromptView view, Image gauge) CreateViewWithGauge()
{
- var viewGo = new GameObject("PromptView");
+ var viewGo = new GameObject("PromptView", typeof(RectTransform));
var view = viewGo.AddComponent();
var gaugeGo = new GameObject("HoldGauge");
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/InputSystemServiceRebindTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/InputSystemServiceRebindTests.cs
index e90f9e96d..967b05c01 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/InputSystemServiceRebindTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/InputSystemServiceRebindTests.cs
@@ -13,13 +13,13 @@ namespace Game.Tests.MVC
[TestFixture]
public class InputSystemServiceRebindTests
{
- private ProjectDefaultInputSystem _input;
+ private ProjectInputActions _input;
private InputActionAsset _asset;
[SetUp]
public void Setup()
{
- _input = new ProjectDefaultInputSystem();
+ _input = new ProjectInputActions();
_asset = _input.asset;
}
@@ -41,42 +41,42 @@ public void TearDown()
public void Resolve_Jump_KeyboardMouse_ReturnsSingleKeyboardBinding()
{
var action = Action("Jump");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, action);
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, action);
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/space"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/space"));
}
[Test]
public void Resolve_Jump_Gamepad_ReturnsSingleGamepadBinding()
{
var action = Action("Jump");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.Gamepad, action);
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.Gamepad, action);
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/buttonSouth"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/buttonSouth"));
}
[Test]
public void Resolve_Move_KeyboardMouse_ReturnsFourCompositeParts()
{
var action = Action("Move");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, action);
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, action);
// WASD コンポジットの4パート(up/down/left/right)
Assert.That(indices.Count, Is.EqualTo(4));
- foreach (var index in indices)
- Assert.That(action.bindings[index].isPartOfComposite, Is.True);
+ foreach (var info in indices)
+ Assert.That(action.bindings[info.Index].isPartOfComposite, Is.True);
}
[Test]
public void Resolve_Move_Gamepad_ReturnsLeftStick()
{
var action = Action("Move");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.Gamepad, action);
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.Gamepad, action);
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/leftStick"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/leftStick"));
}
[Test]
@@ -84,10 +84,10 @@ public void Resolve_Reset_KeyboardMouse_ReturnsBindings()
{
// Reset(UI マップ)の KBM バインドが解決されることを確認
var action = Action("Reset");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, action);
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, action);
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/r"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/r"));
}
[Test]
@@ -95,100 +95,39 @@ public void Resolve_Reset_Gamepad_ReturnsBinding()
{
// Reset(UI マップ)は Gamepad バインド(buttonNorth)も持つ
var action = Action("Reset");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.Gamepad, action);
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.Gamepad, action);
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/buttonNorth"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/buttonNorth"));
}
[Test]
public void Resolve_Move_KeyboardMouse_PartUp_ReturnsSingleW()
{
var action = Action("Move");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, action, "up");
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, action, "up");
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/w"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/w"));
}
[Test]
public void Resolve_Move_KeyboardMouse_PartDown_ReturnsSingleS()
{
var action = Action("Move");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, action, "down");
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, action, "down");
Assert.That(indices.Count, Is.EqualTo(1));
- Assert.That(action.bindings[indices[0]].effectivePath, Is.EqualTo("/s"));
+ Assert.That(action.bindings[indices[0].Index].effectivePath, Is.EqualTo("/s"));
}
[Test]
- public void Resolve_SingleAction_WithPartName_ReturnsEmpty()
+ public void Resolve_SingleAction_WithPartName_ReturnsSingle()
{
- // 単体アクション(Jump)に partName を指定しても該当パートは存在しない
var action = Action("Jump");
- var indices = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, action, "up");
+ var indices = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, action, "up");
- Assert.That(indices.Count, Is.EqualTo(0));
- }
-
- #endregion
-
- #region WouldConflict
-
- [Test]
- public void WouldConflict_SameSchemeDuplicate_ReturnsTrue()
- {
- // Jump(KBM) を対象に、別アクション(Attack)が KBM で使用中のキーを候補にすると衝突
- var jump = Action("Jump");
- var jumpIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, jump)[0];
-
- var attack = Action("Attack");
- var attackIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, attack)[0];
- var attackPath = attack.bindings[attackIndex].effectivePath;
-
- var conflict = InputSystemService.WouldConflict(
- _asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, attackPath);
-
- Assert.That(conflict, Is.True);
- }
-
- [Test]
- public void WouldConflict_DifferentScheme_ReturnsFalse()
- {
- // Gamepad のパスは KBM スキームでは衝突対象にならない
- var jump = Action("Jump");
- var jumpIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, jump)[0];
-
- var conflict = InputSystemService.WouldConflict(
- _asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, "/buttonSouth");
-
- Assert.That(conflict, Is.False);
- }
-
- [Test]
- public void WouldConflict_OwnCurrentPath_ReturnsFalse()
- {
- // 自分自身の現在パスは衝突扱いしない
- var jump = Action("Jump");
- var jumpIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, jump)[0];
- var ownPath = jump.bindings[jumpIndex].effectivePath;
-
- var conflict = InputSystemService.WouldConflict(
- _asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, ownPath);
-
- Assert.That(conflict, Is.False);
- }
-
- [Test]
- public void WouldConflict_UnusedKey_ReturnsFalse()
- {
- var jump = Action("Jump");
- var jumpIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, jump)[0];
-
- var conflict = InputSystemService.WouldConflict(
- _asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, "/numpad5");
-
- Assert.That(conflict, Is.False);
+ Assert.That(indices.Count, Is.EqualTo(1));
}
#endregion
@@ -200,14 +139,14 @@ public void TryFindConflict_SameSchemeDuplicate_ReturnsConflictBinding()
{
// Jump を対象に、別アクションへ既定割当した /numpad5 を候補にすると、その相手が返る
var jump = Action("Jump");
- var jumpIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, jump)[0];
+ var jumpIndex = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, jump)[0].Index;
// Attack(KBM) を numpad5 に固定し、衝突相手として特定できることを確認
var attack = Action("Attack");
- var attackIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, attack)[0];
+ var attackIndex = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, attack)[0].Index;
attack.ApplyBindingOverride(attackIndex, "/numpad5");
- var found = InputSystemService.TryFindConflict(
+ var found = InputSystemService.TryFindConflictAction(
_asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, "/numpad5",
out var conflictAction, out var conflictIndex);
@@ -220,9 +159,9 @@ public void TryFindConflict_SameSchemeDuplicate_ReturnsConflictBinding()
public void TryFindConflict_UnusedKey_ReturnsFalseAndNullOut()
{
var jump = Action("Jump");
- var jumpIndex = InputSystemService.ResolveSchemeBindingIndices(InputControlSchemes.KeyboardAndMouse, jump)[0];
+ var jumpIndex = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, jump)[0].Index;
- var found = InputSystemService.TryFindConflict(
+ var found = InputSystemService.TryFindConflictAction(
_asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, "/numpad5",
out var conflictAction, out var conflictIndex);
@@ -231,6 +170,35 @@ public void TryFindConflict_UnusedKey_ReturnsFalseAndNullOut()
Assert.That(conflictIndex, Is.EqualTo(-1));
}
+ [Test]
+ public void TryFindConflict_DifferentScheme_ReturnsFalse()
+ {
+ // Gamepad のパスは KBM スキームでは衝突対象にならない
+ var jump = Action("Jump");
+ var jumpIndex = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, jump)[0].Index;
+
+ var found = InputSystemService.TryFindConflictAction(
+ _asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, "/buttonSouth",
+ out _, out _);
+
+ Assert.That(found, Is.False);
+ }
+
+ [Test]
+ public void TryFindConflict_OwnCurrentPath_ReturnsFalse()
+ {
+ // 自分自身の現在パスは衝突扱いしない
+ var jump = Action("Jump");
+ var jumpIndex = InputSystemService.GetBindingIndicesByControlScheme(InputControlSchemes.KeyboardAndMouse, jump)[0].Index;
+ var ownPath = jump.bindings[jumpIndex].effectivePath;
+
+ var found = InputSystemService.TryFindConflictAction(
+ _asset, InputControlSchemes.KeyboardAndMouse, jump, jumpIndex, ownPath,
+ out _, out _);
+
+ Assert.That(found, Is.False);
+ }
+
#endregion
}
}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/MessagePipeServiceTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/MessagePipeServiceTests.cs
index d73705457..2bec70669 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/MVC/MessagePipeServiceTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/MVC/MessagePipeServiceTests.cs
@@ -17,6 +17,11 @@ public class MessagePipeServiceTests
public void Setup()
{
_service = new MessagePipeService();
+ _service.AddMessageBroker();
+ _service.AddMessageBroker();
+ _service.AddMessageBroker();
+ _service.AddMessageBroker();
+ _service.Build();
_service.Startup();
}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceBlockPlayModeTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceBlockPlayModeTests.cs
new file mode 100644
index 000000000..a5b072a20
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceBlockPlayModeTests.cs
@@ -0,0 +1,376 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Game.Core.Services;
+using NUnit.Framework;
+using UnityEngine.InputSystem;
+using UnityEngine.TestTools;
+
+namespace Game.Tests.PlayMode
+{
+ ///
+ /// InputSystemService の入力ブロック(アクション単位参照カウント)仕様の PlayMode テスト。
+ /// 仕様:
+ /// - Enable/Disable/Block は全てアクション単位のカウント台帳を共有する
+ /// - Disable/Block はカウント +1、Enable/Block解除 はカウント -1 で 0 以下なら有効化(0 にクランプ)
+ /// - forceEnable はカウントを無視して有効化し、台帳を 0 にリセットする(ドリフト回復用)
+ ///
+ [TestFixture]
+ public class InputSystemServiceBlockPlayModeTests : InputTestFixture
+ {
+ private InputSystemService _service;
+
+ public override void Setup()
+ {
+ base.Setup();
+ _service = new InputSystemService(new LocalizationService());
+ }
+
+ public override void TearDown()
+ {
+ _service?.Shutdown();
+ _service = null;
+ base.TearDown();
+ }
+
+ #region 基本状態
+
+ [Test]
+ public void Startup_InitialState_UIEnabledPlayerDisabled()
+ {
+ _service.Startup();
+
+ Assert.IsTrue(_service.UI.Cancel.enabled, "Startup 直後は UI 入力が有効のはず");
+ Assert.IsTrue(_service.UI.Submit.enabled);
+ Assert.IsFalse(_service.Player.Move.enabled, "Startup 直後は Player 入力が無効のはず");
+ Assert.IsFalse(_service.Player.Jump.enabled);
+ }
+
+ [Test]
+ public void Shutdown_Restartup_DoesNotCarryOverBlockCounts()
+ {
+ _service.Startup();
+ _service.BlockInputAction(_service.UI.Click); // 意図的に未解放のまま
+ Assert.IsFalse(_service.UI.Click.enabled);
+
+ _service.Shutdown();
+ _service.Startup();
+
+ Assert.IsTrue(_service.UI.Click.enabled, "再 Startup 後にブロックカウントが持ち越されないはず");
+ }
+
+ [Test]
+ public void BeforeStartup_Throws()
+ {
+ Assert.Throws(() => _service.EnablePlayer());
+ Assert.Throws(() => _service.DisablePlayer());
+ Assert.Throws(() => _service.EnableUI());
+ Assert.Throws(() => _service.DisableUI());
+ Assert.Throws(() => _service.BlockPlayer());
+ }
+
+ #endregion
+
+ #region BlockInputAction
+
+ [Test]
+ public void BlockAction_DisablesUntilDispose()
+ {
+ _service.Startup();
+
+ var scope = _service.BlockInputAction(_service.UI.Cancel);
+ Assert.IsFalse(_service.UI.Cancel.enabled, "ブロック中は無効のはず");
+ Assert.IsTrue(_service.UI.Submit.enabled, "他のアクションには影響しないはず");
+
+ scope.Dispose();
+ Assert.IsTrue(_service.UI.Cancel.enabled, "解放後は有効に戻るはず");
+ }
+
+ [Test]
+ public void BlockAction_Nested_ReleaseOrderIndependent()
+ {
+ _service.Startup();
+
+ // 取得と逆順(LIFO)の解放
+ var outer = _service.BlockInputAction(_service.UI.Click);
+ var inner = _service.BlockInputAction(_service.UI.Click);
+ inner.Dispose();
+ Assert.IsFalse(_service.UI.Click.enabled, "他方のブロックが残っている間は無効のはず");
+ outer.Dispose();
+ Assert.IsTrue(_service.UI.Click.enabled);
+
+ // 取得順(FIFO)の解放でも同じ結果になる
+ var first = _service.BlockInputAction(_service.UI.Click);
+ var second = _service.BlockInputAction(_service.UI.Click);
+ first.Dispose();
+ Assert.IsFalse(_service.UI.Click.enabled, "解放順に関わらず残ブロックがあれば無効のはず");
+ second.Dispose();
+ Assert.IsTrue(_service.UI.Click.enabled);
+ }
+
+ [Test]
+ public void BlockAction_DoubleDispose_DoesNotOverRelease()
+ {
+ _service.Startup();
+
+ var disposed = _service.BlockInputAction(_service.UI.Click);
+ var alive = _service.BlockInputAction(_service.UI.Click);
+
+ disposed.Dispose();
+ disposed.Dispose(); // 二重解放
+
+ Assert.IsFalse(_service.UI.Click.enabled, "二重 Dispose で生存中のブロックが解除されないはず");
+
+ alive.Dispose();
+ Assert.IsTrue(_service.UI.Click.enabled);
+ }
+
+ [Test]
+ public void BlockAction_ForeignAction_ThrowsKeyNotFound()
+ {
+ _service.Startup();
+
+ // Startup 時に列挙されない別アセット由来のアクションは台帳管理外(仕様として例外で顕在化)
+ var foreign = new InputAction("Foreign", binding: "/g");
+ Assert.Throws(() => _service.BlockInputAction(foreign));
+ foreign.Dispose();
+ }
+
+ #endregion
+
+ #region BlockPlayer / BlockUI
+
+ [Test]
+ public void BlockPlayer_DisablesAllPlayerActions()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+
+ var scope = _service.BlockPlayer();
+ Assert.IsFalse(_service.Player.Move.enabled);
+ Assert.IsFalse(_service.Player.Jump.enabled);
+ Assert.IsFalse(_service.Player.Attack.enabled);
+ Assert.IsTrue(_service.UI.Click.enabled, "UI 側には影響しないはず");
+
+ scope.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ Assert.IsTrue(_service.Player.Jump.enabled);
+ Assert.IsTrue(_service.Player.Attack.enabled);
+ }
+
+ [Test]
+ public void BlockUI_DisablesAllUIActions()
+ {
+ _service.Startup();
+
+ var scope = _service.BlockUI();
+ Assert.IsFalse(_service.UI.Cancel.enabled);
+ Assert.IsFalse(_service.UI.Submit.enabled);
+
+ scope.Dispose();
+ Assert.IsTrue(_service.UI.Cancel.enabled);
+ Assert.IsTrue(_service.UI.Submit.enabled);
+ }
+
+ [Test]
+ public void BlockPlayer_OverlapsWithBlockAction()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+
+ var mapScope = _service.BlockPlayer();
+ var actionScope = _service.BlockInputAction(_service.Player.Move);
+
+ mapScope.Dispose();
+ Assert.IsFalse(_service.Player.Move.enabled, "個別ブロックが残る Move は無効のままのはず");
+ Assert.IsTrue(_service.Player.Jump.enabled, "個別ブロックのない Jump は復帰するはず");
+
+ actionScope.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ [Test]
+ public void DialogPattern_NestedScopes()
+ {
+ // Horror ダイアログの実利用パターン: 親ダイアログ表示中に子ダイアログを開閉する
+ _service.Startup();
+ _service.EnablePlayer();
+
+ var outerPlayer = _service.BlockPlayer();
+
+ var innerPlayer = _service.BlockPlayer();
+
+ innerPlayer.Dispose();
+
+ Assert.IsFalse(_service.Player.Move.enabled, "親ダイアログのブロックが維持されるはず");
+ Assert.IsTrue(_service.UI.Submit.enabled, "ブロック対象外の UI 入力は生きているはず");
+
+ outerPlayer.Dispose();
+
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ #endregion
+
+ #region Enable/Disable の参照カウント
+
+ [Test]
+ public void Disable_Twice_RequiresEnableTwice()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+
+ _service.DisablePlayer();
+ _service.DisablePlayer();
+
+ _service.EnablePlayer();
+ Assert.IsFalse(_service.Player.Move.enabled, "Disable 2回に対し Enable 1回では無効のままのはず");
+
+ _service.EnablePlayer();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ [Test]
+ public void Enable_OverCall_ClampsAtZero()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+ _service.EnablePlayer();
+ _service.EnablePlayer();
+
+ // カウントが負に沈んでいれば以降の Block が 1 対 1 で効かなくなるはず
+ var scope = _service.BlockPlayer();
+ Assert.IsFalse(_service.Player.Move.enabled, "過剰 Enable 後もブロックは 1 回で効くはず");
+
+ scope.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ [Test]
+ public void Disable_And_Block_ShareLedger()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+
+ _service.DisablePlayer();
+ var scope = _service.BlockPlayer();
+
+ _service.EnablePlayer();
+ Assert.IsFalse(_service.Player.Move.enabled, "Block 分のカウントが残っている間は無効のままのはず");
+
+ scope.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ #endregion
+
+ #region forceEnable(リセット方式)
+
+ [Test]
+ public void Force_RecoversFromDrift()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+
+ // Enable/Disable の非対称呼び出しによるドリフトを模擬
+ _service.DisablePlayer();
+ _service.DisablePlayer();
+
+ _service.EnablePlayer(forceEnable: true);
+ Assert.IsTrue(_service.Player.Move.enabled, "force はカウントを無視して有効化するはず");
+
+ // 台帳が 0 にリセットされ、以降のブロックが 1 対 1 で機能する(ドリフト完治)
+ var scope = _service.BlockPlayer();
+ Assert.IsFalse(_service.Player.Move.enabled);
+ scope.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ [Test]
+ public void Force_IsMapScoped()
+ {
+ _service.Startup();
+ _service.EnablePlayer();
+
+ var uiScope = _service.BlockUI();
+ _service.EnablePlayer(forceEnable: true);
+
+ Assert.IsFalse(_service.UI.Click.enabled, "Player の force が UI 側のブロックへ影響しないはず");
+
+ uiScope.Dispose();
+ Assert.IsTrue(_service.UI.Click.enabled);
+ }
+
+ [Test]
+ public void Force_ThenStaleDispose_StaysEnabled()
+ {
+ // force 後に旧スコープの Dispose が着弾しても 0 クランプで無害に処理される境界挙動の固定
+ _service.Startup();
+ _service.EnablePlayer();
+
+ var stale = _service.BlockPlayer();
+ _service.EnablePlayer(forceEnable: true);
+ Assert.IsTrue(_service.Player.Move.enabled);
+
+ stale.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled, "リセット済み台帳への旧 Dispose は無害のはず");
+
+ var scope = _service.BlockPlayer();
+ Assert.IsFalse(_service.Player.Move.enabled);
+ scope.Dispose();
+ Assert.IsTrue(_service.Player.Move.enabled);
+ }
+
+ #endregion
+
+ #region 実入力の遮断
+
+ [UnityTest]
+ public IEnumerator BlockedAction_DoesNotFirePerformed()
+ {
+ var keyboard = InputSystem.AddDevice();
+ _service.Startup();
+ _service.EnablePlayer();
+ yield return null;
+
+ bool fired = false;
+ _service.Player.Jump.performed += _ => fired = true;
+
+ var scope = _service.BlockInputAction(_service.Player.Jump);
+ Press(keyboard.spaceKey);
+ yield return null;
+ Release(keyboard.spaceKey);
+ yield return null;
+
+ Assert.IsFalse(fired, "ブロック中は performed が発火しないはず");
+
+ scope.Dispose();
+ Press(keyboard.spaceKey);
+ yield return null;
+
+ Assert.IsTrue(fired, "ブロック解除後は performed が発火するはず");
+ }
+
+ [UnityTest]
+ public IEnumerator BlockPlayer_DoesNotAffectUIInput()
+ {
+ var mouse = InputSystem.AddDevice();
+ _service.Startup();
+ _service.EnablePlayer();
+ yield return null;
+
+ bool menuFired = false;
+ _service.UI.Click.performed += _ => menuFired = true;
+
+ using (_service.BlockPlayer())
+ {
+ Press(mouse.leftButton);
+ yield return null;
+ }
+
+ Assert.IsTrue(menuFired, "BlockPlayer 中も UI 入力は通るはず");
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceBlockPlayModeTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceBlockPlayModeTests.cs.meta
new file mode 100644
index 000000000..de34507b8
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceBlockPlayModeTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 3fc212f9996a1ae41af84d11b50bda2b
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceRebindPlayModeTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceRebindPlayModeTests.cs
index fdaca8531..b57d0d147 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceRebindPlayModeTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/PlayMode/InputSystemServiceRebindPlayModeTests.cs
@@ -35,10 +35,10 @@ public override void TearDown()
}
private InputAction PlayerAction(IInputSystemService service, string name)
- => service.InputActionAsset.FindActionMap("Player").FindAction(name);
+ => service.FindInputAction(InputActionMaps.Player, name);
private int FirstIndex(InputAction action, string scheme)
- => InputSystemService.ResolveSchemeBindingIndices(scheme, action)[0];
+ => InputSystemService.GetBindingIndicesByControlScheme(scheme, action)[0].Index;
#region Save / Load
@@ -103,7 +103,7 @@ public IEnumerator ResetBinding_RemovesOverride_RestoresDefault()
jump.ApplyBindingOverride(idx, "/j");
Assert.That(jump.bindings[idx].effectivePath, Is.EqualTo("/j"));
- _service.ResetBinding(InputControlSchemes.KeyboardAndMouse, "Jump");
+ _service.ResetBinding(InputControlSchemes.KeyboardAndMouse, InputActionMaps.Player, "Jump");
Assert.That(jump.bindings[idx].effectivePath, Is.EqualTo(defaultPath));
yield return null;
}
@@ -125,7 +125,7 @@ public IEnumerator ResetSchemeBindings_RemovesOnlyTargetScheme()
jump.ApplyBindingOverride(padIdx, "/buttonNorth");
// Gamepad のみリセット
- _service.ResetSchemeBindings(InputControlSchemes.Gamepad);
+ _service.ResetControlSchemeBindings(InputControlSchemes.Gamepad);
Assert.That(jump.bindings[padIdx].effectivePath, Is.EqualTo(padDefault),
"対象スキーム(Gamepad)は既定へ戻る");
@@ -133,7 +133,7 @@ public IEnumerator ResetSchemeBindings_RemovesOnlyTargetScheme()
"他スキーム(KBM)の override は保持される");
// KBM もリセットすると既定へ戻る
- _service.ResetSchemeBindings(InputControlSchemes.KeyboardAndMouse);
+ _service.ResetControlSchemeBindings(InputControlSchemes.KeyboardAndMouse);
Assert.That(jump.bindings[kbmIdx].effectivePath, Is.EqualTo(kbmDefault));
yield return null;
}
@@ -149,8 +149,8 @@ public IEnumerator StartRebind_Completes_AppliesNewBinding()
yield return null;
var completed = false;
- var op = _service.StartRebind(InputControlSchemes.KeyboardAndMouse, "Jump", null,
- _ => completed = true,
+ var op = _service.StartRebinding(InputControlSchemes.KeyboardAndMouse, InputActionMaps.Player, "Jump", null,
+ () => completed = true,
() => { });
yield return null;
@@ -185,8 +185,8 @@ public IEnumerator StartRebind_DuplicateKey_Swaps()
attack.ApplyBindingOverride(attackIdx, "/j");
var completed = false;
- var op = _service.StartRebind(InputControlSchemes.KeyboardAndMouse, "Jump", null,
- _ => completed = true,
+ var op = _service.StartRebinding(InputControlSchemes.KeyboardAndMouse, InputActionMaps.Player, "Jump", null,
+ () => completed = true,
() => { });
yield return null;
@@ -209,6 +209,8 @@ public IEnumerator StartRebind_DuplicateKey_Swaps()
public IEnumerator StartRebind_Dispose_CancelsAndRestoresEnabled()
{
_service.Startup();
+ // StartRebinding は開始前の enabled 状態へ復元する仕様のため、復元対象となる有効状態を事前に作る
+ _service.EnablePlayer();
yield return null;
var jump = PlayerAction(_service, "Jump");
@@ -216,8 +218,8 @@ public IEnumerator StartRebind_Dispose_CancelsAndRestoresEnabled()
var originalPath = jump.bindings[idx].effectivePath;
var canceled = false;
- var op = _service.StartRebind(InputControlSchemes.KeyboardAndMouse, "Jump", null,
- _ => { },
+ var op = _service.StartRebinding(InputControlSchemes.KeyboardAndMouse, InputActionMaps.Player, "Jump", null,
+ () => { },
() => canceled = true);
yield return null;
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/Shared/HierarchicalStateMachineTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/Shared/HierarchicalStateMachineTests.cs
new file mode 100644
index 000000000..99a3d73f3
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/Shared/HierarchicalStateMachineTests.cs
@@ -0,0 +1,554 @@
+using System;
+using System.Collections.Generic;
+using Game.Library.Shared;
+using NUnit.Framework;
+
+namespace Game.Editor.Tests
+{
+ ///
+ /// の単体テスト。
+ /// フラット とのパリティ、任意ステート遷移、
+ /// 階層(初期子降下 / LCA 遷移 / 自己遷移 / IsInState)を検証する。
+ ///
+ [TestFixture]
+ public class HierarchicalStateMachineTests
+ {
+ #region Test Context and States
+
+ private class TestContext
+ {
+ public List CallLog { get; } = new();
+ public int Value { get; set; }
+ }
+
+ private enum TestEvent
+ {
+ ToA,
+ ToB,
+ ToC,
+ ToChase,
+ ToAttack,
+ Stagger,
+ Recover,
+ Die,
+ Precedence
+ }
+
+ /// ライフサイクルを型名付きで CallLog に記録する共通基底(テスト用)。
+ private abstract class LoggingState : HierarchicalState
+ {
+ public override void Enter() => Context.CallLog.Add($"{GetType().Name}.Enter");
+ public override void Update() => Context.CallLog.Add($"{GetType().Name}.Update");
+ public override void FixedUpdate() => Context.CallLog.Add($"{GetType().Name}.FixedUpdate");
+ public override void LateUpdate() => Context.CallLog.Add($"{GetType().Name}.LateUpdate");
+ public override void Exit() => Context.CallLog.Add($"{GetType().Name}.Exit");
+ }
+
+ // フラット(親を持たない)ステート
+ private class StateA : LoggingState { }
+ private class StateB : LoggingState { }
+ private class StateC : LoggingState { }
+
+ // 階層ステート:
+ // Alive(複合)
+ // ├ Patrol(Alive の初期子)
+ // ├ Combat(複合)
+ // │ ├ Chase(Combat の初期子)
+ // │ └ Attack
+ // └ Stagger
+ // Dead(ルート。Alive の兄弟)
+ private class Alive : LoggingState { }
+ private class Patrol : LoggingState { }
+ private class Combat : LoggingState { }
+ private class Chase : LoggingState { }
+ private class Attack : LoggingState { }
+ private class Stagger : LoggingState { }
+ private class Dead : LoggingState { }
+
+ /// Enter 内で遷移をリクエストするステート(Enter 内 Transition の検証用)。
+ private class EnterTransitionState : LoggingState
+ {
+ public override void Enter()
+ {
+ base.Enter();
+ StateMachine.Transition(TestEvent.ToB);
+ }
+ }
+
+ // int イベント版の検証用
+ private class IntStateA : HierarchicalState { }
+ private class IntStateB : HierarchicalState { }
+
+ ///
+ /// 標準的な階層ツリーと遷移を組んだステートマシンを構築して返す。
+ ///
+ private static HierarchicalStateMachine BuildHierarchy(TestContext context)
+ {
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddSubState(isInitial: true);
+ sm.AddSubState();
+ sm.AddSubState();
+ sm.AddSubState(isInitial: true);
+ sm.AddSubState();
+
+ sm.AddTransition(TestEvent.ToChase); // 複合ターゲット(初期子 Chase へ降下)
+ sm.AddTransition(TestEvent.ToAttack); // Combat 内の兄弟間
+ sm.AddTransition(TestEvent.ToChase); // Combat 内の兄弟間
+ sm.AddTransition(TestEvent.Stagger); // 親レベル(全 Alive 子孫から発火)
+ sm.AddTransition(TestEvent.Recover);
+ sm.AddTransition(TestEvent.Die); // 任意ステート → Dead
+
+ sm.SetInitState();
+ return sm;
+ }
+
+ #endregion
+
+ #region Flat Parity
+
+ [Test]
+ public void Constructor_WithContext_SetsContext()
+ {
+ var context = new TestContext { Value = 42 };
+ var sm = new HierarchicalStateMachine(context);
+
+ Assert.That(sm.Context, Is.EqualTo(context));
+ Assert.That(sm.Context.Value, Is.EqualTo(42));
+ }
+
+ [Test]
+ public void SetInitState_ThenUpdate_EntersInitialState()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+ sm.Update();
+
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void FromToTransition_MovesToTargetState()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+ sm.Update();
+
+ Assert.That(sm.Transition(TestEvent.ToB), Is.EqualTo(StateEventResult.Succeeded));
+ sm.Update();
+
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void Transition_ExecutesExitThenEnter()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+ sm.Update();
+ context.CallLog.Clear();
+
+ sm.Transition(TestEvent.ToB);
+ sm.Update();
+
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "StateA.Exit", "StateB.Enter" }));
+ }
+
+ [Test]
+ public void Update_InvokesCurrentStateUpdate()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.SetInitState();
+ sm.Update();
+ context.CallLog.Clear();
+
+ sm.Update();
+
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "StateA.Update" }));
+ }
+
+ [Test]
+ public void FixedUpdate_And_LateUpdate_InvokeLeafOnly()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update(); // Alive -> Patrol
+ sm.Transition(TestEvent.ToChase);
+ sm.Update(); // -> Combat/Chase
+ context.CallLog.Clear();
+
+ sm.FixedUpdate();
+ sm.LateUpdate();
+
+ // 親(Alive/Combat)ではなく葉(Chase)のみ駆動される
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Chase.FixedUpdate", "Chase.LateUpdate" }));
+ }
+
+ [Test]
+ public void Transition_InvalidEvent_ReturnsFailed()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+ sm.Update();
+
+ Assert.That(sm.Transition(TestEvent.ToC), Is.EqualTo(StateEventResult.Failed));
+ }
+
+ [Test]
+ public void Transition_WhenPendingAlreadySet_ReturnsWaiting()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.AddTransition(TestEvent.ToC);
+ sm.SetInitState();
+ sm.Update();
+
+ sm.Transition(TestEvent.ToB);
+ Assert.That(sm.Transition(TestEvent.ToC), Is.EqualTo(StateEventResult.Waiting));
+ }
+
+ [Test]
+ public void Transition_BeforeProcessing_Throws()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+
+ Assert.Throws(() => sm.Transition(TestEvent.ToB));
+ }
+
+ [Test]
+ public void IsProcessing_ReflectsLifecycle()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.SetInitState();
+ Assert.That(sm.IsProcessing(), Is.False);
+
+ sm.Update();
+ Assert.That(sm.IsProcessing(), Is.True);
+ }
+
+ [Test]
+ public void AddTransition_DuringProcessing_Throws()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+ sm.SetInitState();
+ sm.Update();
+
+ Assert.Throws(() => sm.AddTransition(TestEvent.ToB));
+ }
+
+ [Test]
+ public void AddTransition_DuplicateFromTo_Throws()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+
+ Assert.Throws(() => sm.AddTransition(TestEvent.ToB));
+ }
+
+ [Test]
+ public void Reset_ClearsProcessingAndAllowsRestart()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+ sm.Update();
+ sm.Transition(TestEvent.ToB);
+ sm.Update();
+
+ sm.Reset();
+ Assert.That(sm.IsProcessing(), Is.False);
+
+ sm.SetInitState();
+ sm.Update();
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void Transition_RequestedInEnter_IsProcessedSameUpdate()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.SetInitState();
+ sm.Update();
+
+ Assert.That(sm.IsCurrentState(), Is.True);
+ Assert.That(context.CallLog, Is.EqualTo(new[]
+ {
+ "EnterTransitionState.Enter",
+ "EnterTransitionState.Exit",
+ "StateB.Enter"
+ }));
+ }
+
+ [Test]
+ public void IntEventStateMachine_Transitions()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(1);
+ sm.SetInitState();
+ sm.Update();
+
+ Assert.That(sm.Transition(1), Is.EqualTo(StateEventResult.Succeeded));
+ sm.Update();
+
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ #endregion
+
+ #region Any-State Transition
+
+ [Test]
+ public void AnyTransition_FiresFromUnrelatedState()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.ToB);
+ sm.AddTransition(TestEvent.Die); // 任意ステート → StateC
+ sm.SetInitState();
+ sm.Update();
+
+ sm.Transition(TestEvent.ToB);
+ sm.Update(); // StateB へ
+
+ Assert.That(sm.Transition(TestEvent.Die), Is.EqualTo(StateEventResult.Succeeded));
+ sm.Update();
+
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void AnyTransition_TakesPrecedenceOverFromTo()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ // 同一イベントに from-to(StateA->StateB)と 任意(->StateC)を登録
+ sm.AddTransition(TestEvent.Precedence);
+ sm.AddTransition(TestEvent.Precedence);
+ sm.SetInitState();
+ sm.Update();
+
+ sm.Transition(TestEvent.Precedence);
+ sm.Update();
+
+ // 任意ステート遷移が優先されて StateC になる
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void AddTransition_DuplicateAny_Throws()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddTransition(TestEvent.Die);
+
+ Assert.Throws(() => sm.AddTransition(TestEvent.Die));
+ }
+
+ #endregion
+
+ #region Hierarchy
+
+ [Test]
+ public void InitialState_DescendsIntoInitialSubState()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+
+ sm.Update();
+
+ // Alive(初期子 Patrol)へ入ると親 → 子の順に Enter される
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Alive.Enter", "Patrol.Enter" }));
+ Assert.That(sm.IsCurrentState(), Is.True);
+ Assert.That(sm.IsInState(), Is.True);
+ }
+
+ [Test]
+ public void CompositeTarget_ResolvesToInitialSubState_AndKeepsSharedAncestor()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update(); // -> Patrol
+ context.CallLog.Clear();
+
+ sm.Transition(TestEvent.ToChase); // Patrol -> Combat(初期子 Chase へ降下)
+ sm.Update();
+
+ // 共通祖先 Alive は再入されない。Patrol.Exit → Combat.Enter → Chase.Enter
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Patrol.Exit", "Combat.Enter", "Chase.Enter" }));
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void SiblingTransition_DoesNotReenterSharedParent()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+ sm.Transition(TestEvent.ToChase);
+ sm.Update(); // -> Combat/Chase
+ context.CallLog.Clear();
+
+ sm.Transition(TestEvent.ToAttack); // Chase -> Attack(Combat 内の兄弟)
+ sm.Update();
+
+ // Combat / Alive は保持(Chase.Exit → Attack.Enter のみ)
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Chase.Exit", "Attack.Enter" }));
+ Assert.That(sm.IsInState(), Is.True);
+ }
+
+ [Test]
+ public void ParentLevelTransition_FiresFromDeepDescendant()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+ sm.Transition(TestEvent.ToChase);
+ sm.Update(); // -> Combat/Chase(Alive の孫)
+ context.CallLog.Clear();
+
+ // Alive レベルに登録した Stagger 遷移が、孫 Chase から発火する
+ Assert.That(sm.Transition(TestEvent.Stagger), Is.EqualTo(StateEventResult.Succeeded));
+ sm.Update();
+
+ // Chase・Combat を抜けて(Alive は保持)Stagger へ
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Chase.Exit", "Combat.Exit", "Stagger.Enter" }));
+ Assert.That(sm.IsCurrentState(), Is.True);
+ Assert.That(sm.IsInState(), Is.True);
+ }
+
+ [Test]
+ public void AnyTransition_FromDeepState_ExitsAllAncestors()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+ sm.Transition(TestEvent.ToChase);
+ sm.Update(); // -> Combat/Chase
+ context.CallLog.Clear();
+
+ sm.Transition(TestEvent.Die); // 任意 → Dead(ルート)
+ sm.Update();
+
+ // 葉から根まで全て Exit してから Dead を Enter
+ Assert.That(context.CallLog, Is.EqualTo(new[]
+ {
+ "Chase.Exit",
+ "Combat.Exit",
+ "Alive.Exit",
+ "Dead.Enter"
+ }));
+ Assert.That(sm.IsCurrentState(), Is.True);
+ Assert.That(sm.IsInState(), Is.False);
+ }
+
+ [Test]
+ public void SelfTransition_ReentersState()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+ sm.Transition(TestEvent.Stagger);
+ sm.Update(); // Patrol -> Stagger
+ context.CallLog.Clear();
+
+ // Stagger 中に再度 Stagger(Alive レベル遷移が Stagger 自身へ)→ 自己遷移で Exit→Enter
+ sm.Transition(TestEvent.Stagger);
+ sm.Update();
+
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Stagger.Exit", "Stagger.Enter" }));
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void StaggerRecover_ReturnsToSibling()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+ sm.Transition(TestEvent.Stagger);
+ sm.Update(); // -> Stagger
+ context.CallLog.Clear();
+
+ sm.Transition(TestEvent.Recover); // Stagger -> Patrol(Alive 内の兄弟)
+ sm.Update();
+
+ Assert.That(context.CallLog, Is.EqualTo(new[] { "Stagger.Exit", "Patrol.Enter" }));
+ Assert.That(sm.IsCurrentState(), Is.True);
+ }
+
+ [Test]
+ public void IsInState_ChecksAncestors()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+ sm.Transition(TestEvent.ToChase);
+ sm.Update(); // -> Combat/Chase
+
+ Assert.That(sm.IsCurrentState(), Is.True);
+ Assert.That(sm.IsCurrentState(), Is.False); // 葉ではない
+ Assert.That(sm.IsInState(), Is.True);
+ Assert.That(sm.IsInState(), Is.True);
+ Assert.That(sm.IsInState(), Is.True);
+ Assert.That(sm.IsInState(), Is.False);
+ }
+
+ [Test]
+ public void AddSubState_DuplicateInitial_Throws()
+ {
+ var context = new TestContext();
+ var sm = new HierarchicalStateMachine(context);
+
+ sm.AddSubState(isInitial: true);
+
+ Assert.Throws(() => sm.AddSubState(isInitial: true));
+ }
+
+ [Test]
+ public void AddSubState_DuringProcessing_Throws()
+ {
+ var context = new TestContext();
+ var sm = BuildHierarchy(context);
+ sm.Update();
+
+ Assert.Throws(() => sm.AddSubState());
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/Shared/HierarchicalStateMachineTests.cs.meta b/src/Game.Client/Assets/Programs/Editor/Tests/Shared/HierarchicalStateMachineTests.cs.meta
new file mode 100644
index 000000000..d2adcb0f7
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/Shared/HierarchicalStateMachineTests.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 1cb596b800850e643a7520a2bede356e
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Editor/Tests/Shared/StateMachineTests.cs b/src/Game.Client/Assets/Programs/Editor/Tests/Shared/StateMachineTests.cs
index 4cdf56ce5..b3d683f92 100644
--- a/src/Game.Client/Assets/Programs/Editor/Tests/Shared/StateMachineTests.cs
+++ b/src/Game.Client/Assets/Programs/Editor/Tests/Shared/StateMachineTests.cs
@@ -136,15 +136,6 @@ public override void Exit()
}
}
- private class ForceTransitionAllowedStateMachine : StateMachine
- {
- protected override bool AllowForceTransition => true;
-
- public ForceTransitionAllowedStateMachine(TestContext context) : base(context)
- {
- }
- }
-
#endregion
#region Constructor Tests
@@ -353,52 +344,6 @@ public void Transition_ExecutesStateLifecycle()
#endregion
- #region ForceTransition Tests
-
- [Test]
- public void ForceTransition_WhenNotAllowed_ThrowsException()
- {
- var context = new TestContext();
- var stateMachine = new StateMachine(context);
-
- stateMachine.SetInitState();
- stateMachine.Update();
-
- Assert.Throws(() => stateMachine.ForceTransition());
- }
-
- [Test]
- public void ForceTransition_WhenAllowed_TransitionsToState()
- {
- var context = new TestContext();
- var stateMachine = new ForceTransitionAllowedStateMachine(context);
-
- stateMachine.SetInitState();
- stateMachine.Update();
-
- stateMachine.ForceTransition();
- stateMachine.Update();
-
- Assert.That(stateMachine.IsCurrentState(), Is.True);
- }
-
- [Test]
- public void ForceTransition_WhenAllowed_BypassesTransitionTable()
- {
- var context = new TestContext();
- var stateMachine = new ForceTransitionAllowedStateMachine(context);
-
- stateMachine.SetInitState();
- stateMachine.Update();
-
- stateMachine.ForceTransition();
- stateMachine.Update();
-
- Assert.That(stateMachine.IsCurrentState(), Is.True);
- }
-
- #endregion
-
#region IsCurrentState Tests
[Test]
@@ -534,20 +479,6 @@ public void Update_WithExceptionInEnter_ThrowsAndResetsState()
Assert.That(stateMachine.IsProcessing(), Is.False);
}
- [Test]
- public void Update_WithExceptionInExit_ThrowsException()
- {
- var context = new TestContext();
- var stateMachine = new ForceTransitionAllowedStateMachine(context);
-
- stateMachine.SetInitState();
- stateMachine.Update();
-
- stateMachine.ForceTransition();
-
- Assert.Throws(() => stateMachine.Update());
- }
-
#endregion
#region FixedUpdate Tests
diff --git a/src/Game.Client/Assets/Programs/Runtime/App/Title/AppTitleSceneComponent.cs b/src/Game.Client/Assets/Programs/Runtime/App/Title/AppTitleSceneComponent.cs
index b0a582c00..76dcaf413 100644
--- a/src/Game.Client/Assets/Programs/Runtime/App/Title/AppTitleSceneComponent.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/App/Title/AppTitleSceneComponent.cs
@@ -3,6 +3,7 @@
using Cysharp.Threading.Tasks;
using Game.App.Services;
using Game.Library.Shared.Enums;
+using Game.Shared.Bootstrap;
using Game.Shared.Enums;
using Game.Shared.Services;
using Game.Shared.Services.RemoteAsset;
@@ -61,6 +62,8 @@ public void Initialize(IAppServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
+ ApplicationEvents.ShowCursor(); // 現在、UIToolkitではマウス操作オンリーのため
+
SetupUIElements();
BindButtons();
ShowPanel(_initialPanel);
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/AssemblyInfo.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/AssemblyInfo.cs
new file mode 100644
index 000000000..ea15869b1
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/AssemblyInfo.cs
@@ -0,0 +1,6 @@
+using System.Runtime.CompilerServices;
+
+// Game.Tests.MVC / Game.Tests.PlayMode から Game.MVC.Core アセンブリの internal 型を直接利用できるようにする。
+// テスト専用の可視性拡張であり、上記以外の外部アセンブリからは引き続き internal として不可視
+[assembly: InternalsVisibleTo("Game.Tests.MVC")]
+[assembly: InternalsVisibleTo("Game.Tests.PlayMode")]
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/AssemblyInfo.cs.meta b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/AssemblyInfo.cs.meta
new file mode 100644
index 000000000..3b63d43a5
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/AssemblyInfo.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 17c43fd70cf984a408539fe692050692
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageKey.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageKey.cs
index 47a0f9b75..7f0fc9616 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageKey.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageKey.cs
@@ -4,17 +4,10 @@ public partial struct MessageKey
{
private struct Offset
{
- public const int GameScene = 100;
public const int Player = 500;
public const int UI = 600;
}
- public struct GameScene
- {
- public const int FadeIn = Offset.GameScene + 0;
- public const int FadeOut = Offset.GameScene + 1;
- }
-
public struct Player
{
public const int PlayAnimation = Offset.Player + 0;
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageSignals.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageSignals.cs
new file mode 100644
index 000000000..60e3470ce
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageSignals.cs
@@ -0,0 +1,11 @@
+namespace Game.Core.MessagePipe
+{
+ public static class MessageSignals
+ {
+ public static class GameScene
+ {
+ public readonly struct FadeIn { }
+ public readonly struct FadeOut { }
+ }
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageSignals.cs.meta b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageSignals.cs.meta
new file mode 100644
index 000000000..acc0f8c34
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/MessagePipe/MessageSignals.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b6755a1751284ca88ff559e6aea1c630
+timeCreated: 1784568121
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameScene.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameScene.cs
index 12cb6250b..0c99c068f 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameScene.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameScene.cs
@@ -79,6 +79,8 @@ public interface IGameSceneArgHandler
public interface IGameSceneResult
{
+ bool TrySetCanceled();
+ bool TrySetException(Exception e);
}
public interface IGameSceneResult : IGameSceneResult
@@ -87,15 +89,7 @@ public interface IGameSceneResult : IGameSceneResult
UniTaskCompletionSource ResultTcs { get; set; }
- bool TrySetResult(TResult result)
- {
- Result = result;
- return ResultTcs?.TrySetResult(result) ?? false;
- }
-
- bool TrySetCanceled() => ResultTcs?.TrySetCanceled() ?? false;
-
- bool TrySetException(Exception e) => ResultTcs?.TrySetException(e) ?? false;
+ bool TrySetResult(TResult result);
}
public interface ICompositeDisposable
@@ -132,6 +126,7 @@ public override async UniTask LoadAsset()
SceneComponent = GetSceneComponent();
if (Disposables.IsDisposed)
Disposables = new CompositeDisposable();
+ await SceneComponent.PreInitialize();
}
public override async UniTask Startup()
@@ -183,24 +178,19 @@ public abstract class GamePrefabScene : GameSce
where TGameScene : IGameScene
where TGameSceneComponent : IGameSceneComponent
{
- private IAddressableAssetService _assetService;
- protected IAddressableAssetService AssetService => _assetService ??= GameServiceManager.Resolve();
-
- private IMessagePipeService _messagePipeService;
- protected IMessagePipeService MessagePipeService => _messagePipeService ??= GameServiceManager.Resolve();
+ private readonly IAddressableAssetService _assetService = GameServiceManager.Resolve();
+ private readonly IMessagePipeService _messagePipeService = GameServiceManager.Resolve();
private GameObject _asset;
private GameObject _instance;
- private CanvasGroup _canvasGroup;
protected override async UniTask LoadScene()
{
- _asset = await AssetService.LoadAssetAsync(AssetPathOrAddress);
+ _asset = await _assetService.LoadAssetAsync(AssetPathOrAddress);
Scene scene = GameSceneHelper.GetGameRootScene();
_instance = scene.IsValid()
? UnityEngine.Object.Instantiate(_asset, new InstantiateParameters { scene = scene })
: UnityEngine.Object.Instantiate(_asset);
- _instance.TryGetComponent(out _canvasGroup);
}
protected override UniTask UnloadScene()
@@ -209,8 +199,8 @@ protected override UniTask UnloadScene()
{
_instance.SafeDestroy();
_instance = null;
+ _assetService.Release(_asset);
_asset = null;
- _canvasGroup = null;
}
return UniTask.CompletedTask;
@@ -220,33 +210,10 @@ protected override TGameSceneComponent GetSceneComponent()
=> GameSceneHelper.GetSceneComponent(_instance);
public async UniTask FadeInAsync(float duration = 0.3f)
- {
- if (_canvasGroup != null)
- {
- _canvasGroup.alpha = 0f;
- _canvasGroup.interactable = false;
- _canvasGroup.blocksRaycasts = false;
- _instance.SetActive(true);
- await _canvasGroup.DOFade(1f, duration).SetUpdate(true).ToUniTask();
- _canvasGroup.interactable = true;
- _canvasGroup.blocksRaycasts = true;
- }
-
- await MessagePipeService.PublishAsync(MessageKey.GameScene.FadeIn, true);
- }
+ => await _messagePipeService.PublishAsync(new MessageSignals.GameScene.FadeIn());
public async UniTask FadeOutAsync(float duration = 0.3f)
- {
- if (_canvasGroup != null)
- {
- _canvasGroup.interactable = false;
- _canvasGroup.blocksRaycasts = false;
- await _canvasGroup.DOFade(0f, duration).SetUpdate(true).ToUniTask();
- _instance.SetActive(false);
- }
-
- await MessagePipeService.PublishAsync(MessageKey.GameScene.FadeOut, true);
- }
+ => await _messagePipeService.PublishAsync(new MessageSignals.GameScene.FadeOut());
}
// コンポーネント付きのUnityScene
@@ -254,8 +221,7 @@ public abstract class GameUnityScene : GameScen
where TGameScene : IGameScene
where TGameSceneComponent : IGameSceneComponent
{
- private IAddressableAssetService _assetService;
- protected IAddressableAssetService AssetService => _assetService ??= GameServiceManager.Resolve();
+ private readonly IAddressableAssetService _assetService = GameServiceManager.Resolve();
protected virtual LoadSceneMode LoadSceneMode => LoadSceneMode.Additive;
@@ -263,13 +229,13 @@ public abstract class GameUnityScene : GameScen
protected override async UniTask LoadScene()
{
- _instance = await AssetService.LoadSceneAsync(AssetPathOrAddress, LoadSceneMode, activateOnLoad: true);
+ _instance = await _assetService.LoadSceneAsync(AssetPathOrAddress, LoadSceneMode, activateOnLoad: true);
if (LoadSceneMode is LoadSceneMode.Additive) SceneManager.SetActiveScene(_instance.Scene);
}
protected override async UniTask UnloadScene()
{
- await AssetService.UnloadSceneAsync(_instance);
+ await _assetService.UnloadSceneAsync(_instance);
}
protected override TGameSceneComponent GetSceneComponent()
@@ -281,8 +247,7 @@ public abstract class GameDialogScene : GameScene _assetService ??= GameServiceManager.Resolve();
+ private readonly IAddressableAssetService _assetService = GameServiceManager.Resolve();
public TResult Result { get; set; }
public UniTaskCompletionSource ResultTcs { get; set; }
@@ -290,26 +255,9 @@ public abstract class GameDialogScene : GameScene(AssetPathOrAddress);
+ _asset = await _assetService.LoadAssetAsync(AssetPathOrAddress);
Scene scene = GameSceneHelper.GetGameRootScene();
_instance = scene.IsValid()
? UnityEngine.Object.Instantiate(_asset, new InstantiateParameters { scene = scene })
@@ -322,6 +270,7 @@ protected override UniTask UnloadScene()
{
_instance.SafeDestroy();
_instance = null;
+ _assetService.Release(_asset);
_asset = null;
}
@@ -341,9 +290,10 @@ public bool TrySetResult(TResult result)
/// ダイアログをキャンセルして閉じる
///
public bool TrySetCanceled()
- {
- return ResultTcs?.TrySetCanceled() ?? false;
- }
+ => ResultTcs?.TrySetCanceled() ?? false;
+
+ public bool TrySetException(Exception e)
+ => ResultTcs?.TrySetException(e) ?? false;
protected override TComponent GetSceneComponent()
=> GameSceneHelper.GetSceneComponent(_instance);
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameSceneComponent.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameSceneComponent.cs
index 078caa06b..3ea8d5484 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameSceneComponent.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Scenes/GameSceneComponent.cs
@@ -1,4 +1,3 @@
-using System;
using Cysharp.Threading.Tasks;
using Game.Core.Services;
using R3;
@@ -8,6 +7,8 @@ namespace Game.MVC.Core.Scenes
{
public interface IGameSceneComponent : ICompositeDisposable
{
+ UniTask PreInitialize() => UniTask.CompletedTask;
+
UniTask Startup() => UniTask.CompletedTask;
UniTask Ready() => UniTask.CompletedTask;
@@ -22,11 +23,17 @@ public interface IGameSceneComponent : ICompositeDisposable
[RequireComponent(typeof(CanvasGroup))]
public abstract class GameSceneComponent : MonoBehaviour, IGameSceneComponent
{
- private readonly IInputSystemService _inputService = GameServiceManager.Resolve();
+ private IInputSystemService _inputService;
private GameObject _selectedGameObject;
public CompositeDisposable Disposables { get; } = new();
+ public virtual UniTask PreInitialize()
+ {
+ _inputService = GameServiceManager.Resolve();
+ return UniTask.CompletedTask;
+ }
+
public virtual UniTask Startup()
{
return UniTask.CompletedTask;
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameSceneService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameSceneService.cs
index 7dde9a27b..ae2d02e82 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameSceneService.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameSceneService.cs
@@ -3,6 +3,7 @@
using Game.MVC.Core.Constants;
using Game.MVC.Core.Enums;
using Game.MVC.Core.Scenes;
+using Game.Shared.Services.Interfaces;
namespace Game.Core.Services
{
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameService.cs
deleted file mode 100644
index 4b9041b52..000000000
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameService.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace Game.Core.Services
-{
- ///
- /// ゲームサービスの基本インターフェース
- /// サービスのライフサイクル管理(起動・終了)を提供
- ///
- public interface IGameService
- {
- ///
- /// サービスを起動する
- /// GameServiceManagerから呼び出され、サービスの初期化処理を行う
- ///
- void Startup()
- {
- }
-
- ///
- /// サービスを終了する
- /// GameServiceManagerから呼び出され、サービスのクリーンアップ処理を行う
- ///
- void Shutdown()
- {
- }
- }
-}
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameService.cs.meta b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameService.cs.meta
deleted file mode 100644
index f80837bcf..000000000
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IGameService.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: e15ef3af696dc4948b72344ddc58cc1e
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputActionIconService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputActionIconService.cs
new file mode 100644
index 000000000..dc8fdd1bb
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputActionIconService.cs
@@ -0,0 +1,14 @@
+using Cysharp.Threading.Tasks;
+using Game.Shared.Input;
+using Game.Shared.Services.Interfaces;
+using UnityEngine;
+
+namespace Game.Core.Services
+{
+ public interface IInputActionIconService : IGameService
+ {
+ UniTask LoadAsync();
+ void Unload();
+ Sprite GetSprite(InputBindingInfo info);
+ }
+}
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputActionIconService.cs.meta b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputActionIconService.cs.meta
new file mode 100644
index 000000000..e408ed0d8
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputActionIconService.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: aa4c020503e34462aef3691a814fb946
+timeCreated: 1784402773
\ No newline at end of file
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputSystemService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputSystemService.cs
index 3d3b2f084..bf71102c0 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputSystemService.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IInputSystemService.cs
@@ -1,6 +1,6 @@
using System;
-using System.Collections.Generic;
using Game.Shared.Input;
+using Game.Shared.Services.Interfaces;
using R3;
using UnityEngine;
using UnityEngine.InputSystem;
@@ -16,17 +16,17 @@ public interface IInputSystemService : IGameService
///
/// プレイヤー入力アクション(移動、ジャンプ、攻撃等)
///
- ProjectDefaultInputSystem.PlayerActions Player { get; }
+ ProjectInputActions.PlayerActions Player { get; }
///
/// UI入力アクション(メニュー操作、決定、キャンセル等)
///
- ProjectDefaultInputSystem.UIActions UI { get; }
+ ProjectInputActions.UIActions UI { get; }
///
- /// InputActionAsset 実体(このインスタンス上でゲームが動作する)
+ /// 現在のコントロールスキーマ
///
- InputActionAsset InputActionAsset { get; }
+ string ControlScheme { get; }
///
/// 操作スキーマ変更イベント
@@ -36,7 +36,7 @@ public interface IInputSystemService : IGameService
///
/// 操作デバイス切替イベント
///
- Observable<(InputDevice device, InputDeviceChange deviceChange)> OnDeviceChanged { get; }
+ Observable OnDeviceChanged { get; }
///
/// キーバインド変更イベント
@@ -46,7 +46,7 @@ public interface IInputSystemService : IGameService
///
/// プレイヤー入力を有効化する
///
- void EnablePlayer();
+ void EnablePlayer(bool forceEnable = false);
///
/// プレイヤー入力を無効化する(メニュー表示中等)
@@ -56,7 +56,7 @@ public interface IInputSystemService : IGameService
///
/// UI入力を有効化する
///
- void EnableUI();
+ void EnableUI(bool forceEnable = false);
///
/// UI入力を無効化する
@@ -68,6 +68,12 @@ public interface IInputSystemService : IGameService
///
IDisposable BlockPlayer();
+ ///
+ /// プレイヤー入力を一時無効化するスコープ
+ ///
+ /// 無効化せず無視する入力アクション
+ IDisposable BlockPlayer(params InputAction[] ignores);
+
///
/// UI入力を一時無効化するスコープ
///
@@ -76,7 +82,7 @@ public interface IInputSystemService : IGameService
///
/// 特定のインプットアクションを無効化するスコープ
///
- IDisposable BlockInputActions(params InputAction[] actions);
+ IDisposable BlockInputAction(InputAction action);
GameObject GetSelectedGameObject();
@@ -86,14 +92,13 @@ public interface IInputSystemService : IGameService
void ResolveControlScheme(GameObject selectedGameObject = null);
- ///
- /// 指定アクション・スキームの現在のバインド表示文字列を取得する。
- /// コンポジット(WASD 等)は各パートを "/" 区切りで結合して返す。
- /// を指定すると、コンポジットの該当パート1つのみの表示を返す。
- ///
- string GetBindingDisplayString(string scheme, string actionName, string partName = null);
+ InputAction FindInputAction(string actionMapName, string actionName);
+
+ InputBindingInfo[] GetBindingInfos(string scheme, string actionMapName, string actionName, string partName = null);
+
+ InputBindingInfo GetBindingInfo(string scheme, string actionMapName, string actionName, string partName = null, string fallbackScheme = null);
- string GetBindingDisplayString(InputAction action);
+ InputBindingInfo GetBindingInfo(InputAction action, string partName = null);
///
/// 指定アクション・スキームに対するインタラクティブリバインドを開始する。
@@ -102,11 +107,12 @@ public interface IInputSystemService : IGameService
/// 戻り値を Dispose すると進行中のリバインドをキャンセルする。
///
/// コントロールスキーム(Keyboard&Mouse / Gamepad)
- /// Player マップのアクション名
+ /// マップ名
+ /// マップのアクション名
/// コンポジットのパート名(up/down/left/right)。空=全体/単体
/// 確定後に呼ばれる。引数は確定後の表示文字列
/// キャンセル時に呼ばれる
- IDisposable StartRebind(string scheme, string actionName, string partName, Action onComplete, Action onCanceled);
+ IDisposable StartRebinding(string scheme, string actionMapName, string actionName, string partName, Action onComplete, Action onCanceled);
///
/// 現在のバインドオーバーライドを JSON 文字列として取得する(永続化用)。
@@ -122,7 +128,7 @@ public interface IInputSystemService : IGameService
/// 指定アクション・スキームのバインドオーバーライドを既定へ戻す。
/// を指定すると、コンポジットの該当パートのみを戻す。
///
- void ResetBinding(string scheme, string actionName, string partName = null);
+ void ResetBinding(string scheme, string actionMapName, string actionName, string partName = null);
///
/// 全アクションのバインドオーバーライドを既定へ戻す。
@@ -133,6 +139,6 @@ public interface IInputSystemService : IGameService
/// 指定スキーム(Keyboard&Mouse / Gamepad 等)に属するバインドオーバーライドのみを既定へ戻す。
/// 全アクションマップを対象とし、他スキームのオーバーライドは保持する。
///
- void ResetSchemeBindings(string scheme);
+ void ResetControlSchemeBindings(string scheme);
}
}
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IMessagePipeService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IMessagePipeService.cs
index 8160af18e..146151cb5 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IMessagePipeService.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/Abstractions/IMessagePipeService.cs
@@ -1,7 +1,7 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
-using Game.Core.MessagePipe;
+using Game.Shared.Services.Interfaces;
using MessagePipe;
namespace Game.Core.Services
@@ -12,6 +12,15 @@ namespace Game.Core.Services
///
public interface IMessagePipeService : IGameService
{
+ #region Register
+
+ void AddMessageBroker();
+ void AddMessageBroker();
+
+ void Build();
+
+ #endregion
+
#region Signal Methods (値なしのイベント通知用)
// Signal Publish (値なし)
@@ -30,12 +39,15 @@ public interface IMessagePipeService : IGameService
// Message Publish
void Publish(TMessage message);
void Publish(int key, TMessage message);
+ void PublishForget(TMessage message);
void PublishForget(int key, TMessage message);
+ UniTask PublishAsync(TMessage message, CancellationToken ct = default);
UniTask PublishAsync(int key, TMessage message, CancellationToken ct = default);
// Message Subscribe
IDisposable Subscribe(Action handler);
IDisposable Subscribe(int key, Action handler);
+ IDisposable SubscribeAsync(Func handler);
IDisposable SubscribeAsync(int key, Func handler);
#endregion
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AddressableAssetService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AddressableAssetService.cs
index 431731441..673602523 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AddressableAssetService.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AddressableAssetService.cs
@@ -6,7 +6,7 @@ namespace Game.Core.Services
/// MVC用Addressablesアセット読み込みサービス
/// GameServiceManager経由で使用
///
- public class AddressableAssetService : AddressableAssetServiceBase, IGameService
+ public class AddressableAssetService : AddressableAssetServiceBase
{
public AddressableAssetService()
{
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AudioService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AudioService.cs
index 100babec0..e5db6c241 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AudioService.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/AudioService.cs
@@ -6,7 +6,7 @@ namespace Game.Core.Services
/// MVC用オーディオ再生サービス
/// GameServiceManager経由で使用
///
- public class AudioService : AudioServiceBase, IGameService
+ public class AudioService : AudioServiceBase
{
private readonly IAddressableAssetService _assetService;
private readonly IMasterDataService _masterDataService;
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameSceneService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameSceneService.cs
index 9d66b3553..ff26bb9e6 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameSceneService.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameSceneService.cs
@@ -13,8 +13,6 @@ namespace Game.Core.Services
///
public partial class GameSceneService : IGameSceneService
{
- private readonly IInputSystemService _inputService;
-
private readonly List _gameScenes = new(16);
private const GameSceneOperations DefaultOperations = GameSceneConstants.DefaultOperations;
@@ -23,11 +21,6 @@ public GameSceneService()
{
}
- public GameSceneService(IInputSystemService inputService)
- {
- _inputService = inputService;
- }
-
public async UniTask TransitionAsync(GameSceneOperations operations = DefaultOperations)
where TScene : IGameScene, new()
{
@@ -180,9 +173,7 @@ private void CreateArgHandler(IGameScene gameScene, TArg arg)
private UniTaskCompletionSource CreateResultTcs(IGameScene gameScene)
{
if (gameScene is IGameSceneResult result)
- {
return result.ResultTcs = new UniTaskCompletionSource();
- }
return null;
}
@@ -194,17 +185,14 @@ private async UniTask TransitionCore(IGameScene gameScene)
{
gameScene.State = GameSceneState.Processing;
- using (_inputService.BlockUI())
- {
- if (gameScene.ArgHandler != null)
- await gameScene.ArgHandler.Invoke(gameScene);
-
- await gameScene.PreInitialize();
- await gameScene.LoadAsset();
- await gameScene.Startup();
- await DoFadeInAsync(gameScene);
- await gameScene.Ready();
- }
+ if (gameScene.ArgHandler != null)
+ await gameScene.ArgHandler.Invoke(gameScene);
+
+ await gameScene.PreInitialize();
+ await gameScene.LoadAsset();
+ await gameScene.Startup();
+ await gameScene.Ready();
+ await DoFadeInAsync(gameScene);
}
private async UniTask ResultAsync(IGameScene gameScene, UniTaskCompletionSource tcs)
@@ -323,6 +311,7 @@ private async UniTask TerminateCore(IGameScene gameScene)
await DoFadeOutAsync(gameScene);
await gameScene.Terminate();
gameScene.Disposables?.Dispose();
+ if (gameScene is IGameSceneResult result) result.TrySetCanceled();
}
}
@@ -366,18 +355,12 @@ private async UniTask BlockLastSceneAsync(bool visibleLastScen
private async UniTask DoFadeInAsync(IGameScene gameScene)
{
- if (gameScene is IGameSceneFader fader)
- {
- await fader.FadeInAsync();
- }
+ if (gameScene is IGameSceneFader fader) await fader.FadeInAsync();
}
private async UniTask DoFadeOutAsync(IGameScene gameScene)
{
- if (gameScene is IGameSceneFader fader)
- {
- await fader.FadeOutAsync();
- }
+ if (gameScene is IGameSceneFader fader) await fader.FadeOutAsync();
}
}
}
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameServiceManager.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameServiceManager.cs
index b4efe7b44..8b516233e 100644
--- a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameServiceManager.cs
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/GameServiceManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Game.Shared.Services.Interfaces;
namespace Game.Core.Services
{
@@ -41,16 +42,19 @@ public static void Register(TImplement service)
Instance._gameServices[type] = service;
}
- // public static void Register(TInterface service)
- // where TInterface : IGameService
- // {
- // var type = typeof(TInterface);
- // if (Instance._gameServices.ContainsKey(type))
- // return;
- //
- // service.Startup();
- // Instance._gameServices[type] = service;
- // }
+ ///
+ /// Memo: テスト用サービス登録窓口
+ ///
+ public static void Register(TInterface service)
+ where TInterface : IGameService
+ {
+ var type = typeof(TInterface);
+ if (Instance._gameServices.ContainsKey(type))
+ return;
+
+ service.Startup();
+ Instance._gameServices[type] = service;
+ }
public static T Resolve()
{
diff --git a/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/InputActionIconService.cs b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/InputActionIconService.cs
new file mode 100644
index 000000000..d2bb1a715
--- /dev/null
+++ b/src/Game.Client/Assets/Programs/Runtime/MVC/Core/Services/InputActionIconService.cs
@@ -0,0 +1,108 @@
+using System.Collections.Generic;
+using Cysharp.Threading.Tasks;
+using Game.Shared.Enums;
+using Game.Shared.Input;
+using Game.Shared.Services;
+using UnityEngine;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace Game.Core.Services
+{
+ public class InputActionIconService : IInputActionIconService
+ {
+ private readonly IAddressableAssetService _assetService;
+
+ private const string KeyboardAndMouse = "keyboard-&-mouse_sheet_default";
+ private const string PlayStation = "playstation-series_sheet_default";
+ private const string Xbox = "xbox-series_sheet_default";
+
+ private AsyncOperationHandle> _kbmHandle;
+ private AsyncOperationHandle> _psHandle;
+ private AsyncOperationHandle> _xboxHandle;
+
+ private Dictionary _kbmIcons;
+ private Dictionary _psIcons;
+ private Dictionary _xboxIcons;
+
+ public InputActionIconService(IAddressableAssetService assetService)
+ {
+ _assetService = assetService;
+ }
+
+ public async UniTask LoadAsync()
+ {
+ await LoadKbmIconsAsync();
+ await LoadPsIconsAsync();
+ await LoadXboxIconsAsync();
+ }
+
+ public void Unload()
+ {
+ _assetService.Release(_kbmHandle);
+ _kbmIcons.Clear();
+ _kbmIcons = null;
+
+ _assetService.Release(_psHandle);
+ _psIcons.Clear();
+ _psIcons = null;
+
+ _assetService.Release(_xboxHandle);
+ _xboxIcons.Clear();
+ _xboxIcons = null;
+ }
+
+ private async UniTask LoadKbmIconsAsync()
+ {
+ _kbmHandle = _assetService.LoadAssetAsyncHandle>(KeyboardAndMouse);
+ var sprites = await _kbmHandle.ToUniTask();
+ _kbmIcons = new Dictionary(sprites.Count);
+ foreach (var sprite in sprites) _kbmIcons[sprite.name] = sprite;
+ }
+
+ private async UniTask LoadPsIconsAsync()
+ {
+ _psHandle = _assetService.LoadAssetAsyncHandle>(PlayStation);
+ var sprites = await _psHandle.ToUniTask();
+ _psIcons = new Dictionary