tower-sell #12
16 changed files with 5394 additions and 13 deletions
138
Assets/_Project/Art/Materials/M_Gold.mat
Normal file
138
Assets/_Project/Art/Materials/M_Gold.mat
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 8
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: M_Gold
|
||||||
|
m_Shader: {fileID: 4800000, guid: 933532a4fcc9baf4fa0491de14d08ed7, type: 3}
|
||||||
|
m_Parent: {fileID: 0}
|
||||||
|
m_ModifiedSerializedProperties: 0
|
||||||
|
m_ValidKeywords:
|
||||||
|
- _EMISSION
|
||||||
|
m_InvalidKeywords: []
|
||||||
|
m_LightmapFlags: 2
|
||||||
|
m_EnableInstancingVariants: 1
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: -1
|
||||||
|
stringTagMap:
|
||||||
|
RenderType: Opaque
|
||||||
|
disabledShaderPasses:
|
||||||
|
- MOTIONVECTORS
|
||||||
|
m_LockedProperties:
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BaseMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _SpecGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_Lightmaps:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_LightmapsInd:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_ShadowMasks:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Ints: []
|
||||||
|
m_Floats:
|
||||||
|
- _AddPrecomputedVelocity: 0
|
||||||
|
- _AlphaClip: 0
|
||||||
|
- _AlphaToMask: 0
|
||||||
|
- _Blend: 0
|
||||||
|
- _BlendModePreserveSpecular: 1
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _ClearCoatMask: 0
|
||||||
|
- _ClearCoatSmoothness: 0
|
||||||
|
- _Cull: 2
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailAlbedoMapScale: 1
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _DstBlendAlpha: 0
|
||||||
|
- _EnvironmentReflections: 1
|
||||||
|
- _GlossMapScale: 0
|
||||||
|
- _Glossiness: 0
|
||||||
|
- _GlossyReflections: 0
|
||||||
|
- _Metallic: 0.3
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.005
|
||||||
|
- _QueueOffset: 0
|
||||||
|
- _ReceiveShadows: 1
|
||||||
|
- _Smoothness: 0.85
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _SrcBlendAlpha: 1
|
||||||
|
- _Surface: 0
|
||||||
|
- _WorkflowMode: 1
|
||||||
|
- _XRMotionVectorsPass: 1
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _BaseColor: {r: 0.83137256, g: 0.6862745, b: 0.21568628, a: 1}
|
||||||
|
- _Color: {r: 0.83137256, g: 0.68627447, b: 0.2156862, a: 1}
|
||||||
|
- _EmissionColor: {r: 2, g: 0.6755797, b: 0.031372547, a: 1}
|
||||||
|
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||||
|
m_BuildTextureStacks: []
|
||||||
|
m_AllowLocking: 1
|
||||||
|
--- !u!114 &1062544154538963945
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 11
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||||
|
version: 10
|
||||||
8
Assets/_Project/Art/Materials/M_Gold.mat.meta
Normal file
8
Assets/_Project/Art/Materials/M_Gold.mat.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 915f376be23f20941837c0809626fa5f
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/_Project/Art/Models/Blend Files.meta
Normal file
8
Assets/_Project/Art/Models/Blend Files.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d70d84aef04e61749a232234c80f08ce
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/_Project/Art/Models/Coin.fbx
(Stored with Git LFS)
Normal file
BIN
Assets/_Project/Art/Models/Coin.fbx
(Stored with Git LFS)
Normal file
Binary file not shown.
110
Assets/_Project/Art/Models/Coin.fbx.meta
Normal file
110
Assets/_Project/Art/Models/Coin.fbx.meta
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 280a727da941fc544b9aab7df7fe9f2d
|
||||||
|
ModelImporter:
|
||||||
|
serializedVersion: 24200
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
materials:
|
||||||
|
materialImportMode: 2
|
||||||
|
materialName: 0
|
||||||
|
materialSearch: 1
|
||||||
|
materialLocation: 1
|
||||||
|
animations:
|
||||||
|
legacyGenerateAnimations: 4
|
||||||
|
bakeSimulation: 0
|
||||||
|
resampleCurves: 1
|
||||||
|
optimizeGameObjects: 0
|
||||||
|
removeConstantScaleCurves: 0
|
||||||
|
motionNodeName:
|
||||||
|
animationImportErrors:
|
||||||
|
animationImportWarnings:
|
||||||
|
animationRetargetingWarnings:
|
||||||
|
animationDoRetargetingWarnings: 0
|
||||||
|
importAnimatedCustomProperties: 0
|
||||||
|
importConstraints: 0
|
||||||
|
animationCompression: 1
|
||||||
|
animationRotationError: 0.5
|
||||||
|
animationPositionError: 0.5
|
||||||
|
animationScaleError: 0.5
|
||||||
|
animationWrapMode: 0
|
||||||
|
extraExposedTransformPaths: []
|
||||||
|
extraUserProperties: []
|
||||||
|
clipAnimations: []
|
||||||
|
isReadable: 0
|
||||||
|
meshes:
|
||||||
|
lODScreenPercentages: []
|
||||||
|
globalScale: 1
|
||||||
|
meshCompression: 0
|
||||||
|
addColliders: 0
|
||||||
|
useSRGBMaterialColor: 1
|
||||||
|
sortHierarchyByName: 1
|
||||||
|
importPhysicalCameras: 1
|
||||||
|
importVisibility: 1
|
||||||
|
importBlendShapes: 1
|
||||||
|
importCameras: 1
|
||||||
|
importLights: 1
|
||||||
|
nodeNameCollisionStrategy: 1
|
||||||
|
fileIdsGeneration: 2
|
||||||
|
swapUVChannels: 0
|
||||||
|
generateSecondaryUV: 0
|
||||||
|
useFileUnits: 1
|
||||||
|
keepQuads: 0
|
||||||
|
weldVertices: 1
|
||||||
|
bakeAxisConversion: 0
|
||||||
|
preserveHierarchy: 0
|
||||||
|
skinWeightsMode: 0
|
||||||
|
maxBonesPerVertex: 4
|
||||||
|
minBoneWeight: 0.001
|
||||||
|
optimizeBones: 1
|
||||||
|
generateMeshLods: 0
|
||||||
|
meshLodGenerationFlags: 0
|
||||||
|
maximumMeshLod: -1
|
||||||
|
meshOptimizationFlags: -1
|
||||||
|
indexFormat: 0
|
||||||
|
secondaryUVAngleDistortion: 8
|
||||||
|
secondaryUVAreaDistortion: 15.000001
|
||||||
|
secondaryUVHardAngle: 88
|
||||||
|
secondaryUVMarginMethod: 1
|
||||||
|
secondaryUVMinLightmapResolution: 40
|
||||||
|
secondaryUVMinObjectScale: 1
|
||||||
|
secondaryUVPackMargin: 4
|
||||||
|
useFileScale: 1
|
||||||
|
strictVertexDataChecks: 0
|
||||||
|
tangentSpace:
|
||||||
|
normalSmoothAngle: 60
|
||||||
|
normalImportMode: 0
|
||||||
|
tangentImportMode: 3
|
||||||
|
normalCalculationMode: 4
|
||||||
|
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
|
||||||
|
blendShapeNormalImportMode: 1
|
||||||
|
normalSmoothingSource: 0
|
||||||
|
referencedClips: []
|
||||||
|
importAnimation: 1
|
||||||
|
humanDescription:
|
||||||
|
serializedVersion: 3
|
||||||
|
human: []
|
||||||
|
skeleton: []
|
||||||
|
armTwist: 0.5
|
||||||
|
foreArmTwist: 0.5
|
||||||
|
upperLegTwist: 0.5
|
||||||
|
legTwist: 0.5
|
||||||
|
armStretch: 0.05
|
||||||
|
legStretch: 0.05
|
||||||
|
feetSpacing: 0
|
||||||
|
globalScale: 1
|
||||||
|
rootMotionBoneName:
|
||||||
|
hasTranslationDoF: 0
|
||||||
|
hasExtraRoot: 0
|
||||||
|
skeletonHasParents: 1
|
||||||
|
lastHumanDescriptionAvatarSource: {instanceID: 0}
|
||||||
|
autoGenerateAvatarMappingIfUnspecified: 1
|
||||||
|
animationType: 2
|
||||||
|
humanoidOversampling: 1
|
||||||
|
avatarSetup: 0
|
||||||
|
addHumanoidExtraRootOnlyWhenUsingAvatar: 1
|
||||||
|
importBlendShapeDeformPercent: 1
|
||||||
|
remapMaterialsIfMaterialImportModeIsNone: 0
|
||||||
|
additionalBone: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
4896
Assets/_Project/Art/VFX/FX_SellTower.prefab
Normal file
4896
Assets/_Project/Art/VFX/FX_SellTower.prefab
Normal file
File diff suppressed because it is too large
Load diff
7
Assets/_Project/Art/VFX/FX_SellTower.prefab.meta
Normal file
7
Assets/_Project/Art/VFX/FX_SellTower.prefab.meta
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 31f8d8c320b42c54d886d610fb13a1e7
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/_Project/Audio/Sound Effects.meta
Normal file
8
Assets/_Project/Audio/Sound Effects.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d1508e070d3666a4282a219e30ffdbf6
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/_Project/Audio/Sound Effects/SellSoundEffect.ogg
(Stored with Git LFS)
Normal file
BIN
Assets/_Project/Audio/Sound Effects/SellSoundEffect.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
23
Assets/_Project/Audio/Sound Effects/SellSoundEffect.ogg.meta
Normal file
23
Assets/_Project/Audio/Sound Effects/SellSoundEffect.ogg.meta
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 778b3d5981f4c3045a475794435f43cc
|
||||||
|
AudioImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 8
|
||||||
|
defaultSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
loadType: 0
|
||||||
|
sampleRateSetting: 0
|
||||||
|
sampleRateOverride: 44100
|
||||||
|
compressionFormat: 1
|
||||||
|
quality: 1
|
||||||
|
conversionMode: 0
|
||||||
|
preloadAudioData: 0
|
||||||
|
platformSettingOverrides: {}
|
||||||
|
forceToMono: 0
|
||||||
|
normalize: 1
|
||||||
|
loadInBackground: 0
|
||||||
|
ambisonic: 0
|
||||||
|
3D: 1
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -12918,6 +12918,57 @@ BoxCollider:
|
||||||
serializedVersion: 3
|
serializedVersion: 3
|
||||||
m_Size: {x: 24, y: 1, z: 30}
|
m_Size: {x: 24, y: 1, z: 30}
|
||||||
m_Center: {x: 0, y: 0, z: 0}
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &877074910
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 877074912}
|
||||||
|
- component: {fileID: 877074911}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: SellEffectSpawner
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &877074911
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 877074910}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: e2708c6bac8136f45b48c899d3d40630, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::TD.VFX.SellEffectSpawner
|
||||||
|
coinBurstPrefab: {fileID: 2313150535691989867, guid: 31f8d8c320b42c54d886d610fb13a1e7, type: 3}
|
||||||
|
sellSound:
|
||||||
|
clip: {fileID: 8300000, guid: 778b3d5981f4c3045a475794435f43cc, type: 3}
|
||||||
|
volume: 1
|
||||||
|
minPitch: 0.95
|
||||||
|
maxPitch: 1.05
|
||||||
|
verticalOffset: 0.75
|
||||||
|
--- !u!4 &877074912
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 877074910}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 38.13543, y: 0.5, z: 166.56534}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1 &902199259
|
--- !u!1 &902199259
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|
@ -15394,6 +15445,7 @@ MonoBehaviour:
|
||||||
m_Script: {fileID: 11500000, guid: de7d013503af0f74c950f215f8dae1c0, type: 3}
|
m_Script: {fileID: 11500000, guid: de7d013503af0f74c950f215f8dae1c0, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PathfindingService
|
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PathfindingService
|
||||||
|
recomputeBudgetMs: 1.5
|
||||||
--- !u!4 &1149980841
|
--- !u!4 &1149980841
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|
@ -28962,3 +29014,4 @@ SceneRoots:
|
||||||
- {fileID: 176580462}
|
- {fileID: 176580462}
|
||||||
- {fileID: 516125087}
|
- {fileID: 516125087}
|
||||||
- {fileID: 2079088113}
|
- {fileID: 2079088113}
|
||||||
|
- {fileID: 877074912}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,10 @@ namespace TD.Gameplay
|
||||||
/// and stores the tile waypoint list.</item>
|
/// and stores the tile waypoint list.</item>
|
||||||
/// <item>Each frame: moves toward the world center of <c>remainingPath[0]</c>.
|
/// <item>Each frame: moves toward the world center of <c>remainingPath[0]</c>.
|
||||||
/// When within snap distance, pops the waypoint and checks for zone transitions.</item>
|
/// When within snap distance, pops the waypoint and checks for zone transitions.</item>
|
||||||
/// <item>When <see cref="PathfindingService.OnPathsInvalidated"/> fires (tower placed /
|
/// <item>When the maze changes (tower placed / sold), <see cref="PathfindingService"/>'s
|
||||||
/// sold), <see cref="RecomputePath"/> reruns A* from the current tile.</item>
|
/// budgeted scheduler calls <see cref="RecomputePath"/> (registered via
|
||||||
|
/// <see cref="PathfindingService.RegisterMover"/>), rerunning A* from the current
|
||||||
|
/// tile — spread across frames so a full wave never spikes.</item>
|
||||||
/// <item>When <c>remainingPath</c> is empty after a pop, the enemy has reached the
|
/// <item>When <c>remainingPath</c> is empty after a pop, the enemy has reached the
|
||||||
/// goal — <see cref="OnReachedGoal"/> fires and the enemy is despawned.</item>
|
/// goal — <see cref="OnReachedGoal"/> fires and the enemy is despawned.</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
|
|
@ -168,15 +170,18 @@ namespace TD.Gameplay
|
||||||
|
|
||||||
// Recompute when a tower is placed or sold — grounded enemies only.
|
// Recompute when a tower is placed or sold — grounded enemies only.
|
||||||
// Flyers path on the static baked grid, so tower changes can never affect
|
// Flyers path on the static baked grid, so tower changes can never affect
|
||||||
// their route; subscribing would just trigger needless recomputes.
|
// their route; registering would just trigger needless recomputes.
|
||||||
|
// Registration (not a direct event subscription) lets PathfindingService
|
||||||
|
// spread the recomputes across frames under a per-frame budget, so a maze
|
||||||
|
// change with a full wave present never spikes a single frame.
|
||||||
if (!isFlying && PathfindingService.Instance != null)
|
if (!isFlying && PathfindingService.Instance != null)
|
||||||
PathfindingService.Instance.OnPathsInvalidated += RecomputePath;
|
PathfindingService.Instance.RegisterMover(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNetworkDespawn()
|
public override void OnNetworkDespawn()
|
||||||
{
|
{
|
||||||
if (PathfindingService.Instance != null)
|
if (PathfindingService.Instance != null)
|
||||||
PathfindingService.Instance.OnPathsInvalidated -= RecomputePath;
|
PathfindingService.Instance.UnregisterMover(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Server update --------------------------------------------------
|
// ----- Server update --------------------------------------------------
|
||||||
|
|
@ -274,8 +279,9 @@ namespace TD.Gameplay
|
||||||
|
|
||||||
// ----- Path invalidation ----------------------------------------------
|
// ----- Path invalidation ----------------------------------------------
|
||||||
|
|
||||||
// Called on server when LevelLoader.OnWalkabilityChanged fires (tower placed/sold).
|
// Called by PathfindingService's budgeted scheduler after a walkability change
|
||||||
private void RecomputePath()
|
// (tower placed/sold). Public so the scheduler can drive it across frames.
|
||||||
|
public void RecomputePath()
|
||||||
{
|
{
|
||||||
if (!IsServer) return;
|
if (!IsServer) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,15 @@ namespace TD.Gameplay
|
||||||
/// <b>Who calls this:</b>
|
/// <b>Who calls this:</b>
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><see cref="EnemyMovement"/> calls <see cref="ComputePath"/> once on
|
/// <item><see cref="EnemyMovement"/> calls <see cref="ComputePath"/> once on
|
||||||
/// spawn and again whenever <see cref="OnPathsInvalidated"/> fires.</item>
|
/// spawn, then registers via <see cref="RegisterMover"/> to be re-pathed when
|
||||||
|
/// the maze changes.</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
///
|
///
|
||||||
/// <b>Invalidation:</b> Subscribes to <see cref="LevelLoader.OnWalkabilityChanged"/>.
|
/// <b>Invalidation:</b> Subscribes to <see cref="LevelLoader.OnWalkabilityChanged"/>.
|
||||||
/// When a tower is placed or sold, <c>LevelLoader.SetWalkable</c> fires that event
|
/// When a tower is placed or sold, <c>LevelLoader.SetWalkable</c> fires that event; the
|
||||||
/// and <see cref="OnPathsInvalidated"/> is relayed to all active enemies, which
|
/// service then enqueues every registered grounded enemy and recomputes them under a
|
||||||
/// each recompute their own path from their current tile.
|
/// per-frame time budget (<c>recomputeBudgetMs</c>), draining across frames so a maze
|
||||||
|
/// change with a full wave present never spikes a single frame.
|
||||||
///
|
///
|
||||||
/// <b>Goal tile set:</b> Built once on <c>Start</c> from
|
/// <b>Goal tile set:</b> Built once on <c>Start</c> from
|
||||||
/// <c>LevelLoader.LevelData.Goals[].TileArea</c>. Goal tiles never change at
|
/// <c>LevelLoader.LevelData.Goals[].TileArea</c>. Goal tiles never change at
|
||||||
|
|
@ -52,7 +54,10 @@ namespace TD.Gameplay
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fired on every peer when the walkability grid changes (tower placed/sold).
|
/// Fired on every peer when the walkability grid changes (tower placed/sold).
|
||||||
/// <see cref="EnemyMovement"/> subscribes per-instance to recompute its path.
|
/// Enemies no longer subscribe here — they register with the budgeted re-path
|
||||||
|
/// scheduler (<see cref="RegisterMover"/>) so recomputes spread across frames.
|
||||||
|
/// This event remains as an extension point for any non-enemy listener that wants
|
||||||
|
/// immediate notification of a maze change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event System.Action OnPathsInvalidated;
|
public event System.Action OnPathsInvalidated;
|
||||||
|
|
||||||
|
|
@ -79,6 +84,28 @@ namespace TD.Gameplay
|
||||||
private readonly Dictionary<Vector2Int, float> gScore = new Dictionary<Vector2Int, float>();
|
private readonly Dictionary<Vector2Int, float> gScore = new Dictionary<Vector2Int, float>();
|
||||||
private readonly SimplePriorityQueue openSet = new SimplePriorityQueue();
|
private readonly SimplePriorityQueue openSet = new SimplePriorityQueue();
|
||||||
|
|
||||||
|
// ----- Deferred re-path scheduler ---------------------------------
|
||||||
|
//
|
||||||
|
// Grounded enemies register here (flyers never re-path — baked grid). On a
|
||||||
|
// walkability change we enqueue every registered enemy and recompute a
|
||||||
|
// TIME-BUDGETED number of them per frame, draining the backlog over subsequent
|
||||||
|
// frames. Recomputing them all synchronously on the change frame spiked to ~0.5s
|
||||||
|
// with a full wave on this large grid whenever a tower was sold/placed — this
|
||||||
|
// caps the per-frame cost so no single frame ever hitches. The maze can only be
|
||||||
|
// opened (sell) or narrowed with a guaranteed remaining route (placement, BFS-
|
||||||
|
// validated), so an enemy briefly following its slightly-stale path for a few
|
||||||
|
// frames until its turn comes up is safe and visually negligible.
|
||||||
|
|
||||||
|
[Tooltip("Max wall-clock milliseconds spent recomputing enemy paths per frame " +
|
||||||
|
"after a maze change. The backlog drains over following frames; at least " +
|
||||||
|
"one enemy is always processed per frame so it converges. Lower = smoother " +
|
||||||
|
"but slower to fully update; higher = faster to update but larger frame cost.")]
|
||||||
|
[SerializeField] private float recomputeBudgetMs = 1.5f;
|
||||||
|
|
||||||
|
private readonly HashSet<EnemyMovement> movers = new HashSet<EnemyMovement>();
|
||||||
|
private readonly Queue<EnemyMovement> recomputeQueue = new Queue<EnemyMovement>();
|
||||||
|
private readonly HashSet<EnemyMovement> queued = new HashSet<EnemyMovement>();
|
||||||
|
|
||||||
// ----- Lifecycle --------------------------------------------------
|
// ----- Lifecycle --------------------------------------------------
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
|
|
@ -115,6 +142,48 @@ namespace TD.Gameplay
|
||||||
loader.OnWalkabilityChanged -= HandleWalkabilityChanged;
|
loader.OnWalkabilityChanged -= HandleWalkabilityChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drains the deferred re-path backlog under a per-frame time budget. Runs on all
|
||||||
|
// peers, but the queue is only ever populated on the server (clients never register
|
||||||
|
// movers), so this is a no-op cost on clients. At least one enemy is processed per
|
||||||
|
// frame whenever the queue is non-empty, so it always converges.
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (recomputeQueue.Count == 0) return;
|
||||||
|
|
||||||
|
double startMs = Time.realtimeSinceStartupAsDouble * 1000.0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var mover = recomputeQueue.Dequeue();
|
||||||
|
if (!queued.Remove(mover)) continue; // unregistered/cancelled after enqueue
|
||||||
|
if (mover != null) mover.RecomputePath(); // reads the CURRENT grid state
|
||||||
|
}
|
||||||
|
while (recomputeQueue.Count > 0
|
||||||
|
&& Time.realtimeSinceStartupAsDouble * 1000.0 - startMs < recomputeBudgetMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Deferred re-path scheduler API -----------------------------
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a grounded enemy to be re-pathed (budgeted, over following frames)
|
||||||
|
/// whenever the maze changes. Flyers must NOT register — their baked-grid route
|
||||||
|
/// never changes. Called on the server from <see cref="EnemyMovement"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void RegisterMover(EnemyMovement mover)
|
||||||
|
{
|
||||||
|
if (mover != null) movers.Add(mover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes an enemy from the scheduler (on despawn). Any stale entry still sitting
|
||||||
|
/// in the pending queue is skipped when dequeued (the <c>queued</c> membership check).
|
||||||
|
/// </summary>
|
||||||
|
public void UnregisterMover(EnemyMovement mover)
|
||||||
|
{
|
||||||
|
if (mover == null) return;
|
||||||
|
movers.Remove(mover);
|
||||||
|
queued.Remove(mover);
|
||||||
|
}
|
||||||
|
|
||||||
// ----- Public API -------------------------------------------------
|
// ----- Public API -------------------------------------------------
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -471,7 +540,16 @@ namespace TD.Gameplay
|
||||||
|
|
||||||
private void HandleWalkabilityChanged()
|
private void HandleWalkabilityChanged()
|
||||||
{
|
{
|
||||||
|
// Notify any non-enemy listeners immediately (kept for API compatibility).
|
||||||
OnPathsInvalidated?.Invoke();
|
OnPathsInvalidated?.Invoke();
|
||||||
|
|
||||||
|
// Enqueue every registered grounded enemy for a budgeted, deferred recompute
|
||||||
|
// instead of recomputing them all on this frame. Already-queued enemies are
|
||||||
|
// deduped, so rapid successive maze changes just lengthen the drain rather
|
||||||
|
// than compounding into a spike.
|
||||||
|
foreach (var mover in movers)
|
||||||
|
if (queued.Add(mover))
|
||||||
|
recomputeQueue.Enqueue(mover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,15 @@ namespace TD.VFX
|
||||||
|
|
||||||
if (coinBurstPrefab != null)
|
if (coinBurstPrefab != null)
|
||||||
{
|
{
|
||||||
Instantiate(coinBurstPrefab, spawnPos, Quaternion.identity);
|
var instance = Instantiate(coinBurstPrefab, spawnPos, Quaternion.identity);
|
||||||
|
|
||||||
|
// Force the burst to play. The spawner's whole job is to play this effect, so
|
||||||
|
// it shouldn't depend on the prefab's ParticleSystem having "Play On Awake"
|
||||||
|
// ticked — if that's off, the effect would silently never appear. Harmless when
|
||||||
|
// Play On Awake is already on (a freshly instantiated one-shot just restarts at
|
||||||
|
// t=0). VFX Graph effects play from their own settings, so a null PS is fine.
|
||||||
|
var ps = instance.GetComponentInChildren<ParticleSystem>();
|
||||||
|
if (ps != null) ps.Play(withChildren: true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
30
Assets/_Project/Scripts/VFX/TransientEffect.cs
Normal file
30
Assets/_Project/Scripts/VFX/TransientEffect.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Assets/_Project/Scripts/VFX/TransientEffect.cs
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace TD.VFX
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Destroys its GameObject a fixed time after it spawns. Drop this onto any one-shot
|
||||||
|
/// effect prefab — a particle burst, a VFX Graph effect, a sound-only object — so it
|
||||||
|
/// cleans itself up without any bespoke code. Designed for prefabs spawned by
|
||||||
|
/// <see cref="SellEffectSpawner"/> (and any future effect spawner) that fire once and
|
||||||
|
/// should disappear.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>Shuriken shortcut:</b> a plain <see cref="ParticleSystem"/> can instead self-destroy
|
||||||
|
/// with no component at all — set <i>Main → Stop Action → Destroy</i> and make sure Looping
|
||||||
|
/// is off. Use this component for <b>VFX Graph</b> effects (which have no Stop Action) or for
|
||||||
|
/// prefabs that combine several systems and need one predictable lifetime.
|
||||||
|
///
|
||||||
|
/// <para>Set <see cref="lifetime"/> to comfortably exceed the effect's visible duration so it
|
||||||
|
/// isn't cut off mid-play.</para>
|
||||||
|
/// </remarks>
|
||||||
|
public class TransientEffect : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Tooltip("Seconds after spawn before this GameObject destroys itself. Set it a little " +
|
||||||
|
"longer than the effect's visible length so nothing is cut off.")]
|
||||||
|
[SerializeField, Min(0f)] private float lifetime = 1.5f;
|
||||||
|
|
||||||
|
private void Start() => Destroy(gameObject, lifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/_Project/Scripts/VFX/TransientEffect.cs.meta
Normal file
2
Assets/_Project/Scripts/VFX/TransientEffect.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 739f3145f66002d47850b603164d07b8
|
||||||
Loading…
Add table
Add a link
Reference in a new issue