Merge pull request #1 from SepComet/P0-最小可玩闭环

P0-最小可玩闭环-战斗与事件节点
This commit is contained in:
SepComet
2026-02-28 16:03:12 +08:00
committed by GitHub
253 changed files with 46291 additions and 782 deletions
+3
View File
@@ -101,3 +101,6 @@ InitTestScene*.unity*
/数据表
/.vscode
AGENTS.md
/.dotnet-home
/[Aa]ssets/RawResources
/[Aa]ssets/RawResources.meta
+2 -3
View File
@@ -1,7 +1,6 @@
# 底座表
# Id EntityId Name AttackSpeed PropertyType Constraint PossibleTag
# Id 策划备注 EntityId Name AttackSpeed PropertyType Constraint PossibleTag
# int int string float[] AttackPropertyType string TagType[]
# 底座编号 策划备注 实体Id 底座名 攻击速度(秒/次) 攻击属性 属性约束 可能出现的Tag
# 底座编号 实体Id 底座名 攻击速度(秒/次) 攻击属性 属性约束 可能出现的Tag
1 301 元素底座 [2,1.5,1,0.8,0.7] Fire [Fire,BurnSpread,IgniteBurst,Inferno]
2 301 控制底座 [4,3.8,3.6,3.4,3.2] Ice [Ice,FreezeMask,Shatter,AbsoluteZero]
3 301 穿透底座 [1,0,8,0,6,0.4,0.2] Physics [Pierce,Crit,Overpenetrate,Execution]
+2 -3
View File
@@ -1,7 +1,6 @@
# 轴承表
# Id EntityId Name RotateSpeed AttackRange Constraint PossibleTag
# Id 策划备注 EntityId Name RotateSpeed AttackRange Constraint PossibleTag
# int int string float[] float[] string TagType[]
# 轴承编号 策划备注 实体Id 轴承名 各品质旋转速度 各品质攻击范围 属性约束 可能出现的Tag
# 轴承编号 实体Id 轴承名 各品质旋转速度 各品质攻击范围 属性约束 可能出现的Tag
1 201 元素轴承 [10,12,13,14,15] [2,2,2,2,2] [Fire,BurnSpread,IgniteBurst,Inferno]
2 201 控制轴承 [20,25,30,32,35] [6,6.5,7,8,8] [Ice,FreezeMask,Shatter,AbsoluteZero]
3 201 穿透轴承 [60,70,80,90,100] [4,4.5,5,5.5,6] [Pierce,Crit,Overpenetrate,Execution]
+5
View File
@@ -0,0 +1,5 @@
# Id 策划备注 EntityId BaseHp BaseDamage Speed DropCoin
# int int int int float int
# 敌人Id 敌人实体Id 基础血量 基地伤害 移动速度 掉落硬币
1 1001 500 5 1 5
2 1002 5000 50 0.5 100
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d2ccda2aca4a348428805b273ee2aaf2
guid: d860a5c1c09addf4ba7539e97c85c0bd
TextScriptImporter:
externalObjects: {}
userData:
+4 -3
View File
@@ -1,7 +1,8 @@
# 实体表
# Id AssetName
# Id 策划备注 AssetName
# int string
# 实体编号 策划备注 资源名称
# 实体编号 资源名称
101 测试枪口 TestMuzzle
201 测试轴承 TestBearing
301 测试底座 TestBase
1001 测试普通敌人 TestEnemy
1002 测试Boss TestBoss
+6
View File
@@ -0,0 +1,6 @@
# Id 列1 Title Description Options
# int string string string
# 事件编号 策划备注 事件题目 事件描述 事件选项
1 赌马 一名商人邀请你下注。赢了就能赚一笔。 [{"optionText":"下注 100(金)- 稳健:70% 赢 150","requirements":[{"type":"GoldAtLeast","param":{"Count":100}}],"costEffects":[{"type":"AddGold","param":{"Count":-100}}],"rewardEffects":[{"type":"AddGold","param":{"Count":150}}],"probability":0.7},{"optionText":"下注 100(金)- 激进:30% 赢 250","requirements":[{"type":"GoldAtLeast","param":{"Count":100}}],"costEffects":[{"type":"AddGold","param":{"Count":-100}}],"rewardEffects":[{"type":"AddGold","param":{"Count":250}}],"probability":0.3}]
2 工匠的熔炉 工匠以金币交换防御塔组件。 [{"optionText":"交出 3 个白色组件,获得 50 金币","requirements":[{"type":"CompCountAtLeast","param":{"Count":3,"Rarity":"White"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":3,"Rarity":"White"}}],"rewardEffects":[{"type":"AddGold","param":{"Count":50}}]},{"optionText":"交出 2 个绿色组件,获得 70 金币","requirements":[{"type":"CompCountAtLeast","param":{"Count":2,"Rarity":"Green"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":2,"Rarity":"Green"}}],"rewardEffects":[{"type":"AddGold","param":{"Count":70}}]},{"optionText":"交出 1 个蓝色组件,获得 80 金币","requirements":[{"type":"CompCountAtLeast","param":{"Count":1,"Rarity":"Blue"}}],"costEffects":[{"type":"RemoveRandomComps","param":{"Count":1,"Rarity":"Blue"}}],"rewardEffects":[{"type":"AddGold","param":{"Count":80}}]},{"optionText":"拒绝","rewardEffects":[]}]
3 代价与回报 某种黑暗力量向你索取代价。 [{"optionText":"展示你的防御塔","requirements":[{"type":"TowerCountAtLeast","param":{"Count":2}}],"rewardEffects":[{"type":"AddGold","param":{"Count":50}}]},{"optionText":"离开","rewardEffects":[]}]
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d64aa32f74f749c42847421244466515
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+7
View File
@@ -0,0 +1,7 @@
# Id 列1 LevelThemeType BaseHp StartCoin VictoryType VictoryParam RewardGold
# int LevelThemeType int int VictoryType string int
# 关卡号 策划备注 关卡所属主题类型 基地初始生命 初始硬币 胜利条件 胜利参数 奖励金币
1 平原1 Plain 100 100 PhasesCleared 30
2 平原2 Plain 100 100 PhasesCleared 30
3 平原3 Plain 100 100 PhasesCleared 40
4 平原4 Plain 100 100 BossDead 100
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 05abbe814ec127b46a5e1a640a899e09
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+23
View File
@@ -0,0 +1,23 @@
# Id 列1 DurationSeconds EndType EndParam
# int int PhaseEndType string
# 关卡阶段号 策划备注 波次持续时间 波次结束条件 结束参数
1001 平原1.1 60 TimeElapsed 60
1002 平原1.2 60 TimeElapsed 60
1003 平原1.3 60 TimeElapsed 60
1004 平原1.4 60 EnemiesCleared 60
1005 平原1.* 0
2001 平原2.1 60 TimeElapsed 60
2002 平原2.2 60 TimeElapsed 60
2003 平原2.3 60 TimeElapsed 60
2004 平原2.4 60 EnemiesCleared 60
2005 平原2.* 0
3001 平原3.1 60 TimeElapsed 60
3002 平原3.2 60 TimeElapsed 60
3003 平原3.3 60 TimeElapsed 60
3004 平原3.4 60 EnemiesCleared 60
3005 平原3.* 0
4001 平原4.1 60 TimeElapsed 60
4002 平原4.2 60 TimeElapsed 60
4003 平原4.3 60 TimeElapsed 60
4004 平原4.4 60 TimeElapsed 60
4005 平原4.5 0 BossDead
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 392984cd87843f34989251453d4a455b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
# Id 列1 SpawnPointId StartTime EntryType EnemyId Count Interval Duration Gap
# int int int EntryType int int float int float
# 阶段条目号 策划备注 敌人出生口Id 相对时间 条目类型 敌人Id 单次出怪数量 出怪间隔 持续时间 单怪出生时间间隔
1001001 1 5 Stream 1 3 5 60 0
1001002 2 5 Burst 1 10 0 0 0.5
1002001 1 3 Stream 1 3 5 60 0
1002002 2 3 Burst 1 10 0 0 0.5
1003001 1 5 Stream 1 3 5 60 0
1003002 2 5 Burst 1 10 0 0 0.5
1004001 1 3 Stream 1 3 5 60 0
1004002 2 3 Burst 1 10 0 0 0.5
1005001 1 5 Stream 1 3 5 60 0
1005002 2 5 Burst 1 10 0 0 0.5
2001001 1 5 Stream 1 3 5 60 0
2002001 1 5 Burst 1 10 0 0 0.5
2003001 1 5 Stream 1 3 5 60 0
2004001 1 5 Burst 1 10 0 0 0.5
2005001 1 5 Stream 1 3 5 60 0
3001001 1 5 Stream 1 3 5 60 0
3001002 2 5 Burst 1 10 0 0 0.5
3002001 1 3 Stream 1 3 5 60 0
3002002 2 3 Burst 1 10 0 0 0.5
3003001 1 5 Stream 1 3 5 60 0
3003002 2 5 Burst 1 10 0 0 0.5
3004001 1 3 Stream 1 3 5 60 0
3004002 2 3 Burst 1 10 0 0 0.5
3005001 1 5 Stream 1 3 5 60 0
3005002 2 5 Burst 1 10 0 0 0.5
4001001 1 5 Stream 1 3 5 60 0
4002001 1 5 Burst 1 10 0 0 0.5
4003001 1 5 Stream 1 3 5 60 0
4004001 1 5 Burst 1 10 0 0 0.5
4005001 1 5 Boss 2 1 5 60 0
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 554836c1a57522a4e887ae48023708fc
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+3 -4
View File
@@ -1,7 +1,6 @@
# 枪口表
# Id EntityId Name AttackDamage DamageRandomRate Method Constraint PossibleTag
# int int string int[] float AttackMethodType string TagType[]
# 枪口编号 策划备注 实体Id 枪口名 各品质伤害 伤害浮动 攻击方式 属性约束 可能出现的Tag
# Id 策划备注 EntityId Name AttackDamage DamageRandomRate Method Constraint PossibleTag
# int int string int[] float AttackMethodType Constraint TagType[]
# 枪口编号 策划备注 实体Id 枪口名 各品质伤害 伤害浮动 攻击方式 Constraint 可能出现的Tag
1 101 元素枪口 [20,30,40,50,80] 0.05 NormalBullet [Fire,BurnSpread,IgniteBurst,Inferno]
2 101 控制枪口 [30,50,70,90,100] 0.01 NormalBullet [Ice,FreezeMask,Shatter,AbsoluteZero]
3 101 穿透枪口 [50,55,60,80,90] 0.02 NormalBullet [Pierce,Crit,Overpenetrate,Execution]
+8
View File
@@ -0,0 +1,8 @@
# Id 列1 Rarity MinPrice MaxPrice
# int RarityType int int
# 定价编号 策划备注 商品品质 最低价格 最高价格
1 White 10 30
2 Green 50 70
3 Blue 100 120
4 Purple 150 170
5 Red 200 220
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 55f4464fd7c6ad14584b9b10aabc18ba
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+2
View File
@@ -6,3 +6,5 @@
100 主菜单 MenuForm Default False True
101 设置 SettingForm Default False True
102 关于 AboutForm Default False True
200 事件UI EventForm Default False True
201 背包UI RepoForm Default False True
-5
View File
@@ -1,5 +0,0 @@
# 武器表
# Id Attack AttackInterval BulletId BulletSpeed BulletSoundId
# int int float int float int
# 武器编号 策划备注 攻击力 攻击间隔 子弹编号 子弹速度 子弹声音编号
30000 玩家武器 100 0.2 50000 20 10000
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3ecda907b9217e5499e31cb81a154ff4
guid: 7258f46ca3cc69546ad93c07bcd08582
folderAsset: yes
DefaultImporter:
externalObjects: {}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0cc71b1087c7dfd42a8233a7101fc27e
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e69bde6c996edd94397c98e836e81bf3
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 80277bb9563227842a4231e7178e88a7
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f4c8efb3dfa084241abdcb15db1f27e8
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+100
View File
@@ -0,0 +1,100 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6419463151899262168
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2220800525145552943}
- component: {fileID: 6893725239225079109}
- component: {fileID: 1122596187208388309}
m_Layer: 0
m_Name: TestBoss
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2220800525145552943
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6419463151899262168}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.3, y: 1.3, z: 1.3}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &6893725239225079109
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6419463151899262168}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: -1869315837
m_SortingLayer: 1
m_SortingOrder: 0
m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b,
type: 3}
m_Color: {r: 0.7, g: 0.7, b: 0.7, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1, y: 1}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!114 &1122596187208388309
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6419463151899262168}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9baf591df06b9bb44ba5313457ba84b3, type: 3}
m_Name:
m_EditorClassIdentifier:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b375b29295fee874795f6e0f2b9a6fe6
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+100
View File
@@ -0,0 +1,100 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &6419463151899262168
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2220800525145552943}
- component: {fileID: 6893725239225079109}
- component: {fileID: 1122596187208388309}
m_Layer: 0
m_Name: TestEnemy
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &2220800525145552943
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6419463151899262168}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &6893725239225079109
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6419463151899262168}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 0
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 0
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: -1869315837
m_SortingLayer: 1
m_SortingOrder: 0
m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b,
type: 3}
m_Color: {r: 0.7, g: 0.7, b: 0.7, a: 1}
m_FlipX: 0
m_FlipY: 0
m_DrawMode: 0
m_Size: {x: 1, y: 1}
m_AdaptiveModeThreshold: 0.5
m_SpriteTileMode: 0
m_WasSpriteAssigned: 1
m_MaskInteraction: 0
m_SpriteSortPoint: 0
--- !u!114 &1122596187208388309
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6419463151899262168}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9baf591df06b9bb44ba5313457ba84b3, type: 3}
m_Name:
m_EditorClassIdentifier:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 0cd1424cd9c330d4492a6d541cb4275e
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+4 -4
View File
@@ -321,7 +321,7 @@ Camera:
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_BackGroundColor: {r: 0, g: 0, b: 0, a: 1}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
@@ -343,10 +343,10 @@ Camera:
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
far clip plane: 50
field of view: 60
orthographic: 0
orthographic size: 5
orthographic: 1
orthographic size: 7.5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
+4 -4
View File
@@ -343,10 +343,10 @@ Camera:
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
far clip plane: 50
field of view: 60
orthographic: 0
orthographic size: 5
orthographic: 1
orthographic size: 7.5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
@@ -371,7 +371,7 @@ Transform:
m_GameObject: {fileID: 961739749}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalPosition: {x: 0, y: 0, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
@@ -5,6 +5,7 @@
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using CustomComponent;
using GeometryTD.CustomComponent;
using UnityEngine;
@@ -17,12 +18,18 @@ public partial class GameEntry : MonoBehaviour
public static HPBarComponent HPBar { get; private set; }
public static EnemyManagerComponent EnemyManager { get; private set; }
public static UIRouterComponent UIRouter { get; private set; }
public static EventNodeComponent EventNode { get; private set; }
public static CombatNodeComponent CombatNode { get; private set; }
private static void InitCustomComponents()
{
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
HPBar = UnityGameFramework.Runtime.GameEntry.GetComponent<HPBarComponent>();
EnemyManager = UnityGameFramework.Runtime.GameEntry.GetComponent<EnemyManagerComponent>();
UIRouter = UnityGameFramework.Runtime.GameEntry.GetComponent<UIRouterComponent>();
EventNode = UnityGameFramework.Runtime.GameEntry.GetComponent<EventNodeComponent>();
CombatNode = UnityGameFramework.Runtime.GameEntry.GetComponent<CombatNodeComponent>();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c942b083720d4d588063c5f1dde93428
timeCreated: 1772165636
@@ -0,0 +1,255 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
/// <summary>
/// 鎴樻枟鑺傜偣缁勪欢
/// </summary>
public class CombatNodeComponent : GameFrameworkComponent
{
// Level.Id => Level
private readonly Dictionary<int, DRLevel> _levelsById = new();
// LevelId => LevelPhases
private readonly Dictionary<int, List<DRLevelPhase>> _phasesByLevelId = new();
// LevelPhase.Id => LevelSpawnEntries
private readonly Dictionary<int, List<DRLevelSpawnEntry>> _spawnEntriesByPhaseId = new();
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _selectedSpawnEntriesByPhaseId = new();
private readonly List<int> _levelIdBuffer = new();
private readonly CombatScheduler _combatScheduler = new CombatScheduler();
private bool _runtimeInitialized;
public LevelThemeType CurrentThemeType { get; private set; }
public DRLevel CurrentLevel { get; private set; }
public void OnInit(LevelThemeType themeType)
{
ShutdownBattleRuntime();
CurrentThemeType = themeType;
CurrentLevel = null;
_levelsById.Clear();
_phasesByLevelId.Clear();
_spawnEntriesByPhaseId.Clear();
_selectedSpawnEntriesByPhaseId.Clear();
_levelIdBuffer.Clear();
IDataTable<DRLevel> dtLevel = GameEntry.DataTable.GetDataTable<DRLevel>();
IDataTable<DRLevelPhase> dtLevelPhase = GameEntry.DataTable.GetDataTable<DRLevelPhase>();
IDataTable<DRLevelSpawnEntry> dtSpawnEntry = GameEntry.DataTable.GetDataTable<DRLevelSpawnEntry>();
if (dtLevel == null || dtLevelPhase == null || dtSpawnEntry == null)
{
Log.Warning("CombatNodeComponent init failed. Missing data table(s).");
return;
}
DRLevel[] levels = dtLevel.GetAllDataRows();
for (int i = 0; i < levels.Length; i++)
{
DRLevel level = levels[i];
if (level.LevelThemeType != themeType)
{
continue;
}
_levelsById[level.Id] = level;
_phasesByLevelId[level.Id] = new List<DRLevelPhase>();
_levelIdBuffer.Add(level.Id);
}
DRLevelPhase[] levelPhases = dtLevelPhase.GetAllDataRows();
for (int i = 0; i < levelPhases.Length; i++)
{
DRLevelPhase phase = levelPhases[i];
int levelId = phase.Id / 1000;
if (!_levelsById.ContainsKey(levelId))
{
continue;
}
if (!_phasesByLevelId.TryGetValue(levelId, out List<DRLevelPhase> phases))
{
phases = new List<DRLevelPhase>();
_phasesByLevelId[levelId] = phases;
}
phases.Add(phase);
_spawnEntriesByPhaseId[phase.Id] = new List<DRLevelSpawnEntry>();
}
DRLevelSpawnEntry[] spawnEntries = dtSpawnEntry.GetAllDataRows();
for (int i = 0; i < spawnEntries.Length; i++)
{
DRLevelSpawnEntry spawnEntry = spawnEntries[i];
int phaseId = spawnEntry.Id / 1000;
int levelId = phaseId / 1000;
if (!_levelsById.ContainsKey(levelId))
{
continue;
}
if (!_spawnEntriesByPhaseId.TryGetValue(phaseId, out List<DRLevelSpawnEntry> entries))
{
entries = new List<DRLevelSpawnEntry>();
_spawnEntriesByPhaseId[phaseId] = entries;
}
entries.Add(spawnEntry);
}
foreach (List<DRLevelPhase> phaseList in _phasesByLevelId.Values)
{
phaseList.Sort((left, right) => left.Id.CompareTo(right.Id));
}
foreach (List<DRLevelSpawnEntry> phaseSpawnEntries in _spawnEntriesByPhaseId.Values)
{
phaseSpawnEntries.Sort((left, right) =>
{
int timeCompare = left.StartTime.CompareTo(right.StartTime);
if (timeCompare != 0)
{
return timeCompare;
}
return left.Id.CompareTo(right.Id);
});
}
Log.Info(
"CombatNodeComponent initialized. Theme={0}, Levels={1}, Phases={2}, Entries={3}.",
themeType,
_levelsById.Count,
CountPhases(),
CountEntries());
EnsureBattleRuntimeInitialized();
}
public void StartCombat()
{
if (!EnsureBattleRuntimeInitialized())
{
Log.Warning("CombatNodeComponent start failed. Missing scheduler runtime.");
return;
}
if (!TrySelectRandomLevel(out DRLevel selectedLevel))
{
Log.Warning("CombatNodeComponent has no level cache. Call OnInit(levelThemeType) first.");
return;
}
if (!_phasesByLevelId.TryGetValue(selectedLevel.Id, out List<DRLevelPhase> phaseList) ||
phaseList == null ||
phaseList.Count <= 0)
{
Log.Warning("CombatNodeComponent start failed. Level '{0}' has no phase data.", selectedLevel.Id);
return;
}
_selectedSpawnEntriesByPhaseId.Clear();
foreach (var phase in phaseList)
{
if (_spawnEntriesByPhaseId.TryGetValue(phase.Id, out List<DRLevelSpawnEntry> entries) &&
entries != null)
{
_selectedSpawnEntriesByPhaseId[phase.Id] = entries;
}
else
{
_selectedSpawnEntriesByPhaseId[phase.Id] = new List<DRLevelSpawnEntry>();
}
}
CurrentLevel = selectedLevel;
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
}
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_runtimeInitialized)
{
return;
}
_combatScheduler.OnUpdate(elapseSeconds, realElapseSeconds);
}
public void OnShutdown()
{
CurrentLevel = null;
ShutdownBattleRuntime();
}
private void OnDestroy()
{
OnShutdown();
}
private bool EnsureBattleRuntimeInitialized()
{
if (_runtimeInitialized)
{
return true;
}
_combatScheduler.OnInit();
_runtimeInitialized = true;
return true;
}
private void ShutdownBattleRuntime()
{
if (!_runtimeInitialized)
{
return;
}
_combatScheduler.OnDestroy();
_runtimeInitialized = false;
}
private bool TrySelectRandomLevel(out DRLevel level)
{
level = null;
if (_levelIdBuffer.Count <= 0)
{
return false;
}
int selectedIndex = Random.Range(0, _levelIdBuffer.Count);
int selectedLevelId = _levelIdBuffer[selectedIndex];
return _levelsById.TryGetValue(selectedLevelId, out level);
}
private int CountPhases()
{
int count = 0;
foreach (List<DRLevelPhase> phases in _phasesByLevelId.Values)
{
count += phases.Count;
}
return count;
}
private int CountEntries()
{
int count = 0;
foreach (List<DRLevelSpawnEntry> list in _spawnEntriesByPhaseId.Values)
{
count += list.Count;
}
return count;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5e646500b9304d438746f2351c71a0f4
timeCreated: 1772103300
@@ -0,0 +1,422 @@
using System.Collections.Generic;
using System.Globalization;
using GameFramework.Event;
using GameFramework.Resource;
using GeometryTD.CustomUtility;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using GeometryTD.Entity;
using GeometryTD.Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class CombatScheduler
{
private enum SchedulerState : byte
{
Idle = 0,
WaitingForMap = 1,
RunningPhase = 2,
Completed = 3,
Failed = 4
}
private readonly List<DRLevelPhase> _phaseBuffer = new List<DRLevelPhase>();
private readonly Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>> _spawnEntriesByPhaseId =
new Dictionary<int, IReadOnlyList<DRLevelSpawnEntry>>();
private readonly EnemyManager _enemyManager = new EnemyManager();
private EntityComponent _entity;
private DRLevel _currentLevel;
private DRLevelPhase _currentPhase;
private int _currentPhaseIndex;
private float _currentPhaseElapsed;
private SchedulerState _state = SchedulerState.Idle;
private int _loadingMapEntityId;
private int _loadedMapEntityId;
private bool _initialized;
private bool _isEntityEventSubscribed;
private MapEntity _currentMap;
public bool IsRunning => _state == SchedulerState.WaitingForMap || _state == SchedulerState.RunningPhase;
public bool IsCompleted => _state == SchedulerState.Completed;
public DRLevel CurrentLevel => _currentLevel;
public DRLevelPhase CurrentPhase => _currentPhase;
public MapEntity CurrentMap => _currentMap;
public void OnInit()
{
if (!_initialized)
{
_entity = GameEntry.Entity;
EnsureEntityEventSubscribed();
_enemyManager.OnInit(this);
_initialized = true;
}
ResetRuntime();
}
public void Start(
DRLevel level,
IReadOnlyList<DRLevelPhase> phases,
IReadOnlyDictionary<int, IReadOnlyList<DRLevelSpawnEntry>> spawnEntriesByPhaseId)
{
if (!_initialized || _entity == null)
{
_state = SchedulerState.Failed;
Log.Warning("CombatScheduler start failed. Runtime is not initialized.");
return;
}
if (level == null || phases == null || phases.Count <= 0)
{
_state = SchedulerState.Failed;
Log.Warning("CombatScheduler start failed. Invalid level or phase data.");
return;
}
_enemyManager.EndPhase();
HideCurrentMapIfNeeded();
ResetRuntime();
_currentLevel = level;
foreach (var phase in phases)
{
_phaseBuffer.Add(phase);
}
if (spawnEntriesByPhaseId != null)
{
foreach (var pair in spawnEntriesByPhaseId)
{
_spawnEntriesByPhaseId[pair.Key] = pair.Value;
}
}
if (_phaseBuffer.Count <= 0)
{
_state = SchedulerState.Failed;
Log.Warning("CombatScheduler start failed. Level '{0}' has no phase data.", level.Id);
return;
}
if (!TryShowMap(level))
{
_state = SchedulerState.Failed;
return;
}
_currentPhase = null;
_currentPhaseIndex = -1;
_currentPhaseElapsed = 0f;
_state = SchedulerState.WaitingForMap;
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseBuffer.Count);
}
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
switch (_state)
{
case SchedulerState.WaitingForMap:
if (_currentMap == null)
{
return;
}
BeginNextPhase();
return;
case SchedulerState.RunningPhase:
UpdateCurrentPhase(elapseSeconds, realElapseSeconds);
return;
default:
return;
}
}
public void OnDestroy()
{
if (!_initialized)
{
return;
}
_enemyManager.OnDestroy();
HideCurrentMapIfNeeded();
ResetRuntime();
UnsubscribeEntityEvents();
_entity = null;
_initialized = false;
}
private bool TryShowMap(DRLevel level)
{
string mapAssetName = level.Id.ToString();
string mapAssetPath = AssetUtility.GetLevelMapAsset(mapAssetName);
if (GameEntry.Resource.HasAsset(mapAssetPath) == HasAssetResult.NotExist)
{
Log.Warning(
"CombatScheduler start failed. Level '{0}' map asset not found: '{1}'.",
level.Id,
mapAssetPath);
return false;
}
_loadingMapEntityId = _entity.GenerateSerialId();
_entity.ShowMap(new MapData(_loadingMapEntityId, level.Id, Vector3.zero), mapAssetName);
Log.Info(
"CombatScheduler loading map. Level={0}, Asset='{1}', EntityId={2}.",
level.Id,
mapAssetPath,
_loadingMapEntityId);
return true;
}
private void UpdateCurrentPhase(float elapseSeconds, float realElapseSeconds)
{
if (_currentPhase == null)
{
_state = SchedulerState.Failed;
Log.Warning("CombatScheduler update failed. Current phase is null.");
return;
}
_currentPhaseElapsed += elapseSeconds;
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (!ShouldEndCurrentPhase())
{
return;
}
CompleteCurrentPhase();
}
private bool ShouldEndCurrentPhase()
{
switch (_currentPhase.EndType)
{
case PhaseEndType.TimeElapsed:
return _currentPhaseElapsed >= ResolveTimeElapsedThreshold(_currentPhase);
case PhaseEndType.EnemiesCleared:
case PhaseEndType.BossDead:
return _enemyManager.IsPhaseSpawnCompleted && _enemyManager.AliveEnemyCount <= 0;
case PhaseEndType.None:
default:
if (_currentPhase.DurationSeconds > 0 && _currentPhaseElapsed >= _currentPhase.DurationSeconds)
{
return true;
}
return _enemyManager.IsPhaseSpawnCompleted && _enemyManager.AliveEnemyCount <= 0;
}
}
private float ResolveTimeElapsedThreshold(DRLevelPhase phase)
{
if (!string.IsNullOrWhiteSpace(phase.EndParam) &&
float.TryParse(phase.EndParam, NumberStyles.Float, CultureInfo.InvariantCulture,
out float parsedSeconds))
{
return parsedSeconds;
}
if (phase.DurationSeconds > 0)
{
return phase.DurationSeconds;
}
return 0f;
}
private void CompleteCurrentPhase()
{
_enemyManager.EndPhase();
Log.Info(
"CombatScheduler phase completed. Level={0}, Phase={1}, Elapsed={2:F2}s.",
_currentLevel != null ? _currentLevel.Id : 0,
_currentPhase != null ? _currentPhase.Id : 0,
_currentPhaseElapsed);
BeginNextPhase();
}
private void BeginNextPhase()
{
_currentPhaseIndex++;
if (_currentPhaseIndex >= _phaseBuffer.Count)
{
_state = SchedulerState.Completed;
_currentPhase = null;
Log.Info("CombatScheduler level completed. Level={0}.", _currentLevel != null ? _currentLevel.Id : 0);
return;
}
_currentPhase = _phaseBuffer[_currentPhaseIndex];
_currentPhaseElapsed = 0f;
IReadOnlyList<DRLevelSpawnEntry> spawnEntries = null;
_spawnEntriesByPhaseId.TryGetValue(_currentPhase.Id, out spawnEntries);
_enemyManager.BeginPhase(_currentPhase, spawnEntries);
_state = SchedulerState.RunningPhase;
Log.Info(
"CombatScheduler phase started. Level={0}, Phase={1}, EndType={2}, Entries={3}.",
_currentLevel != null ? _currentLevel.Id : 0,
_currentPhase.Id,
_currentPhase.EndType,
spawnEntries != null ? spawnEntries.Count : 0);
}
private void HideCurrentMapIfNeeded()
{
if (_entity == null)
{
_currentMap = null;
_loadingMapEntityId = 0;
_loadedMapEntityId = 0;
return;
}
if (_loadingMapEntityId != 0)
{
EntityBase loadingMap = _entity.GetGameEntity(_loadingMapEntityId);
if (loadingMap != null)
{
_entity.HideEntity(loadingMap);
}
}
if (_loadedMapEntityId != 0)
{
EntityBase loadedMap = _entity.GetGameEntity(_loadedMapEntityId);
if (loadedMap != null)
{
_entity.HideEntity(loadedMap);
}
}
_loadingMapEntityId = 0;
_loadedMapEntityId = 0;
_currentMap = null;
}
private void EnsureEntityEventSubscribed()
{
if (_isEntityEventSubscribed)
{
return;
}
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_isEntityEventSubscribed = true;
}
private void UnsubscribeEntityEvents()
{
if (!_isEntityEventSubscribed)
{
return;
}
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_isEntityEventSubscribed = false;
}
private void ResetRuntime()
{
_phaseBuffer.Clear();
_spawnEntriesByPhaseId.Clear();
_currentLevel = null;
_currentPhase = null;
_currentPhaseIndex = -1;
_currentPhaseElapsed = 0f;
_state = SchedulerState.Idle;
}
#region Event Handlers
private void OnShowEntitySuccess(object sender, GameEventArgs e)
{
if (!(e is ShowEntitySuccessEventArgs ne))
{
return;
}
if (ne.EntityLogicType != typeof(MapEntity) || ne.Entity.Id != _loadingMapEntityId)
{
return;
}
_loadedMapEntityId = _loadingMapEntityId;
_loadingMapEntityId = 0;
_currentMap = ne.Entity.Logic as MapEntity;
if (_currentMap == null)
{
_state = SchedulerState.Failed;
Log.Error("Loaded map entity logic is invalid. EntityId={0}.", ne.Entity.Id);
return;
}
Log.Info(
"Map ready. LevelId={0}, PathCells={1}, FoundationCells={2}, Spawners={3}, House={4}.",
_currentLevel != null ? _currentLevel.Id : 0,
_currentMap.PathCells.Count,
_currentMap.FoundationCells.Count,
_currentMap.Spawners.Length,
_currentMap.House != null ? _currentMap.House.name : "None");
}
private void OnShowEntityFailure(object sender, GameEventArgs e)
{
if (!(e is ShowEntityFailureEventArgs ne))
{
return;
}
if (ne.EntityLogicType != typeof(MapEntity) || ne.EntityId != _loadingMapEntityId)
{
return;
}
_loadingMapEntityId = 0;
_currentMap = null;
_state = SchedulerState.Failed;
Log.Error(
"Map load failed. LevelId={0}, Asset='{1}', Error='{2}'.",
_currentLevel != null ? _currentLevel.Id : 0,
ne.EntityAssetName,
ne.ErrorMessage);
}
private void OnHideEntityComplete(object sender, GameEventArgs e)
{
if (!(e is HideEntityCompleteEventArgs ne))
{
return;
}
if (ne.EntityId == _loadedMapEntityId)
{
_loadedMapEntityId = 0;
_currentMap = null;
}
if (ne.EntityId == _loadingMapEntityId)
{
_loadingMapEntityId = 0;
}
}
#endregion
}
}
@@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 894291f7d20e5dc4bb7264dec378b1ce
timeCreated: 1528026163
licenseType: Pro
guid: 9f43fd8050fd4239b4407a4fedcf0b7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
@@ -0,0 +1,465 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GameFramework.Event;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using GeometryTD.Entity;
using GeometryTD.Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class EnemyManager
{
private sealed class SpawnEntryRuntime
{
public DRLevelSpawnEntry Entry;
public bool Completed;
public float NextTriggerTime;
public float EndTime;
public int RemainingCount;
}
private const int DefaultEnemyConfigId = 1;
private const float MinStreamInterval = 0.05f;
private const float MinBurstGap = 0.01f;
private readonly List<Spawner> _spawners = new List<Spawner>();
private readonly Dictionary<int, Spawner> _spawnerByOrder = new Dictionary<int, Spawner>();
private readonly List<Vector3> _pathBuffer = new List<Vector3>();
private readonly List<SpawnEntryRuntime> _spawnRuntimes = new List<SpawnEntryRuntime>();
private CombatScheduler _combatScheduler;
private EntityComponent _entity;
private IDataTable<DREnemy> _drEnemy;
private int _spawnEnemyMaxCount = 5000;
private int _currentEnemyCount;
private int _nextSpawnerIndex;
private int _currentMapEntityId;
private bool _initialized;
private bool _enemyConfigMissingLogged;
private float _phaseElapsed;
private bool _isPhaseRunning;
public int AliveEnemyCount => _currentEnemyCount;
public bool IsPhaseSpawnCompleted { get; private set; } = true;
public bool IsPhaseRunning => _isPhaseRunning;
public void OnInit(CombatScheduler combatScheduler)
{
_combatScheduler = combatScheduler;
if (_initialized)
{
return;
}
_entity = GameEntry.Entity;
_drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
_currentEnemyCount = 0;
_nextSpawnerIndex = 0;
_currentMapEntityId = 0;
_enemyConfigMissingLogged = false;
_spawners.Clear();
_spawnerByOrder.Clear();
_pathBuffer.Clear();
_spawnRuntimes.Clear();
_phaseElapsed = 0f;
_isPhaseRunning = false;
IsPhaseSpawnCompleted = true;
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_initialized = true;
}
public void BeginPhase(DRLevelPhase phase, IReadOnlyList<DRLevelSpawnEntry> spawnEntries)
{
if (!_initialized || _combatScheduler == null)
{
return;
}
_ = phase;
EndPhase();
_phaseElapsed = 0f;
_isPhaseRunning = true;
IsPhaseSpawnCompleted = false;
RefreshSpawnerCache(true);
if (spawnEntries != null)
{
for (int i = 0; i < spawnEntries.Count; i++)
{
SpawnEntryRuntime runtime = BuildSpawnRuntime(spawnEntries[i]);
if (runtime != null)
{
_spawnRuntimes.Add(runtime);
}
}
}
IsPhaseSpawnCompleted = _spawnRuntimes.Count <= 0;
}
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_initialized || _combatScheduler == null || !_isPhaseRunning)
{
return;
}
RefreshSpawnerCache(false);
_phaseElapsed += elapseSeconds;
UpdateSpawnRuntimes();
}
public void EndPhase()
{
_isPhaseRunning = false;
_phaseElapsed = 0f;
_spawnRuntimes.Clear();
IsPhaseSpawnCompleted = true;
}
public void OnDestroy()
{
if (!_initialized)
{
_combatScheduler = null;
return;
}
EndPhase();
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
_spawners.Clear();
_spawnerByOrder.Clear();
_pathBuffer.Clear();
_currentMapEntityId = 0;
_nextSpawnerIndex = 0;
_combatScheduler = null;
_initialized = false;
}
private SpawnEntryRuntime BuildSpawnRuntime(DRLevelSpawnEntry entry)
{
if (entry == null || entry.EntryType == EntryType.None)
{
return null;
}
SpawnEntryRuntime runtime = new SpawnEntryRuntime
{
Entry = entry,
Completed = false,
NextTriggerTime = Mathf.Max(0f, entry.StartTime),
EndTime = Mathf.Max(0f, entry.StartTime),
RemainingCount = Mathf.Max(0, entry.Count)
};
switch (entry.EntryType)
{
case EntryType.Stream:
{
float duration = Mathf.Max(0f, entry.Duration);
runtime.EndTime = duration > 0f ? runtime.NextTriggerTime + duration : runtime.NextTriggerTime;
runtime.Completed = entry.Count <= 0;
return runtime;
}
case EntryType.Burst:
case EntryType.Boss:
runtime.Completed = runtime.RemainingCount <= 0;
return runtime;
default:
return null;
}
}
private void UpdateSpawnRuntimes()
{
bool allCompleted = true;
for (int i = 0; i < _spawnRuntimes.Count; i++)
{
SpawnEntryRuntime runtime = _spawnRuntimes[i];
if (runtime.Completed)
{
continue;
}
switch (runtime.Entry.EntryType)
{
case EntryType.Stream:
ProcessStreamRuntime(runtime);
break;
case EntryType.Burst:
case EntryType.Boss:
ProcessBurstRuntime(runtime);
break;
default:
runtime.Completed = true;
break;
}
if (!runtime.Completed)
{
allCompleted = false;
}
}
IsPhaseSpawnCompleted = allCompleted;
}
private void ProcessStreamRuntime(SpawnEntryRuntime runtime)
{
if (_phaseElapsed < runtime.NextTriggerTime)
{
return;
}
int countPerWave = Mathf.Max(0, runtime.Entry.Count);
if (countPerWave <= 0)
{
runtime.Completed = true;
return;
}
float interval = runtime.Entry.Interval > 0f ? runtime.Entry.Interval : MinStreamInterval;
while (_phaseElapsed >= runtime.NextTriggerTime && runtime.NextTriggerTime <= runtime.EndTime)
{
SpawnEnemies(runtime.Entry, countPerWave);
runtime.NextTriggerTime += interval;
}
if (runtime.NextTriggerTime > runtime.EndTime)
{
runtime.Completed = true;
}
}
private void ProcessBurstRuntime(SpawnEntryRuntime runtime)
{
if (_phaseElapsed < runtime.NextTriggerTime)
{
return;
}
if (runtime.RemainingCount <= 0)
{
runtime.Completed = true;
return;
}
float gap = runtime.Entry.Gap;
if (gap <= 0f)
{
SpawnEnemies(runtime.Entry, runtime.RemainingCount);
runtime.RemainingCount = 0;
runtime.Completed = true;
return;
}
gap = Mathf.Max(gap, MinBurstGap);
while (_phaseElapsed >= runtime.NextTriggerTime && runtime.RemainingCount > 0)
{
SpawnEnemies(runtime.Entry, 1);
runtime.RemainingCount--;
runtime.NextTriggerTime += gap;
}
runtime.Completed = runtime.RemainingCount <= 0;
}
private void SpawnEnemies(DRLevelSpawnEntry entry, int spawnCount)
{
if (spawnCount <= 0)
{
return;
}
Spawner spawner = ResolveSpawner(entry.SpawnPointId);
if (spawner == null)
{
return;
}
MapEntity currentMap = _combatScheduler != null ? _combatScheduler.CurrentMap : null;
if (currentMap == null ||
!currentMap.TryFindPathWorldPoints(spawner, null, _pathBuffer) ||
_pathBuffer.Count <= 0)
{
return;
}
DREnemy enemyConfig = GetEnemyConfig(entry.EnemyId);
if (enemyConfig == null)
{
return;
}
for (int i = 0; i < spawnCount; i++)
{
if (_currentEnemyCount >= _spawnEnemyMaxCount)
{
break;
}
int enemyEntityId = _entity.GenerateSerialId();
EnemyData enemyData = new EnemyData(
enemyEntityId,
enemyConfig.EntityId,
_pathBuffer[0],
enemyConfig.BaseHp,
enemyConfig.Speed,
_pathBuffer);
_entity.ShowEnemy(enemyData);
}
}
private DREnemy GetEnemyConfig(int enemyId)
{
if (_drEnemy == null)
{
_drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
if (_drEnemy == null)
{
if (!_enemyConfigMissingLogged)
{
Log.Warning("EnemyManagerComponent can not find DREnemy data table.");
_enemyConfigMissingLogged = true;
}
return null;
}
}
if (enemyId > 0)
{
DREnemy targetConfig = _drEnemy.GetDataRow(enemyId);
if (targetConfig != null)
{
return targetConfig;
}
}
DREnemy defaultConfig = _drEnemy.GetDataRow(DefaultEnemyConfigId);
if (defaultConfig != null)
{
return defaultConfig;
}
DREnemy[] allConfigs = _drEnemy.GetAllDataRows();
if (allConfigs.Length > 0)
{
return allConfigs[0];
}
if (!_enemyConfigMissingLogged)
{
Log.Warning("EnemyManagerComponent found no enemy configs.");
_enemyConfigMissingLogged = true;
}
return null;
}
private Spawner ResolveSpawner(int spawnPointId)
{
if (spawnPointId > 0 && _spawnerByOrder.TryGetValue(spawnPointId, out Spawner mappedSpawner))
{
return mappedSpawner;
}
if (_spawners.Count <= 0)
{
return null;
}
Spawner fallbackSpawner = _spawners[_nextSpawnerIndex % _spawners.Count];
_nextSpawnerIndex++;
return fallbackSpawner;
}
private void RefreshSpawnerCache(bool force)
{
MapEntity currentMap = _combatScheduler != null ? _combatScheduler.CurrentMap : null;
if (currentMap == null)
{
_spawners.Clear();
_spawnerByOrder.Clear();
_currentMapEntityId = 0;
_nextSpawnerIndex = 0;
return;
}
if (!force && _currentMapEntityId == currentMap.Id && _spawners.Count > 0)
{
return;
}
_spawners.Clear();
_spawnerByOrder.Clear();
_nextSpawnerIndex = 0;
_currentMapEntityId = currentMap.Id;
Spawner[] mapSpawners = currentMap.Spawners;
for (int i = 0; i < mapSpawners.Length; i++)
{
Spawner spawner = mapSpawners[i];
if (spawner == null)
{
continue;
}
if (!currentMap.TryGetDefaultPathCells(spawner, out _))
{
continue;
}
_spawners.Add(spawner);
if (spawner.SpawnOrder > 0 && !_spawnerByOrder.ContainsKey(spawner.SpawnOrder))
{
_spawnerByOrder[spawner.SpawnOrder] = spawner;
}
}
_spawners.Sort((left, right) => left.SpawnOrder.CompareTo(right.SpawnOrder));
}
private void OnShowEntitySuccess(object sender, GameEventArgs e)
{
if (e is ShowEntitySuccessEventArgs ne)
{
if (ne.EntityLogicType == typeof(EnemyEntity))
{
_currentEnemyCount++;
}
}
}
private void OnShowEntityFailure(object sender, GameEventArgs e)
{
}
private void OnHideEntityComplete(object sender, GameEventArgs e)
{
if (!(e is HideEntityCompleteEventArgs ne))
{
return;
}
if (ne.EntityGroup.Name != "Enemy")
{
return;
}
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
}
}
}
@@ -1,101 +0,0 @@
using GeometryTD.Entity;
using GeometryTD.Entity.EntityData;
using GameFramework.Event;
using GeometryTD;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class EnemyManagerComponent : GameFrameworkComponent
{
private EntityComponent _entity;
private float _spawnEnemyInterval = 5f;
private float _spawnEnemyTimer;
private float _spawnEnemyAccelerate = 0.5f;
private int _spawnEnemyCount = 5;
private int _spawnEnemyMaxCount = 5000;
private int _currentEnemyCount;
private int _spawnDistanceFromPlayer = 10;
private int _currentSpawnEnemyId = 0;
private Transform _player;
public void OnInit()
{
_entity = GameEntry.Entity;
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Subscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Subscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
}
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_spawnEnemyTimer += elapseSeconds;
if (_spawnEnemyTimer < _spawnEnemyInterval) return;
SpawnEnemy();
_spawnEnemyTimer = 0;
_spawnEnemyInterval = Mathf.Max(0.1f, _spawnEnemyInterval - _spawnEnemyAccelerate);
}
public void OnDestroy()
{
GameEntry.Event.Unsubscribe(ShowEntitySuccessEventArgs.EventId, OnShowEntitySuccess);
GameEntry.Event.Unsubscribe(ShowEntityFailureEventArgs.EventId, OnShowEntityFailure);
GameEntry.Event.Unsubscribe(HideEntityCompleteEventArgs.EventId, OnHideEntityComplete);
}
private void SpawnEnemy()
{
if (_player == null) return;
for (int i = 0; i < _spawnEnemyCount; i++)
{
if (_currentEnemyCount >= _spawnEnemyMaxCount) break;
_entity.ShowEnemy(new EnemyData(_currentSpawnEnemyId % _spawnEnemyMaxCount, 1001, _player,
GetRandomPosition(), 10, 2));
_currentSpawnEnemyId++;
}
}
private Vector3 GetRandomPosition()
{
float x = Random.Range(-1f, 1f);
float z = Random.Range(-1f, 1f);
Vector3 dir = new Vector3(x, 0, z).normalized;
return _player.position + dir * _spawnDistanceFromPlayer;
}
private void OnShowEntitySuccess(object sender, GameEventArgs e)
{
if (e is ShowEntitySuccessEventArgs ne)
{
if (ne.EntityLogicType == typeof(Enemy))
{
_currentEnemyCount++;
}
if (ne.EntityLogicType == typeof(Player))
{
_player = ne.Entity.transform;
}
}
}
private void OnShowEntityFailure(object sender, GameEventArgs e)
{
}
private void OnHideEntityComplete(object sender, GameEventArgs e)
{
if (e is HideEntityCompleteEventArgs ne)
{
if (ne.EntityGroup.Name == "Enemy")
{
_currentEnemyCount--;
}
}
}
}
}
@@ -0,0 +1,172 @@
using System.Collections.Generic;
using GameFramework.DataTable;
using GeometryTD.DataTable;
using GeometryTD.Definition;
using Newtonsoft.Json.Linq;
using GeometryTD.UI;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent
{
public class EventNodeComponent : GameFrameworkComponent
{
private readonly List<EventItem> _eventItems = new List<EventItem>();
private EventFormUseCase _eventFormUseCase;
private EventFormController _eventFormController;
private bool _initialized;
public void OnInit()
{
_eventItems.Clear();
IDataTable<DREvent> dtEvent = GameEntry.DataTable.GetDataTable<DREvent>();
if (dtEvent == null)
{
Log.Warning("Event data table is not loaded.");
_initialized = true;
return;
}
DREvent[] rows = dtEvent.GetAllDataRows();
foreach (var drEvent in rows)
{
EventOption[] options = ParseOptions(drEvent.OptionsRaw);
_eventItems.Add(new EventItem(drEvent.Id, drEvent.Title, drEvent.Description, options));
}
if (_eventFormUseCase == null)
{
_eventFormUseCase = new EventFormUseCase();
}
if (_eventFormController == null)
{
_eventFormController = new EventFormController();
_eventFormController.BindUseCase(_eventFormUseCase);
}
_initialized = true;
Log.Info("EventNodeComponent initialized with {0} events.", _eventItems.Count);
}
public void StartEvent()
{
if (!_initialized)
{
OnInit();
}
if (_eventItems.Count <= 0)
{
Log.Warning("EventNodeComponent has no event data.");
return;
}
int index = Random.Range(0, _eventItems.Count);
EventItem randomEvent = _eventItems[index];
if (_eventFormUseCase == null || _eventFormController == null)
{
Log.Warning("EventNodeComponent StartEvent failed. Event form is not initialized.");
return;
}
_eventFormUseCase.SetCurrentEvent(randomEvent);
_eventFormController.OpenUI();
}
private static EventOption[] ParseOptions(string optionsRaw)
{
if (string.IsNullOrWhiteSpace(optionsRaw))
{
return System.Array.Empty<EventOption>();
}
try
{
JArray array = JArray.Parse(optionsRaw);
List<EventOption> options = new List<EventOption>(array.Count);
for (int i = 0; i < array.Count; i++)
{
if (!(array[i] is JObject optionObj))
{
continue;
}
string optionText = optionObj.Value<string>("optionText") ?? string.Empty;
float probability = optionObj.Value<float?>("probability") ?? 1f;
EventRequirementBase[] requirements = ParseRequirements(optionObj["requirements"] as JArray);
EventEffectBase[] costEffects = ParseEffects(optionObj["costEffects"] as JArray, probability);
EventEffectBase[] rewardEffects = ParseEffects(optionObj["rewardEffects"] as JArray, probability);
options.Add(new EventOption(optionText, requirements, costEffects, rewardEffects, probability));
}
return options.ToArray();
}
catch (System.Exception e)
{
Log.Warning("Failed to parse event options json. {0}", e.Message);
return System.Array.Empty<EventOption>();
}
}
private static EventRequirementBase[] ParseRequirements(JArray requirementsArray)
{
if (requirementsArray == null || requirementsArray.Count == 0)
{
return System.Array.Empty<EventRequirementBase>();
}
List<EventRequirementBase> requirements = new List<EventRequirementBase>(requirementsArray.Count);
for (int i = 0; i < requirementsArray.Count; i++)
{
if (!(requirementsArray[i] is JObject reqObj))
{
continue;
}
string type = reqObj.Value<string>("type");
JObject param = reqObj["param"] as JObject;
EventRequirementBase requirement = EventRequirementFactory.Create(type, param);
if (requirement != null)
{
requirements.Add(requirement);
}
}
return requirements.ToArray();
}
private static EventEffectBase[] ParseEffects(JArray effectsArray, float probability)
{
if (effectsArray == null || effectsArray.Count == 0)
{
return System.Array.Empty<EventEffectBase>();
}
List<EventEffectBase> effects = new List<EventEffectBase>(effectsArray.Count);
for (int i = 0; i < effectsArray.Count; i++)
{
if (!(effectsArray[i] is JObject effectObj))
{
continue;
}
string type = effectObj.Value<string>("type");
JObject param = effectObj["param"] as JObject;
EventEffectBase effect = EventEffectFactory.Create(type, param, probability);
if (effect != null)
{
effects.Add(effect);
}
}
return effects.ToArray();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0a0c1c547ca24c95819f5f62f0bd3ea3
timeCreated: 1772102402
@@ -10,6 +10,8 @@ namespace CustomComponent
{
private readonly Dictionary<UIFormType, IUIFormController> _routeControllers = new();
private const string UINameSpace = "GeometryTD.UI";
public void BindUIUseCase(UIFormType uiFormType, IUIUseCase useCase)
{
IUIFormController controller = GetOrCreateController(uiFormType);
@@ -35,11 +37,12 @@ namespace CustomComponent
return controller;
}
string typename = $"UI.{uiFormType}Controller";
string typename = $"{UINameSpace}.{uiFormType}Controller";
Type controllerType = Type.GetType(typename);
if (controllerType == null)
{
controller = new DefaultUIFormController(uiFormType);
Log.Warning("Can not find UI Controller for type '{0}'.", typename);
}
else
{
@@ -0,0 +1,63 @@
using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
/// <summary>
/// 敌人配置表
/// </summary>
public class DREnemy : DataRowBase
{
private int m_Id = 0;
/// <summary>
/// 获取敌人编号
/// </summary>
public override int Id => m_Id;
/// <summary>
/// 获取敌人实体编号
/// </summary>
public int EntityId { get; private set; }
/// <summary>
/// 获取敌人基础血量
/// </summary>
public int BaseHp { get; private set; }
/// <summary>
/// 获取敌人基础基地伤害
/// </summary>
public int BaseDamage { get; private set; }
/// <summary>
/// 获取敌人移动速度
/// </summary>
public float Speed { get; private set; }
/// <summary>
/// 获取敌人掉落金币
/// </summary>
public int DropCoin { get; private set; }
public override bool ParseDataRow(string dataRowString, object userData)
{
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
for (int i = 0; i < columnStrings.Length; i++)
{
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
}
int index = 0;
index++;
m_Id = int.Parse(columnStrings[index++]);
index++;
EntityId = int.Parse(columnStrings[index++]);
BaseHp = int.Parse(columnStrings[index++]);
BaseDamage = int.Parse(columnStrings[index++]);
Speed = float.Parse(columnStrings[index++]);
DropCoin = int.Parse(columnStrings[index++]);
return true;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0f9832b810954c4eb0f45ad735bb5a58
timeCreated: 1772101800
@@ -0,0 +1,52 @@
using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
/// <summary>
/// 事件配置表
/// </summary>
public class DREvent : DataRowBase
{
private int m_Id = 0;
/// <summary>
/// 获取事件编号
/// </summary>
public override int Id => m_Id;
/// <summary>
/// 获取事件题目
/// </summary>
public string Title { get; private set; }
/// <summary>
/// 获取事件描述
/// </summary>
public string Description { get; private set; }
/// <summary>
/// 获取事件选项
/// </summary>
/// <remarks>原始字符串(如 JSON 文本),不在此处做解析。</remarks>
public string OptionsRaw { get; private set; }
public override bool ParseDataRow(string dataRowString, object userData)
{
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
for (int i = 0; i < columnStrings.Length; i++)
{
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
}
int index = 0;
index++;
m_Id = int.Parse(columnStrings[index++]);
index++;
Title = columnStrings[index++];
Description = columnStrings[index++];
OptionsRaw = columnStrings[index++];
return true;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57e29dfa7f1044a1acfbf3abde81859f
timeCreated: 1772097514
@@ -0,0 +1,72 @@
using System;
using GeometryTD.Definition;
using GeometryTD.CustomUtility;
using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
/// <summary>
/// 关卡表
/// </summary>
public class DRLevel : DataRowBase
{
private int m_Id = 0;
/// <summary>
/// 获取关卡编号
/// </summary>
public override int Id => m_Id;
/// <summary>
/// 获取关卡所属主题类型
/// </summary>
public LevelThemeType LevelThemeType { get; private set; }
/// <summary>
/// 获取关卡初始基地生命
/// </summary>
public int BaseHp { get; private set; }
/// <summary>
/// 获取关卡初始硬币数量
/// </summary>
public int StartCoin { get; private set; }
/// <summary>
/// 获取关卡胜利条件
/// </summary>
public LevelVictoryType LevelVictoryType { get; private set; }
/// <summary>
/// 获取关卡胜利奖励金币
/// </summary>
public int RewardGold { get; private set; }
/// <summary>
/// 获取关卡胜利条件参数
/// </summary>
public string VictoryParams { get; private set; }
public override bool ParseDataRow(string dataRowString, object userData)
{
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
for (int i = 0; i < columnStrings.Length; i++)
{
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
}
int index = 0;
index++;
m_Id = int.Parse(columnStrings[index++]);
index++;
LevelThemeType = EnumUtility<LevelThemeType>.Get(columnStrings[index++]);
BaseHp = int.Parse(columnStrings[index++]);
StartCoin = int.Parse(columnStrings[index++]);
LevelVictoryType = EnumUtility<LevelVictoryType>.Get(columnStrings[index++]);
VictoryParams = columnStrings[index++];
RewardGold = int.Parse(columnStrings[index++]);
return true;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0eb47d184fe4204b38dc7c9e86da609
timeCreated: 1772089145
@@ -0,0 +1,64 @@
using System;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
/// <summary>
/// 关卡阶段表
/// </summary>
public class DRLevelPhase : DataRowBase
{
private int m_Id = 0;
/// <summary>
/// 获取关卡阶段编号
/// </summary>
public override int Id => m_Id;
/// <summary>
/// 获取阶段持续时间(秒)
/// </summary>
public int DurationSeconds { get; private set; }
/// <summary>
/// 获取阶段结束条件类型
/// </summary>
public PhaseEndType EndType { get; private set; }
/// <summary>
/// 获取阶段结束参数
/// </summary>
public string EndParam { get; private set; }
public override bool ParseDataRow(string dataRowString, object userData)
{
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
for (int i = 0; i < columnStrings.Length; i++)
{
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
}
int index = 0;
index++;
m_Id = int.Parse(columnStrings[index++]);
index++;
DurationSeconds = int.Parse(columnStrings[index++]);
EndType = ParsePhaseEndType(columnStrings[index++]);
EndParam = columnStrings[index++];
return true;
}
private PhaseEndType ParsePhaseEndType(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
{
return PhaseEndType.None;
}
return EnumUtility<PhaseEndType>.Get(raw);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1fd79e88eca4bfbb38f5bcb8091e2dd
timeCreated: 1772090000
@@ -0,0 +1,94 @@
using System;
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
/// <summary>
/// 关卡阶段出怪条目表
/// </summary>
public class DRLevelSpawnEntry : DataRowBase
{
private int m_Id = 0;
/// <summary>
/// 获取阶段条目编号
/// </summary>
public override int Id => m_Id;
/// <summary>
/// 获取敌人出生口编号
/// </summary>
public int SpawnPointId { get; private set; }
/// <summary>
/// 获取相对开始时间(秒)
/// </summary>
public int StartTime { get; private set; }
/// <summary>
/// 获取出怪条目类型
/// </summary>
public EntryType EntryType { get; private set; }
/// <summary>
/// 获取敌人编号
/// </summary>
public int EnemyId { get; private set; }
/// <summary>
/// 获取单次出怪数量
/// </summary>
public int Count { get; private set; }
/// <summary>
/// 获取出怪间隔(秒)
/// </summary>
public float Interval { get; private set; }
/// <summary>
/// 获取持续时间(秒)
/// </summary>
public int Duration { get; private set; }
/// <summary>
/// 获取单怪出生间隔(秒)
/// </summary>
public float Gap { get; private set; }
public override bool ParseDataRow(string dataRowString, object userData)
{
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
for (int i = 0; i < columnStrings.Length; i++)
{
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
}
int index = 0;
index++;
m_Id = int.Parse(columnStrings[index++]);
index++;
SpawnPointId = int.Parse(columnStrings[index++]);
StartTime = int.Parse(columnStrings[index++]);
EntryType = ParseEntryType(columnStrings[index++]);
EnemyId = int.Parse(columnStrings[index++]);
Count = int.Parse(columnStrings[index++]);
Interval = float.Parse(columnStrings[index++]);
Duration = int.Parse(columnStrings[index++]);
Gap = float.Parse(columnStrings[index++]);
return true;
}
private EntryType ParseEntryType(string raw)
{
if (string.IsNullOrWhiteSpace(raw))
{
return EntryType.None;
}
return EnumUtility<EntryType>.Get(raw);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b06804b242d247bd8a937cdce930cee1
timeCreated: 1772090001
@@ -0,0 +1,38 @@
using GeometryTD.CustomUtility;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.DataTable
{
public class DRShopPrice : DataRowBase
{
private int m_Id = 0;
public override int Id => m_Id;
public RarityType Rarity { get; set; }
public int MinPrice { get; set; }
public int MaxPrice { get; set; }
public override bool ParseDataRow(string dataRowString, object userData)
{
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
for (int i = 0; i < columnStrings.Length; i++)
{
columnStrings[i] = columnStrings[i].Trim(DataTableExtension.DataTrimSeparators);
}
int index = 0;
index++;
m_Id = int.Parse(columnStrings[index++]);
index++;
Rarity = EnumUtility<RarityType>.Get(columnStrings[index++]);
MinPrice = int.Parse(columnStrings[index++]);
MaxPrice = int.Parse(columnStrings[index++]);
return true;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c47aaecd99446f2b8c9ec84c797005b
timeCreated: 1772105886
@@ -33,6 +33,7 @@ namespace GeometryTD
public const int BulletAsset = 80;
public const int AsteroiAsset = 80;
public const int EnemyAsset = 80;
public const int MapAsset = 85;
public const int EffectAsset = 80;
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace GeometryTD.Definition
{
/// <summary>
/// 玩家背包聚合数据。
/// </summary>
[Serializable]
public sealed class BackpackInventoryData
{
/// <summary>
/// 背包金币。
/// </summary>
public int Gold { get; set; }
/// <summary>
/// 背包中的枪口组件实例。
/// </summary>
public List<MuzzleCompItemData> MuzzleComponents { get; set; } = new List<MuzzleCompItemData>();
/// <summary>
/// 背包中的轴承组件实例。
/// </summary>
public List<BearingCompItemData> BearingComponents { get; set; } = new List<BearingCompItemData>();
/// <summary>
/// 背包中的底座组件实例。
/// </summary>
public List<BaseCompItemData> BaseComponents { get; set; } = new List<BaseCompItemData>();
/// <summary>
/// 背包中的防御塔实例。
/// </summary>
public List<DefenseTowerItemData> Towers { get; set; } = new List<DefenseTowerItemData>();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c39f9ad23ee94de9af2b3d9729f47d7f
timeCreated: 1772105403
@@ -0,0 +1,68 @@
using System;
namespace GeometryTD.Definition
{
/// <summary>
/// 防御塔独立属性快照。
/// 注意:这里是塔实例的独立值,不通过组件引用实时计算。
/// </summary>
[Serializable]
public sealed class DefenseTowerStatsData
{
public int AttackDamage { get; set; }
public float DamageRandomRate { get; set; }
public float RotateSpeed { get; set; }
public float AttackRange { get; set; }
public float AttackSpeed { get; set; }
public AttackMethodType AttackMethodType { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
public TagType[] Tags { get; set; }
}
/// <summary>
/// 背包内防御塔实例数据。
/// </summary>
[Serializable]
public sealed class DefenseTowerItemData
{
/// <summary>
/// 防御塔实例唯一 Id。
/// </summary>
public long InstanceId { get; set; }
/// <summary>
/// 防御塔显示名称。
/// </summary>
public string Name { get; set; }
/// <summary>
/// 防御塔品质。
/// </summary>
public RarityType Rarity { get; set; }
/// <summary>
/// 当前耐久(0~100)。
/// </summary>
public float Endurance { get; set; } = 100f;
/// <summary>
/// 构成该防御塔的枪口组件实例 Id。
/// </summary>
public long MuzzleComponentInstanceId { get; set; }
/// <summary>
/// 构成该防御塔的轴承组件实例 Id。
/// </summary>
public long BearingComponentInstanceId { get; set; }
/// <summary>
/// 构成该防御塔的底座组件实例 Id。
/// </summary>
public long BaseComponentInstanceId { get; set; }
/// <summary>
/// 防御塔独立属性,不依赖组件对象引用。
/// </summary>
public DefenseTowerStatsData Stats { get; set; } = new DefenseTowerStatsData();
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ca5782ae52464616bf2b45704feea0d5
timeCreated: 1772105402
@@ -0,0 +1,18 @@
namespace GeometryTD.Definition
{
public class EventItem
{
public EventItem(int id, string title, string description, EventOption[] options)
{
Id = id;
Title = title;
Description = description;
Options = options ?? System.Array.Empty<EventOption>();
}
public int Id { get; private set; }
public string Title { get; private set; }
public string Description { get; private set; }
public EventOption[] Options { get; private set; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2a6dce0183e4efdbada20c25be949c0
timeCreated: 1772097196
@@ -0,0 +1,25 @@
namespace GeometryTD.Definition
{
public class EventOption
{
public string OptionText { get; private set; }
public EventRequirementBase[] Requirements { get; private set; }
public EventEffectBase[] CostEffects { get; private set; }
public EventEffectBase[] RewardEffects { get; private set; }
public float Probability { get; private set; }
public EventOption(
string optionText,
EventRequirementBase[] requirements,
EventEffectBase[] costEffects,
EventEffectBase[] rewardEffects,
float probability = 1f)
{
OptionText = optionText;
Requirements = requirements ?? System.Array.Empty<EventRequirementBase>();
CostEffects = costEffects ?? System.Array.Empty<EventEffectBase>();
RewardEffects = rewardEffects ?? System.Array.Empty<EventEffectBase>();
Probability = probability;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bcb612057a454fcf97f41fdf5c6d9b22
timeCreated: 1772097742
@@ -0,0 +1,97 @@
using System;
namespace GeometryTD.Definition
{
/// <summary>
/// 背包内组件实例基类(非 DataTable,表示玩家持有物)。
/// </summary>
[Serializable]
public abstract class TowerCompItemData
{
/// <summary>
/// 组件实例唯一 Id。
/// </summary>
public long InstanceId { get; set; }
/// <summary>
/// 组件配置 Id(对应 DataTable Id)。
/// </summary>
public int ConfigId { get; set; }
/// <summary>
/// 组件槽位类型。
/// </summary>
public TowerCompSlotType SlotType { get; protected set; }
/// <summary>
/// 组件名称。
/// </summary>
public string Name { get; set; }
/// <summary>
/// 组件品质。
/// </summary>
public RarityType Rarity { get; set; }
/// <summary>
/// 组件当前耐久(0~100)。
/// </summary>
public float Endurance { get; set; } = 100f;
/// <summary>
/// 组件约束(先沿用 DataTable 原定义)。
/// </summary>
public string Constraint { get; set; }
/// <summary>
/// 组件当前 Tag(实例态)。
/// </summary>
public TagType[] Tags { get; set; }
}
[Serializable]
public sealed class MuzzleCompItemData : TowerCompItemData
{
public MuzzleCompItemData()
{
SlotType = TowerCompSlotType.Muzzle;
}
public int[] AttackDamage { get; set; }
public float DamageRandomRate { get; set; }
public AttackMethodType AttackMethodType { get; set; }
}
[Serializable]
public sealed class BearingCompItemData : TowerCompItemData
{
public BearingCompItemData()
{
SlotType = TowerCompSlotType.Bearing;
}
public float[] RotateSpeed { get; set; }
public float[] AttackRange { get; set; }
}
[Serializable]
public sealed class BaseCompItemData : TowerCompItemData
{
public BaseCompItemData()
{
SlotType = TowerCompSlotType.Base;
}
public float[] AttackSpeed { get; set; }
public AttackPropertyType AttackPropertyType { get; set; }
}
[Serializable]
public sealed class AccessoryItemData : TowerCompItemData
{
public AccessoryItemData()
{
SlotType = TowerCompSlotType.Accessory;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9f95a29d5bcf484aa053af1462365936
timeCreated: 1772105401
@@ -3,7 +3,7 @@ namespace GeometryTD.Definition
public enum AttackPropertyType : byte
{
None = 0,
Physical = 1,
Physics = 1,
Fire = 2,
Water = 3,
Earth = 4,
@@ -0,0 +1,13 @@
namespace GeometryTD.Definition
{
/// <summary>
/// 出怪条目类型
/// </summary>
public enum EntryType : byte
{
None = 0,
Stream = 1,
Burst = 2,
Boss = 3
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 14433e1048844c5d942c885c2f50ffe0
timeCreated: 1772090003
@@ -0,0 +1,21 @@
namespace GeometryTD.Definition
{
public enum EventEffectType
{
None,
AddGold,
RemoveGold,
AddRandomComps,
RemoveRandomComps,
AddRandomCompsEndurance,
RemoveRandomCompsEndurance,
AddRandomTowersEndurance,
DamageRandomTowersEndurance,
TransformComponents,
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6f11ba35aa8f491f942964435b32cba6
timeCreated: 1772095333
@@ -0,0 +1,11 @@
namespace GeometryTD.Definition
{
public enum EventRequirementType
{
None,
GoldAtLeast,
CompCountAtLeast,
TowerCountAtLeast,
HasRelic,
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f9da1af87a4f4115b9a5da706b4e1092
timeCreated: 1772095290
@@ -0,0 +1,10 @@
namespace GeometryTD.Definition
{
public enum LevelThemeType
{
None,
Plain,
Volcano,
Mountain
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 06ec7c0ec8924c7988e521abdd97bffe
timeCreated: 1772089321
@@ -0,0 +1,10 @@
namespace GeometryTD.Definition
{
public enum LevelVictoryType
{
None,
PhasesCleared,
BossDead,
TimeElapsed
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea9b7c078511464fa572923f66cc200e
timeCreated: 1772089447
@@ -0,0 +1,13 @@
namespace GeometryTD.Definition
{
/// <summary>
/// 关卡阶段结束类型
/// </summary>
public enum PhaseEndType : byte
{
None = 0,
TimeElapsed = 1,
EnemiesCleared = 2,
BossDead = 3
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eed7685430c24cdaac639581331393f3
timeCreated: 1772090002
@@ -0,0 +1,11 @@
namespace GeometryTD.Definition
{
public enum TowerCompSlotType : byte
{
None = 0,
Muzzle = 1,
Bearing = 2,
Base = 3,
Accessory = 4,
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a34cbb64aede4531b1d0e178352006bb
timeCreated: 1772105400
@@ -26,5 +26,15 @@
/// 关于。
/// </summary>
AboutForm = 102,
/// <summary>
/// 事件节点界面。
/// </summary>
EventForm = 200,
/// <summary>
/// 仓库界面。
/// </summary>
RepoForm = 201,
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a291a17879242cc8c7cec678aa5aaf1
timeCreated: 1772101365
@@ -0,0 +1,25 @@
namespace GeometryTD.Definition
{
public class AddGoldEffect : EventEffectBase
{
public override EventEffectType EffectType => EventEffectType.AddGold;
public override EventEffectParam Param => _param;
private AddGoldParam _param;
public AddGoldEffect(AddGoldParam param, float? probability = null)
{
_param = param;
Probability = probability;
}
}
public class AddGoldParam : EventEffectParam
{
public int Count;
public AddGoldParam(int count)
{
Count = count;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 75cacc997d8f414ca73f381cad303b5b
timeCreated: 1772101474
@@ -0,0 +1,28 @@
namespace GeometryTD.Definition
{
public class AddRandomCompsEffect : EventEffectBase
{
public override EventEffectType EffectType => EventEffectType.AddRandomComps;
public override EventEffectParam Param => _param;
private AddRandomCompsParam _param;
public AddRandomCompsEffect(AddRandomCompsParam param, float? probability = null)
{
_param = param;
Probability = probability;
}
}
public class AddRandomCompsParam : EventEffectParam
{
public int Count;
public RarityType Rarity;
public AddRandomCompsParam(int count, RarityType rarity)
{
Count = count;
Rarity = rarity;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 35656d42c28448268d0547c19938b2d8
timeCreated: 1772101530
@@ -0,0 +1,34 @@
namespace GeometryTD.Definition
{
public class DamageRandomTowerEnduranceEffect : EventEffectBase
{
public override EventEffectType EffectType => EventEffectType.DamageRandomTowersEndurance;
public override EventEffectParam Param => _param;
private DamageRandomTowerEnduranceParam _param;
public DamageRandomTowerEnduranceEffect(DamageRandomTowerEnduranceParam param, float? probability = null)
{
_param = param;
Probability = probability;
}
}
public class DamageRandomTowerEnduranceParam : EventEffectParam
{
/// <summary>
/// 减少耐久的防御塔数量
/// </summary>
public int Count;
/// <summary>
/// 防御塔耐久减少的量
/// </summary>
public int Amount;
public DamageRandomTowerEnduranceParam(int count, int amount)
{
Count = count;
Amount = amount;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1b06f274c054844ad68b1db02d3737a
timeCreated: 1772101563
@@ -0,0 +1,14 @@
namespace GeometryTD.Definition
{
public abstract class EventEffectBase
{
public abstract EventEffectType EffectType { get; }
public abstract EventEffectParam Param { get; }
public float? Probability;
}
public class EventEffectParam
{
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2be5a7ff782240f79bde2dd0310d11ea
timeCreated: 1772101392
@@ -0,0 +1,27 @@
namespace GeometryTD.Definition
{
public class RemoveRandomCompsEffect : EventEffectBase
{
public override EventEffectType EffectType => EventEffectType.RemoveRandomComps;
public override EventEffectParam Param => _param;
private RemoveRandomCompsParam _param;
public RemoveRandomCompsEffect(RemoveRandomCompsParam param, float? probability = null)
{
_param = param;
Probability = probability;
}
}
public class RemoveRandomCompsParam : EventEffectParam
{
public int Count;
public RarityType Rarity;
public RemoveRandomCompsParam(int Count, RarityType Rarity)
{
this.Count = Count;
this.Rarity = Rarity;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77f8e0d1879b48108e6ad5faca07d7f7
timeCreated: 1772101548
@@ -0,0 +1,76 @@
using GeometryTD.CustomUtility;
using Newtonsoft.Json.Linq;
using UnityGameFramework.Runtime;
namespace GeometryTD.Definition
{
public static class EventEffectFactory
{
public static EventEffectBase Create(string rawType, JObject param, float? probability = null)
{
if (string.IsNullOrWhiteSpace(rawType))
{
return null;
}
EventEffectType type = EnumUtility<EventEffectType>.Get(rawType);
switch (type)
{
case EventEffectType.AddGold:
{
int count = GetInt(param, "Count");
return new AddGoldEffect(new AddGoldParam(count), probability);
}
case EventEffectType.RemoveRandomComps:
{
int count = GetInt(param, "Count");
RarityType rarity = EnumUtility<RarityType>.Get(GetString(param, "Rarity"));
return new RemoveRandomCompsEffect(new RemoveRandomCompsParam(count, rarity), probability);
}
case EventEffectType.AddRandomComps:
{
int count = GetInt(param, "Count");
RarityType rarity = EnumUtility<RarityType>.Get(GetString(param, "Rarity"));
return new AddRandomCompsEffect(new AddRandomCompsParam(count, rarity), probability);
}
case EventEffectType.DamageRandomTowersEndurance:
{
int count = GetInt(param, "Count");
int amount = GetInt(param, "Amount");
return new DamageRandomTowerEnduranceEffect(new DamageRandomTowerEnduranceParam(count, amount), probability);
}
default:
Log.Warning("Unsupported EventEffectType '{0}'.", rawType);
return null;
}
}
private static int GetInt(JObject param, params string[] keys)
{
if (param == null)
{
return 0;
}
foreach (string key in keys)
{
if (param.TryGetValue(key, out JToken token) && token.Type != JTokenType.Null)
{
return token.Value<int>();
}
}
return 0;
}
private static string GetString(JObject param, string key)
{
if (param == null || !param.TryGetValue(key, out JToken token) || token.Type == JTokenType.Null)
{
return string.Empty;
}
return token.Value<string>();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fae04d7a401d4c7fbe2de385c3f23580
timeCreated: 1772102401
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cee1f415e08743a9914efd2ecbc6273e
timeCreated: 1772100925
@@ -0,0 +1,26 @@
namespace GeometryTD.Definition
{
public class CompCountAtLeastRequirement : EventRequirementBase
{
public override EventRequirementType RequirementType => EventRequirementType.CompCountAtLeast;
public override EventRequirementParam Param => _param;
private CompCountAtLeastParam _param;
public CompCountAtLeastRequirement(CompCountAtLeastParam param)
{
_param = param;
}
}
public class CompCountAtLeastParam : EventRequirementParam
{
public int Count;
public RarityType Rarity;
public CompCountAtLeastParam(int count, RarityType rarity)
{
Count = count;
Rarity = rarity;
}
}
}

Some files were not shown because too many files have changed in this diff Show More