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
commit b77e579848
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
253 changed files with 46291 additions and 782 deletions

3
.gitignore vendored
View File

@ -101,3 +101,6 @@ InitTestScene*.unity*
/数据表
/.vscode
AGENTS.md
/.dotnet-home
/[Aa]ssets/RawResources
/[Aa]ssets/RawResources.meta

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]

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]

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

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: d2ccda2aca4a348428805b273ee2aaf2
guid: d860a5c1c09addf4ba7539e97c85c0bd
TextScriptImporter:
externalObjects: {}
userData:

View File

@ -1,7 +1,8 @@
# 实体表
# Id AssetName
# Id 策划备注 AssetName
# int string
# 实体编号 策划备注 资源名称
# 实体编号 资源名称
101 测试枪口 TestMuzzle
201 测试轴承 TestBearing
301 测试底座 TestBase
1001 测试普通敌人 TestEnemy
1002 测试Boss TestBoss

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":[]}]

View File

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

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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 05abbe814ec127b46a5e1a640a899e09
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 392984cd87843f34989251453d4a455b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 554836c1a57522a4e887ae48023708fc
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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]

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

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 55f4464fd7c6ad14584b9b10aabc18ba
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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

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

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3ecda907b9217e5499e31cb81a154ff4
guid: 7258f46ca3cc69546ad93c07bcd08582
folderAsset: yes
DefaultImporter:
externalObjects: {}

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 80277bb9563227842a4231e7178e88a7
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

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

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:

View File

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

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:

View File

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

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

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: []

View File

@ -5,6 +5,7 @@
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using CustomComponent;
using GeometryTD.CustomComponent;
using UnityEngine;
@ -16,13 +17,19 @@ public partial class GameEntry : MonoBehaviour
public static BuiltinDataComponent BuiltinData { get; private set; }
public static HPBarComponent HPBar { get; private set; }
public static UIRouterComponent UIRouter { get; private set; }
public static EnemyManagerComponent EnemyManager { 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>();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c942b083720d4d588063c5f1dde93428
timeCreated: 1772165636

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5e646500b9304d438746f2351c71a0f4
timeCreated: 1772103300

View File

@ -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
}
}

View File

@ -1,8 +1,7 @@
fileFormatVersion: 2
guid: 894291f7d20e5dc4bb7264dec378b1ce
timeCreated: 1528026163
licenseType: Pro
guid: 9f43fd8050fd4239b4407a4fedcf0b7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0

View File

@ -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);
}
}
}

View File

@ -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--;
}
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0a0c1c547ca24c95819f5f62f0bd3ea3
timeCreated: 1772102402

View File

@ -9,6 +9,8 @@ namespace CustomComponent
public class UIRouterComponent : GameFrameworkComponent
{
private readonly Dictionary<UIFormType, IUIFormController> _routeControllers = new();
private const string UINameSpace = "GeometryTD.UI";
public void BindUIUseCase(UIFormType uiFormType, IUIUseCase useCase)
{
@ -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
{

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0f9832b810954c4eb0f45ad735bb5a58
timeCreated: 1772101800

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57e29dfa7f1044a1acfbf3abde81859f
timeCreated: 1772097514

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0eb47d184fe4204b38dc7c9e86da609
timeCreated: 1772089145

View File

@ -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);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f1fd79e88eca4bfbb38f5bcb8091e2dd
timeCreated: 1772090000

View File

@ -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);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b06804b242d247bd8a937cdce930cee1
timeCreated: 1772090001

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c47aaecd99446f2b8c9ec84c797005b
timeCreated: 1772105886

View File

@ -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;
}
}

View File

@ -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>();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c39f9ad23ee94de9af2b3d9729f47d7f
timeCreated: 1772105403

View File

@ -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();
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ca5782ae52464616bf2b45704feea0d5
timeCreated: 1772105402

View File

@ -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; }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2a6dce0183e4efdbada20c25be949c0
timeCreated: 1772097196

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bcb612057a454fcf97f41fdf5c6d9b22
timeCreated: 1772097742

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9f95a29d5bcf484aa053af1462365936
timeCreated: 1772105401

View File

@ -3,7 +3,7 @@ namespace GeometryTD.Definition
public enum AttackPropertyType : byte
{
None = 0,
Physical = 1,
Physics = 1,
Fire = 2,
Water = 3,
Earth = 4,

View File

@ -0,0 +1,13 @@
namespace GeometryTD.Definition
{
/// <summary>
/// 出怪条目类型
/// </summary>
public enum EntryType : byte
{
None = 0,
Stream = 1,
Burst = 2,
Boss = 3
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 14433e1048844c5d942c885c2f50ffe0
timeCreated: 1772090003

View File

@ -0,0 +1,21 @@
namespace GeometryTD.Definition
{
public enum EventEffectType
{
None,
AddGold,
RemoveGold,
AddRandomComps,
RemoveRandomComps,
AddRandomCompsEndurance,
RemoveRandomCompsEndurance,
AddRandomTowersEndurance,
DamageRandomTowersEndurance,
TransformComponents,
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6f11ba35aa8f491f942964435b32cba6
timeCreated: 1772095333

View File

@ -0,0 +1,11 @@
namespace GeometryTD.Definition
{
public enum EventRequirementType
{
None,
GoldAtLeast,
CompCountAtLeast,
TowerCountAtLeast,
HasRelic,
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f9da1af87a4f4115b9a5da706b4e1092
timeCreated: 1772095290

View File

@ -0,0 +1,10 @@
namespace GeometryTD.Definition
{
public enum LevelThemeType
{
None,
Plain,
Volcano,
Mountain
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 06ec7c0ec8924c7988e521abdd97bffe
timeCreated: 1772089321

View File

@ -0,0 +1,10 @@
namespace GeometryTD.Definition
{
public enum LevelVictoryType
{
None,
PhasesCleared,
BossDead,
TimeElapsed
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ea9b7c078511464fa572923f66cc200e
timeCreated: 1772089447

View File

@ -0,0 +1,13 @@
namespace GeometryTD.Definition
{
/// <summary>
/// 关卡阶段结束类型
/// </summary>
public enum PhaseEndType : byte
{
None = 0,
TimeElapsed = 1,
EnemiesCleared = 2,
BossDead = 3
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eed7685430c24cdaac639581331393f3
timeCreated: 1772090002

View File

@ -0,0 +1,11 @@
namespace GeometryTD.Definition
{
public enum TowerCompSlotType : byte
{
None = 0,
Muzzle = 1,
Bearing = 2,
Base = 3,
Accessory = 4,
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a34cbb64aede4531b1d0e178352006bb
timeCreated: 1772105400

View File

@ -26,5 +26,15 @@
/// 关于。
/// </summary>
AboutForm = 102,
/// <summary>
/// 事件节点界面。
/// </summary>
EventForm = 200,
/// <summary>
/// 仓库界面。
/// </summary>
RepoForm = 201,
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a291a17879242cc8c7cec678aa5aaf1
timeCreated: 1772101365

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 75cacc997d8f414ca73f381cad303b5b
timeCreated: 1772101474

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 35656d42c28448268d0547c19938b2d8
timeCreated: 1772101530

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a1b06f274c054844ad68b1db02d3737a
timeCreated: 1772101563

View File

@ -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
{
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2be5a7ff782240f79bde2dd0310d11ea
timeCreated: 1772101392

View File

@ -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;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77f8e0d1879b48108e6ad5faca07d7f7
timeCreated: 1772101548

View File

@ -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>();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fae04d7a401d4c7fbe2de385c3f23580
timeCreated: 1772102401

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cee1f415e08743a9914efd2ecbc6273e
timeCreated: 1772100925

View File

@ -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