CombatNode 相关逻辑补充

- CombatFinishForm:战斗节点的结算页面(杀死的敌人、获得的金币、获得的道具)
- CombatSelectForm:战斗节点建造防御塔的选择 UI 以及对防御塔进行操作的 UI(只实现了建造逻辑,在 MapEntity 里做射线检测)
- 构建了基本的防御塔三组件(ShooterMuzzleComp/BasicBearingComp/BasicBaseComp)和防御塔主控(DefenseTowerController)以及相应的实体类(DefenseTowerEntity)
This commit is contained in:
SepComet 2026-03-01 23:11:52 +08:00
parent 92cca14503
commit 564817d752
96 changed files with 7294 additions and 121 deletions

View File

@ -1,5 +1,5 @@
# Id 策划备注 EntityId BaseHp BaseDamage Speed DropCoin # Id 策划备注 EntityId BaseHp BaseDamage Speed DropCoin DropGold DropPercent
# int int int int float int # int int int int float int int float
# 敌人Id 敌人实体Id 基础血量 基地伤害 移动速度 掉落硬币 # 敌人Id 敌人实体Id 基础血量 基地伤害 移动速度 掉落硬币 掉落金币 掉落概率
1 1001 500 5 1 5 1 1001 500 5 1 5 1 0.2
2 1002 5000 50 0.5 100 2 1002 5000 50 0.5 100 20 0.8

View File

@ -4,5 +4,7 @@
101 测试枪口 TestMuzzle 101 测试枪口 TestMuzzle
201 测试轴承 TestBearing 201 测试轴承 TestBearing
301 测试底座 TestBase 301 测试底座 TestBase
401 防御塔实体 TowerEntity
501 普通子弹 NormalBullet
1001 测试普通敌人 TestEnemy 1001 测试普通敌人 TestEnemy
1002 测试Boss TestBoss 1002 测试Boss TestBoss

View File

@ -1,15 +1,16 @@
# Id 列1 AssetName UIGroupName AllowMultiInstance PauseCoveredUIForm # Id 列1 AssetName UIGroupName AllowMultiInstance PauseCoveredUIForm
# int string string bool bool # int string string bool bool
# 界面编号 策划备注 资源名称 界面组名称 是否允许多个界面实例 是否暂停被其覆盖的界面 # 界面编号 策划备注 资源名称 界面组名称 是否允许多个界面实例 是否暂停被其覆盖的界面
1 弹出框 DialogForm Default True True 1 弹出框 DialogForm Overlay True True
100 主菜单 MenuForm Default False True 100 主菜单 MenuForm Medium False True
101 设置 SettingForm Default False True 101 设置 SettingForm Medium False True
102 关于 AboutForm Default False True 102 关于 AboutForm Medium False True
110 主界面 MainForm Default False True 110 主界面 MainForm Medium False True
111 仓库UI RepoForm Default False True 111 仓库UI RepoForm Medium False True
112 详细信息 ItemDescForm Default True False 112 详细信息 ItemDescForm Medium True False
130 事件UI EventForm Default False False 130 事件UI EventForm Medium False False
140 战斗信息UI CombatInfoForm Default False False 140 战斗信息UI CombatInfoForm Medium False False
141 战斗结束UI CombatFinishForm Default False True 141 战斗结束UI CombatFinishForm Medium False True
150 商店UI ShopForm Default False True 142 战斗选择UI CombatSelectForm Overlay False False
150 商店UI ShopForm Medium False True
200 测试UI TestMenuForm Test False False 200 测试UI TestMenuForm Test False False

View File

@ -149,8 +149,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -402,8 +402,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 1 m_SortingOrder: 1
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -484,8 +484,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -7181,8 +7181,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -7347,8 +7347,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -8738,8 +8738,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 2 m_SortingOrder: 2
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}

View File

@ -149,8 +149,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -402,8 +402,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 1 m_SortingOrder: 1
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -7082,8 +7082,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -7247,8 +7247,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -8398,8 +8398,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 2 m_SortingOrder: 2
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}

View File

@ -149,8 +149,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -382,8 +382,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 1 m_SortingOrder: 1
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -464,8 +464,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -7161,8 +7161,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -7327,8 +7327,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -8608,8 +8608,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 2 m_SortingOrder: 2
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}

View File

@ -149,8 +149,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -382,8 +382,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 1 m_SortingOrder: 1
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -7062,8 +7062,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}
@ -7227,8 +7227,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6, m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3} type: 3}
@ -8258,8 +8258,8 @@ TilemapRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0 m_SortingLayerID: -1869315837
m_SortingLayer: 0 m_SortingLayer: -4
m_SortingOrder: 2 m_SortingOrder: 2
m_ChunkSize: {x: 32, y: 32, z: 32} m_ChunkSize: {x: 32, y: 32, z: 32}
m_ChunkCullingBounds: {x: 0, y: 0, z: 0} m_ChunkCullingBounds: {x: 0, y: 0, z: 0}

View File

@ -0,0 +1,103 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &8436939006141951225
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1601478963695598935}
- component: {fileID: 3440631169294921595}
- component: {fileID: 712345678901234567}
m_Layer: 0
m_Name: NormalBullet
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1601478963695598935
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8436939006141951225}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.4, y: 0.4, z: 0.4}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &3440631169294921595
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8436939006141951225}
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: -1805677833
m_SortingLayer: -1
m_SortingOrder: 0
m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b,
type: 3}
m_Color: {r: 1, g: 1, b: 1, 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 &712345678901234567
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8436939006141951225}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 81f9627601c091545a9b92fc7ef41f04, type: 3}
m_Name:
m_EditorClassIdentifier:
_defaultSpeed: 12
_hitDistance: 0.15
_maxLifetime: 3

View File

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

View File

@ -71,8 +71,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: -1869315837 m_SortingLayerID: -1805677833
m_SortingLayer: 1 m_SortingLayer: -1
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b, m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b,
type: 3} type: 3}

View File

@ -71,8 +71,8 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5 m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89 m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0} m_LightmapParameters: {fileID: 0}
m_SortingLayerID: -1869315837 m_SortingLayerID: -1805677833
m_SortingLayer: 1 m_SortingLayer: -1
m_SortingOrder: 0 m_SortingOrder: 0
m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b, m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b,
type: 3} type: 3}

View File

@ -0,0 +1,374 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &931511988754511484
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7637292285124107611}
- component: {fileID: 7170949026143225663}
- component: {fileID: 6677074338184329456}
m_Layer: 0
m_Name: Muzzle
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7637292285124107611
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 931511988754511484}
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: 5517541809701307552}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &7170949026143225663
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 931511988754511484}
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: -1805677833
m_SortingLayer: -1
m_SortingOrder: 2
m_Sprite: {fileID: 7482667652216324306, guid: 75f5f34dc1b5347e0b8351032682f224,
type: 3}
m_Color: {r: 1, g: 0.40566027, b: 0.40566027, 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 &6677074338184329456
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 931511988754511484}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 20a2c131403122146a41148cb72fcd43, type: 3}
m_Name:
m_EditorClassIdentifier:
_attackDamage: 10
_attackMethodType: 1
_bulletTypeId: 501
_muzzlePoint: {fileID: 0}
_bulletSpeed: 12
--- !u!1 &1221576993898367501
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6791423131335728073}
- component: {fileID: 8183383920109690380}
- component: {fileID: 3255949411223456789}
m_Layer: 0
m_Name: TowerEntity
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &6791423131335728073
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1221576993898367501}
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:
- {fileID: 6754288440384573513}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &8183383920109690380
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1221576993898367501}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8b33f7d4136745b8bbf516ec80495b80, type: 3}
m_Name:
m_EditorClassIdentifier:
_muzzleComp: {fileID: 6677074338184329456}
_bearingComp: {fileID: 3784879822105863121}
_baseComp: {fileID: 9032148832249544824}
_scanOrigin: {fileID: 6791423131335728073}
_retargetInterval: 0.1
_autoUpdate: 0
--- !u!114 &3255949411223456789
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1221576993898367501}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d87f56efd9024709a3baf8ef8a6f4fd3, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &2017874305906296486
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5517541809701307552}
- component: {fileID: 7775658596736532582}
- component: {fileID: 3784879822105863121}
m_Layer: 0
m_Name: Bearing
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &5517541809701307552
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2017874305906296486}
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:
- {fileID: 7637292285124107611}
m_Father: {fileID: 6754288440384573513}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &7775658596736532582
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2017874305906296486}
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: -1805677833
m_SortingLayer: -1
m_SortingOrder: 1
m_Sprite: {fileID: -2413806693520163455, guid: a86470a33a6bf42c4b3595704624658b,
type: 3}
m_Color: {r: 1, g: 1, b: 1, 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 &3784879822105863121
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2017874305906296486}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6c8b3d58a8bb41f493706b40fc451558, type: 3}
m_Name:
m_EditorClassIdentifier:
_rotateSpeed: 180
_attackRange: 5
_aimToleranceAngle: 2
_rotateRoot: {fileID: 0}
_yawOnly: 1
--- !u!1 &4867507345079921359
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6754288440384573513}
- component: {fileID: 6516390239457838709}
- component: {fileID: 9032148832249544824}
m_Layer: 0
m_Name: Base
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &6754288440384573513
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4867507345079921359}
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:
- {fileID: 5517541809701307552}
m_Father: {fileID: 6791423131335728073}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &6516390239457838709
SpriteRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4867507345079921359}
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: -1805677833
m_SortingLayer: -1
m_SortingOrder: 0
m_Sprite: {fileID: 7482667652216324306, guid: 311925a002f4447b3a28927169b83ea6,
type: 3}
m_Color: {r: 0.3584906, g: 0.3584906, b: 0.3584906, 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 &9032148832249544824
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4867507345079921359}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 11e3dd172f9d486194b6ca5d7f4dd5e6, type: 3}
m_Name:
m_EditorClassIdentifier:
_attackSpeed: 1
_attackPropertyType: 1

View File

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

View File

@ -0,0 +1,66 @@
using GeometryTD.Definition;
using UnityEngine;
namespace Components
{
[DisallowMultipleComponent]
public class BasicBaseComp : MonoBehaviour
{
[SerializeField] [Min(0.01f)] private float _attackSpeed = 1f;
[SerializeField] private AttackPropertyType _attackPropertyType = AttackPropertyType.Physics;
private float _cooldownRemaining;
public float AttackSpeed => _attackSpeed;
public AttackPropertyType AttackPropertyType => _attackPropertyType;
public bool CanAttack => _cooldownRemaining <= 0f;
public void OnInit(float attackSpeed, AttackPropertyType attackPropertyType)
{
_attackSpeed = Mathf.Max(0.01f, attackSpeed);
_attackPropertyType = attackPropertyType;
_cooldownRemaining = 0f;
}
public void OnReset()
{
_attackSpeed = 1f;
_attackPropertyType = AttackPropertyType.None;
_cooldownRemaining = 0f;
}
public void Tick(float deltaTime)
{
if (_cooldownRemaining <= 0f)
{
return;
}
_cooldownRemaining = Mathf.Max(0f, _cooldownRemaining - Mathf.Max(0f, deltaTime));
}
public bool TryAttack(BasicBearingComp bearingComp, ShooterMuzzleComp shooterMuzzleComp, Transform target, float deltaTime)
{
if (bearingComp == null || shooterMuzzleComp == null || target == null)
{
return false;
}
Tick(deltaTime);
bool isAligned = bearingComp.TrackTarget(target, deltaTime);
if (!isAligned || !CanAttack)
{
return false;
}
bool fired = shooterMuzzleComp.Attack(target, _attackPropertyType);
if (!fired)
{
return false;
}
_cooldownRemaining = 1f / _attackSpeed;
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 11e3dd172f9d486194b6ca5d7f4dd5e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,111 @@
using UnityEngine;
namespace Components
{
[DisallowMultipleComponent]
public class BasicBearingComp : MonoBehaviour
{
[SerializeField] [Min(1f)] private float _rotateSpeed = 180f;
[SerializeField] [Min(0.1f)] private float _attackRange = 5f;
[SerializeField] [Min(0.1f)] private float _aimToleranceAngle = 2f;
[SerializeField] private Transform _rotateRoot;
[SerializeField] private bool _yawOnly = true;
public float RotateSpeed => _rotateSpeed;
public float AttackRange => _attackRange;
public float AimToleranceAngle => _aimToleranceAngle;
public void OnInit(float rotateSpeed, float attackRange = 5f)
{
_rotateSpeed = Mathf.Max(1f, rotateSpeed);
_attackRange = Mathf.Max(0.1f, attackRange);
}
public void OnReset()
{
_rotateSpeed = 1f;
_attackRange = 5f;
}
public bool IsTargetInRange(Transform target, Transform origin = null)
{
if (target == null)
{
return false;
}
Transform originTransform = origin != null ? origin : transform;
Vector3 delta = target.position - originTransform.position;
return delta.sqrMagnitude <= _attackRange * _attackRange;
}
public bool TrackTarget(Transform target, float deltaTime)
{
if (target == null)
{
return false;
}
Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform;
Vector3 toTarget = target.position - rotateRoot.position;
if (_yawOnly)
{
toTarget.y = 0f;
}
if (toTarget.sqrMagnitude <= Mathf.Epsilon)
{
return true;
}
Quaternion targetRotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
float rotateAngle = _rotateSpeed * Mathf.Max(0f, deltaTime);
rotateRoot.rotation = Quaternion.RotateTowards(rotateRoot.rotation, targetRotation, rotateAngle);
return IsTargetAligned(target);
}
public bool IsTargetAligned(Transform target)
{
if (target == null)
{
return false;
}
Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform;
Vector3 toTarget = target.position - rotateRoot.position;
if (_yawOnly)
{
toTarget.y = 0f;
}
if (toTarget.sqrMagnitude <= Mathf.Epsilon)
{
return true;
}
float angle = Vector3.Angle(rotateRoot.forward, toTarget.normalized);
return angle <= _aimToleranceAngle;
}
public bool TryAttack(ShooterMuzzleComp shooterMuzzleComp, Transform target, float deltaTime)
{
if (shooterMuzzleComp == null || target == null)
{
return false;
}
if (!IsTargetInRange(target))
{
return false;
}
bool isAligned = TrackTarget(target, deltaTime);
if (!isAligned)
{
return false;
}
return shooterMuzzleComp.Attack(target);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c8b3d58a8bb41f493706b40fc451558
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,168 @@
using GeometryTD.Definition;
using GeometryTD.Entity;
using UnityEngine;
namespace Components
{
[DisallowMultipleComponent]
public class DefenseTowerController : MonoBehaviour
{
[SerializeField] private ShooterMuzzleComp _muzzleComp;
[SerializeField] private BasicBearingComp _bearingComp;
[SerializeField] private BasicBaseComp _baseComp;
[SerializeField] private Transform _scanOrigin;
[SerializeField] [Min(0.02f)] private float _retargetInterval = 0.1f;
[SerializeField] private bool _autoUpdate = true;
private Transform _currentTarget;
private float _retargetTimer;
public Transform CurrentTarget => _currentTarget;
private void Awake()
{
ResolveComponents();
}
private void Update()
{
if (!_autoUpdate)
{
return;
}
OnUpdate(Time.deltaTime);
}
public void OnInit(DefenseTowerStatsData stats)
{
ResolveComponents();
if (stats == null)
{
return;
}
_muzzleComp?.OnInit(stats.AttackDamage, stats.AttackMethodType);
_bearingComp?.OnInit(stats.RotateSpeed, stats.AttackRange);
_baseComp?.OnInit(stats.AttackSpeed, stats.AttackPropertyType);
_currentTarget = null;
_retargetTimer = 0f;
}
public void OnReset()
{
_currentTarget = null;
_retargetTimer = 0f;
_muzzleComp?.OnReset();
_bearingComp?.OnReset();
_baseComp?.OnReset();
}
public void SetAutoUpdate(bool autoUpdate)
{
_autoUpdate = autoUpdate;
}
public void SetTarget(Transform target)
{
_currentTarget = target;
}
public void ClearTarget()
{
_currentTarget = null;
}
public void OnUpdate(float deltaTime)
{
if (!HasCoreComponents())
{
return;
}
if (!IsTargetValid(_currentTarget) || !_bearingComp.IsTargetInRange(_currentTarget, _scanOrigin))
{
TryRetarget(deltaTime);
}
if (_currentTarget == null)
{
_baseComp.Tick(deltaTime);
return;
}
_baseComp.TryAttack(_bearingComp, _muzzleComp, _currentTarget, deltaTime);
}
private void TryRetarget(float deltaTime)
{
_retargetTimer -= Mathf.Max(0f, deltaTime);
if (_retargetTimer > 0f)
{
return;
}
_retargetTimer = _retargetInterval;
_currentTarget = FindNearestEnemyTarget();
}
private Transform FindNearestEnemyTarget()
{
Vector3 origin = _scanOrigin != null ? _scanOrigin.position : transform.position;
float maxRange = _bearingComp.AttackRange;
float maxRangeSquared = maxRange * maxRange;
EnemyEntity bestEnemy = null;
float bestDistanceSquared = float.MaxValue;
for (int i = 0; i < EnemyEntity.ActiveEnemies.Count; i++)
{
EnemyEntity enemy = EnemyEntity.ActiveEnemies[i];
if (enemy == null || !enemy.isActiveAndEnabled)
{
continue;
}
Transform enemyTransform = enemy.transform;
Vector3 delta = enemyTransform.position - origin;
float distanceSquared = delta.sqrMagnitude;
if (distanceSquared > maxRangeSquared || distanceSquared >= bestDistanceSquared)
{
continue;
}
bestDistanceSquared = distanceSquared;
bestEnemy = enemy;
}
return bestEnemy != null ? bestEnemy.transform : null;
}
private void ResolveComponents()
{
if (_muzzleComp == null)
{
_muzzleComp = GetComponent<ShooterMuzzleComp>();
}
if (_bearingComp == null)
{
_bearingComp = GetComponent<BasicBearingComp>();
}
if (_baseComp == null)
{
_baseComp = GetComponent<BasicBaseComp>();
}
}
private bool HasCoreComponents()
{
return _muzzleComp != null && _bearingComp != null && _baseComp != null;
}
private static bool IsTargetValid(Transform target)
{
return target != null && target.gameObject.activeInHierarchy;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b33f7d4136745b8bbf516ec80495b80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
using GeometryTD.Definition;
namespace Components
{
public interface IDamageReceiver
{
void TakeDamage(int damage, AttackPropertyType attackPropertyType);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b8c22c0144cd4e4c887c3cf49ba35a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,121 @@
using UnityEngine;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
namespace Components
{
[DisallowMultipleComponent]
public class ShooterBullet : MonoBehaviour
{
[SerializeField] [Min(0.1f)] private float _defaultSpeed = 12f;
[SerializeField] [Min(0.02f)] private float _hitDistance = 0.15f;
[SerializeField] [Min(0.1f)] private float _maxLifetime = 3f;
private Transform _target;
private float _speed;
private int _damage;
private AttackPropertyType _attackPropertyType;
private float _lifetime;
private float _runtimeMaxLifetime;
private bool _isRunning;
private bool _despawnRequested;
public void OnShow(BulletData bulletData)
{
_target = bulletData != null ? bulletData.Target : null;
_damage = bulletData != null ? Mathf.Max(0, bulletData.Damage) : 0;
_attackPropertyType = bulletData != null ? bulletData.AttackPropertyType : AttackPropertyType.None;
_speed = bulletData != null && bulletData.Speed > 0f ? bulletData.Speed : _defaultSpeed;
_runtimeMaxLifetime = bulletData != null && bulletData.MaxLifetime > 0f ? bulletData.MaxLifetime : _maxLifetime;
_lifetime = 0f;
_despawnRequested = false;
_isRunning = _target != null;
if (!_isRunning)
{
_despawnRequested = true;
}
}
public void OnReset()
{
_target = null;
_speed = 0f;
_damage = 0;
_attackPropertyType = AttackPropertyType.None;
_lifetime = 0f;
_runtimeMaxLifetime = _maxLifetime;
_isRunning = false;
_despawnRequested = false;
}
public void Tick(float deltaTime)
{
if (!_isRunning)
{
return;
}
_lifetime += Mathf.Max(0f, deltaTime);
if (_lifetime >= _runtimeMaxLifetime || _target == null)
{
_isRunning = false;
_despawnRequested = true;
return;
}
Vector3 toTarget = _target.position - transform.position;
float hitDistanceSquared = _hitDistance * _hitDistance;
if (toTarget.sqrMagnitude <= hitDistanceSquared)
{
HitTarget();
return;
}
Vector3 forward = toTarget.normalized;
float moveDistance = _speed * Mathf.Max(0f, deltaTime);
if (moveDistance * moveDistance >= toTarget.sqrMagnitude)
{
transform.position = _target.position;
HitTarget();
return;
}
transform.position += forward * moveDistance;
transform.forward = forward;
}
public bool TryConsumeDespawnRequest()
{
if (!_despawnRequested)
{
return false;
}
_despawnRequested = false;
return true;
}
private void HitTarget()
{
if (_target == null)
{
_isRunning = false;
_despawnRequested = true;
return;
}
MonoBehaviour[] components = _target.GetComponentsInParent<MonoBehaviour>();
for (int i = 0; i < components.Length; i++)
{
if (components[i] is IDamageReceiver damageReceiver)
{
damageReceiver.TakeDamage(_damage, _attackPropertyType);
break;
}
}
_isRunning = false;
_despawnRequested = true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81f9627601c091545a9b92fc7ef41f04
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,66 @@
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData;
using GeometryTD;
using UnityEngine;
namespace Components
{
[DisallowMultipleComponent]
public class ShooterMuzzleComp : MonoBehaviour
{
[SerializeField] [Min(1f)] private int _attackDamage = 10;
[SerializeField] private AttackMethodType _attackMethodType = AttackMethodType.NormalBullet;
[SerializeField] [Min(1)] private int _bulletTypeId = 501;
[SerializeField] private Transform _muzzlePoint;
[SerializeField] [Min(0.1f)] private float _bulletSpeed = 12f;
public int AttackDamage => _attackDamage;
public AttackMethodType AttackMethodType => _attackMethodType;
public void OnInit(int attackDamage, AttackMethodType attackMethodType = AttackMethodType.NormalBullet, int bulletTypeId = 501)
{
_attackDamage = Mathf.Max(1, attackDamage);
_attackMethodType = attackMethodType;
_bulletTypeId = Mathf.Max(1, bulletTypeId);
}
public void OnReset()
{
_attackDamage = 1;
_attackMethodType = AttackMethodType.None;
_bulletTypeId = 501;
}
public bool Attack(Transform target)
{
return Attack(target, AttackPropertyType.None);
}
public bool Attack(Transform target, AttackPropertyType attackPropertyType)
{
if (_attackMethodType != AttackMethodType.NormalBullet)
{
return false;
}
if (target == null)
{
return false;
}
Transform spawnPoint = _muzzlePoint != null ? _muzzlePoint : transform;
int bulletEntityId = GameEntry.Entity.GenerateSerialId();
BulletData bulletData = new BulletData(
bulletEntityId,
_bulletTypeId,
spawnPoint.position,
spawnPoint.rotation,
target,
_attackDamage,
_bulletSpeed,
attackPropertyType);
GameEntry.Entity.ShowBullet(bulletData);
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20a2c131403122146a41148cb72fcd43
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -20,7 +20,7 @@ namespace GeometryTD.CustomComponent
} }
private EntityComponent _entity; private EntityComponent _entity;
private CombatInfoFormUseCase _useCase; private CombatInfoFormUseCase _combatInfoFormUseCase;
private int _loadingMapEntityId; private int _loadingMapEntityId;
private int _loadedMapEntityId; private int _loadedMapEntityId;
private int? _loadingCombatInfoFormSerialId; private int? _loadingCombatInfoFormSerialId;
@ -113,7 +113,8 @@ namespace GeometryTD.CustomComponent
return EventHandleStatus.Ignored; return EventHandleStatus.Ignored;
} }
if (args.EntityLogicType != typeof(MapEntity) || args.Entity == null || args.Entity.Id != _loadingMapEntityId) if (args.EntityLogicType != typeof(MapEntity) || args.Entity == null ||
args.Entity.Id != _loadingMapEntityId)
{ {
return EventHandleStatus.Ignored; return EventHandleStatus.Ignored;
} }
@ -248,10 +249,10 @@ namespace GeometryTD.CustomComponent
private bool TryOpenCombatInfoForm(out string errorMessage) private bool TryOpenCombatInfoForm(out string errorMessage)
{ {
errorMessage = null; errorMessage = null;
if (_useCase == null) if (_combatInfoFormUseCase == null)
{ {
_useCase = new CombatInfoFormUseCase(); _combatInfoFormUseCase = new CombatInfoFormUseCase();
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _useCase); GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatInfoForm, _combatInfoFormUseCase);
} }
int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm); int? serialId = GameEntry.UIRouter.OpenUI(UIFormType.CombatInfoForm);

View File

@ -27,11 +27,16 @@ namespace GeometryTD.CustomComponent
private bool _runtimeInitialized; private bool _runtimeInitialized;
private int _currentCoin; private int _currentCoin;
private int _currentGold;
public LevelThemeType CurrentThemeType { get; private set; } public LevelThemeType CurrentThemeType { get; private set; }
public DRLevel CurrentLevel { get; private set; } public DRLevel CurrentLevel { get; private set; }
public int CurrentCoin => _currentCoin; public int CurrentCoin => _currentCoin;
public int CurrentGold => _currentGold;
public bool CanEndCombat => _combatScheduler.CanEndCombat; public bool CanEndCombat => _combatScheduler.CanEndCombat;
public int LastDefeatedEnemyCount { get; private set; }
public int LastGainedCoin { get; private set; }
public int LastGainedGold { get; private set; }
public int CurrentLevelPhaseCount public int CurrentLevelPhaseCount
{ {
@ -66,6 +71,10 @@ namespace GeometryTD.CustomComponent
CurrentThemeType = themeType; CurrentThemeType = themeType;
CurrentLevel = null; CurrentLevel = null;
_currentCoin = 0; _currentCoin = 0;
_currentGold = 0;
LastDefeatedEnemyCount = 0;
LastGainedCoin = 0;
LastGainedGold = 0;
_levelsById.Clear(); _levelsById.Clear();
_phasesByLevelId.Clear(); _phasesByLevelId.Clear();
_spawnEntriesByPhaseId.Clear(); _spawnEntriesByPhaseId.Clear();
@ -200,13 +209,21 @@ namespace GeometryTD.CustomComponent
CurrentLevel = selectedLevel; CurrentLevel = selectedLevel;
_currentCoin = Mathf.Max(0, selectedLevel.StartCoin); _currentCoin = Mathf.Max(0, selectedLevel.StartCoin);
_currentGold = 0;
LastDefeatedEnemyCount = 0;
LastGainedCoin = 0;
LastGainedGold = 0;
_combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId); _combatScheduler.Start(selectedLevel, phaseList, _selectedSpawnEntriesByPhaseId);
GameEntry.Event.Fire(this, NodeEnterEventArgs.Create()); GameEntry.Event.Fire(this, NodeEnterEventArgs.Create());
} }
public void EndCombat() public void EndCombat()
{ {
LastDefeatedEnemyCount = _combatScheduler.DefeatedEnemyCount;
LastGainedCoin = _combatScheduler.GainedCoin;
LastGainedGold = _combatScheduler.GainedGold;
_currentCoin = 0; _currentCoin = 0;
_currentGold = 0;
GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create()); GameEntry.Event.Fire(this, NodeCompleteEventArgs.Create());
} }
@ -229,9 +246,54 @@ namespace GeometryTD.CustomComponent
{ {
CurrentLevel = null; CurrentLevel = null;
_currentCoin = 0; _currentCoin = 0;
_currentGold = 0;
LastDefeatedEnemyCount = 0;
LastGainedCoin = 0;
LastGainedGold = 0;
ShutdownCombatRuntime(); ShutdownCombatRuntime();
} }
public void ApplyEnemyDropReward(int coin, int gold)
{
if (coin > 0)
{
_currentCoin += coin;
}
if (gold > 0)
{
_currentGold += gold;
}
}
public bool TryConsumeCoin(int coin)
{
int requiredCoin = Mathf.Max(0, coin);
if (requiredCoin <= 0)
{
return true;
}
if (_currentCoin < requiredCoin)
{
return false;
}
_currentCoin -= requiredCoin;
return true;
}
public void AddCoin(int coin)
{
int gainCoin = Mathf.Max(0, coin);
if (gainCoin <= 0)
{
return;
}
_currentCoin += gainCoin;
}
private void OnDestroy() private void OnDestroy()
{ {
OnShutdown(); OnShutdown();

View File

@ -1,6 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using GeometryTD.DataTable; using GeometryTD.DataTable;
using GeometryTD.Definition;
using GeometryTD.Entity; using GeometryTD.Entity;
using GeometryTD.UI;
using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
namespace GeometryTD.CustomComponent namespace GeometryTD.CustomComponent
@ -12,8 +15,9 @@ namespace GeometryTD.CustomComponent
Idle = 0, Idle = 0,
WaitingForLoading = 1, WaitingForLoading = 1,
RunningPhase = 2, RunningPhase = 2,
Completed = 3, WaitingForFinishReturn = 3,
Failed = 4 Completed = 4,
Failed = 5
} }
private readonly List<DRLevelPhase> _phaseBuffer = new(); private readonly List<DRLevelPhase> _phaseBuffer = new();
@ -25,8 +29,11 @@ namespace GeometryTD.CustomComponent
private EntityComponent _entity; private EntityComponent _entity;
private DRLevel _currentLevel; private DRLevel _currentLevel;
private CombatFinishFormUseCase _combatFinishFormUseCase;
private SchedulerState _state = SchedulerState.Idle; private SchedulerState _state = SchedulerState.Idle;
private bool _initialized; private bool _initialized;
private int _gainedCoin;
private int _gainedGold;
public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase; public bool IsRunning => _state == SchedulerState.WaitingForLoading || _state == SchedulerState.RunningPhase;
public bool IsCompleted => _state == SchedulerState.Completed; public bool IsCompleted => _state == SchedulerState.Completed;
@ -35,6 +42,9 @@ namespace GeometryTD.CustomComponent
public MapEntity CurrentMap => _loadSession.CurrentMap; public MapEntity CurrentMap => _loadSession.CurrentMap;
public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex; public int DisplayPhaseIndex => _phaseLoopRuntime.DisplayPhaseIndex;
public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat; public bool CanEndCombat => _phaseLoopRuntime.CanEndCombat;
public int DefeatedEnemyCount => _enemyManager.DefeatedEnemyCount;
public int GainedCoin => _gainedCoin;
public int GainedGold => _gainedGold;
public void OnInit() public void OnInit()
{ {
@ -50,6 +60,7 @@ namespace GeometryTD.CustomComponent
OnCloseUIFormComplete); OnCloseUIFormComplete);
_enemyManager.OnInit(this); _enemyManager.OnInit(this);
_loadSession.OnInit(_entity); _loadSession.OnInit(_entity);
EnsureCombatFinishFormUseCaseBound();
_initialized = true; _initialized = true;
} }
@ -75,9 +86,13 @@ namespace GeometryTD.CustomComponent
return; return;
} }
CleanupCombatEntities(); CleanupAllCombatEntities();
CloseCombatFinishForm();
_enemyManager.EndPhase(); _enemyManager.EndPhase();
_enemyManager.ResetCombatStats();
ResetRuntime(); ResetRuntime();
_gainedCoin = 0;
_gainedGold = 0;
_currentLevel = level; _currentLevel = level;
for (int i = 0; i < phases.Count; i++) for (int i = 0; i < phases.Count; i++)
@ -113,7 +128,8 @@ namespace GeometryTD.CustomComponent
} }
_state = SchedulerState.WaitingForLoading; _state = SchedulerState.WaitingForLoading;
Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id, _phaseLoopRuntime.PhaseCount); Log.Info("CombatScheduler started. Level={0}, PhaseCount={1}.", _currentLevel.Id,
_phaseLoopRuntime.PhaseCount);
} }
public void OnUpdate(float elapseSeconds, float realElapseSeconds) public void OnUpdate(float elapseSeconds, float realElapseSeconds)
@ -143,17 +159,20 @@ namespace GeometryTD.CustomComponent
return; return;
} }
CleanupCombatEntities(); CleanupAllCombatEntities();
CloseCombatFinishForm();
_enemyManager.OnDestroy(); _enemyManager.OnDestroy();
ResetRuntime(); ResetRuntime();
_eventBridge.Unbind(); _eventBridge.Unbind();
_combatFinishFormUseCase = null;
_entity = null; _entity = null;
_initialized = false; _initialized = false;
} }
public bool TryEndCombatByPlayer() public bool TryEndCombatByPlayer()
{ {
if (_state == SchedulerState.Completed || _state == SchedulerState.Failed || _state == SchedulerState.Idle) if (_state != SchedulerState.RunningPhase)
{ {
return false; return false;
} }
@ -163,7 +182,7 @@ namespace GeometryTD.CustomComponent
return false; return false;
} }
FinishCombat("Combat ended by player."); EnterFinishFlow("Combat ended by player.");
return true; return true;
} }
@ -185,7 +204,8 @@ namespace GeometryTD.CustomComponent
_phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds); _phaseLoopRuntime.AdvancePhaseElapsed(elapseSeconds);
_enemyManager.OnUpdate(elapseSeconds, realElapseSeconds); _enemyManager.OnUpdate(elapseSeconds, realElapseSeconds);
if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted, _enemyManager.AliveEnemyCount)) if (!_phaseLoopRuntime.ShouldEndCurrentPhase(_enemyManager.IsPhaseSpawnCompleted,
_enemyManager.AliveEnemyCount))
{ {
return; return;
} }
@ -209,7 +229,7 @@ namespace GeometryTD.CustomComponent
{ {
if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase)) if (!_phaseLoopRuntime.TryEnterNextPhase(out DRLevelPhase nextPhase))
{ {
FinishCombat("Combat ended after loop completion."); EnterFinishFlow("Combat ended after loop completion.");
return; return;
} }
@ -227,17 +247,21 @@ namespace GeometryTD.CustomComponent
spawnEntries != null ? spawnEntries.Count : 0); spawnEntries != null ? spawnEntries.Count : 0);
} }
private void FinishCombat(string reason) private void EnterFinishFlow(string reason)
{ {
_state = SchedulerState.Completed; int defeatedEnemyCount = _enemyManager.DefeatedEnemyCount;
CleanupCombatEntities();
// Step 1: stop runtime and clear enemy entities only.
_enemyManager.EndPhase(); _enemyManager.EndPhase();
_state = SchedulerState.WaitingForFinishReturn;
_enemyManager.CleanupTrackedEnemies();
Log.Info( Log.Info(
"CombatScheduler level completed. Level={0}. Reason={1}", "CombatScheduler entered finish flow. Level={0}. Reason={1}",
_currentLevel != null ? _currentLevel.Id : 0, _currentLevel != null ? _currentLevel.Id : 0,
reason); reason);
GameEntry.CombatNode.EndCombat();
OpenCombatFinishForm(defeatedEnemyCount, _gainedGold);
} }
private void ResetRuntime() private void ResetRuntime()
@ -250,12 +274,57 @@ namespace GeometryTD.CustomComponent
_state = SchedulerState.Idle; _state = SchedulerState.Idle;
} }
private void CleanupCombatEntities() private void CleanupAllCombatEntities()
{ {
_loadSession.Cleanup(); _loadSession.Cleanup();
_enemyManager.CleanupTrackedEnemies(); _enemyManager.CleanupTrackedEnemies();
} }
private void EnsureCombatFinishFormUseCaseBound()
{
_combatFinishFormUseCase ??= new CombatFinishFormUseCase(this);
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatFinishForm, _combatFinishFormUseCase);
}
private void OpenCombatFinishForm(int defeatedEnemyCount, int gainedGold)
{
EnsureCombatFinishFormUseCaseBound();
_combatFinishFormUseCase.SetSummary(
defeatedEnemyCount,
gainedGold);
GameEntry.UIRouter.OpenUI(UIFormType.CombatFinishForm);
}
public void OnEnemyDefeatedRewardResolved(int gainedCoin, int gainedGold)
{
int coin = Mathf.Max(0, gainedCoin);
int gold = Mathf.Max(0, gainedGold);
_gainedCoin += coin;
_gainedGold += gold;
GameEntry.CombatNode?.ApplyEnemyDropReward(coin, gold);
}
private void CloseCombatFinishForm()
{
GameEntry.UIRouter.CloseUI(UIFormType.CombatFinishForm);
}
public bool OnCombatFinishReturnRequested()
{
if (_state != SchedulerState.WaitingForFinishReturn)
{
return false;
}
// Step 2: clear remaining map/UI resources and close finish form.
_loadSession.Cleanup();
CloseCombatFinishForm();
_state = SchedulerState.Completed;
GameEntry.CombatNode.EndCombat();
return true;
}
#region Event Handlers #region Event Handlers
private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args) private void OnShowEntitySuccess(ShowEntitySuccessEventArgs args)
@ -287,7 +356,8 @@ namespace GeometryTD.CustomComponent
if (status == CombatLoadSession.EventHandleStatus.Failed) if (status == CombatLoadSession.EventHandleStatus.Failed)
{ {
_state = SchedulerState.Failed; _state = SchedulerState.Failed;
Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0, errorMessage); Log.Error("CombatScheduler failed. LevelId={0}, {1}", _currentLevel != null ? _currentLevel.Id : 0,
errorMessage);
} }
} }

View File

@ -31,6 +31,7 @@ namespace GeometryTD.CustomComponent
private readonly List<SpawnEntryRuntime> _spawnRuntimes = new List<SpawnEntryRuntime>(); private readonly List<SpawnEntryRuntime> _spawnRuntimes = new List<SpawnEntryRuntime>();
private readonly HashSet<int> _trackedEnemyEntityIds = new HashSet<int>(); private readonly HashSet<int> _trackedEnemyEntityIds = new HashSet<int>();
private readonly List<int> _trackedEnemyIdBuffer = new List<int>(); private readonly List<int> _trackedEnemyIdBuffer = new List<int>();
private readonly Dictionary<int, DREnemy> _trackedEnemyConfigByEntityId = new Dictionary<int, DREnemy>();
private CombatScheduler _combatScheduler; private CombatScheduler _combatScheduler;
private EntityComponent _entity; private EntityComponent _entity;
@ -38,6 +39,7 @@ namespace GeometryTD.CustomComponent
private int _spawnEnemyMaxCount = 5000; private int _spawnEnemyMaxCount = 5000;
private int _currentEnemyCount; private int _currentEnemyCount;
private int _defeatedEnemyCount;
private int _nextSpawnerIndex; private int _nextSpawnerIndex;
private int _currentMapEntityId; private int _currentMapEntityId;
private bool _initialized; private bool _initialized;
@ -47,6 +49,7 @@ namespace GeometryTD.CustomComponent
private bool _isPhaseRunning; private bool _isPhaseRunning;
public int AliveEnemyCount => _currentEnemyCount; public int AliveEnemyCount => _currentEnemyCount;
public int DefeatedEnemyCount => _defeatedEnemyCount;
public bool IsPhaseSpawnCompleted { get; private set; } = true; public bool IsPhaseSpawnCompleted { get; private set; } = true;
public bool IsPhaseRunning => _isPhaseRunning; public bool IsPhaseRunning => _isPhaseRunning;
@ -61,6 +64,7 @@ namespace GeometryTD.CustomComponent
_entity = GameEntry.Entity; _entity = GameEntry.Entity;
_drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>(); _drEnemy = GameEntry.DataTable.GetDataTable<DREnemy>();
_currentEnemyCount = 0; _currentEnemyCount = 0;
_defeatedEnemyCount = 0;
_nextSpawnerIndex = 0; _nextSpawnerIndex = 0;
_currentMapEntityId = 0; _currentMapEntityId = 0;
_enemyConfigMissingLogged = false; _enemyConfigMissingLogged = false;
@ -70,6 +74,7 @@ namespace GeometryTD.CustomComponent
_spawnRuntimes.Clear(); _spawnRuntimes.Clear();
_trackedEnemyEntityIds.Clear(); _trackedEnemyEntityIds.Clear();
_trackedEnemyIdBuffer.Clear(); _trackedEnemyIdBuffer.Clear();
_trackedEnemyConfigByEntityId.Clear();
_phaseElapsed = 0f; _phaseElapsed = 0f;
_isPhaseRunning = false; _isPhaseRunning = false;
IsPhaseSpawnCompleted = true; IsPhaseSpawnCompleted = true;
@ -149,13 +154,20 @@ namespace GeometryTD.CustomComponent
_pathBuffer.Clear(); _pathBuffer.Clear();
_trackedEnemyEntityIds.Clear(); _trackedEnemyEntityIds.Clear();
_trackedEnemyIdBuffer.Clear(); _trackedEnemyIdBuffer.Clear();
_trackedEnemyConfigByEntityId.Clear();
_currentEnemyCount = 0; _currentEnemyCount = 0;
_defeatedEnemyCount = 0;
_currentMapEntityId = 0; _currentMapEntityId = 0;
_nextSpawnerIndex = 0; _nextSpawnerIndex = 0;
_combatScheduler = null; _combatScheduler = null;
_initialized = false; _initialized = false;
} }
public void ResetCombatStats()
{
_defeatedEnemyCount = 0;
}
public void CleanupTrackedEnemies() public void CleanupTrackedEnemies()
{ {
if (_trackedEnemyEntityIds.Count <= 0) if (_trackedEnemyEntityIds.Count <= 0)
@ -171,6 +183,7 @@ namespace GeometryTD.CustomComponent
} }
_trackedEnemyEntityIds.Clear(); _trackedEnemyEntityIds.Clear();
_trackedEnemyConfigByEntityId.Clear();
_currentEnemyCount = 0; _currentEnemyCount = 0;
if (_entity == null) if (_entity == null)
@ -352,6 +365,7 @@ namespace GeometryTD.CustomComponent
int enemyEntityId = _entity.GenerateSerialId(); int enemyEntityId = _entity.GenerateSerialId();
_trackedEnemyEntityIds.Add(enemyEntityId); _trackedEnemyEntityIds.Add(enemyEntityId);
_trackedEnemyConfigByEntityId[enemyEntityId] = enemyConfig;
EnemyData enemyData = new EnemyData( EnemyData enemyData = new EnemyData(
enemyEntityId, enemyEntityId,
enemyConfig.EntityId, enemyConfig.EntityId,
@ -497,6 +511,7 @@ namespace GeometryTD.CustomComponent
} }
_trackedEnemyEntityIds.Remove(ne.EntityId); _trackedEnemyEntityIds.Remove(ne.EntityId);
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
} }
private void OnHideEntityComplete(object sender, GameEventArgs e) private void OnHideEntityComplete(object sender, GameEventArgs e)
@ -508,10 +523,33 @@ namespace GeometryTD.CustomComponent
if (!_trackedEnemyEntityIds.Remove(ne.EntityId)) if (!_trackedEnemyEntityIds.Remove(ne.EntityId))
{ {
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
return; return;
} }
_currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1); _currentEnemyCount = Mathf.Max(0, _currentEnemyCount - 1);
bool wasKilled = EnemyEntity.TryConsumeKilledFlag(ne.EntityId);
if (_combatScheduler != null && _combatScheduler.IsRunning && wasKilled)
{
_defeatedEnemyCount++;
int droppedCoin = 0;
int droppedGold = 0;
if (_trackedEnemyConfigByEntityId.TryGetValue(ne.EntityId, out DREnemy enemyConfig) && enemyConfig != null)
{
droppedCoin = Mathf.Max(0, enemyConfig.DropCoin);
float dropRate = enemyConfig.DropPercent > 1f
? Mathf.Clamp01(enemyConfig.DropPercent * 0.01f)
: Mathf.Clamp01(enemyConfig.DropPercent);
if (enemyConfig.DropGold > 0 && dropRate > 0f && Random.value <= dropRate)
{
droppedGold = Mathf.Max(0, enemyConfig.DropGold);
}
}
_combatScheduler.OnEnemyDefeatedRewardResolved(droppedCoin, droppedGold);
}
_trackedEnemyConfigByEntityId.Remove(ne.EntityId);
} }
} }
} }

View File

@ -35,10 +35,20 @@ namespace GeometryTD.DataTable
public float Speed { get; private set; } public float Speed { get; private set; }
/// <summary> /// <summary>
/// 获取敌人掉落 /// 获取敌人掉落
/// </summary> /// </summary>
public int DropCoin { get; private set; } public int DropCoin { get; private set; }
/// <summary>
/// 获取敌人掉落金币
/// </summary>
public int DropGold { get; private set; }
/// <summary>
/// 获取敌人掉落金币概率
/// </summary>
public float DropPercent { get; private set; }
public override bool ParseDataRow(string dataRowString, object userData) public override bool ParseDataRow(string dataRowString, object userData)
{ {
string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators); string[] columnStrings = dataRowString.Split(DataTableExtension.DataSplitSeparators);
@ -56,6 +66,8 @@ namespace GeometryTD.DataTable
BaseDamage = int.Parse(columnStrings[index++]); BaseDamage = int.Parse(columnStrings[index++]);
Speed = float.Parse(columnStrings[index++]); Speed = float.Parse(columnStrings[index++]);
DropCoin = int.Parse(columnStrings[index++]); DropCoin = int.Parse(columnStrings[index++]);
DropGold = int.Parse(columnStrings[index++]);
DropPercent = float.Parse(columnStrings[index++]);
return true; return true;
} }

View File

@ -47,6 +47,16 @@
/// </summary> /// </summary>
CombatInfoForm = 140, CombatInfoForm = 140,
/// <summary>
/// 战斗结算界面。
/// </summary>
CombatFinishForm = 141,
/// <summary>
/// 战斗选择界面
/// </summary>
CombatSelectForm = 142,
/// <summary> /// <summary>
/// 测试菜单。 /// 测试菜单。
/// </summary> /// </summary>

View File

@ -0,0 +1,66 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class BulletData : EntityDataBase
{
[SerializeField] private Transform _target = null;
[SerializeField] private int _damage = 0;
[SerializeField] private float _speed = 0f;
[SerializeField] private float _maxLifetime = 3f;
[SerializeField] private AttackPropertyType _attackPropertyType = AttackPropertyType.None;
public BulletData(
int entityId,
int typeId,
Vector3 position,
Quaternion rotation,
Transform target,
int damage,
float speed,
AttackPropertyType attackPropertyType = AttackPropertyType.None,
float maxLifetime = 3f) : base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_target = target;
_damage = damage;
_speed = speed;
_attackPropertyType = attackPropertyType;
_maxLifetime = maxLifetime;
}
public Transform Target
{
get => _target;
set => _target = value;
}
public int Damage
{
get => _damage;
set => _damage = value;
}
public float Speed
{
get => _speed;
set => _speed = value;
}
public float MaxLifetime
{
get => _maxLifetime;
set => _maxLifetime = value;
}
public AttackPropertyType AttackPropertyType
{
get => _attackPropertyType;
set => _attackPropertyType = value;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f7248061a2a4f4f8ecf8d5db99f0a82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,26 @@
using System;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.Entity.EntityData
{
[Serializable]
public class DefenseTowerData : EntityDataBase
{
[SerializeField] private DefenseTowerStatsData _stats = new DefenseTowerStatsData();
public DefenseTowerData(int entityId, int typeId, Vector3 position, Quaternion rotation, DefenseTowerStatsData stats)
: base(entityId, typeId)
{
Position = position;
Rotation = rotation;
_stats = stats ?? new DefenseTowerStatsData();
}
public DefenseTowerStatsData Stats
{
get => _stats;
set => _stats = value ?? new DefenseTowerStatsData();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d46035f451d4e01a5a21ed5984ce42b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -41,16 +41,21 @@ namespace GeometryTD
entityComponent.AttachEntity(entityBase.Entity, ownerId, parentTransformPath, userData); entityComponent.AttachEntity(entityBase.Entity, ownerId, parentTransformPath, userData);
} }
public static void ShowPlayer(this EntityComponent entityComponent, PlayerData data)
{
entityComponent.ShowEntity(typeof(Player), "Player", Constant.AssetPriority.PlayerAsset, data);
}
public static void ShowEnemy(this EntityComponent entityComponent, EnemyData data) public static void ShowEnemy(this EntityComponent entityComponent, EnemyData data)
{ {
entityComponent.ShowEntity(typeof(EnemyEntity), "Enemy", Constant.AssetPriority.EnemyAsset, data); entityComponent.ShowEntity(typeof(EnemyEntity), "Enemy", Constant.AssetPriority.EnemyAsset, data);
} }
public static void ShowDefenseTower(this EntityComponent entityComponent, DefenseTowerData data)
{
entityComponent.ShowEntity(typeof(DefenseTowerEntity), "Tower", Constant.AssetPriority.EnemyAsset, data);
}
public static void ShowBullet(this EntityComponent entityComponent, BulletData data)
{
entityComponent.ShowEntity(typeof(BulletEntity), "Bullet", Constant.AssetPriority.BulletAsset, data);
}
public static void ShowMap(this EntityComponent entityComponent, MapData data) public static void ShowMap(this EntityComponent entityComponent, MapData data)
{ {
ShowMap(entityComponent, data, null); ShowMap(entityComponent, data, null);

View File

@ -0,0 +1,68 @@
using Components;
using GeometryTD.Entity.EntityData;
using UnityGameFramework.Runtime;
namespace GeometryTD.Entity
{
public class BulletEntity : EntityBase
{
private ShooterBullet _shooterBullet;
protected override void OnInit(object userData)
{
base.OnInit(userData);
_shooterBullet = GetComponent<ShooterBullet>();
if (_shooterBullet == null)
{
Log.Error("ShooterBullet component is missing on bullet entity '{0}'.", name);
}
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (_shooterBullet == null)
{
GameEntry.Entity.HideEntity(Entity);
return;
}
if (userData is not BulletData bulletData)
{
Log.Warning("BulletData is invalid for bullet entity '{0}'.", Id);
GameEntry.Entity.HideEntity(Entity);
return;
}
_shooterBullet.OnShow(bulletData);
if (_shooterBullet.TryConsumeDespawnRequest())
{
GameEntry.Entity.HideEntity(Entity);
}
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
if (_shooterBullet == null)
{
return;
}
_shooterBullet.Tick(elapseSeconds);
if (_shooterBullet.TryConsumeDespawnRequest())
{
GameEntry.Entity.HideEntity(Entity);
}
}
protected override void OnHide(bool isShutdown, object userData)
{
_shooterBullet?.OnReset();
base.OnHide(isShutdown, userData);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4912a44cc6c4f3d85f6d54868780f2f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,55 @@
using Components;
using GeometryTD.Entity.EntityData;
using UnityGameFramework.Runtime;
namespace GeometryTD.Entity
{
public class DefenseTowerEntity : EntityBase
{
private DefenseTowerController _towerController;
protected override void OnInit(object userData)
{
base.OnInit(userData);
_towerController = GetComponent<DefenseTowerController>();
if (_towerController == null)
{
Log.Error("DefenseTowerController is missing on tower entity '{0}'.", name);
}
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (_towerController == null)
{
return;
}
if (userData is not DefenseTowerData towerData)
{
Log.Warning("DefenseTowerData is invalid for tower entity '{0}'.", Id);
_towerController.OnReset();
GameEntry.Entity.HideEntity(Entity);
return;
}
_towerController.SetAutoUpdate(false);
_towerController.OnInit(towerData.Stats);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
_towerController?.OnUpdate(elapseSeconds);
}
protected override void OnHide(bool isShutdown, object userData)
{
_towerController?.OnReset();
base.OnHide(isShutdown, userData);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d87f56efd9024709a3baf8ef8a6f4fd3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,21 +1,33 @@
using Components; using Components;
using System.Collections.Generic; using System.Collections.Generic;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData; using GeometryTD.Entity.EntityData;
using UnityEngine; using UnityEngine;
namespace GeometryTD.Entity namespace GeometryTD.Entity
{ {
public class EnemyEntity : EntityBase public class EnemyEntity : EntityBase, IDamageReceiver
{ {
private const float WaypointReachDistance = 0.05f; private const float WaypointReachDistance = 0.05f;
private static readonly List<EnemyEntity> _activeEnemies = new();
private static readonly HashSet<int> _killedEnemyEntityIds = new();
private Transform _target; private Transform _target;
private float _speed; private float _speed;
private int _maxHealth;
private int _currentHealth;
private MovementComponent _movementComponent; private MovementComponent _movementComponent;
private readonly List<Vector3> _pathPoints = new List<Vector3>(); private readonly List<Vector3> _pathPoints = new();
private int _pathPointIndex; private int _pathPointIndex;
private bool _isDespawnRequested; private bool _isDespawnRequested;
public static IReadOnlyList<EnemyEntity> ActiveEnemies => _activeEnemies;
public static bool TryConsumeKilledFlag(int entityId)
{
return _killedEnemyEntityIds.Remove(entityId);
}
protected override void OnInit(object userData) protected override void OnInit(object userData)
{ {
base.OnInit(userData); base.OnInit(userData);
@ -31,10 +43,14 @@ namespace GeometryTD.Entity
_pathPoints.Clear(); _pathPoints.Clear();
_pathPointIndex = 0; _pathPointIndex = 0;
_isDespawnRequested = false; _isDespawnRequested = false;
_maxHealth = 1;
_currentHealth = 1;
if (userData is EnemyData enemyData) if (userData is EnemyData enemyData)
{ {
_speed = enemyData.Speed; _speed = enemyData.Speed;
_target = enemyData.Player; _target = enemyData.Player;
_maxHealth = Mathf.Max(1, enemyData.MaxHealth);
_currentHealth = _maxHealth;
if (enemyData.HasPath) if (enemyData.HasPath)
{ {
IReadOnlyList<Vector3> pathPoints = enemyData.PathPoints; IReadOnlyList<Vector3> pathPoints = enemyData.PathPoints;
@ -47,6 +63,11 @@ namespace GeometryTD.Entity
_movementComponent.OnInit(_speed, CachedTransform); _movementComponent.OnInit(_speed, CachedTransform);
_movementComponent.SetMove(true); _movementComponent.SetMove(true);
_killedEnemyEntityIds.Remove(Id);
if (!_activeEnemies.Contains(this))
{
_activeEnemies.Add(this);
}
} }
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@ -66,10 +87,45 @@ namespace GeometryTD.Entity
_pathPoints.Clear(); _pathPoints.Clear();
_pathPointIndex = 0; _pathPointIndex = 0;
_isDespawnRequested = false; _isDespawnRequested = false;
_maxHealth = 0;
_currentHealth = 0;
_activeEnemies.Remove(this);
_killedEnemyEntityIds.Remove(Id);
base.OnHide(isShutdown, userData); base.OnHide(isShutdown, userData);
} }
private void OnDestroy()
{
_activeEnemies.Remove(this);
if (Entity != null)
{
_killedEnemyEntityIds.Remove(Entity.Id);
}
}
public void TakeDamage(int damage, AttackPropertyType attackPropertyType)
{
_ = attackPropertyType;
if (_isDespawnRequested || damage <= 0 || _currentHealth <= 0)
{
return;
}
int previousHealth = _currentHealth;
_currentHealth = Mathf.Max(0, _currentHealth - damage);
if (_maxHealth > 0)
{
GameEntry.HPBar?.ShowHPBar(this, (float)previousHealth / _maxHealth, (float)_currentHealth / _maxHealth);
}
if (_currentHealth <= 0)
{
_killedEnemyEntityIds.Add(Id);
RequestDespawn();
}
}
private void UpdatePathMovement(float elapseSeconds, float realElapseSeconds) private void UpdatePathMovement(float elapseSeconds, float realElapseSeconds)
{ {
if (_isDespawnRequested) if (_isDespawnRequested)
@ -120,6 +176,11 @@ namespace GeometryTD.Entity
} }
private void DespawnOnReachHouse() private void DespawnOnReachHouse()
{
RequestDespawn();
}
private void RequestDespawn()
{ {
if (_isDespawnRequested) if (_isDespawnRequested)
{ {

View File

@ -1,8 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GeometryTD;
using GeometryTD.Definition;
using GeometryTD.Entity.EntityData; using GeometryTD.Entity.EntityData;
using GeometryTD.Pathfinding; using GeometryTD.Pathfinding;
using GeometryTD.UI;
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Tilemaps; using UnityEngine.Tilemaps;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@ -12,6 +16,7 @@ namespace GeometryTD.Entity
{ {
private const string PathTileName = "Path"; private const string PathTileName = "Path";
private const string FoundationTileName = "Foundation"; private const string FoundationTileName = "Foundation";
private const int DefaultTowerTypeId = 401;
private static readonly Spawner[] EmptySpawners = Array.Empty<Spawner>(); private static readonly Spawner[] EmptySpawners = Array.Empty<Spawner>();
private readonly List<Vector3Int> _pathCells = new(); private readonly List<Vector3Int> _pathCells = new();
@ -22,11 +27,25 @@ namespace GeometryTD.Entity
private readonly List<Vector3Int> _pathCellBuffer = new(); private readonly List<Vector3Int> _pathCellBuffer = new();
private readonly Dictionary<Spawner, Vector3Int> _spawnerPathStartByRef = new(); private readonly Dictionary<Spawner, Vector3Int> _spawnerPathStartByRef = new();
private readonly Dictionary<Spawner, List<Vector3Int>> _defaultPathCellsBySpawner = new(); private readonly Dictionary<Spawner, List<Vector3Int>> _defaultPathCellsBySpawner = new();
private readonly Dictionary<Vector3Int, int> _towerEntityIdByFoundationCell = new();
private readonly Dictionary<int, Vector3Int> _foundationCellByTowerEntityId = new();
private readonly Dictionary<int, DefenseTowerStatsData> _towerStatsByEntityId = new();
private readonly List<int> _towerEntityIdBuffer = new();
[SerializeField] private bool _enableCombatSelectInput = true;
[SerializeField] private int _towerTypeId = DefaultTowerTypeId;
[SerializeField] private int[] _buildTowerCosts = { 80, 120, 160, 220 };
[SerializeField] private int _upgradeCost = 80;
[SerializeField] private int _destroyGain = 40;
private MapDataRefs _mapDataRefs; private MapDataRefs _mapDataRefs;
private MapData _mapData; private MapData _mapData;
private bool _hasHousePathCell; private bool _hasHousePathCell;
private Vector3Int _housePathCell; private Vector3Int _housePathCell;
private CombatSelectFormUseCase _combatSelectFormUseCase;
private bool _hasSelectedFoundationCell;
private Vector3Int _selectedFoundationCell;
private int _selectedTowerEntityId;
public IReadOnlyList<Vector3Int> PathCells => _pathCells; public IReadOnlyList<Vector3Int> PathCells => _pathCells;
public IReadOnlyList<Vector3Int> FoundationCells => _foundationCells; public IReadOnlyList<Vector3Int> FoundationCells => _foundationCells;
@ -156,6 +175,8 @@ namespace GeometryTD.Entity
{ {
Log.Error("MapDataRefs is missing on map entity '{0}'.", name); Log.Error("MapDataRefs is missing on map entity '{0}'.", name);
} }
InitializeCombatSelectUseCase();
} }
protected override void OnShow(object userData) protected override void OnShow(object userData)
@ -169,14 +190,24 @@ namespace GeometryTD.Entity
} }
RefreshTiles(); RefreshTiles();
ConfigureCombatSelectUseCase();
HideCombatSelectForm();
} }
protected override void OnHide(bool isShutdown, object userData) protected override void OnHide(bool isShutdown, object userData)
{ {
HideCombatSelectForm();
ClearPlacedTowers();
ClearRuntimeData(); ClearRuntimeData();
base.OnHide(isShutdown, userData); base.OnHide(isShutdown, userData);
} }
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
HandleCombatSelectInput();
}
private void RefreshTiles() private void RefreshTiles()
{ {
ClearRuntimeData(); ClearRuntimeData();
@ -295,6 +326,479 @@ namespace GeometryTD.Entity
_housePathCell = default; _housePathCell = default;
_spawnerPathStartByRef.Clear(); _spawnerPathStartByRef.Clear();
_defaultPathCellsBySpawner.Clear(); _defaultPathCellsBySpawner.Clear();
_towerEntityIdByFoundationCell.Clear();
_foundationCellByTowerEntityId.Clear();
_towerStatsByEntityId.Clear();
_towerEntityIdBuffer.Clear();
ClearSelectedObject();
}
private void InitializeCombatSelectUseCase()
{
if (_combatSelectFormUseCase == null)
{
_combatSelectFormUseCase = new CombatSelectFormUseCase();
}
GameEntry.UIRouter.BindUIUseCase(UIFormType.CombatSelectForm, _combatSelectFormUseCase);
}
private void ConfigureCombatSelectUseCase()
{
if (_combatSelectFormUseCase == null)
{
return;
}
_combatSelectFormUseCase.SetCoinProvider(GetCurrentCoin);
for (int i = 0; i < 4; i++)
{
int buildIndex = i;
_combatSelectFormUseCase.SetBuildAction(
buildIndex,
() => TryBuildTower(buildIndex),
GetBuildTowerCost(buildIndex));
}
_combatSelectFormUseCase.SetUpgradeAction(
TryUpgradeTower,
Mathf.Max(0, _upgradeCost));
_combatSelectFormUseCase.SetDestroyAction(
TryDestroyTower,
Mathf.Max(0, _destroyGain));
}
private void HandleCombatSelectInput()
{
if (!_enableCombatSelectInput || !Input.GetMouseButtonDown(0))
{
return;
}
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
{
return;
}
if (!TryBuildCombatSelectUserData(out CombatSelectFormUserData userData))
{
userData = new CombatSelectFormUserData
{
ClickObjectType = CombatSelectClickObjectType.None
};
}
ApplySelectedObject(userData);
GameEntry.UIRouter.OpenUI(UIFormType.CombatSelectForm, userData);
}
private bool TryBuildCombatSelectUserData(out CombatSelectFormUserData userData)
{
userData = null;
if (Tilemap == null || !TryGetPointerWorldPosition(out Vector3 worldPosition, out Vector2 contentPosition))
{
return false;
}
Vector3Int clickedCell = Tilemap.WorldToCell(worldPosition);
CombatSelectClickObjectType clickObjectType = CombatSelectClickObjectType.None;
int towerEntityId = 0;
if (_towerEntityIdByFoundationCell.TryGetValue(clickedCell, out int occupiedTowerEntityId))
{
clickObjectType = CombatSelectClickObjectType.Tower;
towerEntityId = occupiedTowerEntityId;
}
else if (IsFoundationCell(clickedCell))
{
clickObjectType = CombatSelectClickObjectType.Foundation;
}
userData = new CombatSelectFormUserData
{
ClickObjectType = clickObjectType,
ContentPosition = contentPosition,
WorldPosition = worldPosition,
CellPosition = clickedCell,
TowerEntityId = towerEntityId,
UpgradeCost = Mathf.Max(0, _upgradeCost),
DestroyGain = Mathf.Max(0, _destroyGain)
};
return true;
}
private bool TryGetPointerWorldPosition(out Vector3 worldPosition, out Vector2 contentPosition)
{
worldPosition = Vector3.zero;
contentPosition = Vector2.zero;
Camera mainCamera = GameEntry.Scene != null ? GameEntry.Scene.MainCamera : Camera.main;
if (mainCamera == null)
{
return false;
}
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
float mapPlaneZ = Tilemap != null ? Tilemap.transform.position.z : CachedTransform.position.z;
Vector3 planeNormal = mainCamera.transform.forward.sqrMagnitude > Mathf.Epsilon
? -mainCamera.transform.forward
: Vector3.forward;
Plane mapPlane = new Plane(planeNormal, new Vector3(0f, 0f, mapPlaneZ));
if (!mapPlane.Raycast(ray, out float enterDistance))
{
return false;
}
worldPosition = ray.GetPoint(enterDistance);
contentPosition = BuildContentPosition(Input.mousePosition);
return true;
}
private static Vector2 BuildContentPosition(Vector3 pointerScreenPosition)
{
return new Vector2(
pointerScreenPosition.x - Screen.width * 0.5f,
pointerScreenPosition.y - Screen.height * 0.5f);
}
private void ApplySelectedObject(CombatSelectFormUserData userData)
{
if (userData == null)
{
ClearSelectedObject();
return;
}
switch (userData.ClickObjectType)
{
case CombatSelectClickObjectType.Foundation:
_hasSelectedFoundationCell = true;
_selectedFoundationCell = userData.CellPosition;
_selectedTowerEntityId = 0;
break;
case CombatSelectClickObjectType.Tower:
_hasSelectedFoundationCell = true;
_selectedFoundationCell = userData.CellPosition;
_selectedTowerEntityId = userData.TowerEntityId;
break;
default:
ClearSelectedObject();
break;
}
}
private bool TryBuildTower(int buildIndex)
{
if (!_hasSelectedFoundationCell || !IsFoundationCell(_selectedFoundationCell))
{
return false;
}
if (_towerEntityIdByFoundationCell.ContainsKey(_selectedFoundationCell))
{
return false;
}
int buildCost = GetBuildTowerCost(buildIndex);
if (!TryConsumeCoin(buildCost))
{
return false;
}
DefenseTowerStatsData towerStats = BuildTowerStats(buildIndex);
if (!TryShowTowerEntity(_selectedFoundationCell, towerStats, out int towerEntityId))
{
GameEntry.CombatNode?.AddCoin(buildCost);
return false;
}
_towerEntityIdByFoundationCell[_selectedFoundationCell] = towerEntityId;
_foundationCellByTowerEntityId[towerEntityId] = _selectedFoundationCell;
_towerStatsByEntityId[towerEntityId] = CloneTowerStats(towerStats);
_selectedTowerEntityId = towerEntityId;
return true;
}
private bool TryUpgradeTower()
{
if (!TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell))
{
return false;
}
int upgradeCost = Mathf.Max(0, _upgradeCost);
if (!TryConsumeCoin(upgradeCost))
{
return false;
}
DefenseTowerStatsData oldStats = _towerStatsByEntityId.TryGetValue(towerEntityId, out DefenseTowerStatsData cachedStats)
? CloneTowerStats(cachedStats)
: BuildTowerStats(0);
DefenseTowerStatsData upgradedStats = CloneTowerStats(oldStats);
ApplyUpgradeToStats(upgradedStats);
HideTowerEntity(towerEntityId);
_towerEntityIdByFoundationCell.Remove(foundationCell);
_foundationCellByTowerEntityId.Remove(towerEntityId);
_towerStatsByEntityId.Remove(towerEntityId);
if (!TryShowTowerEntity(foundationCell, upgradedStats, out int newTowerEntityId))
{
if (TryShowTowerEntity(foundationCell, oldStats, out int fallbackTowerEntityId))
{
_towerEntityIdByFoundationCell[foundationCell] = fallbackTowerEntityId;
_foundationCellByTowerEntityId[fallbackTowerEntityId] = foundationCell;
_towerStatsByEntityId[fallbackTowerEntityId] = CloneTowerStats(oldStats);
_selectedTowerEntityId = fallbackTowerEntityId;
}
GameEntry.CombatNode?.AddCoin(upgradeCost);
return false;
}
_towerEntityIdByFoundationCell[foundationCell] = newTowerEntityId;
_foundationCellByTowerEntityId[newTowerEntityId] = foundationCell;
_towerStatsByEntityId[newTowerEntityId] = CloneTowerStats(upgradedStats);
_hasSelectedFoundationCell = true;
_selectedFoundationCell = foundationCell;
_selectedTowerEntityId = newTowerEntityId;
return true;
}
private bool TryDestroyTower()
{
if (!TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell))
{
return false;
}
HideTowerEntity(towerEntityId);
_towerEntityIdByFoundationCell.Remove(foundationCell);
_foundationCellByTowerEntityId.Remove(towerEntityId);
_towerStatsByEntityId.Remove(towerEntityId);
GameEntry.CombatNode?.AddCoin(Mathf.Max(0, _destroyGain));
ClearSelectedObject();
return true;
}
private bool TryGetSelectedTower(out int towerEntityId, out Vector3Int foundationCell)
{
towerEntityId = 0;
foundationCell = default;
if (_selectedTowerEntityId == 0)
{
return false;
}
if (!_foundationCellByTowerEntityId.TryGetValue(_selectedTowerEntityId, out foundationCell))
{
return false;
}
towerEntityId = _selectedTowerEntityId;
return true;
}
private bool TryShowTowerEntity(Vector3Int foundationCell, DefenseTowerStatsData towerStats, out int towerEntityId)
{
towerEntityId = 0;
if (GameEntry.Entity == null)
{
return false;
}
int entityId = GameEntry.Entity.GenerateSerialId();
int typeId = _towerTypeId > 0 ? _towerTypeId : DefaultTowerTypeId;
Vector3 towerPosition = Tilemap != null ? Tilemap.GetCellCenterWorld(foundationCell) : foundationCell;
towerPosition.z = 0f;
var towerData = new DefenseTowerData(entityId, typeId, towerPosition, Quaternion.identity, towerStats);
GameEntry.Entity.ShowDefenseTower(towerData);
towerEntityId = entityId;
return true;
}
private static void HideTowerEntity(int towerEntityId)
{
if (towerEntityId == 0 || GameEntry.Entity == null)
{
return;
}
UnityGameFramework.Runtime.Entity towerEntity = GameEntry.Entity.GetEntity(towerEntityId);
if (towerEntity != null)
{
GameEntry.Entity.HideEntity(towerEntity);
}
}
private void ClearPlacedTowers()
{
_towerEntityIdBuffer.Clear();
foreach (KeyValuePair<int, Vector3Int> pair in _foundationCellByTowerEntityId)
{
_towerEntityIdBuffer.Add(pair.Key);
}
for (int i = 0; i < _towerEntityIdBuffer.Count; i++)
{
HideTowerEntity(_towerEntityIdBuffer[i]);
}
_towerEntityIdByFoundationCell.Clear();
_foundationCellByTowerEntityId.Clear();
_towerStatsByEntityId.Clear();
_towerEntityIdBuffer.Clear();
ClearSelectedObject();
}
private void HideCombatSelectForm()
{
_combatSelectFormUseCase?.Hide();
GameEntry.UIRouter.CloseUI(UIFormType.CombatSelectForm);
}
private void ClearSelectedObject()
{
_hasSelectedFoundationCell = false;
_selectedFoundationCell = default;
_selectedTowerEntityId = 0;
}
private int GetBuildTowerCost(int buildIndex)
{
if (_buildTowerCosts == null || buildIndex < 0 || buildIndex >= _buildTowerCosts.Length)
{
return 0;
}
return Mathf.Max(0, _buildTowerCosts[buildIndex]);
}
private static bool TryConsumeCoin(int cost)
{
int requiredCoin = Mathf.Max(0, cost);
if (requiredCoin <= 0)
{
return true;
}
if (GameEntry.CombatNode == null)
{
return false;
}
return GameEntry.CombatNode.TryConsumeCoin(requiredCoin);
}
private static int GetCurrentCoin()
{
return GameEntry.CombatNode != null ? Mathf.Max(0, GameEntry.CombatNode.CurrentCoin) : 0;
}
private static DefenseTowerStatsData BuildTowerStats(int buildIndex)
{
switch (buildIndex)
{
case 0:
return new DefenseTowerStatsData
{
AttackDamage = 10,
DamageRandomRate = 0f,
RotateSpeed = 200f,
AttackRange = 4.5f,
AttackSpeed = 1.5f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = Array.Empty<TagType>()
};
case 1:
return new DefenseTowerStatsData
{
AttackDamage = 13,
DamageRandomRate = 0f,
RotateSpeed = 160f,
AttackRange = 5f,
AttackSpeed = 1.2f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Fire,
Tags = Array.Empty<TagType>()
};
case 2:
return new DefenseTowerStatsData
{
AttackDamage = 17,
DamageRandomRate = 0f,
RotateSpeed = 140f,
AttackRange = 5.5f,
AttackSpeed = 0.95f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Ice,
Tags = Array.Empty<TagType>()
};
case 3:
return new DefenseTowerStatsData
{
AttackDamage = 22,
DamageRandomRate = 0f,
RotateSpeed = 120f,
AttackRange = 6f,
AttackSpeed = 0.75f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Poison,
Tags = Array.Empty<TagType>()
};
default:
return new DefenseTowerStatsData
{
AttackDamage = 10,
DamageRandomRate = 0f,
RotateSpeed = 180f,
AttackRange = 5f,
AttackSpeed = 1f,
AttackMethodType = AttackMethodType.NormalBullet,
AttackPropertyType = AttackPropertyType.Physics,
Tags = Array.Empty<TagType>()
};
}
}
private static DefenseTowerStatsData CloneTowerStats(DefenseTowerStatsData source)
{
if (source == null)
{
return BuildTowerStats(0);
}
TagType[] copiedTags = source.Tags != null ? (TagType[])source.Tags.Clone() : Array.Empty<TagType>();
return new DefenseTowerStatsData
{
AttackDamage = source.AttackDamage,
DamageRandomRate = source.DamageRandomRate,
RotateSpeed = source.RotateSpeed,
AttackRange = source.AttackRange,
AttackSpeed = source.AttackSpeed,
AttackMethodType = source.AttackMethodType,
AttackPropertyType = source.AttackPropertyType,
Tags = copiedTags
};
}
private static void ApplyUpgradeToStats(DefenseTowerStatsData stats)
{
if (stats == null)
{
return;
}
stats.AttackDamage = Mathf.Max(1, stats.AttackDamage + 3);
stats.AttackSpeed = Mathf.Max(0.01f, stats.AttackSpeed + 0.2f);
stats.AttackRange = Mathf.Max(0.1f, stats.AttackRange + 0.4f);
stats.RotateSpeed = Mathf.Max(1f, stats.RotateSpeed + 10f);
} }
} }
} }

View File

@ -0,0 +1,21 @@
using GameFramework;
using GameFramework.Event;
namespace GeometryTD.CustomEvent
{
public class CombatFinishReturnEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatFinishReturnEventArgs).GetHashCode();
public override int Id => EventId;
public static CombatFinishReturnEventArgs Create()
{
return ReferencePool.Acquire<CombatFinishReturnEventArgs>();
}
public override void Clear()
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ff2a3f68ab24bf294b24907548c88de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using GameFramework;
using GameFramework.Event;
using GeometryTD.UI;
namespace GeometryTD.CustomEvent
{
public class CombatSelectItemClickEventArgs : GameEventArgs
{
public static int EventId => typeof(CombatSelectItemClickEventArgs).GetHashCode();
public override int Id => EventId;
public CombatSelectActionType ActionType { get; private set; } = CombatSelectActionType.None;
public int ActionIndex { get; private set; } = -1;
public static CombatSelectItemClickEventArgs Create(CombatSelectActionType actionType, int actionIndex)
{
CombatSelectItemClickEventArgs args = ReferencePool.Acquire<CombatSelectItemClickEventArgs>();
args.ActionType = actionType;
args.ActionIndex = actionIndex;
return args;
}
public override void Clear()
{
ActionType = CombatSelectActionType.None;
ActionIndex = -1;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8a9619fe7cdb054ea7eeac820f95ae4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -63,6 +63,7 @@ namespace GeometryTD.Procedure
GameEntry.CombatNode.OnShutdown(); GameEntry.CombatNode.OnShutdown();
GameEntry.Event.Unsubscribe(NodeEnterEventArgs.EventId, OnNodeEnter); GameEntry.Event.Unsubscribe(NodeEnterEventArgs.EventId, OnNodeEnter);
GameEntry.Event.Unsubscribe(NodeCompleteEventArgs.EventId, OnNodeComplete); GameEntry.Event.Unsubscribe(NodeCompleteEventArgs.EventId, OnNodeComplete);
_repoFormUseCase = null;
base.OnLeave(procedureOwner, isShutdown); base.OnLeave(procedureOwner, isShutdown);
} }

View File

@ -0,0 +1,10 @@
namespace GeometryTD.UI
{
public class CombatFinishFormContext : UIContext
{
public string EnemyKilledText;
public string GoldGainedText;
public RepoItemContext[] RewardItems;
public bool CanReturn;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8fd0de74a04422488e2e78615713bca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,10 @@
namespace GeometryTD.UI
{
public enum CombatSelectActionType : byte
{
None = 0,
BuildTower = 1,
UpgradeTower = 2,
DestroyTower = 3
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2813faca2af4a574ea60171b6221c69c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatSelectFormContext : UIContext
{
public bool IsVisible;
public Vector2 ContentPosition;
public bool ShowBuildArea;
public bool ShowFuncArea;
public TowerSelectItemContext[] BuildItems;
public TowerSelectItemContext UpgradeItem;
public TowerSelectItemContext DestroyItem;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10d666a1cb0d16a44a6fa5e92d6e8f3f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class TowerSelectItemContext : UIContext
{
public Sprite Icon;
public string PriceText;
public bool IsVisible;
public bool IsInteractable;
public CombatSelectActionType ActionType;
public int ActionIndex;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1a54121a8acc89c43b45617601960c33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,189 @@
using System.Collections.Generic;
using GameFramework.Event;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CombatFinishFormController : UIFormControllerCommonBase<CombatFinishFormContext, CombatFinishForm>
{
private CombatFinishFormUseCase _useCase;
protected override UIFormType UIFormTypeId => UIFormType.CombatFinishForm;
protected override void RefreshUI(CombatFinishForm form, CombatFinishFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(CombatFinishReturnEventArgs.EventId, OnCombatFinishReturnButtonClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(CombatFinishReturnEventArgs.EventId, OnCombatFinishReturnButtonClicked);
}
public override int? OpenUI(object userData = null)
{
if (userData is CombatFinishFormContext context)
{
return OpenUIInternal(context);
}
if (userData is CombatFinishFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("CombatFinishFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("CombatFinishFormController.OpenUI() useCase is null.");
return null;
}
CombatFinishFormRawData rawData = _useCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(CombatFinishFormRawData rawData)
{
CombatFinishFormContext context = BuildContext(rawData);
return OpenUIInternal(context);
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is CombatFinishFormUseCase combatFinishFormUseCase))
{
Log.Error("CombatFinishFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = combatFinishFormUseCase;
}
private static CombatFinishFormContext BuildContext(CombatFinishFormRawData rawData)
{
if (rawData == null)
{
return null;
}
return new CombatFinishFormContext
{
EnemyKilledText = rawData.DefeatedEnemyCount.ToString(),
GoldGainedText = rawData.GainedGold.ToString(),
RewardItems = BuildRewardItems(rawData.RewardInventory),
CanReturn = rawData.CanReturn
};
}
private static RepoItemContext[] BuildRewardItems(BackpackInventoryData inventory)
{
if (inventory == null)
{
return System.Array.Empty<RepoItemContext>();
}
List<RepoItemContext> itemContexts = new List<RepoItemContext>();
if (inventory.Towers != null)
{
for (int i = 0; i < inventory.Towers.Count; i++)
{
var tower = inventory.Towers[i];
if (tower == null)
{
continue;
}
itemContexts.Add(new RepoItemContext
{
Title = $"[Tower] {tower.Name} ({tower.Rarity})",
InstanceId = tower.InstanceId,
ComponentSlotType = TowerCompSlotType.None
});
}
}
if (inventory.MuzzleComponents != null)
{
for (int i = 0; i < inventory.MuzzleComponents.Count; i++)
{
var item = inventory.MuzzleComponents[i];
if (item == null)
{
continue;
}
itemContexts.Add(new RepoItemContext
{
Title = $"[Muzzle] {item.Name} ({item.Rarity})",
InstanceId = item.InstanceId,
ComponentSlotType = TowerCompSlotType.Muzzle
});
}
}
if (inventory.BearingComponents != null)
{
for (int i = 0; i < inventory.BearingComponents.Count; i++)
{
var item = inventory.BearingComponents[i];
if (item == null)
{
continue;
}
itemContexts.Add(new RepoItemContext
{
Title = $"[Bearing] {item.Name} ({item.Rarity})",
InstanceId = item.InstanceId,
ComponentSlotType = TowerCompSlotType.Bearing
});
}
}
if (inventory.BaseComponents != null)
{
for (int i = 0; i < inventory.BaseComponents.Count; i++)
{
var item = inventory.BaseComponents[i];
if (item == null)
{
continue;
}
itemContexts.Add(new RepoItemContext
{
Title = $"[Base] {item.Name} ({item.Rarity})",
InstanceId = item.InstanceId,
ComponentSlotType = TowerCompSlotType.Base
});
}
}
return itemContexts.ToArray();
}
private void OnCombatFinishReturnButtonClicked(object sender, GameEventArgs e)
{
if (!(sender is CombatFinishForm) || !(e is CombatFinishReturnEventArgs))
{
return;
}
_useCase?.TryReturnToMenu();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f9701c56f0541208d99c47b7e64e58c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,266 @@
using GameFramework.Event;
using GeometryTD.CustomEvent;
using GeometryTD.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CombatSelectFormController : UIFormControllerCommonBase<CombatSelectFormContext, CombatSelectForm>
{
private CombatSelectFormUseCase _useCase;
protected override UIFormType UIFormTypeId => UIFormType.CombatSelectForm;
protected override void RefreshUI(CombatSelectForm form, CombatSelectFormContext context)
{
form.RefreshUI(context);
}
protected override void SubscribeCustomEvents()
{
GameEntry.Event.Subscribe(CombatSelectItemClickEventArgs.EventId, OnSelectItemClicked);
}
protected override void UnsubscribeCustomEvents()
{
GameEntry.Event.Unsubscribe(CombatSelectItemClickEventArgs.EventId, OnSelectItemClicked);
}
public int? OpenUI(CombatSelectFormRawData rawData)
{
CombatSelectFormContext context = BuildContext(rawData);
if (context == null || !context.IsVisible)
{
CloseUI();
return null;
}
return OpenUIInternal(context);
}
public override int? OpenUI(object userData = null)
{
if (userData is CombatSelectFormUserData clickUserData)
{
return OpenUI(clickUserData);
}
if (userData is CombatSelectFormContext context)
{
if (context == null || !context.IsVisible)
{
CloseUI();
return null;
}
return OpenUIInternal(context);
}
if (userData is CombatSelectFormRawData rawDataFromUserData)
{
return OpenUI(rawDataFromUserData);
}
if (userData != null)
{
Log.Warning("CombatSelectFormController.OpenUI() userData type is invalid.");
return null;
}
if (_useCase == null)
{
Log.Error("CombatSelectFormController.OpenUI() useCase is null.");
return null;
}
CombatSelectFormRawData rawData = _useCase.CreateInitialModel();
return OpenUI(rawData);
}
public int? OpenUI(CombatSelectFormUserData userData)
{
if (_useCase == null)
{
Log.Error("CombatSelectFormController.OpenUI() useCase is null.");
return null;
}
if (userData == null)
{
_useCase.Hide();
CloseUI();
return null;
}
switch (userData.ClickObjectType)
{
case CombatSelectClickObjectType.Foundation:
_useCase.ShowForFoundation(userData.ContentPosition);
break;
case CombatSelectClickObjectType.Tower:
_useCase.SetUpgradePrice(userData.UpgradeCost);
_useCase.SetDestroyGain(userData.DestroyGain);
_useCase.ShowForTower(userData.ContentPosition);
break;
default:
_useCase.Hide();
CloseUI();
return null;
}
return OpenUI(_useCase.TryRefresh());
}
public override void BindUseCase(IUIUseCase useCase)
{
if (!(useCase is CombatSelectFormUseCase combatSelectFormUseCase))
{
Log.Error("CombatSelectFormController.BindUseCase() useCase is invalid.");
return;
}
_useCase = combatSelectFormUseCase;
}
public int? ShowForFoundation(Vector2 contentPosition)
{
if (_useCase == null)
{
Log.Error("CombatSelectFormController.ShowForFoundation() useCase is null.");
return null;
}
_useCase.ShowForFoundation(contentPosition);
return OpenUI(_useCase.TryRefresh());
}
public int? ShowForTower(Vector2 contentPosition, int upgradeCost, int destroyGain)
{
if (_useCase == null)
{
Log.Error("CombatSelectFormController.ShowForTower() useCase is null.");
return null;
}
_useCase.SetUpgradePrice(upgradeCost);
_useCase.SetDestroyGain(destroyGain);
_useCase.ShowForTower(contentPosition);
return OpenUI(_useCase.TryRefresh());
}
public void HideSelection()
{
_useCase?.Hide();
CloseUI();
}
public void RefreshFromUseCase()
{
if (_useCase == null)
{
return;
}
CombatSelectFormRawData rawData = _useCase.TryRefresh();
CombatSelectFormContext context = BuildContext(rawData);
if (context == null || !context.IsVisible)
{
CloseUI();
return;
}
SetContext(context);
RefreshCurrentUI();
}
private static CombatSelectFormContext BuildContext(CombatSelectFormRawData rawData)
{
if (rawData == null)
{
return null;
}
return new CombatSelectFormContext
{
IsVisible = rawData.IsVisible,
ContentPosition = rawData.ContentPosition,
ShowBuildArea = rawData.IsVisible && rawData.DisplayMode == CombatSelectDisplayMode.Build,
ShowFuncArea = rawData.IsVisible && rawData.DisplayMode == CombatSelectDisplayMode.Func,
BuildItems = BuildItemContexts(rawData.BuildItems),
UpgradeItem = BuildItemContext(rawData.UpgradeItem),
DestroyItem = BuildItemContext(rawData.DestroyItem)
};
}
private static TowerSelectItemContext[] BuildItemContexts(TowerSelectItemRawData[] itemRawData)
{
if (itemRawData == null || itemRawData.Length <= 0)
{
return System.Array.Empty<TowerSelectItemContext>();
}
TowerSelectItemContext[] contexts = new TowerSelectItemContext[itemRawData.Length];
for (int i = 0; i < itemRawData.Length; i++)
{
contexts[i] = BuildItemContext(itemRawData[i]);
}
return contexts;
}
private static TowerSelectItemContext BuildItemContext(TowerSelectItemRawData rawData)
{
if (rawData == null)
{
return null;
}
return new TowerSelectItemContext
{
Icon = rawData.Icon,
PriceText = BuildPriceText(rawData.Price, rawData.IsGain),
IsVisible = rawData.IsVisible,
IsInteractable = rawData.IsInteractable,
ActionType = rawData.ActionType,
ActionIndex = rawData.ActionIndex
};
}
private static string BuildPriceText(int price, bool isGain)
{
int positivePrice = Mathf.Max(0, price);
if (isGain)
{
return $"+{positivePrice}";
}
return positivePrice.ToString();
}
private void OnSelectItemClicked(object sender, GameEventArgs e)
{
if (!(e is CombatSelectItemClickEventArgs args))
{
return;
}
if (_useCase == null)
{
return;
}
if (Form != null && sender is Component component)
{
CombatSelectForm ownerForm = component.GetComponentInParent<CombatSelectForm>();
if (ownerForm != Form)
{
return;
}
}
_useCase.TryExecuteAction(args.ActionType, args.ActionIndex);
RefreshFromUseCase();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 03cc7fd540e32b34bad5b8786717725f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@
using GeometryTD.Definition;
namespace GeometryTD.UI
{
public class CombatFinishFormRawData
{
public int DefeatedEnemyCount;
public int GainedGold;
public BackpackInventoryData RewardInventory;
public bool CanReturn;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ee396100a8f4c7f99e3978c7154c00b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
namespace GeometryTD.UI
{
public enum CombatSelectClickObjectType : byte
{
None = 0,
Foundation = 1,
Tower = 2
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7dfced55a9048eea7df3b269e55e81e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
namespace GeometryTD.UI
{
public enum CombatSelectDisplayMode : byte
{
Hidden = 0,
Build = 1,
Func = 2
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cc38eaebc2c34d9428b1233aa57c653e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatSelectFormRawData
{
public bool IsVisible;
public Vector2 ContentPosition;
public CombatSelectDisplayMode DisplayMode;
public TowerSelectItemRawData[] BuildItems;
public TowerSelectItemRawData UpgradeItem;
public TowerSelectItemRawData DestroyItem;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0958de13e46a34742acfa4352c7b6d4e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatSelectFormUserData
{
public CombatSelectClickObjectType ClickObjectType;
public Vector2 ContentPosition;
public Vector3 WorldPosition;
public Vector3Int CellPosition;
public int TowerEntityId;
public int UpgradeCost;
public int DestroyGain;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53ae7da2d92244c893cea60fa4c05951
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class TowerSelectItemRawData
{
public Sprite Icon;
public int Price;
public bool IsGain;
public bool IsVisible;
public bool IsInteractable;
public CombatSelectActionType ActionType;
public int ActionIndex;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 583bd0c79840d6649a4f340af2a8bf5b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,73 @@
using GeometryTD.CustomComponent;
using GeometryTD.Definition;
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatFinishFormUseCase : IUIUseCase
{
private readonly RepoFormUseCase _repoFormUseCase = new RepoFormUseCase();
private CombatScheduler _combatScheduler;
private int _defeatedEnemyCount;
private int _gainedGold;
private BackpackInventoryData _rewardInventory;
private bool _isSummaryPrepared;
public CombatFinishFormRawData CreateInitialModel()
{
return BuildModel();
}
public CombatFinishFormRawData TryRefresh()
{
return BuildModel();
}
public void SetSummary(int defeatedEnemyCount, int gainedGold, BackpackInventoryData rewardInventory = null)
{
_defeatedEnemyCount = Mathf.Max(0, defeatedEnemyCount);
_gainedGold = Mathf.Max(0, gainedGold);
_rewardInventory = rewardInventory;
_isSummaryPrepared = true;
}
public CombatFinishFormUseCase(CombatScheduler combatScheduler)
{
this._combatScheduler = combatScheduler;
}
public bool TryReturnToMenu()
{
return _combatScheduler.OnCombatFinishReturnRequested();
}
private CombatFinishFormRawData BuildModel()
{
if (!_isSummaryPrepared)
{
_defeatedEnemyCount = GameEntry.CombatNode != null
? Mathf.Max(0, GameEntry.CombatNode.LastDefeatedEnemyCount)
: 0;
_gainedGold = GameEntry.CombatNode != null
? Mathf.Max(0, GameEntry.CombatNode.LastGainedGold)
: 0;
_rewardInventory = null;
}
BackpackInventoryData rewardInventory = _rewardInventory;
if (rewardInventory == null)
{
RepoFormRawData repoRawData = _repoFormUseCase.CreateInitialModel();
rewardInventory = repoRawData != null ? repoRawData.Inventory : null;
}
return new CombatFinishFormRawData
{
DefeatedEnemyCount = _defeatedEnemyCount,
GainedGold = _gainedGold,
RewardInventory = rewardInventory,
CanReturn = true
};
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e5ee9e49e70480c9f98e6c77eb86f5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,300 @@
using System;
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatSelectFormUseCase : IUIUseCase
{
private const int BuildOptionCount = 4;
private readonly Option[] _buildOptions = new Option[BuildOptionCount];
private readonly Option _upgradeOption = new Option(CombatSelectActionType.UpgradeTower, 0, true);
private readonly Option _destroyOption = new Option(CombatSelectActionType.DestroyTower, 0, false);
private Func<int> _coinProvider;
private bool _isVisible;
private CombatSelectDisplayMode _displayMode = CombatSelectDisplayMode.Hidden;
private Vector2 _contentPosition;
public CombatSelectFormUseCase()
{
_coinProvider = DefaultCoinProvider;
for (int i = 0; i < BuildOptionCount; i++)
{
_buildOptions[i] = new Option(CombatSelectActionType.BuildTower, i, true);
}
}
public CombatSelectFormRawData CreateInitialModel()
{
return BuildModel();
}
public CombatSelectFormRawData TryRefresh()
{
return BuildModel();
}
public void SetCoinProvider(Func<int> coinProvider)
{
_coinProvider = coinProvider ?? DefaultCoinProvider;
}
public void SetBuildAction(int buildIndex, Func<bool> buildAction, int cost, Sprite icon = null)
{
if (buildIndex < 0 || buildIndex >= _buildOptions.Length)
{
return;
}
Option option = _buildOptions[buildIndex];
option.Action = buildAction;
option.Price = Mathf.Max(0, cost);
option.Icon = icon;
option.IsGain = false;
option.IsVisible = true;
}
public void SetBuildAction(int buildIndex, Action buildAction, int cost, Sprite icon = null)
{
if (buildAction == null)
{
SetBuildAction(buildIndex, (Func<bool>)null, cost, icon);
return;
}
SetBuildAction(
buildIndex,
() =>
{
buildAction.Invoke();
return true;
},
cost,
icon);
}
public void SetUpgradeAction(Func<bool> upgradeAction, int cost, Sprite icon = null)
{
_upgradeOption.Action = upgradeAction;
_upgradeOption.Price = Mathf.Max(0, cost);
_upgradeOption.Icon = icon;
_upgradeOption.IsGain = false;
_upgradeOption.IsVisible = true;
}
public void SetUpgradeAction(Action upgradeAction, int cost, Sprite icon = null)
{
if (upgradeAction == null)
{
SetUpgradeAction((Func<bool>)null, cost, icon);
return;
}
SetUpgradeAction(
() =>
{
upgradeAction.Invoke();
return true;
},
cost,
icon);
}
public void SetDestroyAction(Func<bool> destroyAction, int gain, Sprite icon = null)
{
_destroyOption.Action = destroyAction;
_destroyOption.Price = Mathf.Max(0, gain);
_destroyOption.Icon = icon;
_destroyOption.IsGain = true;
_destroyOption.IsVisible = true;
}
public void SetDestroyAction(Action destroyAction, int gain, Sprite icon = null)
{
if (destroyAction == null)
{
SetDestroyAction((Func<bool>)null, gain, icon);
return;
}
SetDestroyAction(
() =>
{
destroyAction.Invoke();
return true;
},
gain,
icon);
}
public void SetUpgradePrice(int cost)
{
_upgradeOption.Price = Mathf.Max(0, cost);
}
public void SetDestroyGain(int gain)
{
_destroyOption.Price = Mathf.Max(0, gain);
_destroyOption.IsGain = true;
}
public void ShowForFoundation(Vector2 contentPosition)
{
_contentPosition = contentPosition;
_displayMode = CombatSelectDisplayMode.Build;
_isVisible = true;
}
public void ShowForTower(Vector2 contentPosition)
{
_contentPosition = contentPosition;
_displayMode = CombatSelectDisplayMode.Func;
_isVisible = true;
}
public void Hide()
{
_isVisible = false;
_displayMode = CombatSelectDisplayMode.Hidden;
}
public bool TryExecuteAction(CombatSelectActionType actionType, int actionIndex)
{
if (!_isVisible)
{
return false;
}
Option option = GetOption(actionType, actionIndex);
if (option == null || !option.IsVisible)
{
return false;
}
int currentCoin = Mathf.Max(0, _coinProvider != null ? _coinProvider.Invoke() : 0);
if (!CanExecute(option, currentCoin))
{
return false;
}
if (option.Action == null)
{
return false;
}
bool result = option.Action.Invoke();
if (result)
{
Hide();
}
return result;
}
private CombatSelectFormRawData BuildModel()
{
int currentCoin = Mathf.Max(0, _coinProvider != null ? _coinProvider.Invoke() : 0);
TowerSelectItemRawData[] buildItems = new TowerSelectItemRawData[_buildOptions.Length];
for (int i = 0; i < _buildOptions.Length; i++)
{
buildItems[i] = BuildOptionRawData(_buildOptions[i], currentCoin);
}
return new CombatSelectFormRawData
{
IsVisible = _isVisible,
ContentPosition = _contentPosition,
DisplayMode = _displayMode,
BuildItems = buildItems,
UpgradeItem = BuildOptionRawData(_upgradeOption, currentCoin),
DestroyItem = BuildOptionRawData(_destroyOption, currentCoin)
};
}
private Option GetOption(CombatSelectActionType actionType, int actionIndex)
{
switch (actionType)
{
case CombatSelectActionType.BuildTower:
if (actionIndex < 0 || actionIndex >= _buildOptions.Length)
{
return null;
}
return _buildOptions[actionIndex];
case CombatSelectActionType.UpgradeTower:
return _upgradeOption;
case CombatSelectActionType.DestroyTower:
return _destroyOption;
default:
return null;
}
}
private static int DefaultCoinProvider()
{
if (GameEntry.CombatNode == null)
{
return 0;
}
return Mathf.Max(0, GameEntry.CombatNode.CurrentCoin);
}
private static bool CanExecute(Option option, int currentCoin)
{
if (option == null)
{
return false;
}
if (option.RequiresAffordable && currentCoin < option.Price)
{
return false;
}
return true;
}
private static TowerSelectItemRawData BuildOptionRawData(Option option, int currentCoin)
{
if (option == null)
{
return null;
}
return new TowerSelectItemRawData
{
Icon = option.Icon,
Price = option.Price,
IsGain = option.IsGain,
IsVisible = option.IsVisible,
IsInteractable = option.Action != null && CanExecute(option, currentCoin),
ActionType = option.ActionType,
ActionIndex = option.ActionIndex
};
}
private sealed class Option
{
public readonly CombatSelectActionType ActionType;
public readonly int ActionIndex;
public readonly bool RequiresAffordable;
public int Price;
public bool IsGain;
public bool IsVisible = true;
public Sprite Icon;
public Func<bool> Action;
public Option(CombatSelectActionType actionType, int actionIndex, bool requiresAffordable)
{
ActionType = actionType;
ActionIndex = actionIndex;
RequiresAffordable = requiresAffordable;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 040ca52500c562c4d9737d44a8940050
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,173 @@
using System.Collections.Generic;
using GameFramework.ObjectPool;
using GeometryTD.CustomEvent;
using GeometryTD.PoolObjectBase;
using TMPro;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CombatFinishForm : UGuiForm
{
[SerializeField] private TMP_Text _enemyKilledText;
[SerializeField] private TMP_Text _goldGainedText;
[SerializeField] private Transform _itemGainedParent;
[SerializeField] private RepoItem _itemTemplate;
[SerializeField] private int _instancePoolCapacity = 16;
private readonly List<RepoItem> _activeItems = new List<RepoItem>();
private IObjectPool<RepoItemObject> _itemPool;
private string _poolName;
private CombatFinishFormContext _context;
public void RefreshUI(CombatFinishFormContext context)
{
_context = context;
if (_enemyKilledText != null)
{
_enemyKilledText.text = context?.EnemyKilledText ?? "0";
}
if (_goldGainedText != null)
{
_goldGainedText.text = context?.GoldGainedText ?? "0";
}
RefreshRewardItems(context?.RewardItems);
}
public void OnReturnButtonClick()
{
if (_context != null && !_context.CanReturn)
{
return;
}
GameEntry.Event.Fire(this, CombatFinishReturnEventArgs.Create());
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is CombatFinishFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("CombatFinishForm requires CombatFinishFormContext as userData.");
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
if (_enemyKilledText != null)
{
_enemyKilledText.text = string.Empty;
}
if (_goldGainedText != null)
{
_goldGainedText.text = string.Empty;
}
ClearRewardItems();
base.OnClose(isShutdown, userData);
}
private void OnDestroy()
{
ClearRewardItems();
_itemPool?.ReleaseAllUnused();
}
private void EnsurePool()
{
if (_itemPool != null)
{
return;
}
_poolName = $"CombatFinishRepoItemPool_{GetInstanceID()}";
_itemPool = GameEntry.ObjectPool.CreateSingleSpawnObjectPool<RepoItemObject>(_poolName, _instancePoolCapacity);
}
private void RefreshRewardItems(RepoItemContext[] rewardItems)
{
ClearRewardItems();
if (rewardItems == null || rewardItems.Length <= 0)
{
return;
}
EnsurePool();
for (int i = 0; i < rewardItems.Length; i++)
{
RepoItem item = CreateOrSpawnItem();
if (item == null)
{
continue;
}
item.gameObject.SetActive(true);
item.OnInit(rewardItems[i]);
_activeItems.Add(item);
}
}
private void ClearRewardItems()
{
for (int i = _activeItems.Count - 1; i >= 0; i--)
{
RepoItem item = _activeItems[i];
if (item == null)
{
continue;
}
item.OnReset();
item.gameObject.SetActive(false);
_itemPool?.Unspawn(item);
}
_activeItems.Clear();
}
private RepoItem CreateOrSpawnItem()
{
if (_itemGainedParent == null)
{
Log.Warning("CombatFinishForm requires an item gained parent.");
return null;
}
if (_itemPool == null)
{
return null;
}
RepoItemObject itemObject = _itemPool.Spawn();
RepoItem item = itemObject != null ? (RepoItem)itemObject.Target : null;
if (item == null)
{
if (_itemTemplate == null)
{
Log.Warning("CombatFinishForm requires an item template.");
return null;
}
item = Instantiate(_itemTemplate);
_itemPool.Register(RepoItemObject.Create(item), true);
}
item.transform.SetParent(_itemGainedParent, false);
return item;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9679d912cb0ce9e4d95db41021d94463
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,60 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatSelectBuildArea : MonoBehaviour
{
[SerializeField] private TowerSelectItem[] _towerBuildItems;
public void OnInit(TowerSelectItemContext[] itemContexts)
{
if (_towerBuildItems == null)
{
return;
}
for (int i = 0; i < _towerBuildItems.Length; i++)
{
TowerSelectItem item = _towerBuildItems[i];
if (item == null)
{
continue;
}
TowerSelectItemContext itemContext =
itemContexts != null && i < itemContexts.Length ? itemContexts[i] : null;
bool isVisible = itemContext != null && itemContext.IsVisible;
item.gameObject.SetActive(isVisible);
if (isVisible)
{
item.OnInit(itemContext);
}
else
{
item.OnReset();
}
}
}
public void OnReset()
{
if (_towerBuildItems == null)
{
return;
}
for (int i = 0; i < _towerBuildItems.Length; i++)
{
TowerSelectItem item = _towerBuildItems[i];
if (item == null)
{
continue;
}
item.OnReset();
item.gameObject.SetActive(false);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73470d4b91d03924b9233b58311e917c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,111 @@
using UnityEngine;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class CombatSelectForm : UGuiForm
{
[SerializeField] private RectTransform _content;
[SerializeField] private CombatSelectBuildArea _buildArea;
[SerializeField] private CombatSelectFuncArea _funcArea;
private CombatSelectFormContext _context;
public void RefreshUI(CombatSelectFormContext context)
{
_context = context;
EnsureContentReference();
if (_content == null)
{
Log.Warning("CombatSelectForm content is missing.");
return;
}
bool isVisible = context != null && context.IsVisible;
_content.gameObject.SetActive(isVisible);
if (!isVisible)
{
_buildArea?.OnReset();
_funcArea?.OnReset();
return;
}
_content.anchoredPosition = context.ContentPosition;
if (_buildArea != null)
{
bool showBuildArea = context.ShowBuildArea;
_buildArea.gameObject.SetActive(showBuildArea);
if (showBuildArea)
{
_buildArea.OnInit(context.BuildItems);
}
else
{
_buildArea.OnReset();
}
}
if (_funcArea != null)
{
bool showFuncArea = context.ShowFuncArea;
_funcArea.gameObject.SetActive(showFuncArea);
if (showFuncArea)
{
_funcArea.OnInit(context.UpgradeItem, context.DestroyItem);
}
else
{
_funcArea.OnReset();
}
}
}
protected override void OnOpen(object userData)
{
base.OnOpen(userData);
if (userData is CombatSelectFormContext context)
{
RefreshUI(context);
return;
}
Log.Warning("CombatSelectForm requires CombatSelectFormContext as userData.");
RefreshUI(null);
}
protected override void OnClose(bool isShutdown, object userData)
{
_context = null;
_buildArea?.OnReset();
_funcArea?.OnReset();
if (_content != null)
{
_content.gameObject.SetActive(false);
}
base.OnClose(isShutdown, userData);
}
private void Awake()
{
EnsureContentReference();
}
private void EnsureContentReference()
{
if (_content != null)
{
return;
}
Transform contentTransform = transform.Find("Content");
_content = contentTransform as RectTransform;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e62b0f7fcf3ccfd4e88da34d80cc14ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,53 @@
using UnityEngine;
namespace GeometryTD.UI
{
public class CombatSelectFuncArea : MonoBehaviour
{
[SerializeField] private TowerSelectItem _upgradeItem;
[SerializeField] private TowerSelectItem _destroyItem;
public void OnInit(TowerSelectItemContext upgradeItemContext, TowerSelectItemContext destroyItemContext)
{
RefreshItem(_upgradeItem, upgradeItemContext);
RefreshItem(_destroyItem, destroyItemContext);
}
public void OnReset()
{
ResetItem(_upgradeItem);
ResetItem(_destroyItem);
}
private static void RefreshItem(TowerSelectItem item, TowerSelectItemContext context)
{
if (item == null)
{
return;
}
bool isVisible = context != null && context.IsVisible;
item.gameObject.SetActive(isVisible);
if (isVisible)
{
item.OnInit(context);
}
else
{
item.OnReset();
}
}
private static void ResetItem(TowerSelectItem item)
{
if (item == null)
{
return;
}
item.OnReset();
item.gameObject.SetActive(false);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 891304d1e879da34c9b989ca2fcd14a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,96 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using GeometryTD.CustomEvent;
using UnityGameFramework.Runtime;
namespace GeometryTD.UI
{
public class TowerSelectItem : MonoBehaviour
{
[SerializeField] private Image _icon;
[SerializeField] private TMP_Text _price;
[SerializeField] private CommonButton _button;
private TowerSelectItemContext _context;
private Sprite _defaultIcon;
private bool _hasCachedDefaultIcon;
public void OnInit(TowerSelectItemContext context)
{
CacheDefaultIcon();
_context = context;
if (_icon != null)
{
if (context != null && context.Icon != null)
{
_icon.sprite = context.Icon;
}
else if (_defaultIcon != null)
{
_icon.sprite = _defaultIcon;
}
_icon.enabled = _icon.sprite != null;
}
if (_price != null)
{
_price.text = context?.PriceText ?? string.Empty;
}
if (_button != null)
{
_button.Interactive = context != null && context.IsInteractable;
}
}
public void OnReset()
{
_context = null;
CacheDefaultIcon();
if (_icon != null)
{
_icon.sprite = _defaultIcon;
_icon.enabled = _icon.sprite != null;
}
if (_price != null)
{
_price.text = string.Empty;
}
if (_button != null)
{
_button.Interactive = false;
}
}
public void OnClick()
{
if (_context == null || !_context.IsInteractable)
{
return;
}
GameEntry.Event.Fire(
this,
CombatSelectItemClickEventArgs.Create(_context.ActionType, _context.ActionIndex));
}
private void CacheDefaultIcon()
{
if (_hasCachedDefaultIcon || _icon == null)
{
return;
}
_defaultIcon = _icon.sprite;
_hasCachedDefaultIcon = true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4da5ef45ee18db44ebe3c015dddb61d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -580,7 +580,7 @@ MonoBehaviour:
m_Right: 0 m_Right: 0
m_Top: 0 m_Top: 0
m_Bottom: 0 m_Bottom: 0
m_ChildAlignment: 4 m_ChildAlignment: 5
m_Spacing: 0 m_Spacing: 0
m_ChildForceExpandWidth: 1 m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1 m_ChildForceExpandHeight: 1

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -61,7 +61,7 @@ MonoBehaviour:
m_Color: {r: 1, g: 1, b: 1, a: 1} m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0 m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 0 m_Maskable: 1
m_OnCullStateChanged: m_OnCullStateChanged:
m_PersistentCalls: m_PersistentCalls:
m_Calls: [] m_Calls: []

View File

@ -0,0 +1,328 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &5412566102378209566
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4152009404387722330}
- component: {fileID: 3873445585384354699}
- component: {fileID: 272351400812969041}
m_Layer: 5
m_Name: Icon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4152009404387722330
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5412566102378209566}
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: 274828358581623048}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 80, y: 80}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3873445585384354699
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5412566102378209566}
m_CullTransparentMesh: 1
--- !u!114 &272351400812969041
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5412566102378209566}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 0
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300146, guid: 29c9cded5e558164aaf8c9bf08aa1510, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &7694008747632310308
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 274828358581623048}
- component: {fileID: 4724049278947147719}
m_Layer: 5
m_Name: TowerSelectItem
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &274828358581623048
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7694008747632310308}
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:
- {fileID: 8201350274026178469}
- {fileID: 4152009404387722330}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 175}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &4724049278947147719
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7694008747632310308}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4da5ef45ee18db44ebe3c015dddb61d6, type: 3}
m_Name:
m_EditorClassIdentifier:
_icon: {fileID: 272351400812969041}
_price: {fileID: 6134693184638222351}
_button: {fileID: 8644563062325311724}
--- !u!1001 &5730140866277425490
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 274828358581623048}
m_Modifications:
- target: {fileID: 770565994539545022, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_Name
value: CommonButton
objectReference: {fileID: 0}
- target: {fileID: 1920576543566152029, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_text
value: 100
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Mode
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Target
value:
objectReference: {fileID: 4724049278947147719}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_MethodName
value: OnClick
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_TargetAssemblyTypeName
value: GeometryTD.UI.TowerSelectItem, Assembly-CSharp
objectReference: {fileID: 0}
- target: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: _onClick.m_PersistentCalls.m_Calls.Array.data[1].m_Arguments.m_ObjectArgumentAssemblyTypeName
value: UnityEngine.Object, UnityEngine
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMax.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMax.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchorMin.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_SizeDelta.y
value: 60
objectReference: {fileID: 0}
- target: {fileID: 7744090569424522082, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
propertyPath: m_AnchoredPosition.y
value: -80
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 2307f223279813546a43b221ddd496cc, type: 3}
--- !u!114 &6134693184638222351 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 1920576543566152029, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
m_PrefabInstance: {fileID: 5730140866277425490}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!224 &8201350274026178469 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 4491355866364659447, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
m_PrefabInstance: {fileID: 5730140866277425490}
m_PrefabAsset: {fileID: 0}
--- !u!114 &8644563062325311724 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 4067353614215461310, guid: 2307f223279813546a43b221ddd496cc,
type: 3}
m_PrefabInstance: {fileID: 5730140866277425490}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a5079836f95c2a44b96fa331487ebb70, type: 3}
m_Name:
m_EditorClassIdentifier:

View File

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

View File

@ -490,7 +490,7 @@ PrefabInstance:
objectReference: {fileID: 1576232203} objectReference: {fileID: 1576232203}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.size propertyPath: m_UIGroups.Array.size
value: 2 value: 3
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroupHelperTypeName propertyPath: m_UIGroupHelperTypeName
@ -498,12 +498,16 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[0].m_Name propertyPath: m_UIGroups.Array.data[0].m_Name
value: Default value: Medium
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[1].m_Name propertyPath: m_UIGroups.Array.data[1].m_Name
value: Test value: Test
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[2].m_Name
value: Overlay
objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[0].m_Depth propertyPath: m_UIGroups.Array.data[0].m_Depth
value: 0 value: 0
@ -512,6 +516,10 @@ PrefabInstance:
propertyPath: m_UIGroups.Array.data[1].m_Depth propertyPath: m_UIGroups.Array.data[1].m_Depth
value: 2 value: 2
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11454530, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_UIGroups.Array.data[2].m_Depth
value: 2
objectReference: {fileID: 0}
- target: {fileID: 11461470, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11461470, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_LocalizationHelperTypeName propertyPath: m_LocalizationHelperTypeName
value: GeometryTD.XmlLocalizationHelper value: GeometryTD.XmlLocalizationHelper
@ -522,7 +530,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.size propertyPath: m_EntityGroups.Array.size
value: 4 value: 5
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[0].m_Name propertyPath: m_EntityGroups.Array.data[0].m_Name
@ -542,7 +550,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_Name propertyPath: m_EntityGroups.Array.data[4].m_Name
value: Map value: Tower
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[5].m_Name propertyPath: m_EntityGroups.Array.data[5].m_Name
@ -578,7 +586,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[4].m_InstanceCapacity
value: 2 value: 10
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[5].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[5].m_InstanceCapacity
@ -682,7 +690,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_RunInBackground propertyPath: m_RunInBackground
value: 0 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11499388, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EditorResourceMode propertyPath: m_EditorResourceMode
@ -812,6 +820,79 @@ MonoBehaviour:
type: 3} type: 3}
_updateResourceFormTemplate: {fileID: 11487720, guid: a6c731de80e9d824d8f657301a357269, _updateResourceFormTemplate: {fileID: 11487720, guid: a6c731de80e9d824d8f657301a357269,
type: 3} type: 3}
--- !u!1001 &571687048
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 0}
m_Modifications:
- target: {fileID: 7091080056390424735, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_Name
value: Level1
objectReference: {fileID: 0}
- target: {fileID: 7091080056390424735, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 7948599801963745741, guid: 0cc71b1087c7dfd42a8233a7101fc27e,
type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 0cc71b1087c7dfd42a8233a7101fc27e, type: 3}
--- !u!850595691 &819759088 --- !u!850595691 &819759088
LightingSettings: LightingSettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1218,8 +1299,8 @@ Canvas:
m_GameObject: {fileID: 1576232202} m_GameObject: {fileID: 1576232202}
m_Enabled: 1 m_Enabled: 1
serializedVersion: 3 serializedVersion: 3
m_RenderMode: 0 m_RenderMode: 1
m_Camera: {fileID: 0} m_Camera: {fileID: 1376238058}
m_PlaneDistance: 100 m_PlaneDistance: 100
m_PixelPerfect: 0 m_PixelPerfect: 0
m_ReceivesEvents: 1 m_ReceivesEvents: 1
@ -1229,7 +1310,7 @@ Canvas:
m_VertexColorAlwaysGammaSpace: 0 m_VertexColorAlwaysGammaSpace: 0
m_AdditionalShaderChannelsFlag: 25 m_AdditionalShaderChannelsFlag: 25
m_UpdateRectTransformForStandalone: 0 m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0 m_SortingLayerID: 583862207
m_SortingOrder: 0 m_SortingOrder: 0
m_TargetDisplay: 0 m_TargetDisplay: 0
--- !u!1 &1604812192 --- !u!1 &1604812192
@ -1421,3 +1502,4 @@ SceneRoots:
- {fileID: 1852670053} - {fileID: 1852670053}
- {fileID: 120093242} - {fileID: 120093242}
- {fileID: 1376238059} - {fileID: 1376238059}
- {fileID: 571687048}

View File

@ -38,15 +38,30 @@ TagManager:
- -
- -
m_SortingLayers: m_SortingLayers:
- name: MapLayer 1
uniqueID: 2425651459
locked: 0
- name: MapLayer 2
uniqueID: 4250941897
locked: 0
- name: MapLayer 3
uniqueID: 4189426099
locked: 0
- name: Entity
uniqueID: 2489289463
locked: 0
- name: Default - name: Default
uniqueID: 0 uniqueID: 0
locked: 0 locked: 0
- name: Layer 1 - name:
uniqueID: 2425651459 uniqueID: 1359116331
locked: 0 locked: 0
- name: Layer 2 - name: Medium
uniqueID: 4250941897 uniqueID: 578558901
locked: 0 locked: 0
- name: Layer 3 - name: Overlay
uniqueID: 4189426099 uniqueID: 583862207
locked: 0
- name: Test
uniqueID: 1320507645
locked: 0 locked: 0

View File

@ -73,7 +73,11 @@
| [ ] | D-08 | P0 | 先定义数值预算(经济/敌强/掉落)再调参 | `docs/NumericBudget.md`<br>`Assets/GameMain/DataTables/*.txt` | 关键曲线有目标区间3 局测试结果可与目标对比复盘 | | [ ] | D-08 | P0 | 先定义数值预算(经济/敌强/掉落)再调参 | `docs/NumericBudget.md`<br>`Assets/GameMain/DataTables/*.txt` | 关键曲线有目标区间3 局测试结果可与目标对比复盘 |
## 小目标 ## 小目标
1. GameHud游戏内的 UI展示当前的金币数 1. CombatNodeComponent战斗节点的逻辑补全
2. CombatHud战斗场景的 UI展示关卡相关信息 - 完整塔防流程:初始硬币,敌人掉落硬币数量、基地生命与失败设计
- 硬币数Coin - 敌人随机掉落局外资源(金币、组件/配件)
- 关卡当前波次与总波次1/5 -> 12/无限) - 无限波次下敌人血量、资源爆率的增加
- 基地血量满足一定条件的额外奖励
2. ShopNodeComponent商店节点的逻辑补全
3. RepoForm仓库的逻辑补全
- 组装防御塔逻辑的落实