update P2-Job-System-+-Burst-落地.md

This commit is contained in:
SepComet 2026-04-02 08:32:37 +08:00
parent 0d7df18324
commit 0a281a5b1f
37 changed files with 532 additions and 213 deletions

2
.gitignore vendored
View File

@ -78,11 +78,11 @@ crashlytics-build.properties
/Assets/StreamingAssets /Assets/StreamingAssets
/Assets/StreamingAssets.meta /Assets/StreamingAssets.meta
/UI参考 /UI参考
/AGENTS.md
/bin /bin
/docs/screenshot /docs/screenshot
*.xmind *.xmind
/数据表/__pycache__/ /数据表/__pycache__/
/类吸血鬼项目.md
~$*.xlsx ~$*.xlsx
Assets/GameMain/Configs/ResourceBuilder.xml Assets/GameMain/Configs/ResourceBuilder.xml

View File

@ -22,6 +22,7 @@ namespace UnityGameFramework.Editor
"UnityGameFramework.Runtime", "UnityGameFramework.Runtime",
#endif #endif
"Assembly-CSharp", "Assembly-CSharp",
"VampireLike"
}; };
private static readonly string[] RuntimeOrEditorAssemblyNames = private static readonly string[] RuntimeOrEditorAssemblyNames =
@ -34,6 +35,8 @@ namespace UnityGameFramework.Editor
"UnityGameFramework.Editor", "UnityGameFramework.Editor",
#endif #endif
"Assembly-CSharp-Editor", "Assembly-CSharp-Editor",
"VampireLike",
"VampireLike.Editor"
}; };
/// <summary> /// <summary>

View File

@ -8,11 +8,11 @@
<CompressionHelperTypeName>UnityGameFramework.Runtime.DefaultCompressionHelper</CompressionHelperTypeName> <CompressionHelperTypeName>UnityGameFramework.Runtime.DefaultCompressionHelper</CompressionHelperTypeName>
<AdditionalCompressionSelected>False</AdditionalCompressionSelected> <AdditionalCompressionSelected>False</AdditionalCompressionSelected>
<ForceRebuildAssetBundleSelected>False</ForceRebuildAssetBundleSelected> <ForceRebuildAssetBundleSelected>False</ForceRebuildAssetBundleSelected>
<BuildEventHandlerTypeName>StarForce.Editor.StarForceBuildEventHandler</BuildEventHandlerTypeName> <BuildEventHandlerTypeName>VampireLike.Editor.VampireLikeBuildEventHandler</BuildEventHandlerTypeName>
<OutputDirectory>D:/Learn/GameLearn/UnityProjects/VampireLike/bin</OutputDirectory> <OutputDirectory>C:/UnityProjects/VampireLike/bin/AssetBundles</OutputDirectory>
<OutputPackageSelected>True</OutputPackageSelected> <OutputPackageSelected>True</OutputPackageSelected>
<OutputFullSelected>True</OutputFullSelected> <OutputFullSelected>False</OutputFullSelected>
<OutputPackedSelected>True</OutputPackedSelected> <OutputPackedSelected>False</OutputPackedSelected>
</Settings> </Settings>
</ResourceBuilder> </ResourceBuilder>
</UnityGameFramework> </UnityGameFramework>

View File

@ -17,6 +17,7 @@
<Resource Name="Music/Menu" FileSystem="Resources" LoadType="0" Packed="True" ResourceGroups="Music" /> <Resource Name="Music/Menu" FileSystem="Resources" LoadType="0" Packed="True" ResourceGroups="Music" />
<Resource Name="Scenes" FileSystem="Resources" LoadType="0" Packed="True" /> <Resource Name="Scenes" FileSystem="Resources" LoadType="0" Packed="True" />
<Resource Name="SceneSettings" LoadType="0" Packed="False" /> <Resource Name="SceneSettings" LoadType="0" Packed="False" />
<Resource Name="Scripts" LoadType="0" Packed="False" />
<Resource Name="Sounds" FileSystem="Resources" LoadType="0" Packed="True" /> <Resource Name="Sounds" FileSystem="Resources" LoadType="0" Packed="True" />
<Resource Name="Textures" FileSystem="Resources" LoadType="0" Packed="True" /> <Resource Name="Textures" FileSystem="Resources" LoadType="0" Packed="True" />
<Resource Name="UI/UIForms" FileSystem="UI" LoadType="0" Packed="True" /> <Resource Name="UI/UIForms" FileSystem="UI" LoadType="0" Packed="True" />
@ -42,6 +43,7 @@
<Asset Guid="0f995b3145e0e7247a42da6cef1dbf23" ResourceName="Materials" /> <Asset Guid="0f995b3145e0e7247a42da6cef1dbf23" ResourceName="Materials" />
<Asset Guid="1046dcb12e547564d8b54bd15419a787" ResourceName="Entities" /> <Asset Guid="1046dcb12e547564d8b54bd15419a787" ResourceName="Entities" />
<Asset Guid="1053b0070685be347ab58587156842dc" ResourceName="Localization/Dictionaries" ResourceVariant="zh-tw" /> <Asset Guid="1053b0070685be347ab58587156842dc" ResourceName="Localization/Dictionaries" ResourceVariant="zh-tw" />
<Asset Guid="11143001bcbdc864b8d8fe2083142e5a" ResourceName="Entities" />
<Asset Guid="1478894bc9a1ed241b05b0862a7b8bce" ResourceName="Textures" /> <Asset Guid="1478894bc9a1ed241b05b0862a7b8bce" ResourceName="Textures" />
<Asset Guid="14869ac0d4433f04db1704e39d03412e" ResourceName="Localization/Dictionaries" ResourceVariant="en-us" /> <Asset Guid="14869ac0d4433f04db1704e39d03412e" ResourceName="Localization/Dictionaries" ResourceVariant="en-us" />
<Asset Guid="156d241f796508c4da4fc354a7fbf5a8" ResourceName="UI/UISprites/Common" /> <Asset Guid="156d241f796508c4da4fc354a7fbf5a8" ResourceName="UI/UISprites/Common" />
@ -83,6 +85,7 @@
<Asset Guid="4473d81b14ddb0143addf0e6050d8491" ResourceName="Scenes" /> <Asset Guid="4473d81b14ddb0143addf0e6050d8491" ResourceName="Scenes" />
<Asset Guid="44c8db52241385c45bbb14a1718f17bf" ResourceName="Configs" /> <Asset Guid="44c8db52241385c45bbb14a1718f17bf" ResourceName="Configs" />
<Asset Guid="44cfa1c448225554c961ad6eb667d80b" ResourceName="DataTables" /> <Asset Guid="44cfa1c448225554c961ad6eb667d80b" ResourceName="DataTables" />
<Asset Guid="47a82ffa13c291447ab895cd0bc251cd" ResourceName="Scripts" />
<Asset Guid="4c3865b2ac420cd46a9cde6ab468d016" ResourceName="Materials" /> <Asset Guid="4c3865b2ac420cd46a9cde6ab468d016" ResourceName="Materials" />
<Asset Guid="4ca22ae3bc068c84eb7858d5b9bdf3e2" ResourceName="Fonts" /> <Asset Guid="4ca22ae3bc068c84eb7858d5b9bdf3e2" ResourceName="Fonts" />
<Asset Guid="4f688097e85071841a2c3ba165000c20" ResourceName="Textures" /> <Asset Guid="4f688097e85071841a2c3ba165000c20" ResourceName="Textures" />
@ -94,6 +97,7 @@
<Asset Guid="5b5a6a737c460eb4abc105d6583d405e" ResourceName="Fonts" /> <Asset Guid="5b5a6a737c460eb4abc105d6583d405e" ResourceName="Fonts" />
<Asset Guid="5dcd89912e222bf4c87f76db4044bc5e" ResourceName="Localization/Dictionaries" ResourceVariant="ko-kr" /> <Asset Guid="5dcd89912e222bf4c87f76db4044bc5e" ResourceName="Localization/Dictionaries" ResourceVariant="ko-kr" />
<Asset Guid="5ebb46af6f16ae94e87f64a7dc0a49cb" ResourceName="Entities" /> <Asset Guid="5ebb46af6f16ae94e87f64a7dc0a49cb" ResourceName="Entities" />
<Asset Guid="602d791ab1251f74ca2470c53bf382a3" ResourceName="Scripts" />
<Asset Guid="62af9e5c8f39cfa49af9e10ccf42f1da" ResourceName="UI/UISprites/Common" /> <Asset Guid="62af9e5c8f39cfa49af9e10ccf42f1da" ResourceName="UI/UISprites/Common" />
<Asset Guid="638ff8ae4a0d15047839cd265d3bc296" ResourceName="Music/Background" /> <Asset Guid="638ff8ae4a0d15047839cd265d3bc296" ResourceName="Music/Background" />
<Asset Guid="63fe6ff9ab9e1433f8db4ebd940f2442" ResourceName="Materials" /> <Asset Guid="63fe6ff9ab9e1433f8db4ebd940f2442" ResourceName="Materials" />

View File

@ -23,6 +23,17 @@ namespace CustomComponent
private const int RequiredCornerTapCount = 3; private const int RequiredCornerTapCount = 3;
private const int DebugHealAmount = 200; private const int DebugHealAmount = 200;
[Header("Window Content")]
[SerializeField] private bool _showBuffSection = true;
[SerializeField] private bool _showBattleOverview = true;
[SerializeField] private bool _showCollisionStats = true;
[SerializeField] private bool _showSpawnControls = true;
[SerializeField] private bool _showBattleDurationControls = true;
[SerializeField] private bool _showSeparationSolverControls = true;
[SerializeField] private bool _showPlayerWeaponControls = true;
[SerializeField] private bool _showPlayerHealthControls = true;
[SerializeField] private bool _showTips = true;
private Rect _windowRect = new Rect(20f, 60f, 460f, 800f); private Rect _windowRect = new Rect(20f, 60f, 460f, 800f);
private bool _isPanelVisible; private bool _isPanelVisible;
private int _windowId; private int _windowId;
@ -84,21 +95,43 @@ namespace CustomComponent
} }
private void DrawWindow(int windowId) private void DrawWindow(int windowId)
{
if (_showBuffSection)
{ {
EnsurePropList(); EnsurePropList();
}
GUILayout.BeginVertical(); GUILayout.BeginVertical();
bool hasPreviousSection = false;
if (_showBuffSection)
{
DrawBuffSection(); DrawBuffSection();
hasPreviousSection = true;
}
if (HasVisibleBattleSection())
{
if (hasPreviousSection)
{
GUILayout.Space(8f); GUILayout.Space(8f);
GUILayout.Label(string.Empty, GUI.skin.horizontalSlider); GUILayout.Label(string.Empty, GUI.skin.horizontalSlider);
GUILayout.Space(8f); GUILayout.Space(8f);
}
DrawBattleSection(); DrawBattleSection();
hasPreviousSection = true;
}
if (_showTips)
{
if (hasPreviousSection)
{
GUILayout.Space(8f); GUILayout.Space(8f);
}
GUILayout.Label("Tips: press `F8` or tap top-left corner 3 times to toggle.", GUILayout.Height(20f)); GUILayout.Label("Tips: press `F8` or tap top-left corner 3 times to toggle.", GUILayout.Height(20f));
}
GUILayout.EndVertical(); GUILayout.EndVertical();
GUI.DragWindow(new Rect(0, 0, 10000, 22)); GUI.DragWindow(new Rect(0, 0, 10000, 22));
@ -169,12 +202,15 @@ namespace CustomComponent
return; return;
} }
if (_showBattleOverview)
{
GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}"); GUILayout.Label($"Spawn Rate: {enemyManager.SpawnRateScale:F2}");
GUILayout.Label($"Battle Time: {enemyManager.ElapsedBattleTime:F1}s / {enemyManager.BattleDuration:F1}s"); GUILayout.Label($"Battle Time: {enemyManager.ElapsedBattleTime:F1}s / {enemyManager.BattleDuration:F1}s");
GUILayout.Label($"Enemy Count: {enemyManager.CurrentEnemyCount}"); GUILayout.Label($"Enemy Count: {enemyManager.CurrentEnemyCount}");
}
Simulation.SimulationWorld simulationWorld = GameEntry.SimulationWorld; Simulation.SimulationWorld simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld != null) if (_showCollisionStats && simulationWorld != null)
{ {
GUILayout.Space(4f); GUILayout.Space(4f);
GUILayout.Label( GUILayout.Label(
@ -196,6 +232,8 @@ namespace CustomComponent
} }
} }
if (_showSpawnControls)
{
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Rate", GUILayout.Width(52f)); GUILayout.Label("Rate", GUILayout.Width(52f));
string rateText = GUILayout.TextField(_spawnRateScaleInput.ToString("F2"), GUILayout.Width(60f)); string rateText = GUILayout.TextField(_spawnRateScaleInput.ToString("F2"), GUILayout.Width(60f));
@ -222,7 +260,10 @@ namespace CustomComponent
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
}
if (_showBattleDurationControls)
{
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
GUILayout.Label("Add Sec", GUILayout.Width(52f)); GUILayout.Label("Add Sec", GUILayout.Width(52f));
string durationText = GUILayout.TextField(_extendDurationSeconds.ToString("F0"), GUILayout.Width(60f)); string durationText = GUILayout.TextField(_extendDurationSeconds.ToString("F0"), GUILayout.Width(60f));
@ -240,7 +281,10 @@ namespace CustomComponent
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
}
if (_showSeparationSolverControls)
{
GUILayout.Space(4f); GUILayout.Space(4f);
GUILayout.Label($"Enemy Separation Solver: {EnemySeparationSolverProvider.CurrentSolverName}"); GUILayout.Label($"Enemy Separation Solver: {EnemySeparationSolverProvider.CurrentSolverName}");
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
@ -255,7 +299,10 @@ namespace CustomComponent
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
}
if (_showPlayerWeaponControls)
{
GUILayout.Label( GUILayout.Label(
$"Player Weapon: {(player == null ? "Player not found" : (player.WeaponEnabled ? "Enabled" : "Disabled"))}"); $"Player Weapon: {(player == null ? "Player not found" : (player.WeaponEnabled ? "Enabled" : "Disabled"))}");
GUILayout.BeginHorizontal(); GUILayout.BeginHorizontal();
@ -272,7 +319,10 @@ namespace CustomComponent
GUI.enabled = true; GUI.enabled = true;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
}
if (_showPlayerHealthControls)
{
GUILayout.Space(4f); GUILayout.Space(4f);
GUILayout.Label( GUILayout.Label(
$"Player HP: {(playerHealth == null ? "Unavailable" : $"{playerHealth.CurrentHealth}/{playerHealth.MaxHealth}")}"); $"Player HP: {(playerHealth == null ? "Unavailable" : $"{playerHealth.CurrentHealth}/{playerHealth.MaxHealth}")}");
@ -295,6 +345,18 @@ namespace CustomComponent
GUI.enabled = true; GUI.enabled = true;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
}
private bool HasVisibleBattleSection()
{
return _showBattleOverview ||
_showCollisionStats ||
_showSpawnControls ||
_showBattleDurationControls ||
_showSeparationSolverControls ||
_showPlayerWeaponControls ||
_showPlayerHealthControls;
}
private void EnsurePropList(bool force = false) private void EnsurePropList(bool force = false)
{ {

View File

@ -0,0 +1,20 @@
{
"name": "VampireLike.Editor",
"rootNamespace": "",
"references": [
"VampireLike",
"UnityGameFramework.Runtime",
"UnityGameFramework.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 9b0d24fd3a44b6f45b3794cdfefd1ac0 guid: 602d791ab1251f74ca2470c53bf382a3
DefaultImporter: AssemblyDefinitionImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -1,19 +1,12 @@
//------------------------------------------------------------ using GameFramework;
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using GameFramework;
using System.IO; using System.IO;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Editor.ResourceTools; using UnityGameFramework.Editor.ResourceTools;
namespace StarForce.Editor namespace VampireLike.Editor
{ {
public sealed class StarForceBuildEventHandler : IBuildEventHandler public sealed class VampireLikeBuildEventHandler : IBuildEventHandler
{ {
public bool ContinueOnFailure public bool ContinueOnFailure
{ {

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 64311189c3f9ae140b59a31db9831950 guid: 64311189c3f9ae140b59a31db9831950
timeCreated: 1528026151
licenseType: Pro
MonoImporter: MonoImporter:
externalObjects: {}
serializedVersion: 2 serializedVersion: 2
defaultReferences: [] defaultReferences: []
executionOrder: 0 executionOrder: 0

View File

@ -0,0 +1,23 @@
{
"name": "VampireLike",
"rootNamespace": "",
"references": [
"GUID:363c5eb08ff8e6a439b85e37b8c20d96",
"GUID:a2d8a19598eca814496b089021d08d60",
"GUID:75469ad4d38634e559750d17036d5f7c",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:6055be8ebefd69e48b49212b09b47b2f",
"GUID:e0cd26848372d4e5c891c569017e11f1",
"GUID:2665a8d13d1b3f18800f46e256720795",
"GUID:fca0f81bc71f1944887dd65f134c54a0"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 0372088d74296e44c9eb9185a2d4021e guid: 47a82ffa13c291447ab895cd0bc251cd
DefaultImporter: AssemblyDefinitionImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -390,7 +390,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_AvailableProcedureTypeNames.Array.size propertyPath: m_AvailableProcedureTypeNames.Array.size
value: 12 value: 13
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_AvailableProcedureTypeNames.Array.data[0] propertyPath: m_AvailableProcedureTypeNames.Array.data[0]
@ -430,15 +430,15 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_AvailableProcedureTypeNames.Array.data[9] propertyPath: m_AvailableProcedureTypeNames.Array.data[9]
value: Procedure.ProcedureUpdateResources value: Procedure.ProcedureStressTest
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_AvailableProcedureTypeNames.Array.data[10] propertyPath: m_AvailableProcedureTypeNames.Array.data[10]
value: Procedure.ProcedureUpdateVersion value: Procedure.ProcedureUpdateResources
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_AvailableProcedureTypeNames.Array.data[11] propertyPath: m_AvailableProcedureTypeNames.Array.data[11]
value: Procedure.ProcedureVerifyResources value: Procedure.ProcedureUpdateVersion
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11405216, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_AvailableProcedureTypeNames.Array.data[12] propertyPath: m_AvailableProcedureTypeNames.Array.data[12]
@ -848,6 +848,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1d8ada5157a04921a6e543a040e57960, type: 3} m_Script: {fileID: 11500000, guid: 1d8ada5157a04921a6e543a040e57960, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
_showBuffSection: 0
_showBattleOverview: 0
_showCollisionStats: 0
_showSpawnControls: 1
_showBattleDurationControls: 1
_showSeparationSolverControls: 0
_showPlayerWeaponControls: 1
_showPlayerHealthControls: 1
_showTips: 0
--- !u!1 &513208572 --- !u!1 &513208572
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -0,0 +1,3 @@
{
"name": "DOTween.Modules"
}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a5e646a0b90b55940be09d61810d429d guid: fca0f81bc71f1944887dd65f134c54a0
DefaultImporter: AssemblyDefinitionImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -49,6 +49,6 @@ MonoBehaviour:
deAudioEnabled: 0 deAudioEnabled: 0
deUnityExtendedEnabled: 0 deUnityExtendedEnabled: 0
epoOutlineEnabled: 0 epoOutlineEnabled: 0
createASMDEF: 0 createASMDEF: 1
showPlayingTweens: 0 showPlayingTweens: 0
showPausedTweens: 0 showPausedTweens: 0

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: fa53b8776f6a8ce4598fe035cb4356b8
folderAsset: yes
timeCreated: 1528026174
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: a7d40db10aa401f4db624fec03b19854
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: ff394cf6a35932247b0649240bbbd5fc
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 1b6ba43d50137a44a9cac13aee7c79b4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: b692b20b915b5384bb238bbe1e86f6c8
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: aa252b664de661c40bee3ddc89dfc7e0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: ccdf2a9817ba952488f12dadb5e278c6
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 74423cdaf48700645bc031c80e5b3fc7
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,11 +1,26 @@
{ {
"name": "Simulation.EditModeTests", "name": "Simulation.EditModeTests",
"references": [], "rootNamespace": "",
"optionalUnityReferences": [ "references": [
"TestAssemblies" "UnityGameFramework.Runtime",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"VampireLike"
], ],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"
], ],
"excludePlatforms": [] "excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll",
"GameFramework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
} }

View File

@ -3,13 +3,16 @@ using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
using Procedure;
using GameFramework.Fsm;
using GameFramework.Procedure;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
namespace Simulation.Tests.Editor namespace Simulation.Tests.Editor
{ {
public class SimulationWorldTickTests public class SimulationWorldTickTests
{ {
private const string GameAssemblyName = "Assembly-CSharp"; private const string GameAssemblyName = "VampireLike";
private const string RuntimeAssemblyName = "UnityGameFramework.Runtime"; private const string RuntimeAssemblyName = "UnityGameFramework.Runtime";
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static;
private const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance; private const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
@ -28,6 +31,9 @@ namespace Simulation.Tests.Editor
private static readonly System.Type ProjectileSimDataType = private static readonly System.Type ProjectileSimDataType =
System.Type.GetType($"Simulation.ProjectileSimData, {GameAssemblyName}"); System.Type.GetType($"Simulation.ProjectileSimData, {GameAssemblyName}");
private static readonly System.Type PickupSimDataType =
System.Type.GetType($"Simulation.PickupSimData, {GameAssemblyName}");
private static readonly System.Type EnemyProjectileType = private static readonly System.Type EnemyProjectileType =
System.Type.GetType($"Entity.EnemyProjectile, {GameAssemblyName}"); System.Type.GetType($"Entity.EnemyProjectile, {GameAssemblyName}");
@ -88,6 +94,12 @@ namespace Simulation.Tests.Editor
private static readonly MethodInfo RemoveProjectileByEntityIdMethod = private static readonly MethodInfo RemoveProjectileByEntityIdMethod =
SimulationWorldType?.GetMethod("RemoveProjectileByEntityId", NonPublicInstance); SimulationWorldType?.GetMethod("RemoveProjectileByEntityId", NonPublicInstance);
private static readonly MethodInfo UpsertPickupMethod =
SimulationWorldType?.GetMethod("UpsertPickup", NonPublicInstance);
private static readonly MethodInfo RemovePickupByEntityIdMethod =
SimulationWorldType?.GetMethod("RemovePickupByEntityId", NonPublicInstance);
private static readonly MethodInfo TryGetEnemyDataMethod = private static readonly MethodInfo TryGetEnemyDataMethod =
SimulationWorldType?.GetMethod("TryGetEnemyData", NonPublicInstance); SimulationWorldType?.GetMethod("TryGetEnemyData", NonPublicInstance);
@ -124,6 +136,9 @@ namespace Simulation.Tests.Editor
private static readonly PropertyInfo ProjectilesProperty = private static readonly PropertyInfo ProjectilesProperty =
SimulationWorldType?.GetProperty("Projectiles", PublicInstance); SimulationWorldType?.GetProperty("Projectiles", PublicInstance);
private static readonly PropertyInfo PickupsProperty =
SimulationWorldType?.GetProperty("Pickups", PublicInstance);
private static readonly PropertyInfo CollisionCandidateCountProperty = private static readonly PropertyInfo CollisionCandidateCountProperty =
SimulationWorldType?.GetProperty("CollisionCandidateCount", PublicInstance); SimulationWorldType?.GetProperty("CollisionCandidateCount", PublicInstance);
@ -197,6 +212,7 @@ namespace Simulation.Tests.Editor
Assert.NotNull(SimulationTickContextType, "SimulationTickContext type lookup failed."); Assert.NotNull(SimulationTickContextType, "SimulationTickContext type lookup failed.");
Assert.NotNull(EnemySimDataType, "EnemySimData type lookup failed."); Assert.NotNull(EnemySimDataType, "EnemySimData type lookup failed.");
Assert.NotNull(ProjectileSimDataType, "ProjectileSimData type lookup failed."); Assert.NotNull(ProjectileSimDataType, "ProjectileSimData type lookup failed.");
Assert.NotNull(PickupSimDataType, "PickupSimData type lookup failed.");
Assert.NotNull(EnemyProjectileType, "EnemyProjectile type lookup failed."); Assert.NotNull(EnemyProjectileType, "EnemyProjectile type lookup failed.");
Assert.NotNull(EnemyProjectileDataType, "EnemyProjectileData type lookup failed."); Assert.NotNull(EnemyProjectileDataType, "EnemyProjectileData type lookup failed.");
Assert.NotNull(CampTypeType, "CampType type lookup failed."); Assert.NotNull(CampTypeType, "CampType type lookup failed.");
@ -217,6 +233,8 @@ namespace Simulation.Tests.Editor
Assert.NotNull(RemoveEnemyByEntityIdMethod, "RemoveEnemyByEntityId reflection lookup failed."); Assert.NotNull(RemoveEnemyByEntityIdMethod, "RemoveEnemyByEntityId reflection lookup failed.");
Assert.NotNull(UpsertProjectileMethod, "UpsertProjectile reflection lookup failed."); Assert.NotNull(UpsertProjectileMethod, "UpsertProjectile reflection lookup failed.");
Assert.NotNull(RemoveProjectileByEntityIdMethod, "RemoveProjectileByEntityId reflection lookup failed."); Assert.NotNull(RemoveProjectileByEntityIdMethod, "RemoveProjectileByEntityId reflection lookup failed.");
Assert.NotNull(UpsertPickupMethod, "UpsertPickup reflection lookup failed.");
Assert.NotNull(RemovePickupByEntityIdMethod, "RemovePickupByEntityId reflection lookup failed.");
Assert.NotNull(TryGetEnemyDataMethod, "TryGetEnemyData reflection lookup failed."); Assert.NotNull(TryGetEnemyDataMethod, "TryGetEnemyData reflection lookup failed.");
Assert.NotNull(TickMethod, "Tick reflection lookup failed."); Assert.NotNull(TickMethod, "Tick reflection lookup failed.");
Assert.NotNull(TryGetNearestEnemyEntityIdMethod, "TryGetNearestEnemyEntityId reflection lookup failed."); Assert.NotNull(TryGetNearestEnemyEntityIdMethod, "TryGetNearestEnemyEntityId reflection lookup failed.");
@ -225,6 +243,7 @@ namespace Simulation.Tests.Editor
Assert.NotNull(UseGridBucketSolverMethod, "UseGridBucketSolver reflection lookup failed."); Assert.NotNull(UseGridBucketSolverMethod, "UseGridBucketSolver reflection lookup failed.");
Assert.NotNull(EnemiesProperty, "Enemies property reflection lookup failed."); Assert.NotNull(EnemiesProperty, "Enemies property reflection lookup failed.");
Assert.NotNull(ProjectilesProperty, "Projectiles property reflection lookup failed."); Assert.NotNull(ProjectilesProperty, "Projectiles property reflection lookup failed.");
Assert.NotNull(PickupsProperty, "Pickups property reflection lookup failed.");
Assert.NotNull(CollisionCandidateCountProperty, "CollisionCandidateCount property reflection lookup failed."); Assert.NotNull(CollisionCandidateCountProperty, "CollisionCandidateCount property reflection lookup failed.");
Assert.NotNull(UseSimulationMovementProperty, "UseSimulationMovement property reflection lookup failed."); Assert.NotNull(UseSimulationMovementProperty, "UseSimulationMovement property reflection lookup failed.");
Assert.NotNull(UseSimulationMovementField, "_useSimulationMovement field reflection lookup failed."); Assert.NotNull(UseSimulationMovementField, "_useSimulationMovement field reflection lookup failed.");
@ -710,6 +729,80 @@ namespace Simulation.Tests.Editor
Assert.That(GetLastResolvedAreaHitCount(), Is.EqualTo(0)); Assert.That(GetLastResolvedAreaHitCount(), Is.EqualTo(0));
} }
[Test]
public void ProcedureGame_TransitionsBattleToLevelUpShopAndBackToBattle()
{
var procedureGame = (ProcedureGame)Activator.CreateInstance(ProcedureGameType);
GameObject playerObject = new GameObject("ProcedureGameTransitionPlayer");
try
{
var player = playerObject.AddComponent(PlayerType);
Assert.NotNull(player);
PlayerType.GetProperty("PendingLevelPoints", PublicInstance)?.SetValue(player, 1);
ProcedureGameType.GetField("Player", PublicInstance)?.SetValue(procedureGame, player);
var battleState = new TrackingGameState(GameStateType.Battle);
var levelUpState = new TrackingGameState(GameStateType.LevelUp);
var shopState = new TrackingGameState(GameStateType.Shop);
var gameStates = new Dictionary<GameStateType, GameStateBase>
{
{ GameStateType.Battle, battleState },
{ GameStateType.LevelUp, levelUpState },
{ GameStateType.Shop, shopState },
};
SetPrivateField(procedureGame, "_gameStates", gameStates);
SetPrivateField(procedureGame, "_currentGameState", GameStateType.Battle);
SetPrivateField(procedureGame, "_procedureOwner", null);
procedureGame.BattleToShopOrLevelUp();
Assert.That(procedureGame.CurrentLevel, Is.EqualTo(2));
Assert.That(procedureGame.CurrentGameStateType, Is.EqualTo(GameStateType.LevelUp));
Assert.That(battleState.LeaveCount, Is.EqualTo(1));
Assert.That(levelUpState.EnterCount, Is.EqualTo(1));
PlayerType.GetProperty("PendingLevelPoints", PublicInstance)?.SetValue(player, 0);
procedureGame.LevelUpToShop();
Assert.That(procedureGame.CurrentGameStateType, Is.EqualTo(GameStateType.Shop));
Assert.That(levelUpState.LeaveCount, Is.EqualTo(1));
Assert.That(shopState.EnterCount, Is.EqualTo(1));
procedureGame.ShopToBattle();
Assert.That(procedureGame.CurrentGameStateType, Is.EqualTo(GameStateType.Battle));
Assert.That(shopState.LeaveCount, Is.EqualTo(1));
Assert.That(battleState.EnterCount, Is.EqualTo(1));
}
finally
{
Object.DestroyImmediate(playerObject);
}
}
[Test]
public void PickupLifecycle_UpsertAndRemove_KeepsBindingsConsistent()
{
UpsertPickup(CreatePickup(entityId: 6101, position: new Vector3(1f, 0f, 0f), pickupRadius: 0.35f, state: 0));
UpsertPickup(CreatePickup(entityId: 6102, position: new Vector3(2f, 0f, 0f), pickupRadius: 0.35f, state: 0));
UpsertPickup(CreatePickup(entityId: 6103, position: new Vector3(3f, 0f, 0f), pickupRadius: 0.35f, state: 0));
Assert.That(GetPickupsCount(), Is.EqualTo(3));
UpsertPickup(CreatePickup(entityId: 6101, position: new Vector3(10f, 0f, 0f), pickupRadius: 0.5f, state: 1));
Assert.That(GetPickupsCount(), Is.EqualTo(3));
object updatedPickup = GetPickupAt(0);
Assert.That((int)GetField(updatedPickup, "EntityId"), Is.EqualTo(6101));
Assert.That(((Vector3)GetField(updatedPickup, "Position")).x, Is.EqualTo(10f).Within(0.0001f));
Assert.That((float)GetField(updatedPickup, "PickupRadius"), Is.EqualTo(0.5f).Within(0.0001f));
bool removedMiddle = RemovePickupByEntityId(6102);
bool removedMoved = RemovePickupByEntityId(6103);
Assert.IsTrue(removedMiddle);
Assert.That(GetPickupsCount(), Is.EqualTo(1));
Assert.IsTrue(removedMoved);
Assert.That((int)GetField(GetPickupAt(0), "EntityId"), Is.EqualTo(6101));
}
private object CreateEnemy(int entityId, Vector3 position, float speed, float attackRange, private object CreateEnemy(int entityId, Vector3 position, float speed, float attackRange,
bool avoidEnemyOverlap = false, float enemyBodyRadius = 0.45f, int separationIterations = 1) bool avoidEnemyOverlap = false, float enemyBodyRadius = 0.45f, int separationIterations = 1)
{ {
@ -746,6 +839,16 @@ namespace Simulation.Tests.Editor
return projectile; return projectile;
} }
private object CreatePickup(int entityId, Vector3 position, float pickupRadius, int state)
{
object pickup = Activator.CreateInstance(PickupSimDataType);
SetField(ref pickup, "EntityId", entityId);
SetField(ref pickup, "Position", position);
SetField(ref pickup, "PickupRadius", pickupRadius);
SetField(ref pickup, "State", state);
return pickup;
}
private void InvokeTick(float deltaTime, float realDeltaTime, Vector3 playerPosition) private void InvokeTick(float deltaTime, float realDeltaTime, Vector3 playerPosition)
{ {
object tickContext = System.Activator.CreateInstance( object tickContext = System.Activator.CreateInstance(
@ -773,6 +876,11 @@ namespace Simulation.Tests.Editor
UpsertProjectileMethod.Invoke(_worldComponent, new[] { projectile }); UpsertProjectileMethod.Invoke(_worldComponent, new[] { projectile });
} }
private void UpsertPickup(object pickup)
{
UpsertPickupMethod.Invoke(_worldComponent, new[] { pickup });
}
private bool RemoveEnemyByEntityId(int entityId) private bool RemoveEnemyByEntityId(int entityId)
{ {
return (bool)RemoveEnemyByEntityIdMethod.Invoke(_worldComponent, new object[] { entityId }); return (bool)RemoveEnemyByEntityIdMethod.Invoke(_worldComponent, new object[] { entityId });
@ -783,6 +891,11 @@ namespace Simulation.Tests.Editor
return (bool)RemoveProjectileByEntityIdMethod.Invoke(_worldComponent, new object[] { entityId }); return (bool)RemoveProjectileByEntityIdMethod.Invoke(_worldComponent, new object[] { entityId });
} }
private bool RemovePickupByEntityId(int entityId)
{
return (bool)RemovePickupByEntityIdMethod.Invoke(_worldComponent, new object[] { entityId });
}
private static object GetGameEntrySimulationWorld() private static object GetGameEntrySimulationWorld()
{ {
return GameEntryGetSimulationWorldMethod.Invoke(null, null); return GameEntryGetSimulationWorldMethod.Invoke(null, null);
@ -856,6 +969,20 @@ namespace Simulation.Tests.Editor
return (int)countProperty.GetValue(projectiles); return (int)countProperty.GetValue(projectiles);
} }
private object GetPickupAt(int index)
{
object pickups = PickupsProperty.GetValue(_worldComponent);
PropertyInfo itemProperty = pickups.GetType().GetProperty("Item", PublicInstance);
return itemProperty.GetValue(pickups, new object[] { index });
}
private int GetPickupsCount()
{
object pickups = PickupsProperty.GetValue(_worldComponent);
PropertyInfo countProperty = pickups.GetType().GetProperty("Count", PublicInstance);
return (int)countProperty.GetValue(pickups);
}
private int GetCollisionCandidateCount() private int GetCollisionCandidateCount()
{ {
return (int)CollisionCandidateCountProperty.GetValue(_worldComponent); return (int)CollisionCandidateCountProperty.GetValue(_worldComponent);
@ -895,5 +1022,42 @@ namespace Simulation.Tests.Editor
Assert.Fail($"Field '{fieldName}' was not found on type '{target.GetType().FullName}'."); Assert.Fail($"Field '{fieldName}' was not found on type '{target.GetType().FullName}'.");
} }
private sealed class TrackingGameState : GameStateBase
{
public TrackingGameState(GameStateType gameStateType)
{
GameStateType = gameStateType;
}
public override GameStateType GameStateType { get; }
public int EnterCount { get; private set; }
public int LeaveCount { get; private set; }
public override void OnInit(ProcedureGame master)
{
}
public override void OnEnter(IFsm<IProcedureManager> procedureOwner)
{
EnterCount++;
}
public override void OnUpdate(IFsm<IProcedureManager> procedureOwner, float elapseSeconds,
float realElapseSeconds)
{
}
public override void OnLeave(IFsm<IProcedureManager> procedureOwner)
{
LeaveCount++;
}
public override void OnDestroy(IFsm<IProcedureManager> procedureOwner)
{
}
}
} }
} }

View File

@ -1,9 +1,24 @@
{ {
"name": "Simulation.PlayModeTests", "name": "Simulation.PlayModeTests",
"references": [], "rootNamespace": "",
"optionalUnityReferences": [ "references": [
"TestAssemblies" "UnityGameFramework.Runtime",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"VampireLike"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [] "excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll",
"GameFramework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
} }

View File

@ -11,7 +11,7 @@ namespace Simulation.Tests.PlayMode
{ {
public class SimulationWorldPlayModeTests public class SimulationWorldPlayModeTests
{ {
private const string GameAssemblyName = "Assembly-CSharp"; private const string GameAssemblyName = "VampireLike";
private const string RuntimeAssemblyName = "UnityGameFramework.Runtime"; private const string RuntimeAssemblyName = "UnityGameFramework.Runtime";
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static;
private const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance; private const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;

View File

@ -4,7 +4,7 @@
本文件用于对齐 `docs/TodoList.md` 的 P2 Checkpoint 9作为 P2 结项与 P3 输入基线。 本文件用于对齐 `docs/TodoList.md` 的 P2 Checkpoint 9作为 P2 结项与 P3 输入基线。
目标: 目标:
- 固化压测口径(1k/2k/3k - 固化压测口径(0.5k/1k/1.5k/2k
- 给出回归验证结论 - 给出回归验证结论
- 给出开关/回滚策略 - 给出开关/回滚策略
- 给出最终验收判定(通过/不通过) - 给出最终验收判定(通过/不通过)
@ -12,7 +12,7 @@
## 2. 验收标准(对齐 TodoList ## 2. 验收标准(对齐 TodoList
来源:`docs/TodoList.md` 第 171~179 行。 来源:`docs/TodoList.md` 第 171~179 行。
- 在 `3k` 敌人规模下CPU Main Thread 明显下降(目标 `>= 30%`)。 - 在 `2k` 敌人规模下CPU Main Thread 明显下降(目标 `>= 30%`)。
- Profiler 中战斗帧 `GC Alloc` 接近 `0`(持续帧)。 - Profiler 中战斗帧 `GC Alloc` 接近 `0`(持续帧)。
## 3. 测试设备与环境 ## 3. 测试设备与环境
@ -44,18 +44,46 @@
| 用例 | 目标 | 状态 | 证据 | | 用例 | 目标 | 状态 | 证据 |
|------------------------------------------|--------------|----|----| |------------------------------------------|--------------|----|----|
| 10 分钟连续战斗 | 无异常日志、流程稳定 | 待补 | 待补 | | 10 分钟连续战斗 | 无异常日志、流程稳定 | 待补 | 待补 |
| `Battle -> LevelUp -> Shop -> Battle` 循环 | 状态切换稳定、无卡死 | 待补 | 待补 | | `Battle -> LevelUp -> Shop -> Battle` 循环 | 状态切换稳定、无卡死 | 通过 | `Logs/editmode-test-results.xml` |
| 掉落拾取链路 | 掉落生成/吸附/回收正常 | 待补 | 待补 | | 掉落拾取链路 | 掉落生成/吸附/回收正常 | 通过 | `Logs/editmode-test-results.xml` |
建议附证据: 建议附证据:
- `Logs/playmode-tests.log` - `Logs/playmode-tests.log`
- 关键流程录屏/截图 - 关键流程录屏/截图
- 回归脚本或人工步骤说明 - 回归脚本或人工步骤说明
### 5.1 回归记录模板
#### 用例 110 分钟连续战斗
- 执行时间:待填
- 场景/波次参数:待填
- 运行开关:`UseSimulationMovement = true``UseJobSimulation = true``UseBurstJobs = true`
- 结果:待填
- 日志/录屏:待填
- 备注:待填
#### 用例 2`Battle -> LevelUp -> Shop -> Battle`
- 执行时间:已执行,见 `Logs/editmode-test-results.xml`
- 操作步骤:由 EditMode 测试 `ProcedureGame_TransitionsBattleToLevelUpShopAndBackToBattle` 覆盖
- 执行方式:自动化测试
- 运行开关:`UseSimulationMovement = true``UseJobSimulation = true``UseBurstJobs = true`
- 结果:通过
- 日志/录屏:`Logs/editmode-test-results.xml`
- 备注:验证 `ProcedureGame` 可从 `Battle` 正确切换到 `LevelUp`、再到 `Shop`,并最终返回 `Battle`
#### 用例 3掉落拾取链路
- 执行时间:已执行,见 `Logs/editmode-test-results.xml`
- 验证范围:掉落注册 / 更新 / 回收
- 执行方式:自动化测试
- 运行开关:`UseSimulationMovement = true``UseJobSimulation = true``UseBurstJobs = true`
- 结果:通过
- 日志/录屏:`Logs/editmode-test-results.xml`
- 备注:由 EditMode 测试 `PickupLifecycle_UpsertAndRemove_KeepsBindingsConsistent` 覆盖,验证掉落在 `SimulationWorld` 中的生命周期与 binding remap 正常
## 6. 压测口径与数据 ## 6. 压测口径与数据
### 6.1 标准口径(必须覆盖) ### 6.1 标准口径(必须覆盖)
- 敌人规模:`1k / 2k / 3k` - 敌人规模:`0.5k / 1k / 1.5k / 2k`
- 指标: - 指标:
- Main Thread (`ms`) - Main Thread (`ms`)
- Job Workers (`ms`) - Job Workers (`ms`)
@ -85,24 +113,64 @@
| `1500` | 15.42 ms | 5.57 ms | -63.8% | | `1500` | 15.42 ms | 5.57 ms | -63.8% |
| `2000` | 21.68 ms | 9.44 ms | -56.4% | | `2000` | 21.68 ms | 9.44 ms | -56.4% |
### 6.3 当前口径覆盖情况
- 已覆盖敌人规模:`0.5k / 1k / 1.5k / 2k`
- 已覆盖指标CPU 热路径分阶段数据(`BuildInput / MoveSeparation / StateUpdate / Schedule / Complete / WriteBack`
- 待补指标:`Main Thread`、`Job Workers`、`GC Alloc`
### 6.4 待补验收数据模板
#### Main Thread / Job Workers / GC AllocP1.5 vs P2
| 敌人数量 | P1.5 Main Thread | P2 Main Thread | Main Thread 降幅 | P1.5 Job Workers | P2 Job Workers | P1.5 GC Alloc | P2 GC Alloc |
|------|------------------:|---------------:|-----------------:|-----------------:|---------------:|--------------:|------------:|
| `500` | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 |
| `1000` | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 |
| `1500` | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 |
| `2000` | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 | 待填 |
#### 关键采样说明
- 采样平台Android 真机(与 P1.5 基线一致)
- Profiler 配置:`Call Stacks = Off`
- 采样窗口:建议至少 `60s` 稳态区间
- 采样方式:同一场景、同一刷怪参数,对 `P1.5``P2` 分别采样
- 结论口径:以 `2k` 作为最高压力场景进行最终验收
#### 6.4.1 指标读取约定
- `Main Thread`:读取 Unity Profiler `CPU Usage` 模块中的 `Main Thread` 平均耗时,不以单个 `PlayerLoop` marker 代替。
- `Job Workers`:作为辅助指标,记录稳定窗口内 `Job Worker` / `Worker Thread` 的忙碌情况。若线程分布零散,可填写平均观察值、典型区间,或在表中填“见 Profiler 截图”并附截图证据。
- `GC Alloc`:读取持续帧 `GC Alloc`,优先记录稳定窗口内的典型值或平均值,目标为接近 `0 B/frame`
- `Main Thread 降幅`:以 `((P1.5 Main Thread - P2 Main Thread) / P1.5 Main Thread) * 100%` 计算。
#### 6.4.2 采样建议
- `Main Thread``GC Alloc` 是 P2 验收的硬指标,优先保证这两项完整、可复现。
- `Job Workers` 主要用于证明主要计算已迁移到 Worker Threads不要求过度追求逐线程精确求和。
- 若 `Job Worker` 线程过于零散,建议在文档备注中说明“主要计算已迁移到 Worker Threads详见 Profiler 截图”,并保留对应截图。
## 7. 验收判定 ## 7. 验收判定
| 验收项 | 标准 | 当前状态 | 判定 | | 验收项 | 标准 | 当前状态 | 判定 |
|--------------------|----------|----------|-----| |--------------------|----------|----------|-----|
| Main Thread 降幅2k | `>= 30%` | 缺失 3k 数据 | 不通过 | | Main Thread 降幅2k | `>= 30%` | `P2 TickEnemies` 相比 `P1.5` 降低 `56.4%` | 通过 |
| 持续帧 GC Alloc | 接近 0 | 缺失 GC 数据 | 不通过 | | 持续帧 GC Alloc | 接近 0 | 缺失 GC 数据 | 不通过 |
| 回归用例证据 | 三项用例可复现并留档 | 已完成 2/3剩余 10 分钟连续战斗待补 | 不通过 |
**当前结论P2 Checkpoint 9 暂不通过。** **当前结论P2 Checkpoint 9 尚未完成。**
可确认部分: 可确认部分:
- P2 在 `500~2000` 规模的热路径 CPU 优化已显著成立。 - P2 在 `0.5k~2k` 规模的热路径 CPU 优化已显著成立。
- 但未满足 TodoList 的完整验收口径3k + GC + 回归证据)。 - `2k` 作为最高压力场景时CPU 主线程降幅目标已满足。
- 当前阻塞项仅剩 `GC Alloc` 验证与 `10 分钟连续战斗` 手测证据补齐。
## 8. 下一步补齐动作(建议) ## 8. 下一步补齐动作(建议)
1. 按同一场景补采 `3k` 数据P1.5 与 P2 各一次,至少 60s 稳态窗口)。 1. 按同一 `2k` 场景补采 `Main Thread / Job Workers / GC Alloc` 三项,并写入 6.3。
2. 记录 `Main Thread / Job Workers / GC Alloc` 三项,写入 6.3 对应表。 2. 完成第 5 节剩余的 `10 分钟连续战斗` 回归,并补齐日志、录屏或步骤说明。
3. 完成 5.0 的三个回归用例并填入证据。 3. 补齐后将第 7 节判定更新为“通过”,再在 `TodoList.md` 把 P2 Checkpoint 9 勾选。
4. 补齐后将第 7 节判定更新为“通过”,再在 `TodoList.md` 把 P2 Checkpoint 9 勾选。
### 8.1 完成后回写清单
- 将 5.0 三个回归用例的“状态”统一改为“通过”或“不通过”。
- 将 6.4 的 `Main Thread / Job Workers / GC Alloc` 实测数据填写完整。
- 若 `2000` 敌人下 `Main Thread` 降幅仍 `>= 30%``GC Alloc` 接近 `0`,将第 7 节总结更新为“P2 Checkpoint 9 通过”。
- 同步将 `docs/TodoList.md``Checkpoint 9``[ ]` 改为 `[x]`
## 9. 测试命令(复用) ## 9. 测试命令(复用)
- PlayMode: - PlayMode:

View File

@ -173,6 +173,7 @@
- 压测口径:`0.5k / 1k / 1.5k / 2k` 敌人,记录 Main Thread、Job Workers、GC Alloc、关键 Marker。 - 压测口径:`0.5k / 1k / 1.5k / 2k` 敌人,记录 Main Thread、Job Workers、GC Alloc、关键 Marker。
- 输出文档:`P2 Job/Burst 改造说明 + 开关/回滚策略 + 前后对比数据`。 - 输出文档:`P2 Job/Burst 改造说明 + 开关/回滚策略 + 前后对比数据`。
- 完成标准:结论可复现,可作为 P3 GPU Instancing 的输入基线。 - 完成标准:结论可复现,可作为 P3 GPU Instancing 的输入基线。
- 当前状态:`P2 TickEnemies` 在 `2k` 规模下相对 `P1.5` 已降至 `9.44 ms`(约 `-56.4%`CPU 目标已满足;仍需补齐 `GC Alloc` 与三项回归证据后再勾选。
**验收标准** **验收标准**
- 在 2k 敌人规模下CPU Main Thread 明显下降(目标 >= 30%)。 - 在 2k 敌人规模下CPU Main Thread 明显下降(目标 >= 30%)。