2.5d test
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 323f26e40e9341819b74539ab1205e3a
|
||||
folderAsset: yes
|
||||
@@ -0,0 +1,71 @@
|
||||
%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: Base
|
||||
m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
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}
|
||||
- _MainTex:
|
||||
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
|
||||
- _BlendOp: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _QueueOffset: 0
|
||||
- _SampleGI: 0
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.45, g: 0.77, b: 0.82, a: 1}
|
||||
- _Color: {r: 0.45, g: 0.77, b: 0.82, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &6200414664021777617
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27680ecdcb0d9c4408af43beb753bd64
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
%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: ColliderBoundary
|
||||
m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
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}
|
||||
- _MainTex:
|
||||
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
|
||||
- _BlendOp: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _QueueOffset: 0
|
||||
- _SampleGI: 0
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.38, g: 0.71, b: 0.55, a: 1}
|
||||
- _Color: {r: 0.37999994, g: 0.71, b: 0.54999995, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &7555243765816861683
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96ad047810b9ae846bc2f54bf2968c31
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
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: de640fe3d0db1804a85f9fc8f5cadab6, type: 3}
|
||||
m_Name: ComparisonRenderer
|
||||
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalRendererData
|
||||
debugShaders:
|
||||
debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3}
|
||||
hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3}
|
||||
probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, type: 3}
|
||||
probeVolumeResources:
|
||||
probeVolumeDebugShader: {fileID: 0}
|
||||
probeVolumeFragmentationDebugShader: {fileID: 0}
|
||||
probeVolumeOffsetDebugShader: {fileID: 0}
|
||||
probeVolumeSamplingDebugShader: {fileID: 0}
|
||||
probeSamplingDebugMesh: {fileID: 0}
|
||||
probeSamplingDebugTexture: {fileID: 0}
|
||||
probeVolumeBlendStatesCS: {fileID: 0}
|
||||
m_RendererFeatures: []
|
||||
m_RendererFeatureMap:
|
||||
m_UseNativeRenderPass: 0
|
||||
xrSystemData: {fileID: 0}
|
||||
postProcessData: {fileID: 0}
|
||||
m_AssetVersion: 3
|
||||
m_PrepassLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_OpaqueLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_TransparentLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_DefaultStencilState:
|
||||
overrideStencilState: 0
|
||||
stencilReference: 0
|
||||
stencilCompareFunction: 8
|
||||
passOperation: 2
|
||||
failOperation: 0
|
||||
zFailOperation: 0
|
||||
m_ShadowTransparentReceive: 1
|
||||
m_RenderingMode: 0
|
||||
m_DepthPrimingMode: 0
|
||||
m_CopyDepthMode: 1
|
||||
m_DepthAttachmentFormat: 0
|
||||
m_DepthTextureFormat: 0
|
||||
m_AccurateGbufferNormals: 0
|
||||
m_IntermediateTextureMode: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05aadf49be3030546aafc018b71af214
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
Shader "BumpCombat/Comparison Sprite Depth"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex ("Existing sprite texture", 2D) = "white" {}
|
||||
_BaseColor ("Tint", Color) = (1,1,1,1)
|
||||
_Cutoff ("Alpha clip", Range(0,1)) = 0.4
|
||||
[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Source blend", Float) = 1
|
||||
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Destination blend", Float) = 0
|
||||
[Toggle] _ZWrite ("Depth write", Float) = 1
|
||||
}
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="TransparentCutout" "Queue"="AlphaTest" }
|
||||
Pass
|
||||
{
|
||||
Name "ComparisonUnlit"
|
||||
Tags { "LightMode"="UniversalForward" }
|
||||
Cull Off
|
||||
ZTest LEqual
|
||||
ZWrite [_ZWrite]
|
||||
Blend [_SrcBlend] [_DstBlend]
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment Frag
|
||||
#pragma multi_compile_instancing
|
||||
#include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/Core2D.hlsl"
|
||||
struct Attributes
|
||||
{
|
||||
float3 positionOS : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
half4 color : COLOR;
|
||||
UNITY_VERTEX_INPUT_INSTANCE_ID
|
||||
};
|
||||
struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; };
|
||||
TEXTURE2D(_MainTex);
|
||||
SAMPLER(sampler_MainTex);
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
half4 _BaseColor;
|
||||
half _Cutoff;
|
||||
CBUFFER_END
|
||||
Varyings Vert(Attributes input)
|
||||
{
|
||||
Varyings output;
|
||||
UNITY_SETUP_INSTANCE_ID(input);
|
||||
SetUpSpriteInstanceProperties();
|
||||
input.positionOS = UnityFlipSprite(input.positionOS, unity_SpriteProps.xy);
|
||||
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
|
||||
output.uv = input.uv;
|
||||
output.color = input.color;
|
||||
return output;
|
||||
}
|
||||
half4 Frag(Varyings input) : SV_Target
|
||||
{
|
||||
half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * input.color * _BaseColor;
|
||||
clip(color.a - _Cutoff);
|
||||
return color;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4aadfb1143d5401f9af3e4eb53746d77
|
||||
@@ -0,0 +1,71 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-4571542010389989135
|
||||
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
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: CycloneHitBoundary
|
||||
m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
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}
|
||||
- _MainTex:
|
||||
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
|
||||
- _BlendOp: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _QueueOffset: 0
|
||||
- _SampleGI: 0
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 1, g: 0.72, b: 0.18, a: 1}
|
||||
- _Color: {r: 1, g: 0.72, b: 0.17999998, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eee57eaf919f7b347b8bc513b3804b89
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
%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: Ground
|
||||
m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
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}
|
||||
- _MainTex:
|
||||
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
|
||||
- _BlendOp: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _QueueOffset: 0
|
||||
- _SampleGI: 0
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.12, g: 0.18, b: 0.2, a: 1}
|
||||
- _Color: {r: 0.11999995, g: 0.17999998, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &7274897316288786868
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ab5f733c0b0e8a428f90a7aa1f33d1b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
%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: GroundShadow
|
||||
m_Shader: {fileID: 4800000, guid: 4aadfb1143d5401f9af3e4eb53746d77, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: 3000
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Cutoff: 0.001
|
||||
- _DstBlend: 10
|
||||
- _SrcBlend: 5
|
||||
- _ZWrite: 0
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0, g: 0, b: 0, a: 0.35}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7262b6d429f43844ebd26ac4dfcf2cd6
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &-8376117332504685327
|
||||
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
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 8
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Route
|
||||
m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
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}
|
||||
- _MainTex:
|
||||
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
|
||||
- _BlendOp: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _QueueOffset: 0
|
||||
- _SampleGI: 0
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.22, g: 0.34, b: 0.31, a: 1}
|
||||
- _Color: {r: 0.21999997, g: 0.33999997, b: 0.30999997, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65a6bf5b1c33b80489a703cc8fe91efb
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
%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: SpriteCutout
|
||||
m_Shader: {fileID: 4800000, guid: 4aadfb1143d5401f9af3e4eb53746d77, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords:
|
||||
- _ZWRITE_ON
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: 2450
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Cutoff: 0.4
|
||||
- _DstBlend: 0
|
||||
- _SrcBlend: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30482bc8080f8594a92573c7e91e7d44
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
%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: SpriteTransparent
|
||||
m_Shader: {fileID: 4800000, guid: 4aadfb1143d5401f9af3e4eb53746d77, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: 3000
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_LockedProperties:
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Ints: []
|
||||
m_Floats:
|
||||
- _Cutoff: 0.001
|
||||
- _DstBlend: 10
|
||||
- _SrcBlend: 5
|
||||
- _ZWrite: 0
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 1, g: 1, b: 1, a: 0.55}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfafc04d69db1824191ef8b87cf1295a
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
%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: Wall
|
||||
m_Shader: {fileID: 4800000, guid: 650dd9526735d5b46b79224bc6e94025, type: 3}
|
||||
m_Parent: {fileID: 0}
|
||||
m_ModifiedSerializedProperties: 0
|
||||
m_ValidKeywords: []
|
||||
m_InvalidKeywords: []
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
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}
|
||||
- _MainTex:
|
||||
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
|
||||
- _BlendOp: 0
|
||||
- _Cull: 2
|
||||
- _Cutoff: 0.5
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _QueueOffset: 0
|
||||
- _SampleGI: 0
|
||||
- _SrcBlend: 1
|
||||
- _SrcBlendAlpha: 1
|
||||
- _Surface: 0
|
||||
- _XRMotionVectorsPass: 1
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _BaseColor: {r: 0.48, g: 0.51, b: 0.48, a: 1}
|
||||
- _Color: {r: 0.47999996, g: 0.50999993, b: 0.47999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
--- !u!114 &7177300302112874480
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b982e9bfd1eb9ce4c80b628b9b2b6c91
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 549 B |
@@ -0,0 +1,195 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d35e75312d3418ea2f58a33521b2ebb
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: Eyeball Monster_Beam-Clean_0
|
||||
- first:
|
||||
213: 21300002
|
||||
second: Eyeball Monster_Beam-Clean_1
|
||||
- first:
|
||||
213: 21300004
|
||||
second: Eyeball Monster_Beam-Clean_2
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: Eyeball Monster_Beam-Clean_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 3004ea3b219640239711f5b7b844b6c8
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Eyeball Monster_Beam-Clean_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 100
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: aaf17a191ca741768f84425aa8f5ef0a
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Eyeball Monster_Beam-Clean_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 200
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: f78d7efc0c5f4f7289005c5130b031dd
|
||||
internalID: 21300004
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 3822a7d3d5c64ffe9cd15697e04623c2
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
Eyeball Monster_Beam-Clean_0: 21300000
|
||||
Eyeball Monster_Beam-Clean_1: 21300002
|
||||
Eyeball Monster_Beam-Clean_2: 21300004
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 715 B |
@@ -0,0 +1,195 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fbe849b409d46c682a64fe95eaf2e8a
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: Eyeball Monster_Beam_0
|
||||
- first:
|
||||
213: 21300002
|
||||
second: Eyeball Monster_Beam_1
|
||||
- first:
|
||||
213: 21300004
|
||||
second: Eyeball Monster_Beam_2
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: Eyeball Monster_Beam_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 55045f58609a4086830a0380c190ac68
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Eyeball Monster_Beam_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 100
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: ce5cb0d22820462184cee78fe93d0542
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Eyeball Monster_Beam_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 200
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: e357145bdc1c4e0cb6379be3ab7a4e10
|
||||
internalID: 21300004
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 03e7593104da4d908b5a05f6ec08bb41
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
Eyeball Monster_Beam_0: 21300000
|
||||
Eyeball Monster_Beam_1: 21300002
|
||||
Eyeball Monster_Beam_2: 21300004
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 889 B |
@@ -0,0 +1,273 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c04e8afcbb04d1c838e3d7b8ed4f052
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 499323505792861607
|
||||
second: Blizzard-Wave-v1_0
|
||||
- first:
|
||||
213: 634351548535658973
|
||||
second: Blizzard-Wave-v1_1
|
||||
- first:
|
||||
213: 753672340007810633
|
||||
second: Blizzard-Wave-v1_2
|
||||
- first:
|
||||
213: 136483814077234494
|
||||
second: Blizzard-Wave-v1_3
|
||||
- first:
|
||||
213: 237513938985374204
|
||||
second: Blizzard-Wave-v1_4
|
||||
- first:
|
||||
213: 238326498544796154
|
||||
second: Blizzard-Wave-v1_5
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: Blizzard-Wave-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 224300080edb492d88531ed4614fde1f
|
||||
internalID: 499323505792861607
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Blizzard-Wave-v1_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 100
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 1248c85d569747eab70540ccd056a016
|
||||
internalID: 634351548535658973
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Blizzard-Wave-v1_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 200
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 9befe5b709184ebfad67cd6d02081fde
|
||||
internalID: 753672340007810633
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Blizzard-Wave-v1_3
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 300
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 0b12349417254db2aecdb9f222fb4046
|
||||
internalID: 136483814077234494
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Blizzard-Wave-v1_4
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 400
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: a36b8f4a1187414994d28eb6ade93b39
|
||||
internalID: 237513938985374204
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: Blizzard-Wave-v1_5
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 500
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: b825c6cd44144880ae70b27a6326cf2d
|
||||
internalID: 238326498544796154
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: a0051b77a00549349642a6abc5dbeeb7
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
Blizzard-Wave-v1_0: 499323505792861607
|
||||
Blizzard-Wave-v1_1: 634351548535658973
|
||||
Blizzard-Wave-v1_2: 753672340007810633
|
||||
Blizzard-Wave-v1_3: 136483814077234494
|
||||
Blizzard-Wave-v1_4: 237513938985374204
|
||||
Blizzard-Wave-v1_5: 238326498544796154
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 340 B |
@@ -0,0 +1,143 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2617f115eff24ff185a333b7112250a5
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 97093349900010584
|
||||
second: Shard-Blizzard-v1_0
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: Shard-Blizzard-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 100
|
||||
height: 100
|
||||
alignment: 9
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: f6f836e9ee53460391035373f7943c1a
|
||||
internalID: 97093349900010584
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 33e656590cae43ca8d0440f93b760ccb
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
Shard-Blizzard-v1_0: 97093349900010584
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 944 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 993 B After Width: | Height: | Size: 1.1 KiB |
@@ -59,12 +59,26 @@ MonoBehaviour:
|
||||
TowerRange: 5.5
|
||||
DirectTowerDamage: 10
|
||||
DirectTowerAttackInterval: 0.75
|
||||
FireT4BeamSecondTierSeconds: 1
|
||||
FireT4BeamMaximumTierSeconds: 2
|
||||
FireT4BeamBaseDamageMultiplier: 0.4
|
||||
FireT4BeamSecondTierMultiplier: 1.5
|
||||
FireT4BeamMaximumMultiplier: 2
|
||||
FireT4BeamResetAfterSeconds: 1
|
||||
FireT4BeamSecondTierWidthMultiplier: 1.5
|
||||
FireT4BeamMaximumWidthMultiplier: 2
|
||||
SummonDamage: 6
|
||||
SummonAttackInterval: 0.9
|
||||
SummonUnitCount: 2
|
||||
SummonUnitMaximumHealth: 30
|
||||
SummonUnitCollisionRadius: 0.32
|
||||
SummonUnitRespawnSeconds: 3
|
||||
SummonAreaRadius: 2.25
|
||||
SummonUnitMoveSpeed: 2.5
|
||||
SummonAttackRange: 1.2
|
||||
SummonBodySlamImpactFrame: 3
|
||||
SummonBodySlamContactMargin: 0.06
|
||||
SummonBodySlamSpeedMultiplier: 2
|
||||
LightningDamage: 6
|
||||
LightningInterval: 1.1
|
||||
LightningRadius: 1.3
|
||||
@@ -72,6 +86,8 @@ MonoBehaviour:
|
||||
ColdInterval: 1.2
|
||||
ColdSlowFraction: 0.35
|
||||
ColdSlowSeconds: 1.5
|
||||
ColdProjectileBurstInterval: 0.12
|
||||
FreezeboltProjectileCount: 5
|
||||
SeismicDamage: 2
|
||||
SeismicInterval: 2
|
||||
SeismicStunSeconds: 0.45
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d600a0ef58439f4480db67b07e4d705
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
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: 5366a103ee5d47b79aa3feb1b3ca3f4d, type: 3}
|
||||
m_Name: ComparisonSettings
|
||||
m_EditorClassIdentifier: BumpCombat.Runtime::BumpCombat.Constants.Defense25DComparisonSettings
|
||||
GroundY: 0
|
||||
MapHalfExtents: {x: 10.5, y: 5.5}
|
||||
PlayerSpawn: {x: -5, y: -2}
|
||||
TowerPosition: {x: 1, y: 1.8}
|
||||
Route:
|
||||
- {x: -9, y: 0}
|
||||
- {x: -4, y: 0}
|
||||
- {x: -2, y: 2.4}
|
||||
- {x: 4, y: 2.4}
|
||||
- {x: 9, y: 0}
|
||||
SummonSlots:
|
||||
- {x: -0.6, y: -0.9}
|
||||
- {x: 0.6, y: -0.9}
|
||||
PortalImmunity: 0
|
||||
CameraPitch: 50
|
||||
CameraSize: 6.5
|
||||
SouthBoundaryRenderHeightFraction: 0.4
|
||||
PlayerRadius: 0.275
|
||||
PlayerColliderOffset: {x: 0, y: -0.075}
|
||||
PlayerVisualScale: 1.25
|
||||
PlayerFootOffset: -0.29296875
|
||||
EnemyRadius: 0.275
|
||||
EnemyColliderOffset: {x: 0, y: -0.35}
|
||||
EnemyVisualScale: 1.25
|
||||
EnemyFootOffset: -0.4
|
||||
EnemyBodyCenterCorrectionPixels: 3
|
||||
SummonVisualScale: 1.1
|
||||
SummonFootOffset: -0.30937502
|
||||
ActorHeight: 0.85
|
||||
MovementSkin: 0.002
|
||||
ContactSensorMargin: 0.025
|
||||
Enemy: {fileID: 11400000, guid: 7f938c46038bd274896aa9402aadad11, type: 2}
|
||||
EnemyAttackClip: {fileID: 7400000, guid: 9b8ced6bd5e565a4da13186c14ccbfbb, type: 2}
|
||||
Cyclone: {fileID: 11400000, guid: 0cd054022c7b4781bc8fa2d35121a0de, type: 2}
|
||||
PlayerArt:
|
||||
Idle:
|
||||
- {fileID: 8592238648091824432, guid: cf5ee5ee251612148a5a3cceb77e3e09, type: 3}
|
||||
- {fileID: 1250004922889109162, guid: cf5ee5ee251612148a5a3cceb77e3e09, type: 3}
|
||||
- {fileID: -5028620923284104887, guid: cf5ee5ee251612148a5a3cceb77e3e09, type: 3}
|
||||
- {fileID: 2022252980215292256, guid: cf5ee5ee251612148a5a3cceb77e3e09, type: 3}
|
||||
- {fileID: -2599092321403200321, guid: cf5ee5ee251612148a5a3cceb77e3e09, type: 3}
|
||||
- {fileID: 5488730835254470119, guid: cf5ee5ee251612148a5a3cceb77e3e09, type: 3}
|
||||
Walk:
|
||||
- {fileID: -510655448263928137, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: 3479404773181816430, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: -2283166651885375184, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: 3210028164496969578, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: -2276603239645387600, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: 4573818798471642851, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: 8383016689336500281, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
- {fileID: 4467538224481176286, guid: 6f1776132693af24c9999cf2e7c518c7, type: 3}
|
||||
Attack:
|
||||
- {fileID: 21300000, guid: 6c25effeb9ec57a4a20f65d23369edad, type: 3}
|
||||
- {fileID: 21300001, guid: 6c25effeb9ec57a4a20f65d23369edad, type: 3}
|
||||
- {fileID: 21300002, guid: 6c25effeb9ec57a4a20f65d23369edad, type: 3}
|
||||
- {fileID: 21300003, guid: 6c25effeb9ec57a4a20f65d23369edad, type: 3}
|
||||
- {fileID: 21300004, guid: 6c25effeb9ec57a4a20f65d23369edad, type: 3}
|
||||
- {fileID: 21300005, guid: 6c25effeb9ec57a4a20f65d23369edad, type: 3}
|
||||
Hurt:
|
||||
- {fileID: 457371520115034197, guid: 5940cfeb72cce444fb5a7bf7c5373eca, type: 3}
|
||||
- {fileID: 3404584728210486745, guid: 5940cfeb72cce444fb5a7bf7c5373eca, type: 3}
|
||||
- {fileID: 7129331961366924758, guid: 5940cfeb72cce444fb5a7bf7c5373eca, type: 3}
|
||||
- {fileID: 8197722601435448183, guid: 5940cfeb72cce444fb5a7bf7c5373eca, type: 3}
|
||||
- {fileID: 7704259262516875320, guid: 5940cfeb72cce444fb5a7bf7c5373eca, type: 3}
|
||||
Death:
|
||||
- {fileID: 2811593804088240790, guid: 70b33e6f7545e6f47a46385d74aaaf83, type: 3}
|
||||
- {fileID: -7828097450302239583, guid: 70b33e6f7545e6f47a46385d74aaaf83, type: 3}
|
||||
- {fileID: 361189222234674287, guid: 70b33e6f7545e6f47a46385d74aaaf83, type: 3}
|
||||
- {fileID: 9116452327212125477, guid: 70b33e6f7545e6f47a46385d74aaaf83, type: 3}
|
||||
EnemyArt:
|
||||
Idle:
|
||||
- {fileID: 8488328654572304352, guid: 07f9aef17c7d24e4399f66b931c1c71f, type: 3}
|
||||
- {fileID: 3789744679490320462, guid: 07f9aef17c7d24e4399f66b931c1c71f, type: 3}
|
||||
- {fileID: 2240755264892537048, guid: 07f9aef17c7d24e4399f66b931c1c71f, type: 3}
|
||||
- {fileID: 2034087598805426463, guid: 07f9aef17c7d24e4399f66b931c1c71f, type: 3}
|
||||
- {fileID: 8271520994771697968, guid: 07f9aef17c7d24e4399f66b931c1c71f, type: 3}
|
||||
- {fileID: 5978591873814285882, guid: 07f9aef17c7d24e4399f66b931c1c71f, type: 3}
|
||||
Walk:
|
||||
- {fileID: -319509429510461966, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: -5704084491819767634, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: 8477824621842717129, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: 3051669912371892857, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: 7539542233003775106, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: 2788143195985776107, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: -119383558338234881, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
- {fileID: 1567333206898370016, guid: dbe397424a57ec34991241bdec4068bf, type: 3}
|
||||
Attack:
|
||||
- {fileID: -4931541286253664743, guid: 017709d2bd1cb5d489eb197051a78427, type: 3}
|
||||
- {fileID: 6703666654972841543, guid: 017709d2bd1cb5d489eb197051a78427, type: 3}
|
||||
- {fileID: 5374936872560645210, guid: 017709d2bd1cb5d489eb197051a78427, type: 3}
|
||||
- {fileID: -5799585484308707375, guid: 017709d2bd1cb5d489eb197051a78427, type: 3}
|
||||
- {fileID: 4528416531530387699, guid: 017709d2bd1cb5d489eb197051a78427, type: 3}
|
||||
- {fileID: 3317778627300228566, guid: 017709d2bd1cb5d489eb197051a78427, type: 3}
|
||||
Hurt:
|
||||
- {fileID: 933120629603562079, guid: 142640e0a095a714c82d152b666b9884, type: 3}
|
||||
- {fileID: -494057186547109401, guid: 142640e0a095a714c82d152b666b9884, type: 3}
|
||||
- {fileID: 2785721821212994034, guid: 142640e0a095a714c82d152b666b9884, type: 3}
|
||||
- {fileID: -5188259159075104939, guid: 142640e0a095a714c82d152b666b9884, type: 3}
|
||||
Death:
|
||||
- {fileID: 8610279347594985942, guid: 7d5e7c2c790e42346a9f2ebbf23461e4, type: 3}
|
||||
- {fileID: -3134359789022687535, guid: 7d5e7c2c790e42346a9f2ebbf23461e4, type: 3}
|
||||
- {fileID: 8399632352712144361, guid: 7d5e7c2c790e42346a9f2ebbf23461e4, type: 3}
|
||||
- {fileID: -2072202752667699138, guid: 7d5e7c2c790e42346a9f2ebbf23461e4, type: 3}
|
||||
- {fileID: 7143198583629916187, guid: 7d5e7c2c790e42346a9f2ebbf23461e4, type: 3}
|
||||
- {fileID: 4093069291906818420, guid: 7d5e7c2c790e42346a9f2ebbf23461e4, type: 3}
|
||||
CutoutMaterial: {fileID: 2100000, guid: 30482bc8080f8594a92573c7e91e7d44, type: 2}
|
||||
TransparentMaterial: {fileID: 2100000, guid: dfafc04d69db1824191ef8b87cf1295a, type: 2}
|
||||
GroundMaterial: {fileID: 2100000, guid: 0ab5f733c0b0e8a428f90a7aa1f33d1b, type: 2}
|
||||
WallMaterial: {fileID: 2100000, guid: b982e9bfd1eb9ce4c80b628b9b2b6c91, type: 2}
|
||||
PathMaterial: {fileID: 2100000, guid: 65a6bf5b1c33b80489a703cc8fe91efb, type: 2}
|
||||
ShadowMaterial: {fileID: 2100000, guid: 7262b6d429f43844ebd26ac4dfcf2cd6, type: 2}
|
||||
BaseMaterial: {fileID: 2100000, guid: 27680ecdcb0d9c4408af43beb753bd64, type: 2}
|
||||
BoundaryMaterial: {fileID: 2100000, guid: 96ad047810b9ae846bc2f54bf2968c31, type: 2}
|
||||
HitBoundaryMaterial: {fileID: 2100000, guid: eee57eaf919f7b347b8bc513b3804b89, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc0c99c602cf67042a4305ccbdcc8c2f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 314 B |
@@ -0,0 +1,221 @@
|
||||
fileFormatVersion: 2
|
||||
guid: daaafe9016d35f219b2a03842f2b90cc
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: TowerImpact-Cold-Clean-v1_0
|
||||
- first:
|
||||
213: 21300001
|
||||
second: TowerImpact-Cold-Clean-v1_1
|
||||
- first:
|
||||
213: 21300002
|
||||
second: TowerImpact-Cold-Clean-v1_2
|
||||
- first:
|
||||
213: 21300003
|
||||
second: TowerImpact-Cold-Clean-v1_3
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Cold-Clean-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 62887aabfb2455d6bf9ba33d6ab6ee61
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Cold-Clean-v1_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 64
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 1ef8844679b85eb38e016775dabbeb5c
|
||||
internalID: 21300001
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Cold-Clean-v1_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 128
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: df4addd380a154b4ac0aede7503160b3
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Cold-Clean-v1_3
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 192
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 2114fea9861751a0a42fd244ace547fd
|
||||
internalID: 21300003
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: b4ebc1971c8c555ba6bce87e94485543
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
TowerImpact-Cold-Clean-v1_0: 21300000
|
||||
TowerImpact-Cold-Clean-v1_1: 21300001
|
||||
TowerImpact-Cold-Clean-v1_2: 21300002
|
||||
TowerImpact-Cold-Clean-v1_3: 21300003
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 305 B |
@@ -0,0 +1,221 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 136d80cc377d54c59d4cab2156b087c0
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: TowerImpact-Direct-Clean-v1_0
|
||||
- first:
|
||||
213: 21300001
|
||||
second: TowerImpact-Direct-Clean-v1_1
|
||||
- first:
|
||||
213: 21300002
|
||||
second: TowerImpact-Direct-Clean-v1_2
|
||||
- first:
|
||||
213: 21300003
|
||||
second: TowerImpact-Direct-Clean-v1_3
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Direct-Clean-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 48954119e7eb5350add1264f92577ff3
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Direct-Clean-v1_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 64
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 6969f47957f454d78c21068a664e7911
|
||||
internalID: 21300001
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Direct-Clean-v1_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 128
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: efb88da9b27c593890a8c47611dc8457
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Direct-Clean-v1_3
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 192
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 72ac250e7eec588db4555181fd91dd57
|
||||
internalID: 21300003
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 21ab6e30d13251e19433ff928affccb8
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
TowerImpact-Direct-Clean-v1_0: 21300000
|
||||
TowerImpact-Direct-Clean-v1_1: 21300001
|
||||
TowerImpact-Direct-Clean-v1_2: 21300002
|
||||
TowerImpact-Direct-Clean-v1_3: 21300003
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 313 B |
@@ -0,0 +1,221 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a71b5120f2055fea4503f9620cf7d26
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: TowerImpact-Lightning-Clean-v1_0
|
||||
- first:
|
||||
213: 21300001
|
||||
second: TowerImpact-Lightning-Clean-v1_1
|
||||
- first:
|
||||
213: 21300002
|
||||
second: TowerImpact-Lightning-Clean-v1_2
|
||||
- first:
|
||||
213: 21300003
|
||||
second: TowerImpact-Lightning-Clean-v1_3
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Lightning-Clean-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: cd9ac527673f5338bdc9dccb1e306861
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Lightning-Clean-v1_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 64
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 34dea063251f5665aa3d45a5878ffa9d
|
||||
internalID: 21300001
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Lightning-Clean-v1_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 128
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 7e65f69e56405469811e382ff9dacd41
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Lightning-Clean-v1_3
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 192
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 956bbc5f98175616893445c3b4599b23
|
||||
internalID: 21300003
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: b9d5f7a5978651938d2a349901de74b6
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
TowerImpact-Lightning-Clean-v1_0: 21300000
|
||||
TowerImpact-Lightning-Clean-v1_1: 21300001
|
||||
TowerImpact-Lightning-Clean-v1_2: 21300002
|
||||
TowerImpact-Lightning-Clean-v1_3: 21300003
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 316 B |
@@ -0,0 +1,221 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be2fa36cda1f59498a70c2eca27cff28
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: TowerImpact-Seismic-Clean-v1_0
|
||||
- first:
|
||||
213: 21300001
|
||||
second: TowerImpact-Seismic-Clean-v1_1
|
||||
- first:
|
||||
213: 21300002
|
||||
second: TowerImpact-Seismic-Clean-v1_2
|
||||
- first:
|
||||
213: 21300003
|
||||
second: TowerImpact-Seismic-Clean-v1_3
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Clean-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 9708f16c72a05b94b510c07e866e9269
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Clean-v1_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 64
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 60535391499d593eb24ee76f37793b0d
|
||||
internalID: 21300001
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Clean-v1_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 128
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 875de6a3cda757c786c7d8e63a69b0e7
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Clean-v1_3
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 192
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: 7905bdb7ff9e592a9933c01c744b578a
|
||||
internalID: 21300003
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 1201601e4d9c55189882aaed409487e7
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
TowerImpact-Seismic-Clean-v1_0: 21300000
|
||||
TowerImpact-Seismic-Clean-v1_1: 21300001
|
||||
TowerImpact-Seismic-Clean-v1_2: 21300002
|
||||
TowerImpact-Seismic-Clean-v1_3: 21300003
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
After Width: | Height: | Size: 310 B |
@@ -0,0 +1,221 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7246d13b8b525c719cb6e7a2e3fef6f2
|
||||
TextureImporter:
|
||||
internalIDToNameTable:
|
||||
- first:
|
||||
213: 21300000
|
||||
second: TowerImpact-Seismic-Normal-v1_0
|
||||
- first:
|
||||
213: 21300001
|
||||
second: TowerImpact-Seismic-Normal-v1_1
|
||||
- first:
|
||||
213: 21300002
|
||||
second: TowerImpact-Seismic-Normal-v1_2
|
||||
- first:
|
||||
213: 21300003
|
||||
second: TowerImpact-Seismic-Normal-v1_3
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 2
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 32
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 4
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 4
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites:
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Normal-v1_0
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: fefcecbb2c555d6c977e3582d697f9ca
|
||||
internalID: 21300000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Normal-v1_1
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 64
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: b7aefd876ef65b27bacaef971ecc6c57
|
||||
internalID: 21300001
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Normal-v1_2
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 128
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: adc350e7b0dd5b95b944af14ea921d9b
|
||||
internalID: 21300002
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
- serializedVersion: 2
|
||||
name: TowerImpact-Seismic-Normal-v1_3
|
||||
rect:
|
||||
serializedVersion: 2
|
||||
x: 192
|
||||
y: 0
|
||||
width: 64
|
||||
height: 64
|
||||
alignment: 0
|
||||
pivot: {x: 0.5, y: 0.5}
|
||||
border: {x: 0, y: 0, z: 0, w: 0}
|
||||
customData:
|
||||
outline: []
|
||||
physicsShape: []
|
||||
tessellationDetail: 0
|
||||
bones: []
|
||||
spriteID: e55b0e212cf0547bbc9f8b19f51b228e
|
||||
internalID: 21300003
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
outline: []
|
||||
customData:
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 75d8c3fb144e5c81887a154320c7f74f
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spriteCustomMetadata:
|
||||
entries: []
|
||||
nameFileIdTable:
|
||||
TowerImpact-Seismic-Normal-v1_0: 21300000
|
||||
TowerImpact-Seismic-Normal-v1_1: 21300001
|
||||
TowerImpact-Seismic-Normal-v1_2: 21300002
|
||||
TowerImpact-Seismic-Normal-v1_3: 21300003
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0496bc9cbfa2af48bbfa51b8b378ee2
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -4,6 +4,7 @@ using BumpCombat.Core;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Defense;
|
||||
using BumpCombat.UI;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -81,6 +82,7 @@ namespace BumpCombat.Combat
|
||||
private readonly HashSet<GameObject> blockedBumpVisuals = new();
|
||||
private readonly HashSet<GameObject> guardBlockImpactVisuals = new();
|
||||
private readonly HashSet<GameObject> artifactImpactVisuals = new();
|
||||
private readonly HashSet<GameObject> towerImpactVisuals = new();
|
||||
private readonly HashSet<GameObject> playerHurtImpactVisuals = new();
|
||||
private readonly Dictionary<string, Sprite[]> artifactImpactFrameCache = new();
|
||||
private SpriteRenderer playerRenderer;
|
||||
@@ -129,6 +131,7 @@ namespace BumpCombat.Combat
|
||||
public int ActiveBlockedBumpVisualCount => blockedBumpVisuals.Count;
|
||||
public int ActiveGuardBlockImpactVisualCount => guardBlockImpactVisuals.Count;
|
||||
public int ActiveArtifactImpactVisualCount => artifactImpactVisuals.Count;
|
||||
public int ActiveTowerImpactVisualCount => towerImpactVisuals.Count;
|
||||
public int ActivePlayerHurtImpactVisualCount => playerHurtImpactVisuals.Count;
|
||||
|
||||
public static string GetPlayerHurtImpactResourcePath()
|
||||
@@ -189,6 +192,69 @@ namespace BumpCombat.Combat
|
||||
+ "-v1";
|
||||
}
|
||||
|
||||
public static string GetTowerImpactResourcePath(DefenseTowerKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => "Combat/TowerImpact-Direct-Clean-v1",
|
||||
DefenseTowerKind.Summon => null,
|
||||
DefenseTowerKind.Lightning => "Combat/TowerImpact-Lightning-Clean-v1",
|
||||
DefenseTowerKind.Cold => "Combat/TowerImpact-Cold-Clean-v1",
|
||||
DefenseTowerKind.Seismic => "Combat/TowerImpact-Seismic-Clean-v1",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public static void ShowTowerImpact(
|
||||
DefenseTowerKind kind, EnemyController target, Vector2 hitPosition)
|
||||
{
|
||||
CombatFeedback feedback = GetActive();
|
||||
if (feedback != null && feedback.isActiveAndEnabled)
|
||||
{
|
||||
feedback.CreateTowerImpact(kind, target, hitPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateTowerImpact(
|
||||
DefenseTowerKind kind, EnemyController target, Vector2 hitPosition)
|
||||
{
|
||||
string resourcePath = GetTowerImpactResourcePath(kind);
|
||||
if (string.IsNullOrEmpty(resourcePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite[] frames = LoadBumpImpactFrames(resourcePath);
|
||||
if (frames == null || frames.Length != BumpImpactFrameCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject root = new($"Tower Impact {kind}");
|
||||
root.transform.localScale = Vector3.one * 2f;
|
||||
root.transform.position = hitPosition;
|
||||
SpriteRenderer renderer = root.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = frames[0];
|
||||
renderer.color = Color.white;
|
||||
SpriteRenderer targetRenderer = target != null
|
||||
? target.GetComponent<SpriteRenderer>() : null;
|
||||
if (targetRenderer != null)
|
||||
{
|
||||
renderer.sortingLayerID = targetRenderer.sortingLayerID;
|
||||
renderer.sortingOrder = targetRenderer.sortingOrder
|
||||
+ DefenseTowerArtBounds.TowerHitSortingOffset;
|
||||
}
|
||||
else if (playerRenderer != null)
|
||||
{
|
||||
renderer.sortingLayerID = playerRenderer.sortingLayerID;
|
||||
renderer.sortingOrder = playerRenderer.sortingOrder
|
||||
+ DefenseTowerArtBounds.TowerHitSortingOffset;
|
||||
}
|
||||
RegisterTransient(root, false);
|
||||
towerImpactVisuals.Add(root);
|
||||
StartCoroutine(AnimateArtifactImpact(root, renderer, frames));
|
||||
}
|
||||
|
||||
public static float GetArtifactImpactFrameDuration(int frameIndex)
|
||||
{
|
||||
return GetBumpImpactFrameDuration(frameIndex);
|
||||
@@ -526,6 +592,7 @@ namespace BumpCombat.Combat
|
||||
ClearBlockedBumpVisuals();
|
||||
ClearGuardBlockImpactVisuals();
|
||||
ClearArtifactImpactVisuals();
|
||||
ClearTowerImpactVisuals();
|
||||
ClearPlayerHurtImpactVisuals();
|
||||
ClearCycloneAfterimages();
|
||||
}
|
||||
@@ -1894,6 +1961,22 @@ namespace BumpCombat.Combat
|
||||
artifactImpactVisuals.Clear();
|
||||
}
|
||||
|
||||
private void ClearTowerImpactVisuals()
|
||||
{
|
||||
if (towerImpactVisuals.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject[] visuals = new GameObject[towerImpactVisuals.Count];
|
||||
towerImpactVisuals.CopyTo(visuals);
|
||||
foreach (GameObject visual in visuals)
|
||||
{
|
||||
DestroyTransient(visual);
|
||||
}
|
||||
towerImpactVisuals.Clear();
|
||||
}
|
||||
|
||||
private void ClearPlayerHurtImpactVisuals()
|
||||
{
|
||||
if (playerHurtImpactVisuals.Count == 0)
|
||||
@@ -2398,6 +2481,7 @@ namespace BumpCombat.Combat
|
||||
blockedBumpVisuals.RemoveWhere(visual => visual == null);
|
||||
guardBlockImpactVisuals.RemoveWhere(visual => visual == null);
|
||||
artifactImpactVisuals.RemoveWhere(visual => visual == null);
|
||||
towerImpactVisuals.RemoveWhere(visual => visual == null);
|
||||
playerHurtImpactVisuals.RemoveWhere(visual => visual == null);
|
||||
return;
|
||||
}
|
||||
@@ -2406,6 +2490,7 @@ namespace BumpCombat.Combat
|
||||
blockedBumpVisuals.Remove(transientObject);
|
||||
guardBlockImpactVisuals.Remove(transientObject);
|
||||
artifactImpactVisuals.Remove(transientObject);
|
||||
towerImpactVisuals.Remove(transientObject);
|
||||
playerHurtImpactVisuals.Remove(transientObject);
|
||||
int instanceId = transientObject.GetInstanceID();
|
||||
activeTransientIds.Remove(instanceId);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Combat
|
||||
{
|
||||
// Authored ground geometry shared by the XY controller and XZ comparison.
|
||||
// Sprite frames and decoration consume elapsed time independently of damage ticks.
|
||||
public static class CycloneGeometry
|
||||
{
|
||||
public const float PixelsPerUnit = 32f;
|
||||
public const float ShadowLocalY = -7.5f / PixelsPerUnit;
|
||||
public const float CenterOffsetY = 5f / PixelsPerUnit;
|
||||
public const float FrameSeconds = .05f;
|
||||
|
||||
public static float VisualScale(float range, bool charged) => range / (charged ? 1.25f : .85f);
|
||||
|
||||
public static Vector2 Radii(float range, bool charged) =>
|
||||
(charged ? new Vector2(46f, 18f) : new Vector2(38.0625f, 15.03125f))
|
||||
/ PixelsPerUnit * VisualScale(range, charged);
|
||||
|
||||
public static Vector2 CenterOffset(float range, bool charged, float rootScaleY) =>
|
||||
Vector2.up * (ShadowLocalY * rootScaleY + CenterOffsetY * VisualScale(range, charged));
|
||||
|
||||
public static int ScheduledHitCount(float duration, float interval) =>
|
||||
duration <= 0f || interval <= 0f ? 1
|
||||
: Mathf.Max(1, Mathf.FloorToInt((duration + .0001f) / interval) + 1);
|
||||
|
||||
public static float NormalizedDistance(Vector2 offset, Vector2 radii) =>
|
||||
offset.x * offset.x / (radii.x * radii.x)
|
||||
+ offset.y * offset.y / (radii.y * radii.y);
|
||||
|
||||
// Keep the original closest-point and 64 boundary samples, including tolerances.
|
||||
// The adapter supplies Collider2D.ClosestPoint or an XZ circular footprint.
|
||||
public static bool Overlaps(Vector2 center, Vector2 radii, Func<Vector2, Vector2> closestPoint)
|
||||
{
|
||||
if (NormalizedDistance(closestPoint(center) - center, radii) <= 1.0001f) return true;
|
||||
const int samples = 64;
|
||||
for (int i = 0; i < samples; i++)
|
||||
{
|
||||
float angle = Mathf.PI * 2f * i / samples;
|
||||
Vector2 point = center + new Vector2(Mathf.Cos(angle) * radii.x, Mathf.Sin(angle) * radii.y);
|
||||
if ((closestPoint(point) - point).sqrMagnitude <= .000001f) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a2c269eb2424df68bfbd7d08ffee36d
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Player;
|
||||
|
||||
namespace BumpCombat.Constants
|
||||
{
|
||||
/// <summary>Existing authored feedback timings and paths used by the 2.5D presentation.</summary>
|
||||
public static class Defense25DCombatFeedbackConstants
|
||||
{
|
||||
public const string ChargeResourceRoot = "Artifacts/ThreeColor-v1/Charge/";
|
||||
public const string ChargeAuraSuffix = "-Aura-v1";
|
||||
public const string ChargeReadyAuraSuffix = "-ReadyAura-v1";
|
||||
public const string ChargeFlashSuffix = "-Flash-v1";
|
||||
public const string ChargeReleaseSuffix = "-Release-v1";
|
||||
public const string BlockedBumpResource = "Combat/BlockedBump-Wizard-v1";
|
||||
public const float PulseGatherDuration = .1f;
|
||||
public const float InteractionStreakDuration = .11f;
|
||||
|
||||
public static IReadOnlyList<float> BumpFrames { get; } = new[]
|
||||
{
|
||||
CombatFeedback.GetBumpImpactFrameDuration(0),
|
||||
CombatFeedback.GetBumpImpactFrameDuration(1),
|
||||
CombatFeedback.GetBumpImpactFrameDuration(2),
|
||||
CombatFeedback.GetBumpImpactFrameDuration(3),
|
||||
};
|
||||
|
||||
public static IReadOnlyList<float> ArtifactFrames { get; } = new[]
|
||||
{
|
||||
CombatFeedback.GetArtifactImpactFrameDuration(0),
|
||||
CombatFeedback.GetArtifactImpactFrameDuration(1),
|
||||
CombatFeedback.GetArtifactImpactFrameDuration(2),
|
||||
CombatFeedback.GetArtifactImpactFrameDuration(3),
|
||||
};
|
||||
|
||||
public static IReadOnlyList<float> PlayerHurtFrames { get; } = new[]
|
||||
{
|
||||
CombatFeedback.GetPlayerHurtImpactFrameDuration(0),
|
||||
CombatFeedback.GetPlayerHurtImpactFrameDuration(1),
|
||||
CombatFeedback.GetPlayerHurtImpactFrameDuration(2),
|
||||
};
|
||||
|
||||
public static IReadOnlyList<float> ChargeAuraFrames { get; } = new[]
|
||||
{ .06f, .06f, .06f, .06f, .06f, .06f };
|
||||
public static IReadOnlyList<float> ChargeFlashFrames { get; } = new[]
|
||||
{ .03f, .04f, .04f, .03f };
|
||||
public static IReadOnlyList<float> ChargeReleaseFrames { get; } = new[]
|
||||
{ .03f, .04f, .04f, .04f, .04f, .05f };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2a7338f9d1144eb9ab4bc9b7d29c26f
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Constants
|
||||
{
|
||||
public static class Defense25DCombatPopupConstants
|
||||
{
|
||||
public const int MaximumConcurrentPopups = 12;
|
||||
public const float LifetimeSeconds = 0.6f;
|
||||
public const float RisePixelsAt1080p = 40f;
|
||||
public const float ReferenceScreenHeight = 1080f;
|
||||
public const float ReferenceInternalHeight = 360f;
|
||||
public const float FontPixelsAt1080p = 30f;
|
||||
public const float OutlinePixels = 1f;
|
||||
public const float PopupWidthInternal = 100f;
|
||||
public const float PopupHeightInternal = 20f;
|
||||
|
||||
public static readonly Color PlayerDamageColor =
|
||||
new(244f / 255f, 117f / 255f, 112f / 255f, 1f);
|
||||
public static readonly Color LaunchColor = new(0.95f, 0.55f, 1f);
|
||||
public static readonly Color BackHitColor = new(1f, 0.75f, 0.2f);
|
||||
public static readonly Color SideHitColor = new(0.65f, 1f, 0.3f);
|
||||
public static readonly Color OutlineColor = new(18f / 255f, 23f / 255f, 34f / 255f,
|
||||
230f / 255f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bdb0ea9afc64f17a25d84d1cf8a7a31
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using BumpCombat.Defense;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Constants
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class ComparisonSpriteFrames
|
||||
{
|
||||
public Sprite[] Idle = Array.Empty<Sprite>();
|
||||
public Sprite[] Walk = Array.Empty<Sprite>();
|
||||
public Sprite[] Attack = Array.Empty<Sprite>();
|
||||
public Sprite[] Hurt = Array.Empty<Sprite>();
|
||||
public Sprite[] Death = Array.Empty<Sprite>();
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "BumpCombat/Constants/Defense 2.5D Comparison")]
|
||||
public sealed class Defense25DComparisonSettings : ScriptableObject
|
||||
{
|
||||
public const float SummonBodyPixelsPerUnit = 32f;
|
||||
public const float SlimeBodyCoreDiameterPixels = 11f;
|
||||
public const float GiantSlimeBodyCoreDiameterPixels = 22f;
|
||||
public const float LavaSlimeBodyCoreDiameterPixels = 13f;
|
||||
public static readonly Vector2 SlimeBodyCenterFromFootPixels = new(-1.5f, 7.5f);
|
||||
public static readonly Vector2 GiantSlimeBodyCenterFromFootPixels = new(-3f, 17f);
|
||||
public static readonly Vector2 LavaSlimeBodyCenterFromFootPixels = new(-2f, 8.5f);
|
||||
|
||||
[Header("Independent comparison layout; not final balance")]
|
||||
public float GroundY;
|
||||
public Vector2 MapHalfExtents = new(10.5f, 5.5f);
|
||||
public Vector2 PlayerSpawn = new(-5f, -2f);
|
||||
public Vector2 TowerPosition = new(1f, 1.8f);
|
||||
public Vector2[] Route = { new(-9f, 0f), new(-4f, 0f), new(-2f, 2.4f), new(4f, 2.4f), new(9f, 0f) };
|
||||
public Vector2[] SummonSlots = { new(-.6f, -.9f), new(.6f, -.9f) };
|
||||
public DefenseTowerKind PortalImmunity = DefenseTowerKind.Direct;
|
||||
public float CameraPitch = 50f;
|
||||
public float CameraSize = 6.5f;
|
||||
[Range(.1f, 1f)] public float SouthBoundaryRenderHeightFraction = .4f;
|
||||
|
||||
[Header("XZ footprints baked from existing prefab XY colliders")]
|
||||
public float PlayerRadius = .275f;
|
||||
public Vector2 PlayerColliderOffset = new(0f, -.075f);
|
||||
public float PlayerVisualScale = 1.25f;
|
||||
public float PlayerFootOffset = -7.5f / 32f * 1.25f;
|
||||
public float EnemyRadius = .275f;
|
||||
public Vector2 EnemyColliderOffset = new(0f, -.35f);
|
||||
public float EnemyVisualScale = 1.25f;
|
||||
public float EnemyFootOffset = -.4f;
|
||||
public float EnemyBodyCenterCorrectionPixels = 3f;
|
||||
public float SummonVisualScale = 1.1f;
|
||||
public float SummonFootOffset = -9f / 32f * 1.1f;
|
||||
public float ActorHeight = .85f;
|
||||
public float MovementSkin = .002f;
|
||||
public float ContactSensorMargin = .025f;
|
||||
|
||||
// Unnamed, otherwise unused layers in the baseline project; no TagManager edits.
|
||||
public const int TerrainLayer = 9;
|
||||
public const int ActorLayer = 10;
|
||||
public const int TerrainMask = 1 << TerrainLayer;
|
||||
public const int ActorMask = 1 << ActorLayer;
|
||||
|
||||
[Header("Existing definitions and art; originals are not edited")]
|
||||
public EnemyDefinition Enemy;
|
||||
public AnimationClip EnemyAttackClip;
|
||||
public ActiveArtifactDefinition Cyclone;
|
||||
public ComparisonSpriteFrames PlayerArt = new();
|
||||
public ComparisonSpriteFrames EnemyArt = new();
|
||||
public Material CutoutMaterial;
|
||||
public Material TransparentMaterial;
|
||||
public Material GroundMaterial;
|
||||
public Material WallMaterial;
|
||||
public Material PathMaterial;
|
||||
public Material ShadowMaterial;
|
||||
public Material BaseMaterial;
|
||||
public Material BoundaryMaterial;
|
||||
public Material HitBoundaryMaterial;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5366a103ee5d47b79aa3feb1b3ca3f4d
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace BumpCombat.Constants
|
||||
{
|
||||
/// <summary>Sample cadence copied from the authored Swordsman animation clips.</summary>
|
||||
public static class Defense25DPlayerAnimationConstants
|
||||
{
|
||||
public const float IdleFrameSeconds = 1f / 8f;
|
||||
public const float WalkFrameSeconds = 1f / 10f;
|
||||
public const float HurtFrameSeconds = 1f / 10f;
|
||||
public const float DeathFrameSeconds = 1f / 8f;
|
||||
public const float DashFrameSeconds = 1f / 60f;
|
||||
public const float BumpFrameSeconds = 1f / 25f;
|
||||
public const float ArtifactUseFrameSeconds = 1f / 20f;
|
||||
public const float NonPlayerDeathFallbackDuration = 0.5f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ee47e8d5cf8ceb40bf54520dc0485a1
|
||||
@@ -72,12 +72,26 @@ namespace BumpCombat.Constants
|
||||
[Min(0.1f)] public float TowerRange = 5.5f;
|
||||
[Min(0.1f)] public float DirectTowerDamage = 10f;
|
||||
[Min(0.05f)] public float DirectTowerAttackInterval = 0.75f;
|
||||
[Min(0f)] public float FireT4BeamSecondTierSeconds = 1f;
|
||||
[Min(0f)] public float FireT4BeamMaximumTierSeconds = 2f;
|
||||
[Min(0f)] public float FireT4BeamBaseDamageMultiplier = 0.4f;
|
||||
[Min(1f)] public float FireT4BeamSecondTierMultiplier = 1.5f;
|
||||
[Min(1f)] public float FireT4BeamMaximumMultiplier = 2f;
|
||||
[Min(0f)] public float FireT4BeamResetAfterSeconds = 1f;
|
||||
[Min(1f)] public float FireT4BeamSecondTierWidthMultiplier = 1.5f;
|
||||
[Min(1f)] public float FireT4BeamMaximumWidthMultiplier = 2f;
|
||||
[Min(0.1f)] public float SummonDamage = 6f;
|
||||
[Min(0.05f)] public float SummonAttackInterval = 0.9f;
|
||||
[Min(1)] public int SummonUnitCount = 2;
|
||||
[Min(1f)] public float SummonUnitMaximumHealth = 30f;
|
||||
[Min(0.05f)] public float SummonUnitCollisionRadius = 0.32f;
|
||||
[Min(0f)] public float SummonUnitRespawnSeconds = 3f;
|
||||
[Min(0.1f)] public float SummonAreaRadius = 2.25f;
|
||||
[Min(0.1f)] public float SummonUnitMoveSpeed = 2.5f;
|
||||
[Min(0.1f)] public float SummonAttackRange = 1.2f;
|
||||
[Range(0, 5)] public int SummonBodySlamImpactFrame = 3;
|
||||
[Min(0f)] public float SummonBodySlamContactMargin = 0.06f;
|
||||
[Min(1f)] public float SummonBodySlamSpeedMultiplier = 2f;
|
||||
[Header("Six tower family comparison values")]
|
||||
[Min(0.1f)] public float LightningDamage = 6f;
|
||||
[Min(0.05f)] public float LightningInterval = 1.1f;
|
||||
@@ -86,6 +100,8 @@ namespace BumpCombat.Constants
|
||||
[Min(0.05f)] public float ColdInterval = 1.2f;
|
||||
[Min(0f)] public float ColdSlowFraction = 0.35f;
|
||||
[Min(0f)] public float ColdSlowSeconds = 1.5f;
|
||||
[Min(0.01f)] public float ColdProjectileBurstInterval = 0.12f;
|
||||
[Min(1)] public int FreezeboltProjectileCount = 5;
|
||||
[Min(0.1f)] public float SeismicDamage = 2f;
|
||||
[Min(0.05f)] public float SeismicInterval = 2f;
|
||||
[Min(0f)] public float SeismicStunSeconds = 0.45f;
|
||||
@@ -156,4 +172,41 @@ namespace BumpCombat.Constants
|
||||
: fallback;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefenseTowerHitTestLayout
|
||||
{
|
||||
public static readonly DefenseTowerKind[] KindsByPad =
|
||||
{
|
||||
DefenseTowerKind.Direct,
|
||||
DefenseTowerKind.Summon,
|
||||
DefenseTowerKind.Cold,
|
||||
DefenseTowerKind.Lightning,
|
||||
DefenseTowerKind.Empowering,
|
||||
DefenseTowerKind.Seismic,
|
||||
};
|
||||
|
||||
public static readonly Vector2[] DummyPositionsByPad =
|
||||
{
|
||||
new(-2.25f, 2.95f),
|
||||
new(3.95f, -3.2f),
|
||||
new(-4.9f, -1.4f),
|
||||
new(4.9f, 1.4f),
|
||||
new(1f, 2.5f),
|
||||
new(0f, -1.4f),
|
||||
};
|
||||
|
||||
public static Vector2 GetDummyPosition(DefenseTowerKind kind)
|
||||
{
|
||||
for (int i = 0; i < KindsByPad.Length; i++)
|
||||
{
|
||||
if (KindsByPad[i] == kind)
|
||||
{
|
||||
return DummyPositionsByPad[i];
|
||||
}
|
||||
}
|
||||
|
||||
return Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -96,11 +96,13 @@ namespace BumpCombat.Defense
|
||||
["Effects/Fire/Meteor-Impact"] = new[] { 40, 50, 60, 70, 60, 60, 60, 80 },
|
||||
["Sources/BlackKnight-NoSkull"] = new[] { 125, 125, 125, 125, 125, 125, 125 },
|
||||
["Effects/Fire/Flamethrower"] = new[] { 80, 80, 80, 70, 65, 65, 70 },
|
||||
["Effects/Fire/Eyeball Monster_Beam-Clean"] = new[] { 100, 100, 120 },
|
||||
["Effects/Lightning/T1-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
|
||||
["Effects/Lightning/T2-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
|
||||
["Effects/Lightning/T3-Bolt"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
|
||||
["Effects/Lightning/Arc"] = new[] { 30, 40, 30, 30, 40, 40, 40, 50 },
|
||||
["Effects/Lightning/ImpactWave"] = new[] { 40, 40, 50, 50, 60, 60 },
|
||||
["Effects/Ice/Blizzard-Wave-v1"] = new[] { 40, 40, 50, 50, 60, 60 },
|
||||
["Effects/Lightning/LightningStorm"] = new[] { 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 },
|
||||
["Effects/Ice/T1-Volley"] = new[] { 125 },
|
||||
["Effects/Ice/T2-Volley"] = new[] { 125 },
|
||||
@@ -167,6 +169,58 @@ namespace BumpCombat.Constants
|
||||
/// <summary>Offline pixel measurements for non-readable defense art and original shadows.</summary>
|
||||
public static class DefenseTowerArtBounds
|
||||
{
|
||||
public const float TowerBillboardScale = 0.72f * 2.1f;
|
||||
public static readonly Vector2 LightningStormGroundCenterPixels = new(61f, 34f);
|
||||
public const float LightningStormGroundRadiusPixels = 55.362442f;
|
||||
public const float SeismicGroundMarkerWidthPixels = 18f;
|
||||
public const int FireBeamStartX = 1;
|
||||
public const int FireBeamEndX = 99;
|
||||
public const float FireBeamCenterY = 49.5f;
|
||||
public const float FireBeamIgnitionSeconds = 0.1f;
|
||||
public const float FireBeamExtinguishSeconds = 0.12f;
|
||||
public const float FireT4BeamBaseWidthMultiplier = 1f;
|
||||
public static Vector2 StaffTipPixels(bool attack, int frame)
|
||||
{
|
||||
frame = Mathf.Clamp(frame, 0, 5);
|
||||
if (attack)
|
||||
{
|
||||
return frame switch
|
||||
{
|
||||
0 => new Vector2(56.5f, 44.5f),
|
||||
1 => new Vector2(57.5f, 43.5f),
|
||||
_ => new Vector2(58.5f, 42.5f),
|
||||
};
|
||||
}
|
||||
return frame < 2
|
||||
? new Vector2(56.5f, 44.5f)
|
||||
: new Vector2(56.5f, 43.5f);
|
||||
}
|
||||
public const int TowerHitSortingOffset = 102;
|
||||
public const int BlizzardShardCount = 8;
|
||||
public const float BlizzardLaunchIntervalSeconds = 0.04f;
|
||||
public const float BlizzardLandingSeconds = 0.24f;
|
||||
public const float BlizzardLastLandingSeconds = BlizzardLandingSeconds
|
||||
+ (BlizzardShardCount - 1) * BlizzardLaunchIntervalSeconds;
|
||||
public const float BlizzardLandingRadiusFraction = 0.65f;
|
||||
public const float BlizzardRangeAlpha = 0.15f;
|
||||
public const float BlizzardImpactRadiusAlpha = 0.18f;
|
||||
public const float BlizzardShardScale = 0.52f;
|
||||
public const float BlizzardTrailScale = 0.3f;
|
||||
public const float BlizzardTrailAlpha = 0.32f;
|
||||
public const float BlizzardTrailOffset = 0.12f;
|
||||
public const float BlizzardImpactScale = 0.16f;
|
||||
public const float BlizzardWaveScale = 0.7f;
|
||||
// Maximum visible half-extents measured from Blizzard-Wave-v1's alpha frames.
|
||||
public const float BlizzardWaveHalfWidthPixels = 22.5f;
|
||||
public const float BlizzardWaveHalfHeightPixels = 11f;
|
||||
public const float BlizzardWavePixelsPerUnit = 32f;
|
||||
public const float BlizzardWaveRadiusX = BlizzardWaveHalfWidthPixels
|
||||
/ BlizzardWavePixelsPerUnit * BlizzardWaveScale;
|
||||
public const float BlizzardWaveRadiusY = BlizzardWaveHalfHeightPixels
|
||||
/ BlizzardWavePixelsPerUnit * BlizzardWaveScale;
|
||||
public const float BlizzardDropHeight = 1.2f;
|
||||
public const float BlizzardDropHeightStep = 0.22f;
|
||||
|
||||
private static readonly Vector2Int[] LightningBeamBounds =
|
||||
{
|
||||
new(8, 56), new(8, 89), new(8, 89), new(8, 89),
|
||||
@@ -181,6 +235,14 @@ namespace BumpCombat.Constants
|
||||
public static Vector2Int LightningBeamXBounds(int frame) =>
|
||||
LightningBeamBounds[Mathf.Clamp(frame, 0, LightningBeamBounds.Length - 1)];
|
||||
|
||||
public static Vector2 BlizzardLandingOffset(int index, float radius)
|
||||
{
|
||||
float fraction = BlizzardLandingRadiusFraction
|
||||
* Mathf.Sqrt((index + 0.5f) / BlizzardShardCount);
|
||||
float angle = index * 2.39996323f;
|
||||
return radius * fraction * new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
|
||||
}
|
||||
|
||||
public static Vector2 ImpactWaveCenterPixels(int frame, Sprite sprite)
|
||||
{
|
||||
return frame >= ImpactWaveCenters.Length
|
||||
|
||||
@@ -103,6 +103,7 @@ namespace BumpCombat.Core
|
||||
|
||||
public event Action<CharacterStat> OnStatChanged;
|
||||
|
||||
public bool IsMinion { get; set; }
|
||||
public abstract float MoveSpeed { get; }
|
||||
public abstract float MaxHealth { get; }
|
||||
|
||||
|
||||
@@ -407,6 +407,7 @@ namespace BumpCombat.Core
|
||||
|
||||
public void RestartToTitle()
|
||||
{
|
||||
BumpCombat.Defense.DefensePrototypeController.CancelTowerHitTestRun();
|
||||
runModeOnNextLoad = null;
|
||||
skipTitleOnNextLoad = false;
|
||||
Time.timeScale = 1f;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c2979e1dc2744f2b23a56fac198fc91
|
||||
folderAsset: yes
|
||||
@@ -0,0 +1,453 @@
|
||||
using System;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public enum ComparisonActorKind { Player, Enemy, Summon }
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class Defense25DActor : CharacterModel
|
||||
{
|
||||
private Defense25DComparison world;
|
||||
private CapsuleCollider sensor;
|
||||
private readonly RaycastHit[] movementHits = new RaycastHit[64];
|
||||
private Vector2 knockbackVelocity;
|
||||
private Vector2 knockbackStartPosition;
|
||||
private float knockbackRemaining;
|
||||
private float hurtUntil;
|
||||
private float invulnerableUntil;
|
||||
private float shieldAmount, shieldExpiresAt;
|
||||
private float guardUntil, guardCooldownUntil;
|
||||
private bool guardHasBlocked;
|
||||
private bool knockbackCompletionPending;
|
||||
private float enemyMaximumHealthOverride;
|
||||
private Func<Vector2, Vector2> closestPoint;
|
||||
private Vector2 facing = Vector2.right;
|
||||
private Vector2 unflippedBodyCenterOffset;
|
||||
|
||||
public ComparisonActorKind Kind { get; private set; }
|
||||
public Rigidbody Body { get; private set; }
|
||||
public CapsuleCollider BodyCollider { get; private set; }
|
||||
public Defense25DSprite Visual { get; private set; }
|
||||
public Vector2 Facing
|
||||
{
|
||||
get => facing;
|
||||
set
|
||||
{
|
||||
bool wasLeft = facing.x < 0f;
|
||||
facing = value;
|
||||
if (BodyCollider != null && wasLeft != (facing.x < 0f)) ApplyColliderCenters();
|
||||
}
|
||||
}
|
||||
public Vector2 Position => Ground(Body.position);
|
||||
public Vector2 Center => Position + Offset + BodyCenterOffset;
|
||||
public float Radius { get; private set; }
|
||||
public Vector2 Offset { get; private set; }
|
||||
public Vector2 BodyCenterOffset => Facing.x < 0f
|
||||
? new Vector2(-unflippedBodyCenterOffset.x, unflippedBodyCenterOffset.y)
|
||||
: unflippedBodyCenterOffset;
|
||||
public float FootOffset { get; private set; }
|
||||
public float CurrentHealth { get; private set; }
|
||||
public float CurrentShield => world.GameTime < shieldExpiresAt ? shieldAmount : 0f;
|
||||
public bool IsAlive => isActiveAndEnabled && CurrentHealth > 0f;
|
||||
public bool IsHurt => world.GameTime < hurtUntil;
|
||||
public bool IsGuarding => Kind == ComparisonActorKind.Player && IsAlive
|
||||
&& world.GameTime < guardUntil;
|
||||
public float GuardCooldownRemaining => Mathf.Max(0f, guardCooldownUntil - world.GameTime);
|
||||
public float GuardReadinessNormalized => GameplayConstants.Current.Player.GuardCooldownDuration > 0f
|
||||
? 1f - Mathf.Clamp01(GuardCooldownRemaining / GameplayConstants.Current.Player.GuardCooldownDuration)
|
||||
: 1f;
|
||||
public bool HasKnockback => knockbackVelocity.sqrMagnitude > .0025f;
|
||||
public int DamageEvents { get; private set; }
|
||||
public int ContactCallbacks { get; private set; }
|
||||
public bool MovementBlocked { get; private set; }
|
||||
public Vector2 LastMove { get; private set; }
|
||||
public Collider LastMovementBlocker { get; private set; }
|
||||
public Func<Vector2, Vector2> ClosestPointFunction => closestPoint;
|
||||
|
||||
public override float MoveSpeed => Evaluate(CharacterStat.MoveSpeed, Kind switch
|
||||
{
|
||||
ComparisonActorKind.Player => GameplayConstants.Current.Player.BaseMoveSpeed,
|
||||
ComparisonActorKind.Enemy => world.Settings.Enemy.MoveSpeed,
|
||||
_ => GameplayConstants.Current.Defense.SummonUnitMoveSpeed,
|
||||
});
|
||||
|
||||
public override float MaxHealth => Evaluate(CharacterStat.MaxHealth, Kind switch
|
||||
{
|
||||
ComparisonActorKind.Player => GameplayConstants.Current.Player.BaseMaxHealth,
|
||||
ComparisonActorKind.Enemy => enemyMaximumHealthOverride > 0f
|
||||
? enemyMaximumHealthOverride : world.Settings.Enemy.MaxHealth,
|
||||
_ => GameplayConstants.Current.Defense.SummonUnitMaximumHealth,
|
||||
});
|
||||
|
||||
public static Vector2 Ground(Vector3 point) => new(point.x, point.z);
|
||||
public static Vector3 OnGround(Vector2 point, float y = 0f) => new(point.x, y, point.y);
|
||||
|
||||
public void Configure(Defense25DComparison owner, ComparisonActorKind kind)
|
||||
{
|
||||
world = owner;
|
||||
Kind = kind;
|
||||
IsMinion = kind == ComparisonActorKind.Summon;
|
||||
Defense25DComparisonSettings s = owner.Settings;
|
||||
Radius = kind == ComparisonActorKind.Player ? s.PlayerRadius
|
||||
: kind == ComparisonActorKind.Enemy ? s.EnemyRadius
|
||||
: s.SummonVisualScale * Defense25DComparisonSettings.SlimeBodyCoreDiameterPixels
|
||||
/ Defense25DComparisonSettings.SummonBodyPixelsPerUnit * .5f;
|
||||
Offset = kind == ComparisonActorKind.Player ? s.PlayerColliderOffset
|
||||
: kind == ComparisonActorKind.Enemy ? s.EnemyColliderOffset : Vector2.zero;
|
||||
Vector2 initialCenterPixels = kind == ComparisonActorKind.Enemy
|
||||
? new Vector2(0f, s.EnemyBodyCenterCorrectionPixels)
|
||||
: kind == ComparisonActorKind.Summon
|
||||
? Defense25DComparisonSettings.SlimeBodyCenterFromFootPixels
|
||||
: Vector2.zero;
|
||||
float initialScale = kind == ComparisonActorKind.Player ? s.PlayerVisualScale
|
||||
: kind == ComparisonActorKind.Enemy ? s.EnemyVisualScale : s.SummonVisualScale;
|
||||
float initialHeight = kind == ComparisonActorKind.Summon ? 2f * Radius : Mathf.Max(s.ActorHeight, 2f * Radius);
|
||||
Vector2 initialScreenCenter = initialCenterPixels
|
||||
* (kind == ComparisonActorKind.Enemy
|
||||
? 2f * s.CameraSize / Defense25DView.Height
|
||||
: initialScale / Defense25DComparisonSettings.SummonBodyPixelsPerUnit);
|
||||
Camera camera = owner.View != null ? owner.View.Camera : null;
|
||||
Quaternion projectionRotation = BodyProjectionRotation(camera, s.CameraPitch);
|
||||
Vector3 cameraUp = projectionRotation * Vector3.up;
|
||||
if (kind == ComparisonActorKind.Enemy)
|
||||
initialScreenCenter.y += cameraUp.y * initialHeight * .5f;
|
||||
unflippedBodyCenterOffset = kind == ComparisonActorKind.Player
|
||||
? Vector2.zero
|
||||
: ScreenCenterOffset(initialScreenCenter, initialHeight * .5f, camera, s.CameraPitch);
|
||||
FootOffset = kind == ComparisonActorKind.Player ? s.PlayerFootOffset
|
||||
: kind == ComparisonActorKind.Enemy ? s.EnemyFootOffset : s.SummonFootOffset;
|
||||
gameObject.layer = Defense25DComparisonSettings.ActorLayer;
|
||||
Body = gameObject.AddComponent<Rigidbody>();
|
||||
Body.isKinematic = true;
|
||||
Body.useGravity = false;
|
||||
Body.constraints = RigidbodyConstraints.FreezePositionY | RigidbodyConstraints.FreezeRotation;
|
||||
Body.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
Body.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
BodyCollider = gameObject.AddComponent<CapsuleCollider>();
|
||||
BodyCollider.radius = Radius;
|
||||
BodyCollider.height = kind == ComparisonActorKind.Summon ? 2f * Radius : Mathf.Max(s.ActorHeight, 2f * Radius);
|
||||
ApplyColliderCenters();
|
||||
sensor = gameObject.AddComponent<CapsuleCollider>();
|
||||
sensor.center = BodyCollider.center;
|
||||
sensor.radius = Radius + s.ContactSensorMargin;
|
||||
sensor.height = BodyCollider.height + 2f * s.ContactSensorMargin;
|
||||
sensor.isTrigger = true;
|
||||
closestPoint = ClosestGroundPoint;
|
||||
Visual = gameObject.AddComponent<Defense25DSprite>();
|
||||
Visual.Configure(world, this);
|
||||
ResetAt(Ground(transform.position));
|
||||
}
|
||||
|
||||
public void ResetAt(Vector2 position)
|
||||
{
|
||||
foreach (AppliedStatModifier modifier in GetAppliedModifiers()) RemoveModifiersFromSource(modifier.SourceId);
|
||||
gameObject.SetActive(true);
|
||||
enabled = true;
|
||||
CurrentHealth = MaxHealth;
|
||||
shieldAmount = shieldExpiresAt = 0f;
|
||||
guardUntil = guardCooldownUntil = 0f;
|
||||
guardHasBlocked = false;
|
||||
DamageEvents = ContactCallbacks = 0;
|
||||
hurtUntil = invulnerableUntil = 0f;
|
||||
knockbackVelocity = LastMove = Vector2.zero;
|
||||
knockbackRemaining = 0f;
|
||||
knockbackCompletionPending = false;
|
||||
MovementBlocked = false;
|
||||
Facing = Vector2.right;
|
||||
Body.position = OnGround(position, world.Settings.GroundY);
|
||||
Body.rotation = Quaternion.identity;
|
||||
transform.position = Body.position;
|
||||
BodyCollider.enabled = sensor.enabled = true;
|
||||
Visual.ResetVisual();
|
||||
}
|
||||
|
||||
public void SetEnemyMaximumHealth(float maximumHealth)
|
||||
{
|
||||
if (Kind == ComparisonActorKind.Enemy)
|
||||
enemyMaximumHealthOverride = Mathf.Max(0f, maximumHealth);
|
||||
}
|
||||
|
||||
public void SetSummonVariantScale(float scale, float coreDiameterPixels, Vector2 bodyCenterFromFootPixels)
|
||||
{
|
||||
if (Kind != ComparisonActorKind.Summon) return;
|
||||
float pixelsPerUnit = Defense25DComparisonSettings.SummonBodyPixelsPerUnit;
|
||||
Radius = coreDiameterPixels * scale / pixelsPerUnit * .5f;
|
||||
unflippedBodyCenterOffset = ScreenCenterOffset(bodyCenterFromFootPixels * (scale / pixelsPerUnit), Radius,
|
||||
world.View != null ? world.View.Camera : null, world.Settings.CameraPitch);
|
||||
BodyCollider.radius = Radius;
|
||||
BodyCollider.height = 2f * Radius;
|
||||
ApplyColliderCenters();
|
||||
sensor.radius = Radius + world.Settings.ContactSensorMargin;
|
||||
sensor.height = BodyCollider.height + 2f * world.Settings.ContactSensorMargin;
|
||||
}
|
||||
|
||||
private void ApplyColliderCenters()
|
||||
{
|
||||
Vector3 center = new(Offset.x + BodyCenterOffset.x, BodyCollider.height * .5f,
|
||||
Offset.y + BodyCenterOffset.y);
|
||||
BodyCollider.center = center;
|
||||
if (sensor != null) sensor.center = center;
|
||||
}
|
||||
|
||||
private static Vector2 ScreenCenterOffset(Vector2 screenOffset, float colliderCenterY,
|
||||
Camera camera, float cameraPitch)
|
||||
{
|
||||
Quaternion rotation = BodyProjectionRotation(camera, cameraPitch);
|
||||
Vector3 right = rotation * Vector3.right;
|
||||
Vector3 up = rotation * Vector3.up;
|
||||
float targetRight = screenOffset.x;
|
||||
float targetUp = screenOffset.y - up.y * colliderCenterY;
|
||||
float determinant = right.x * up.z - right.z * up.x;
|
||||
return new Vector2(
|
||||
(targetRight * up.z - right.z * targetUp) / determinant,
|
||||
(right.x * targetUp - targetRight * up.x) / determinant);
|
||||
}
|
||||
|
||||
private static Quaternion BodyProjectionRotation(Camera camera, float cameraPitch)
|
||||
{
|
||||
Quaternion fallback = Quaternion.Euler(cameraPitch, 0f, 0f);
|
||||
if (camera == null) return fallback;
|
||||
Quaternion current = camera.transform.rotation;
|
||||
Vector3 right = current * Vector3.right;
|
||||
Vector3 up = current * Vector3.up;
|
||||
float determinant = right.x * up.z - right.z * up.x;
|
||||
return Mathf.Abs(determinant) > .0001f ? current : fallback;
|
||||
}
|
||||
|
||||
public Vector2 ClosestGroundPoint(Vector2 point) => Center + Vector2.ClampMagnitude(point - Center, Radius);
|
||||
public float GapTo(Defense25DActor other) => Vector2.Distance(Center, other.Center) - Radius - other.Radius;
|
||||
|
||||
public bool TakeDamage(float rawDamage, Vector2 direction, float knockback = 0f,
|
||||
EnemyDamageSource source = EnemyDamageSource.Player, bool ordinaryBump = false)
|
||||
{
|
||||
return TakeDamageWithResult(rawDamage, direction, knockback, source, ordinaryBump, out _);
|
||||
}
|
||||
|
||||
public bool TakeDamageWithResult(float rawDamage, Vector2 direction, float knockback,
|
||||
EnemyDamageSource source, bool ordinaryBump, out float actualDamage)
|
||||
{
|
||||
actualDamage = 0f;
|
||||
if (!IsAlive || !world.IsRunning || world.Paused
|
||||
|| (Kind == ComparisonActorKind.Summon && !world.TowersOperational)) return false;
|
||||
Defense25DEnemy enemy = Kind == ComparisonActorKind.Enemy
|
||||
? GetComponent<Defense25DEnemy>() : null;
|
||||
float incoming = CalculateIncomingDamage(rawDamage);
|
||||
if (enemy != null) incoming = enemy.ApplyDamageTakenEffects(incoming, ordinaryBump);
|
||||
float applied = DamageCalculator.FinalizeDamage(incoming);
|
||||
if (applied <= 0f) return false;
|
||||
if (Kind == ComparisonActorKind.Player && IsGuarding)
|
||||
{
|
||||
if (!guardHasBlocked)
|
||||
{
|
||||
guardHasBlocked = true;
|
||||
PlayerConstants player = GameplayConstants.Current.Player;
|
||||
float recharge = player.GuardCooldownDuration
|
||||
* Mathf.Clamp01(player.GuardSuccessCooldownRechargePercent / 100f);
|
||||
guardCooldownUntil = Mathf.Max(world.GameTime, guardCooldownUntil - recharge);
|
||||
}
|
||||
world.CombatFeedback?.NotifyGuardImpact(direction);
|
||||
return false;
|
||||
}
|
||||
if (Kind == ComparisonActorKind.Player && world.GameTime < invulnerableUntil)
|
||||
return false;
|
||||
enemy?.SetLastDamageSource(source);
|
||||
if (Kind == ComparisonActorKind.Player && CurrentShield > 0f)
|
||||
{
|
||||
float absorbed = Mathf.Min(shieldAmount, applied);
|
||||
shieldAmount -= absorbed;
|
||||
applied -= absorbed;
|
||||
if (applied <= 0f)
|
||||
{
|
||||
GrantInvulnerability(GameplayConstants.Current.Player.InvulnerabilityDuration);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
float healthBefore = CurrentHealth;
|
||||
CurrentHealth = Mathf.Max(0f, CurrentHealth - applied);
|
||||
actualDamage = healthBefore - CurrentHealth;
|
||||
DamageEvents++;
|
||||
if (Kind == ComparisonActorKind.Player)
|
||||
{
|
||||
invulnerableUntil = world.GameTime + GameplayConstants.Current.Player.InvulnerabilityDuration;
|
||||
hurtUntil = world.GameTime + GameplayConstants.Current.Player.HurtMovementLockDuration;
|
||||
world.CancelCyclone();
|
||||
world.NotifyPlayerDamageTaken(healthBefore - CurrentHealth);
|
||||
if (actualDamage > 0f)
|
||||
{
|
||||
Vector3 popupPosition = transform.position;
|
||||
if (world.View != null) popupPosition += world.View.transform.up * .75f;
|
||||
world.NotifyPlayerDamagePopup(actualDamage, popupPosition);
|
||||
world.CombatFeedback?.NotifyPlayerHurt(transform.position, direction, knockback);
|
||||
}
|
||||
}
|
||||
ApplyKnockback(direction, knockback);
|
||||
Visual.ShowHurt();
|
||||
if (CurrentHealth <= 0f)
|
||||
{
|
||||
StopBody();
|
||||
if (Kind == ComparisonActorKind.Player || Kind == ComparisonActorKind.Enemy) Visual.ShowDeath();
|
||||
else Visual.Hide();
|
||||
BodyCollider.enabled = sensor.enabled = false;
|
||||
world.NotifyDeath(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void GrantInvulnerability(float duration) => invulnerableUntil = world.GameTime + duration;
|
||||
|
||||
public bool TryActivateGuard()
|
||||
{
|
||||
PlayerConstants tuning = GameplayConstants.Current.Player;
|
||||
if (Kind != ComparisonActorKind.Player || !isActiveAndEnabled || !IsAlive
|
||||
|| Time.timeScale <= 0f || !world.IsRunning || world.Paused || IsHurt || IsGuarding
|
||||
|| GuardCooldownRemaining > 0f)
|
||||
return false;
|
||||
guardHasBlocked = false;
|
||||
guardUntil = world.GameTime + tuning.GuardDuration;
|
||||
guardCooldownUntil = world.GameTime + tuning.GuardCooldownDuration;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Heal(float amount)
|
||||
{
|
||||
if (Kind != ComparisonActorKind.Player || !IsAlive || amount <= 0f) return false;
|
||||
float before = CurrentHealth;
|
||||
CurrentHealth = Mathf.Min(MaxHealth, CurrentHealth + amount);
|
||||
return CurrentHealth > before;
|
||||
}
|
||||
|
||||
public bool GrantShield(float amount, float duration)
|
||||
{
|
||||
if (Kind != ComparisonActorKind.Player || !IsAlive || amount <= 0f || duration <= 0f)
|
||||
return false;
|
||||
shieldAmount = amount;
|
||||
shieldExpiresAt = world.GameTime + duration;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ApplyKnockback(Vector2 direction, float magnitude)
|
||||
{
|
||||
if (magnitude <= 0f) return;
|
||||
knockbackStartPosition = Position;
|
||||
knockbackCompletionPending = true;
|
||||
// Existing enemy force is a velocity; the player's value is a distance.
|
||||
float speed = Kind == ComparisonActorKind.Player
|
||||
? magnitude / Mathf.Max(.001f, GameplayConstants.Current.Player.KnockbackDuration) : magnitude;
|
||||
knockbackVelocity = direction.normalized * speed;
|
||||
knockbackRemaining = GameplayConstants.Current.Player.KnockbackDuration;
|
||||
}
|
||||
|
||||
public Vector2 ConsumeKnockback(float dt)
|
||||
{
|
||||
Vector2 delta = knockbackVelocity * (Kind == ComparisonActorKind.Player ? Mathf.Min(dt, knockbackRemaining) : dt);
|
||||
if (Kind == ComparisonActorKind.Player)
|
||||
{
|
||||
knockbackRemaining = Mathf.Max(0f, knockbackRemaining - dt);
|
||||
if (knockbackRemaining <= 0f) knockbackVelocity = Vector2.zero;
|
||||
}
|
||||
else knockbackVelocity = Vector2.MoveTowards(knockbackVelocity, Vector2.zero,
|
||||
GameplayConstants.Current.Enemies.KnockbackDeceleration * dt);
|
||||
return delta;
|
||||
}
|
||||
|
||||
public Vector2 Move(Vector2 displacement, bool slide = true)
|
||||
{
|
||||
Vector2 start = Position;
|
||||
Vector2 next = start;
|
||||
Vector2 remainder = displacement;
|
||||
MovementBlocked = false;
|
||||
LastMovementBlocker = null;
|
||||
for (int pass = 0; pass < (slide ? 3 : 1) && remainder.sqrMagnitude > .00000001f; pass++)
|
||||
{
|
||||
float length = remainder.magnitude;
|
||||
Vector3 direction = OnGround(remainder / length);
|
||||
float cylinder = Mathf.Max(0f, BodyCollider.height * .5f - Radius);
|
||||
Vector3 center = OnGround(next + Offset + BodyCenterOffset,
|
||||
world.Settings.GroundY + BodyCollider.center.y);
|
||||
int count = Physics.CapsuleCastNonAlloc(center + Vector3.up * cylinder,
|
||||
center - Vector3.up * cylinder, Radius, direction, movementHits,
|
||||
length + world.Settings.MovementSkin,
|
||||
Defense25DComparisonSettings.TerrainMask | Defense25DComparisonSettings.ActorMask,
|
||||
QueryTriggerInteraction.Ignore);
|
||||
float allowed = length;
|
||||
Vector2 normal = Vector2.zero;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
RaycastHit hit = movementHits[i];
|
||||
if (hit.collider.attachedRigidbody == Body) continue;
|
||||
Defense25DActor other = hit.collider.GetComponentInParent<Defense25DActor>();
|
||||
if (other != null && (Kind == ComparisonActorKind.Enemy || other.Kind == ComparisonActorKind.Enemy)) continue;
|
||||
if (other != null && Ignores(other)) continue;
|
||||
Vector2 hitNormal = Ground(hit.normal).normalized;
|
||||
if (Vector2.Dot(remainder, hitNormal) >= -.00001f) continue;
|
||||
float distance = Mathf.Max(0f, hit.distance - world.Settings.MovementSkin);
|
||||
if (distance < allowed)
|
||||
{
|
||||
allowed = distance;
|
||||
normal = hitNormal;
|
||||
LastMovementBlocker = hit.collider;
|
||||
}
|
||||
}
|
||||
next += remainder / length * allowed;
|
||||
if (allowed >= length) break;
|
||||
MovementBlocked = true;
|
||||
Vector2 remaining = remainder / length * (length - allowed);
|
||||
remainder = remaining - normal * Mathf.Min(0f, Vector2.Dot(remaining, normal));
|
||||
}
|
||||
Body.MovePosition(OnGround(next, world.Settings.GroundY));
|
||||
LastMove = next - start;
|
||||
if (knockbackCompletionPending && !HasKnockback)
|
||||
{
|
||||
knockbackCompletionPending = false;
|
||||
world.CombatFeedback?.NotifyKnockbackCompleted(this, knockbackStartPosition, next);
|
||||
}
|
||||
return LastMove;
|
||||
}
|
||||
|
||||
public bool Ignores(Defense25DActor other) =>
|
||||
(Kind == ComparisonActorKind.Player && other.IsMinion)
|
||||
|| (IsMinion && other.Kind == ComparisonActorKind.Player);
|
||||
|
||||
public void IgnorePhysicalPair(Defense25DActor other)
|
||||
{
|
||||
foreach (Collider mine in GetComponents<Collider>())
|
||||
foreach (Collider theirs in other.GetComponents<Collider>()) Physics.IgnoreCollision(mine, theirs);
|
||||
}
|
||||
|
||||
public void StopBody()
|
||||
{
|
||||
knockbackVelocity = LastMove = Vector2.zero;
|
||||
knockbackRemaining = 0f;
|
||||
knockbackCompletionPending = false;
|
||||
BodyCollider.enabled = sensor.enabled = false;
|
||||
}
|
||||
|
||||
public void RestoreLiveCollisionAndVisuals()
|
||||
{
|
||||
if (!isActiveAndEnabled || CurrentHealth <= 0f) return;
|
||||
BodyCollider.enabled = sensor.enabled = true;
|
||||
Visual.RestoreVisibility();
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.isTrigger || !IsAlive || !world.IsRunning || world.Paused) return;
|
||||
Defense25DActor actor = other.GetComponentInParent<Defense25DActor>();
|
||||
if (actor == this || (actor != null && Ignores(actor))) return;
|
||||
ContactCallbacks++;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (Kind == ComparisonActorKind.Player) guardUntil = 0f;
|
||||
if (BodyCollider != null) StopBody();
|
||||
if (Visual != null) Visual.Hide();
|
||||
if (world != null && Kind == ComparisonActorKind.Player) world.CancelCyclone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38cdcdac9597465fa33d491290a6e574
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7f4860a4f6547628f229a4ef4a81231
|
||||
@@ -0,0 +1,156 @@
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public sealed partial class Defense25DArtifactExecutor
|
||||
{
|
||||
public bool TryReplay(Defense25DArtifactUse use, Vector3 origin, Vector3 direction, float damageMultiplier)
|
||||
{
|
||||
ActiveArtifactDefinition definition = use.Definition;
|
||||
if (world == null || definition == null || !world.IsRunning || world.Paused
|
||||
|| world.Player == null || !world.Player.IsAlive || damageMultiplier <= 0f
|
||||
|| use.Damage <= 0f || use.Effect == ActiveArtifactEffect.Pulse)
|
||||
return false;
|
||||
|
||||
Vector2 replayOrigin = new(origin.x, origin.z);
|
||||
Vector2 replayDirection = new(direction.x, direction.z);
|
||||
if (replayDirection.sqrMagnitude < DirectionEpsilon)
|
||||
replayDirection = use.Effect == ActiveArtifactEffect.ThunderCrash
|
||||
? GetFacingSector(world.Player.Facing) : world.Player.Facing;
|
||||
if (replayDirection.sqrMagnitude < DirectionEpsilon) replayDirection = Vector2.right;
|
||||
replayDirection.Normalize();
|
||||
|
||||
var replay = new Cast
|
||||
{
|
||||
Use = use,
|
||||
Identity = castIdentity == int.MaxValue ? 1 : castIdentity + 1,
|
||||
Origin = replayOrigin,
|
||||
Direction = replayDirection,
|
||||
};
|
||||
castIdentity = replay.Identity;
|
||||
|
||||
if (use.Effect == ActiveArtifactEffect.ChainLightning)
|
||||
return ReplayChain(replay, damageMultiplier);
|
||||
|
||||
float range = use.Range;
|
||||
Vector2 center = replayOrigin;
|
||||
Vector2 radii = Vector2.one * range;
|
||||
bool ray = use.Effect is ActiveArtifactEffect.Dash or ActiveArtifactEffect.Phoenix;
|
||||
float halfWidth = Mathf.Max(.05f, use.Width * .5f);
|
||||
if (use.Effect == ActiveArtifactEffect.Cyclone)
|
||||
{
|
||||
center += CycloneGeometry.CenterOffset(range, use.Charged, world.Settings.PlayerVisualScale);
|
||||
radii = CycloneGeometry.Radii(range, use.Charged);
|
||||
}
|
||||
else if (use.Effect == ActiveArtifactEffect.ThunderCrash)
|
||||
{
|
||||
center = replayOrigin + replayDirection * .45f;
|
||||
radii = new Vector2(17f, 7.25f) / 32f * (ThunderCrashScale * range);
|
||||
}
|
||||
|
||||
int hits = 0;
|
||||
float igniteDamage = world.Player.CalculateDamage(
|
||||
(use.Charged ? definition.ChargedIgniteTickDamage : definition.NormalIgniteTickDamage)
|
||||
* damageMultiplier, definition.DamageTag);
|
||||
foreach (Defense25DActor target in GetReplayCandidates(
|
||||
ray ? replayOrigin + replayDirection * (range * .5f) : center,
|
||||
Mathf.Max(radii.x, radii.y, range)))
|
||||
{
|
||||
if (hits >= use.MaxTargets) break;
|
||||
Vector2 offset = target.Position - replayOrigin;
|
||||
bool inside = ray
|
||||
? Vector2.Dot(offset, replayDirection) >= 0f
|
||||
&& Vector2.Dot(offset, replayDirection) <= range
|
||||
&& Mathf.Abs(Vector2.Dot(offset, new Vector2(-replayDirection.y, replayDirection.x))) <= halfWidth
|
||||
: EllipseDistance(target.Position - center, radii) <= 1.0001f;
|
||||
if (!inside) continue;
|
||||
|
||||
Vector2 hitDirection = ray ? replayDirection : target.Position - center;
|
||||
if (hitDirection.sqrMagnitude < DirectionEpsilon) hitDirection = replayDirection;
|
||||
if (ApplyReplayDamage(replay, target, use.Damage * damageMultiplier,
|
||||
hitDirection.normalized, use.Knockback, igniteDamage))
|
||||
hits++;
|
||||
}
|
||||
return hits > 0;
|
||||
}
|
||||
|
||||
private bool ReplayChain(Cast cast, float damageMultiplier)
|
||||
{
|
||||
if (!PlanChain(cast)) return false;
|
||||
ActiveArtifactDefinition definition = cast.Use.Definition;
|
||||
float igniteDamage = world.Player.CalculateDamage(
|
||||
(cast.Use.Charged ? definition.ChargedIgniteTickDamage : definition.NormalIgniteTickDamage)
|
||||
* damageMultiplier, definition.DamageTag);
|
||||
var strikes = new List<ChainStrike>(cast.Strikes);
|
||||
bool applied = false;
|
||||
foreach (ChainStrike strike in strikes)
|
||||
{
|
||||
Vector2 hitDirection = strike.End - strike.Start;
|
||||
if (hitDirection.sqrMagnitude < DirectionEpsilon) hitDirection = cast.Direction;
|
||||
applied |= ApplyReplayDamage(cast, strike.Target, strike.Damage * damageMultiplier,
|
||||
hitDirection, cast.Use.Knockback, igniteDamage);
|
||||
}
|
||||
DestroyCast(cast);
|
||||
return applied;
|
||||
}
|
||||
|
||||
private List<Defense25DActor> GetReplayCandidates(Vector2 center, float radius)
|
||||
{
|
||||
var result = new List<Defense25DActor>(Query(center, radius, null));
|
||||
result.Sort((left, right) =>
|
||||
(left.Position - center).sqrMagnitude.CompareTo((right.Position - center).sqrMagnitude));
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool ApplyReplayDamage(Cast cast, Defense25DActor target, float rawDamage,
|
||||
Vector2 direction, float knockback, float igniteTickDamage)
|
||||
{
|
||||
if (target == null || !target.IsAlive || rawDamage <= 0f) return false;
|
||||
ActiveArtifactDefinition d = cast.Use.Definition;
|
||||
direction = direction.sqrMagnitude > DirectionEpsilon ? direction.normalized : cast.Direction;
|
||||
float scaledDamage = world.Player.CalculateDamage(rawDamage, d.DamageTag);
|
||||
Defense25DEnemy enemy = target.GetComponent<Defense25DEnemy>();
|
||||
if (enemy != null && RegisterArtifactContact != null)
|
||||
{
|
||||
Defense25DArtifactContact contact = RegisterArtifactContact(enemy, d.ArtifactColor, cast.Identity);
|
||||
if (contact.Result == EnemyArtifactContactResult.Ignored) return false;
|
||||
if (contact.Result == EnemyArtifactContactResult.Shielded) return false;
|
||||
}
|
||||
if (!target.TakeDamage(scaledDamage, direction, knockback)) return false;
|
||||
|
||||
float incomingDamage = DamageCalculator.FinalizeDamage(target.CalculateIncomingDamage(scaledDamage));
|
||||
if (enemy != null)
|
||||
{
|
||||
bool secondary = d.Effect switch
|
||||
{
|
||||
ActiveArtifactEffect.Phoenix =>
|
||||
(cast.Use.Charged ? d.ChargedIgniteDuration : d.NormalIgniteDuration) > 0f,
|
||||
ActiveArtifactEffect.ThunderCrash =>
|
||||
(cast.Use.Charged ? d.ChargedVulnerabilityDuration : d.NormalVulnerabilityDuration) > 0f,
|
||||
ActiveArtifactEffect.ChainLightning =>
|
||||
UnityEngine.Random.value < (cast.Use.Charged ? d.ChargedShockChance : d.NormalShockChance),
|
||||
_ => false,
|
||||
};
|
||||
if (secondary)
|
||||
ApplySecondaryEffect?.Invoke(enemy, d, cast.Use.Charged, igniteTickDamage);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
world.Player.gameObject, target.gameObject, false, HitSide.Front,
|
||||
incomingDamage, direction, knockback,
|
||||
target.Position, damageTag: d.DamageTag,
|
||||
sourceId: Defense25DRelicRuntime.EchoSourceId, usesPositionBonus: false,
|
||||
showsHitFeedback: incomingDamage > 0f, isArtifactHit: true,
|
||||
artifactEffect: d.Effect, isChargedArtifact: cast.Use.Charged,
|
||||
baseBumpDamage: world.Player.CalculateDamage(
|
||||
GameplayConstants.Current.Combat.BaseDamage, DamageTag.Collision)));
|
||||
}
|
||||
target.Visual?.ShowHurt();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09c2eaf519a34c1e9e61bc3598716b7b
|
||||
@@ -0,0 +1,882 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Defense;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
/// <summary>Restores the original combat's sprite feedback on the Physics3D presentation.</summary>
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class Defense25DCombatFeedback : MonoBehaviour
|
||||
{
|
||||
private sealed class SpriteEffect
|
||||
{
|
||||
public GameObject Root;
|
||||
public SpriteRenderer Renderer;
|
||||
public Sprite[] Frames;
|
||||
public IReadOnlyList<float> Durations;
|
||||
public int Frame;
|
||||
public float Elapsed;
|
||||
public bool Loop;
|
||||
public float Alpha = 1f;
|
||||
public EffectKind Kind;
|
||||
public LineRenderer Line;
|
||||
public float Duration;
|
||||
public float Hold;
|
||||
public float Fade;
|
||||
public bool Heavy;
|
||||
public bool Unscaled;
|
||||
public bool PoolLineWhenRemoved;
|
||||
public bool FollowPlayer;
|
||||
public bool FollowVisualGroundAnchor;
|
||||
public Vector2 FollowGroundOffset;
|
||||
public bool Expand;
|
||||
public Vector3 MoveStart;
|
||||
public Vector3 MoveEnd;
|
||||
public float FollowHeight;
|
||||
public SpriteRenderer[] Ghosts;
|
||||
public float[] GhostAlphas;
|
||||
}
|
||||
|
||||
private readonly List<SpriteEffect> effects = new();
|
||||
private readonly List<SpriteEffect> heavyEffects = new();
|
||||
private readonly Stack<GameObject> lineEffectPool = new();
|
||||
private readonly Dictionary<string, Sprite[]> frameCache = new();
|
||||
private Defense25DComparison world;
|
||||
private SpriteEffect aura;
|
||||
private SpriteEffect readyAura;
|
||||
private SpriteEffect chargeFlash;
|
||||
private SpriteEffect switchPulse;
|
||||
private SpriteEffect guardImpact;
|
||||
private SpriteEffect blockedBump;
|
||||
private SpriteRenderer playerRenderer;
|
||||
private float flashRemaining;
|
||||
private float blinkRemaining;
|
||||
private float blinkElapsed;
|
||||
private float hitStopUntil;
|
||||
private float cameraShakeUntil;
|
||||
private float cameraShakeDuration;
|
||||
private Color flashColor = Color.white;
|
||||
private bool directionalPunch;
|
||||
private float lastRearPunchAt = float.NegativeInfinity;
|
||||
private float nextGuardImpactAt = float.NegativeInfinity;
|
||||
private float capturedTimeScale = 1f;
|
||||
private bool ownsTimeFreeze;
|
||||
private Vector2 shakeDirection;
|
||||
private float shakePixels;
|
||||
private Material transparentEffectMaterial;
|
||||
private bool bound;
|
||||
|
||||
public int ActiveEffectCount => effects.Count;
|
||||
public int ActiveHeavyEffectCount => heavyEffects.Count;
|
||||
public int ActiveBumpImpactCount { get; private set; }
|
||||
public int ActiveArtifactImpactCount { get; private set; }
|
||||
public int ActivePlayerHurtImpactCount { get; private set; }
|
||||
public bool IsChargingVisualVisible => aura != null || readyAura != null;
|
||||
public bool IsReadyFlashVisible => chargeFlash != null;
|
||||
public Material TransparentEffectMaterial => transparentEffectMaterial;
|
||||
|
||||
public void Bind(Defense25DComparison owner)
|
||||
{
|
||||
if (bound) Unbind();
|
||||
world = owner;
|
||||
if (world == null || !world.isActiveAndEnabled || world.Player == null) return;
|
||||
bound = true;
|
||||
capturedTimeScale = Time.timeScale > 0f ? Time.timeScale : 1f;
|
||||
playerRenderer = world.Player.Visual != null ? world.Player.Visual.Renderer : null;
|
||||
if (world.Settings.TransparentMaterial != null)
|
||||
{
|
||||
transparentEffectMaterial = new Material(world.Settings.TransparentMaterial)
|
||||
{
|
||||
name = "Defense25D Combat Feedback Transparent",
|
||||
};
|
||||
if (transparentEffectMaterial.HasProperty("_BaseColor"))
|
||||
transparentEffectMaterial.SetColor("_BaseColor", Color.white);
|
||||
if (transparentEffectMaterial.HasProperty("_Cutoff"))
|
||||
transparentEffectMaterial.SetFloat("_Cutoff", .001f);
|
||||
}
|
||||
CombatEvents.OnValidHit += HandleValidHit;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (world != null && world.Player != null && !bound) Bind(world);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TickFeedback(Time.deltaTime, Time.unscaledDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>Advances scaled sprite effects and unscaled camera/hit-stop timers.</summary>
|
||||
public void TickFeedback(float scaledDeltaTime, float unscaledDeltaTime)
|
||||
{
|
||||
if (!bound || world == null) return;
|
||||
if (!world.isActiveAndEnabled)
|
||||
{
|
||||
Unbind();
|
||||
return;
|
||||
}
|
||||
scaledDeltaTime = Mathf.Max(0f, scaledDeltaTime);
|
||||
if (world.Paused) scaledDeltaTime = 0f;
|
||||
unscaledDeltaTime = Mathf.Max(0f, unscaledDeltaTime);
|
||||
AdvanceEffects(scaledDeltaTime, unscaledDeltaTime);
|
||||
AdvanceChargeVisuals(scaledDeltaTime);
|
||||
if (flashRemaining > 0f) flashRemaining = Mathf.Max(0f, flashRemaining - unscaledDeltaTime);
|
||||
if (blinkRemaining > 0f)
|
||||
{
|
||||
blinkRemaining = Mathf.Max(0f, blinkRemaining - scaledDeltaTime);
|
||||
blinkElapsed += scaledDeltaTime;
|
||||
}
|
||||
ApplyPlayerTint();
|
||||
|
||||
if (hitStopUntil > 0f && Time.unscaledTime >= hitStopUntil)
|
||||
{
|
||||
hitStopUntil = 0f;
|
||||
RestoreOwnedTimeFreeze();
|
||||
}
|
||||
if (cameraShakeUntil > 0f)
|
||||
{
|
||||
if (Time.unscaledTime >= cameraShakeUntil)
|
||||
{
|
||||
cameraShakeUntil = 0f;
|
||||
if (world.View != null) world.View.ClearFeedbackCameraOffset();
|
||||
}
|
||||
else if (world.View != null)
|
||||
{
|
||||
float fade = Mathf.Clamp01((cameraShakeUntil - Time.unscaledTime)
|
||||
/ Mathf.Max(.001f, cameraShakeDuration));
|
||||
if (directionalPunch)
|
||||
{
|
||||
Vector2 punch = shakeDirection * shakePixels * fade;
|
||||
world.View.SetFeedbackCameraOffset(new Vector2(Mathf.Round(punch.x), Mathf.Round(punch.y)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 jitter = UnityEngine.Random.insideUnitCircle * shakePixels * fade;
|
||||
world.View.SetFeedbackCameraOffset(new Vector2(Mathf.Round(jitter.x), Mathf.Round(jitter.y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyValidHit(CombatHitResult result)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || world.Player == null
|
||||
|| result.Attacker != world.Player.gameObject || result.Target == null) return;
|
||||
|
||||
// Per-contact artifact flashes are deliberately emitted before this feedback gate,
|
||||
// matching CombatFeedback.HandleValidHit for zero-damage/hidden contacts.
|
||||
bool artifactVisualCreated = result.IsArtifactHit && CreateArtifactImpact(result);
|
||||
if (!result.ShowsHitFeedback) return;
|
||||
|
||||
CombatFeedbackSettings tuning = GameplayConstants.Current.CombatFeedback;
|
||||
if (result.IsOrdinaryBump && result.HasBackBonus)
|
||||
{
|
||||
float now = Time.time;
|
||||
if (now - lastRearPunchAt >= tuning.RearHitCameraPunchRearmDelay)
|
||||
{
|
||||
if (cameraShakeUntil <= Time.unscaledTime
|
||||
&& result.KnockbackDirection.sqrMagnitude > Mathf.Epsilon)
|
||||
{
|
||||
shakePixels = tuning.RearHitCameraPunchPixels;
|
||||
shakeDirection = result.KnockbackDirection.normalized;
|
||||
cameraShakeDuration = tuning.RearHitCameraPunchDuration;
|
||||
cameraShakeUntil = Time.unscaledTime + tuning.RearHitCameraPunchDuration;
|
||||
directionalPunch = true;
|
||||
}
|
||||
}
|
||||
lastRearPunchAt = now;
|
||||
}
|
||||
|
||||
Color flash = HitColor(result);
|
||||
flashColor = Color.Lerp(Color.white, flash, .55f);
|
||||
if (!artifactVisualCreated && result.IsOrdinaryBump)
|
||||
CreateImpact(result.HitPosition, BumpFrames(result.Side), Defense25DCombatFeedbackConstants.BumpFrames,
|
||||
2f, Color.white, result.KnockbackDirection, "Bump Impact", EffectKind.Bump,
|
||||
true, 102);
|
||||
if (result.KnockbackForce > 0f)
|
||||
CreateFloorStreak(result.HitPosition, result.KnockbackDirection, flash);
|
||||
|
||||
flashRemaining = Mathf.Max(flashRemaining, tuning.FlashDuration);
|
||||
RequestHitStop(result.IsStrongDash ? tuning.StrongDashHitStopDuration
|
||||
: result.IsDash ? tuning.DashHitStopDuration
|
||||
: result.HasBackBonus ? tuning.BackHitStopDuration
|
||||
: tuning.NormalHitStopDuration);
|
||||
if (result.IsStrongDash) RequestCameraShake(tuning.HeavyCameraShakePixels, result.KnockbackDirection);
|
||||
}
|
||||
|
||||
public void NotifyPlayerHurt(Vector2 groundPosition, bool heavy) =>
|
||||
NotifyPlayerHurt(groundPosition, Vector2.zero, heavy);
|
||||
|
||||
public void NotifyPlayerHurt(Vector2 groundPosition, Vector2 knockbackDirection, bool heavy)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || world.Player == null) return;
|
||||
CombatFeedbackSettings tuning = GameplayConstants.Current.CombatFeedback;
|
||||
Vector2 origin = groundPosition;
|
||||
if (origin == Vector2.zero)
|
||||
origin = world.Player.Position;
|
||||
if (knockbackDirection.sqrMagnitude > 0f) origin -= knockbackDirection.normalized * .18f;
|
||||
CreateImpact(origin, LoadFrames(CombatFeedback.GetPlayerHurtImpactResourcePath()),
|
||||
Defense25DCombatFeedbackConstants.PlayerHurtFrames, 1f, Color.white, Vector2.zero, "Player Hurt Impact", EffectKind.Hurt);
|
||||
flashRemaining = Mathf.Max(flashRemaining, tuning.DamageFlashDuration);
|
||||
flashColor = Color.Lerp(Color.white, new Color(1f, .18f, .12f, 1f), .55f);
|
||||
blinkRemaining = Mathf.Max(blinkRemaining, GameplayConstants.Current.Player.InvulnerabilityDuration);
|
||||
blinkElapsed = 0f;
|
||||
RequestHitStop(tuning.DamageHitStopDuration);
|
||||
RequestCameraShake(heavy ? tuning.HeavyCameraShakePixels : tuning.CameraShakePixels, Vector2.zero);
|
||||
}
|
||||
|
||||
public void NotifyPlayerHurt(Vector3 worldPosition, bool heavy) =>
|
||||
NotifyPlayerHurt(new Vector2(worldPosition.x, worldPosition.z), heavy);
|
||||
|
||||
public void NotifyPlayerHurt(Vector3 worldPosition, Vector2 knockbackDirection, float knockbackDistance)
|
||||
{
|
||||
float threshold = GameplayConstants.Current.CombatFeedback.HeavyKnockbackThreshold;
|
||||
NotifyPlayerHurt(new Vector2(worldPosition.x, worldPosition.z), knockbackDirection,
|
||||
knockbackDistance >= threshold);
|
||||
}
|
||||
|
||||
public void NotifyChargeChanged(ActiveArtifactDefinition definition, float normalized, bool becameFull)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || definition == null || world.Player == null) return;
|
||||
normalized = Mathf.Clamp01(normalized);
|
||||
if (normalized <= 0f)
|
||||
{
|
||||
ClearChargeVisuals();
|
||||
return;
|
||||
}
|
||||
if (normalized >= 1f)
|
||||
{
|
||||
ClearEffect(ref aura);
|
||||
EnsureChargeSlot(ref readyAura, definition, Defense25DCombatFeedbackConstants.ChargeReadyAuraSuffix, true, Defense25DCombatFeedbackConstants.ChargeAuraFrames, -1);
|
||||
if (becameFull)
|
||||
{
|
||||
EnsureChargeSlot(ref chargeFlash, definition, Defense25DCombatFeedbackConstants.ChargeFlashSuffix, false, Defense25DCombatFeedbackConstants.ChargeFlashFrames, 1,
|
||||
ArtifactChargeVisual.FlashFrameCount);
|
||||
if (chargeFlash != null) chargeFlash.Loop = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
ClearEffect(ref readyAura);
|
||||
ClearEffect(ref chargeFlash);
|
||||
EnsureChargeSlot(ref aura, definition, Defense25DCombatFeedbackConstants.ChargeAuraSuffix, true, Defense25DCombatFeedbackConstants.ChargeAuraFrames, -1,
|
||||
ArtifactChargeVisual.FrameCount);
|
||||
if (aura != null) aura.Alpha = Mathf.Lerp(.55f, .85f, normalized);
|
||||
}
|
||||
|
||||
public void NotifyArtifactReleased(ActiveArtifactDefinition definition, Vector2 groundOrigin, bool charged)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || definition == null) return;
|
||||
string path = Defense25DCombatFeedbackConstants.ChargeResourceRoot + definition.Effect + Defense25DCombatFeedbackConstants.ChargeReleaseSuffix;
|
||||
Sprite[] frames = LoadFrames(path);
|
||||
if (frames.Length == ArtifactChargeVisual.FrameCount)
|
||||
CreateImpact(groundOrigin, frames, Defense25DCombatFeedbackConstants.ChargeReleaseFrames, 1f, Color.white,
|
||||
Vector2.zero, "Artifact Release", EffectKind.Release, false, 1);
|
||||
ClearChargeVisuals();
|
||||
}
|
||||
|
||||
public void NotifyArtifactSwitched(ActiveArtifactDefinition definition)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || definition == null || playerRenderer == null) return;
|
||||
EnsureChargeSlot(ref switchPulse, definition, Defense25DCombatFeedbackConstants.ChargeReleaseSuffix, false, Defense25DCombatFeedbackConstants.ChargeAuraFrames, 1,
|
||||
ArtifactChargeVisual.FrameCount);
|
||||
if (switchPulse != null) switchPulse.Loop = false;
|
||||
}
|
||||
|
||||
public void NotifyGuardImpact(Vector2 incomingDirection)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || world.Player == null || playerRenderer == null
|
||||
|| Time.time < nextGuardImpactAt) return;
|
||||
CombatFeedbackSettings tuning = GameplayConstants.Current.CombatFeedback;
|
||||
Sprite sprite = Resources.Load<Sprite>(CombatFeedback.GetGuardBlockImpactResourcePath());
|
||||
if (sprite == null) return;
|
||||
nextGuardImpactAt = Time.time + tuning.GuardBlockImpactInterval;
|
||||
Vector2 direction = incomingDirection.sqrMagnitude > 0f
|
||||
? incomingDirection.normalized : Vector2.zero;
|
||||
Vector2 position = world.Player.Center - direction * tuning.GuardBlockImpactOffset;
|
||||
SpriteEffect effect = CreateStaticSprite("Player Guard Block Impact", sprite,
|
||||
ToWorld(position), playerRenderer.sortingOrder + 20, EffectKind.Guard);
|
||||
if (effect != null)
|
||||
{
|
||||
effect.FollowPlayer = true;
|
||||
effect.FollowGroundOffset = -direction * tuning.GuardBlockImpactOffset;
|
||||
effect.Hold = tuning.GuardBlockImpactHoldDuration;
|
||||
effect.Fade = tuning.GuardBlockImpactFadeDuration;
|
||||
effect.Duration = effect.Hold + effect.Fade;
|
||||
guardImpact = effect;
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyBlockedBump(Vector2 hitPosition, Vector2 approachDirection)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || playerRenderer == null) return;
|
||||
CombatFeedbackSettings tuning = GameplayConstants.Current.CombatFeedback;
|
||||
Sprite sprite = Resources.Load<Sprite>(Defense25DCombatFeedbackConstants.BlockedBumpResource);
|
||||
if (sprite == null) return;
|
||||
Vector3 position = ToWorld(hitPosition) + Vector3.up * tuning.BlockedBumpVerticalOffset;
|
||||
SpriteEffect effect = CreateStaticSprite("Blocked Bump", sprite, position,
|
||||
playerRenderer.sortingOrder + 102, EffectKind.Blocked);
|
||||
if (effect != null)
|
||||
{
|
||||
effect.Renderer.flipX = approachDirection.x < 0f;
|
||||
effect.Hold = tuning.BlockedBumpHoldDuration;
|
||||
effect.Fade = tuning.BlockedBumpFadeDuration;
|
||||
effect.Duration = effect.Hold + effect.Fade;
|
||||
blockedBump = effect;
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyTowerImpact(DefenseTowerKind kind, Defense25DEnemy target, Vector2 hitPosition)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || target == null) return;
|
||||
string path = CombatFeedback.GetTowerImpactResourcePath(kind);
|
||||
Sprite[] frames = LoadFrames(path);
|
||||
if (frames.Length != CombatFeedback.BumpImpactFrameCount) return;
|
||||
SpriteEffect effect = new();
|
||||
if (!CreateImpact(hitPosition, frames, Defense25DCombatFeedbackConstants.ArtifactFrames, 2f, Color.white,
|
||||
Vector2.zero, $"Tower Impact {kind}", EffectKind.Tower, false,
|
||||
DefenseTowerArtBounds.TowerHitSortingOffset, effect)) return;
|
||||
SpriteRenderer targetRenderer = target.Actor != null && target.Actor.Visual != null
|
||||
? target.Actor.Visual.Renderer : null;
|
||||
if (targetRenderer != null && effect.Renderer != null)
|
||||
{
|
||||
effect.Renderer.sortingLayerID = targetRenderer.sortingLayerID;
|
||||
effect.Renderer.sortingOrder = targetRenderer.sortingOrder
|
||||
+ DefenseTowerArtBounds.TowerHitSortingOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyShield(float duration)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || duration <= 0f) return;
|
||||
SpriteEffect ring = CreateRing(PlayerVisualGroundPosition, .6f,
|
||||
new Color(.15f, .95f, .95f, 1f), duration, true);
|
||||
if (ring != null)
|
||||
{
|
||||
ring.FollowPlayer = true;
|
||||
ring.FollowVisualGroundAnchor = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void NotifyPulseGather(Defense25DEnemy target, Vector2 previousPosition, Vector2 finalPosition)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || target == null
|
||||
|| target.Actor == null || target.Actor.Visual == null) return;
|
||||
float distance = Vector2.Distance(previousPosition, finalPosition);
|
||||
if (!CombatFeedback.ShouldShowPulseStreak(distance))
|
||||
{
|
||||
CreateRing(finalPosition, .26f, new Color(1f, .39f, .39f, 1f), .12f, true);
|
||||
return;
|
||||
}
|
||||
|
||||
SpriteRenderer source = target.Actor.Visual.Renderer;
|
||||
if (source == null || source.sprite == null) return;
|
||||
GameObject root = new("Pulse Gather Afterimage");
|
||||
root.transform.rotation = world.View != null ? world.View.transform.rotation : Quaternion.identity;
|
||||
root.transform.localScale = source.transform.lossyScale;
|
||||
float height = source.transform.position.y - world.Settings.GroundY;
|
||||
root.transform.position = ToWorld(previousPosition) + Vector3.up * height;
|
||||
SpriteRenderer first = root.AddComponent<SpriteRenderer>();
|
||||
CopyGhost(source, first, 2, new Color(1f, .39f, .39f, .65f));
|
||||
GameObject secondObject = new("Pulse Gather Afterimage 2");
|
||||
secondObject.transform.SetParent(root.transform, false);
|
||||
secondObject.transform.localPosition = Vector3.left * .08f;
|
||||
SpriteRenderer second = secondObject.AddComponent<SpriteRenderer>();
|
||||
CopyGhost(source, second, 1, new Color(1f, .65f, .65f, .4f));
|
||||
LineRenderer streak = AcquireLine("Pulse Gather Streak");
|
||||
CombatFeedback.ConfigureWorldStreakRenderer(streak, transparentEffectMaterial,
|
||||
new Color(1f, .39f, .39f, .8f), .055f, ToWorld(previousPosition), ToWorld(finalPosition));
|
||||
SpriteEffect effect = new()
|
||||
{
|
||||
Root = root, Line = streak, Frames = Array.Empty<Sprite>(),
|
||||
Durations = new[] { Defense25DCombatFeedbackConstants.PulseGatherDuration },
|
||||
Duration = Defense25DCombatFeedbackConstants.PulseGatherDuration,
|
||||
Unscaled = true, Heavy = true,
|
||||
MoveStart = ToWorld(previousPosition) + Vector3.up * height,
|
||||
MoveEnd = ToWorld(finalPosition) + Vector3.up * height,
|
||||
FollowHeight = height,
|
||||
Ghosts = new[] { first, second },
|
||||
GhostAlphas = new[] { .65f, .4f },
|
||||
};
|
||||
AddEffect(effect, true);
|
||||
}
|
||||
|
||||
public void NotifyKnockbackCompleted(Defense25DActor target, Vector2 start, Vector2 end)
|
||||
{
|
||||
if (!bound || world == null || !world.isActiveAndEnabled || target == null
|
||||
|| target.Kind != ComparisonActorKind.Enemy
|
||||
|| !IsOwnedEnemy(target)) return;
|
||||
if (CombatFeedback.ShouldShowKnockbackDust(Vector2.Distance(start, end)))
|
||||
CreateRing(end, .2f, new Color(.9f, .8f, 1f, 1f), .13f, true);
|
||||
}
|
||||
|
||||
public void ResetFeedback()
|
||||
{
|
||||
ClearAllEffects();
|
||||
ClearChargeVisuals();
|
||||
flashRemaining = blinkRemaining = blinkElapsed = hitStopUntil = cameraShakeUntil = 0f;
|
||||
ActiveBumpImpactCount = ActiveArtifactImpactCount = ActivePlayerHurtImpactCount = 0;
|
||||
directionalPunch = false;
|
||||
shakeDirection = Vector2.zero;
|
||||
shakePixels = cameraShakeDuration = 0f;
|
||||
if (world != null && world.Player != null && world.Player.Visual != null)
|
||||
world.Player.Visual.ClearFeedbackTint();
|
||||
if (world != null && world.View != null) world.View.ClearFeedbackCameraOffset();
|
||||
RestoreOwnedTimeFreeze();
|
||||
lastRearPunchAt = float.NegativeInfinity;
|
||||
nextGuardImpactAt = float.NegativeInfinity;
|
||||
}
|
||||
|
||||
private void HandleValidHit(CombatHitResult result) => NotifyValidHit(result);
|
||||
|
||||
private void Unbind()
|
||||
{
|
||||
if (bound) CombatEvents.OnValidHit -= HandleValidHit;
|
||||
bound = false;
|
||||
ResetFeedback();
|
||||
while (lineEffectPool.Count > 0)
|
||||
{
|
||||
GameObject pooled = lineEffectPool.Pop();
|
||||
if (pooled != null) Destroy(pooled);
|
||||
}
|
||||
frameCache.Clear();
|
||||
playerRenderer = null;
|
||||
if (transparentEffectMaterial != null)
|
||||
{
|
||||
Destroy(transparentEffectMaterial);
|
||||
transparentEffectMaterial = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable() => Unbind();
|
||||
|
||||
private void OnDestroy() => Unbind();
|
||||
|
||||
private void RestoreOwnedTimeFreeze()
|
||||
{
|
||||
if (!ownsTimeFreeze) return;
|
||||
Time.timeScale = capturedTimeScale;
|
||||
ownsTimeFreeze = false;
|
||||
}
|
||||
|
||||
private enum EffectKind { Generic, Bump, Artifact, Hurt, Release, Charge, Guard, Blocked, Tower }
|
||||
|
||||
private bool CreateArtifactImpact(CombatHitResult result)
|
||||
{
|
||||
Sprite[] frames = LoadFrames(CombatFeedback.GetArtifactImpactResourcePath(
|
||||
result.ArtifactEffect, result.IsChargedArtifact));
|
||||
if (frames.Length != CombatFeedback.ArtifactImpactFrameCount) return false;
|
||||
Vector2 direction = result.KnockbackDirection.sqrMagnitude > 0f
|
||||
? result.KnockbackDirection.normalized : Vector2.right;
|
||||
bool created = CreateImpact(result.HitPosition, frames, Defense25DCombatFeedbackConstants.ArtifactFrames, 2f,
|
||||
Color.white, direction, "Artifact Impact " + result.ArtifactEffect,
|
||||
EffectKind.Artifact, true, 102);
|
||||
return created;
|
||||
}
|
||||
|
||||
private Sprite[] BumpFrames(HitSide side)
|
||||
{
|
||||
ActiveArtifactDefinition selected = world.Loadout != null ? world.Loadout.CurrentArtifact : null;
|
||||
string path = selected != null
|
||||
? $"Artifacts/ThreeColor-v1/Combat/BumpImpact-{(side == HitSide.Back ? "Strong" : side == HitSide.Side ? "Medium" : "Weak")}-{selected.ArtifactColor}-v1"
|
||||
: CombatFeedback.GetBumpImpactResourcePath(side);
|
||||
Sprite[] frames = LoadFrames(path);
|
||||
return frames.Length == CombatFeedback.BumpImpactFrameCount
|
||||
? frames : LoadFrames(CombatFeedback.GetBumpImpactResourcePath(side));
|
||||
}
|
||||
|
||||
private bool CreateImpact(Vector2 groundPosition, Sprite[] frames, IReadOnlyList<float> durations,
|
||||
float scale, Color color, Vector2 direction, string label, EffectKind kind,
|
||||
bool rotate = false, int sortingOffset = 102, SpriteEffect capture = null)
|
||||
{
|
||||
if (frames == null || frames.Length == 0 || world == null || world.Player == null) return false;
|
||||
SpriteEffect effect = new()
|
||||
{
|
||||
Root = new GameObject(label), Frames = frames, Durations = durations,
|
||||
Loop = false, Renderer = null,
|
||||
};
|
||||
effect.Root.transform.position = ToWorld(groundPosition);
|
||||
effect.Root.transform.localScale = Vector3.one * scale;
|
||||
if (world.View != null)
|
||||
{
|
||||
effect.Root.transform.rotation = world.View.transform.rotation;
|
||||
if (rotate && direction.sqrMagnitude > .0001f)
|
||||
{
|
||||
Vector3 worldDirection = new(direction.x, 0f, direction.y);
|
||||
Vector3 projected = new(Vector3.Dot(worldDirection, world.View.transform.right),
|
||||
Vector3.Dot(worldDirection, world.View.transform.up), 0f);
|
||||
if (projected.sqrMagnitude > .0001f)
|
||||
effect.Root.transform.rotation *= Quaternion.AngleAxis(
|
||||
Mathf.Atan2(projected.y, projected.x) * Mathf.Rad2Deg, Vector3.forward);
|
||||
}
|
||||
}
|
||||
effect.Renderer = effect.Root.AddComponent<SpriteRenderer>();
|
||||
effect.Renderer.sprite = frames[0];
|
||||
effect.Renderer.color = color;
|
||||
if (transparentEffectMaterial != null) effect.Renderer.sharedMaterial = transparentEffectMaterial;
|
||||
if (playerRenderer != null)
|
||||
{
|
||||
effect.Renderer.sortingLayerID = playerRenderer.sortingLayerID;
|
||||
effect.Renderer.sortingOrder = playerRenderer.sortingOrder + sortingOffset;
|
||||
}
|
||||
effects.Add(effect);
|
||||
effect.Kind = kind;
|
||||
if (capture != null)
|
||||
{
|
||||
capture.Root = effect.Root;
|
||||
capture.Renderer = effect.Renderer;
|
||||
capture.Frames = effect.Frames;
|
||||
capture.Durations = effect.Durations;
|
||||
capture.Kind = effect.Kind;
|
||||
}
|
||||
if (kind == EffectKind.Bump) ActiveBumpImpactCount++;
|
||||
if (kind == EffectKind.Artifact) ActiveArtifactImpactCount++;
|
||||
if (kind == EffectKind.Hurt) ActivePlayerHurtImpactCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private SpriteEffect CreateStaticSprite(string label, Sprite sprite, Vector3 position,
|
||||
int sortingOffset, EffectKind kind)
|
||||
{
|
||||
if (sprite == null || world == null) return null;
|
||||
GameObject root = new(label);
|
||||
root.transform.position = position;
|
||||
if (world.View != null) root.transform.rotation = world.View.transform.rotation;
|
||||
SpriteRenderer renderer = root.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = sprite;
|
||||
renderer.color = Color.white;
|
||||
if (transparentEffectMaterial != null) renderer.sharedMaterial = transparentEffectMaterial;
|
||||
if (playerRenderer != null)
|
||||
{
|
||||
renderer.sortingLayerID = playerRenderer.sortingLayerID;
|
||||
renderer.sortingOrder = playerRenderer.sortingOrder + sortingOffset;
|
||||
}
|
||||
SpriteEffect effect = new() { Root = root, Renderer = renderer,
|
||||
Frames = Array.Empty<Sprite>(), Durations = new[] { .001f }, Kind = kind };
|
||||
effects.Add(effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
private void CreateFloorStreak(Vector2 position, Vector2 direction, Color color)
|
||||
{
|
||||
if (direction.sqrMagnitude < .0001f || world == null) return;
|
||||
LineRenderer line = AcquireLine("Interaction Streak");
|
||||
GameObject root = line.gameObject;
|
||||
CombatFeedback.ConfigureWorldStreakRenderer(line, transparentEffectMaterial,
|
||||
color, .04f, ToWorld(position), ToWorld(position + direction.normalized * .34f));
|
||||
SpriteEffect effect = new() { Root = root, Line = line,
|
||||
Duration = Defense25DCombatFeedbackConstants.InteractionStreakDuration,
|
||||
Durations = new[] { Defense25DCombatFeedbackConstants.InteractionStreakDuration },
|
||||
Frames = Array.Empty<Sprite>(), Loop = false, Unscaled = true, Heavy = true,
|
||||
PoolLineWhenRemoved = true };
|
||||
AddEffect(effect, true);
|
||||
}
|
||||
|
||||
private SpriteEffect CreateRing(Vector2 position, float radius, Color color,
|
||||
float duration, bool useScaledTime)
|
||||
{
|
||||
if (!bound || world == null || duration <= 0f) return null;
|
||||
LineRenderer line = AcquireLine("Interaction Ring");
|
||||
GameObject root = line.gameObject;
|
||||
root.transform.SetPositionAndRotation(ToWorld(position) + Vector3.up * .025f, Quaternion.identity);
|
||||
root.transform.localScale = Vector3.one * .72f;
|
||||
line.enabled = true;
|
||||
line.loop = true;
|
||||
line.useWorldSpace = false;
|
||||
line.positionCount = 24;
|
||||
line.startWidth = line.endWidth = 0f;
|
||||
line.widthMultiplier = .045f;
|
||||
line.numCornerVertices = 0;
|
||||
line.numCapVertices = 2;
|
||||
line.sharedMaterial = transparentEffectMaterial;
|
||||
line.startColor = line.endColor = color;
|
||||
line.sortingOrder = 210;
|
||||
for (int i = 0; i < line.positionCount; i++)
|
||||
{
|
||||
float angle = i * Mathf.PI * 2f / line.positionCount;
|
||||
line.SetPosition(i, new Vector3(Mathf.Cos(angle) * radius, 0f,
|
||||
Mathf.Sin(angle) * radius));
|
||||
}
|
||||
SpriteEffect effect = new() { Root = root, Line = line, Duration = duration,
|
||||
Durations = new[] { duration }, Frames = Array.Empty<Sprite>(),
|
||||
Unscaled = !useScaledTime, Heavy = true, PoolLineWhenRemoved = true,
|
||||
Expand = true };
|
||||
AddEffect(effect, true);
|
||||
return effects.Contains(effect) ? effect : null;
|
||||
}
|
||||
|
||||
private LineRenderer AcquireLine(string label)
|
||||
{
|
||||
GameObject root = lineEffectPool.Count > 0 ? lineEffectPool.Pop() : new GameObject(label);
|
||||
root.name = label;
|
||||
root.SetActive(true);
|
||||
LineRenderer line = root.GetComponent<LineRenderer>();
|
||||
if (line == null) line = root.AddComponent<LineRenderer>();
|
||||
line.enabled = true;
|
||||
line.loop = false;
|
||||
return line;
|
||||
}
|
||||
|
||||
private void AddEffect(SpriteEffect effect, bool heavy)
|
||||
{
|
||||
effects.Add(effect);
|
||||
effect.Heavy = heavy;
|
||||
if (!heavy) return;
|
||||
heavyEffects.Add(effect);
|
||||
int limit = CombatFeedback.MaxConcurrentHeavyEffects;
|
||||
while (heavyEffects.Count > limit)
|
||||
{
|
||||
SpriteEffect oldest = heavyEffects[0];
|
||||
int index = effects.IndexOf(oldest);
|
||||
if (index >= 0) RemoveEffectAt(index, oldest);
|
||||
else heavyEffects.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsOwnedEnemy(Defense25DActor actor)
|
||||
{
|
||||
foreach (Defense25DEnemy enemy in world.Enemies)
|
||||
if (enemy != null && enemy.Actor == actor) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CopyGhost(SpriteRenderer source, SpriteRenderer ghost, int sortingOffset, Color color)
|
||||
{
|
||||
ghost.sprite = source.sprite;
|
||||
ghost.flipX = source.flipX;
|
||||
ghost.flipY = source.flipY;
|
||||
ghost.color = color;
|
||||
ghost.sharedMaterial = transparentEffectMaterial;
|
||||
ghost.sortingLayerID = source.sortingLayerID;
|
||||
ghost.sortingOrder = source.sortingOrder + sortingOffset;
|
||||
}
|
||||
|
||||
private void EnsureChargeSlot(ref SpriteEffect slot, ActiveArtifactDefinition definition,
|
||||
string suffix, bool loop, IReadOnlyList<float> durations, int sortingOffset, int expectedFrameCount = ArtifactChargeVisual.FrameCount)
|
||||
{
|
||||
if (slot != null) return;
|
||||
Sprite[] frames = LoadFrames(Defense25DCombatFeedbackConstants.ChargeResourceRoot + definition.Effect + suffix);
|
||||
if (frames.Length != expectedFrameCount || playerRenderer == null) return;
|
||||
GameObject root = new("Artifact Charge " + suffix);
|
||||
root.transform.SetParent(playerRenderer.transform, false);
|
||||
SpriteRenderer renderer = root.AddComponent<SpriteRenderer>();
|
||||
renderer.sprite = frames[0];
|
||||
renderer.color = Color.white;
|
||||
renderer.sharedMaterial = playerRenderer.sharedMaterial;
|
||||
renderer.sortingLayerID = playerRenderer.sortingLayerID;
|
||||
renderer.sortingOrder = playerRenderer.sortingOrder + sortingOffset;
|
||||
if (transparentEffectMaterial != null) renderer.sharedMaterial = transparentEffectMaterial;
|
||||
slot = new SpriteEffect { Root = root, Renderer = renderer, Frames = frames,
|
||||
Durations = durations, Loop = loop, Kind = EffectKind.Charge };
|
||||
effects.Add(slot);
|
||||
}
|
||||
|
||||
private void AdvanceChargeVisuals(float dt)
|
||||
{
|
||||
if (aura != null) aura.Renderer.color = new Color(1f, 1f, 1f, aura.Alpha);
|
||||
}
|
||||
|
||||
private void AdvanceEffects(float scaledDeltaTime, float unscaledDeltaTime)
|
||||
{
|
||||
for (int i = effects.Count - 1; i >= 0; i--)
|
||||
{
|
||||
SpriteEffect effect = effects[i];
|
||||
if (effect == null || effect.Root == null) { effects.RemoveAt(i); continue; }
|
||||
float dt = effect.Unscaled ? unscaledDeltaTime : scaledDeltaTime;
|
||||
if (dt <= 0f) continue;
|
||||
if (effect.Frames.Length == 0)
|
||||
{
|
||||
effect.Elapsed += dt;
|
||||
float progress = Mathf.Clamp01(effect.Elapsed / Mathf.Max(.001f, effect.Duration));
|
||||
if (effect.FollowPlayer && world.Player != null)
|
||||
{
|
||||
Vector2 followPosition = effect.FollowVisualGroundAnchor
|
||||
? PlayerVisualGroundPosition : world.Player.Position;
|
||||
effect.Root.transform.position = ToWorld(followPosition + effect.FollowGroundOffset)
|
||||
+ Vector3.up * .025f;
|
||||
}
|
||||
if (effect.MoveEnd != Vector3.zero)
|
||||
effect.Root.transform.position = Vector3.Lerp(effect.MoveStart, effect.MoveEnd, progress);
|
||||
if (effect.Expand)
|
||||
effect.Root.transform.localScale = Vector3.one * Mathf.Lerp(.72f, 1.2f, progress);
|
||||
if (effect.Ghosts != null)
|
||||
for (int g = 0; g < effect.Ghosts.Length; g++)
|
||||
if (effect.Ghosts[g] != null)
|
||||
{
|
||||
Color ghostColor = effect.Ghosts[g].color;
|
||||
ghostColor.a = effect.GhostAlphas[g] * (1f - progress);
|
||||
effect.Ghosts[g].color = ghostColor;
|
||||
}
|
||||
if (effect.Renderer != null && effect.Duration > 0f)
|
||||
{
|
||||
float fadeProgress = effect.Fade > 0f
|
||||
? Mathf.Clamp01((effect.Elapsed - effect.Hold) / effect.Fade) : 1f;
|
||||
Color spriteColor = effect.Renderer.color;
|
||||
spriteColor.a = 1f - fadeProgress;
|
||||
effect.Renderer.color = spriteColor;
|
||||
if (effect.Elapsed >= effect.Duration) RemoveEffectAt(i, effect);
|
||||
continue;
|
||||
}
|
||||
if (effect.Line != null)
|
||||
{
|
||||
Color color = effect.Line.startColor;
|
||||
color.a = 1f - progress;
|
||||
effect.Line.startColor = effect.Line.endColor = color;
|
||||
}
|
||||
if (effect.Elapsed >= effect.Duration) RemoveEffectAt(i, effect);
|
||||
continue;
|
||||
}
|
||||
effect.Elapsed += dt;
|
||||
while (effect.Elapsed >= effect.Durations[Mathf.Clamp(effect.Frame, 0, effect.Durations.Count - 1)])
|
||||
{
|
||||
effect.Elapsed -= effect.Durations[Mathf.Clamp(effect.Frame, 0, effect.Durations.Count - 1)];
|
||||
int next = effect.Frame + 1;
|
||||
if (next >= effect.Frames.Length)
|
||||
{
|
||||
if (!effect.Loop) { RemoveEffectAt(i, effect); break; }
|
||||
next = 0;
|
||||
}
|
||||
effect.Frame = next;
|
||||
if (effect.Renderer != null) effect.Renderer.sprite = effect.Frames[next];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveEffectAt(int index, SpriteEffect effect)
|
||||
{
|
||||
if (effect == aura) aura = null;
|
||||
if (effect == readyAura) readyAura = null;
|
||||
if (effect == chargeFlash) chargeFlash = null;
|
||||
if (effect == switchPulse) switchPulse = null;
|
||||
if (effect == guardImpact) guardImpact = null;
|
||||
if (effect == blockedBump) blockedBump = null;
|
||||
if (effect.Kind == EffectKind.Bump) ActiveBumpImpactCount = Mathf.Max(0, ActiveBumpImpactCount - 1);
|
||||
if (effect.Kind == EffectKind.Artifact) ActiveArtifactImpactCount = Mathf.Max(0, ActiveArtifactImpactCount - 1);
|
||||
if (effect.Kind == EffectKind.Hurt) ActivePlayerHurtImpactCount = Mathf.Max(0, ActivePlayerHurtImpactCount - 1);
|
||||
heavyEffects.Remove(effect);
|
||||
if (effect.PoolLineWhenRemoved && effect.Root != null)
|
||||
{
|
||||
RecycleLineRoot(effect.Root, effect.Line);
|
||||
}
|
||||
else if (effect.Root != null) Destroy(effect.Root);
|
||||
effects.RemoveAt(index);
|
||||
}
|
||||
|
||||
private void RecycleLineRoot(GameObject root, LineRenderer line)
|
||||
{
|
||||
if (line != null) line.enabled = false;
|
||||
root.SetActive(false);
|
||||
lineEffectPool.Push(root);
|
||||
}
|
||||
|
||||
private void ApplyPlayerTint()
|
||||
{
|
||||
if (world == null || world.Player == null || world.Player.Visual == null) return;
|
||||
Color tint = Color.white;
|
||||
if (blinkRemaining > 0f && GameplayConstants.Current.CombatFeedback.InvulnerabilityBlinkInterval > 0f
|
||||
&& Mathf.FloorToInt(blinkElapsed / GameplayConstants.Current.CombatFeedback.InvulnerabilityBlinkInterval) % 2 == 0)
|
||||
{
|
||||
float alpha = GameplayConstants.Current.CombatFeedback.InvulnerabilityBlinkAlpha;
|
||||
tint = new Color(1f, 1f, 1f, alpha);
|
||||
}
|
||||
if (flashRemaining > 0f)
|
||||
{
|
||||
tint.r = flashColor.r;
|
||||
tint.g = flashColor.g;
|
||||
tint.b = flashColor.b;
|
||||
}
|
||||
world.Player.Visual.SetFeedbackTint(tint);
|
||||
}
|
||||
|
||||
private Color HitColor(CombatHitResult? hit)
|
||||
{
|
||||
if (hit.HasValue && hit.Value.IsArtifactHit)
|
||||
return ActiveArtifactDefinition.GetArtifactPaletteColor(ActiveArtifactDefinition.GetArtifactColor(hit.Value.ArtifactEffect));
|
||||
if (world.Loadout != null && world.Loadout.CurrentArtifact != null)
|
||||
return ActiveArtifactDefinition.GetArtifactPaletteColor(world.Loadout.CurrentArtifact.ArtifactColor);
|
||||
if (!hit.HasValue) return new Color(1f, .18f, .12f, 1f);
|
||||
if (hit.Value.IsStrongDash) return new Color(.95f, .5f, 1f, 1f);
|
||||
if (hit.Value.IsDash) return new Color(.2f, .95f, 1f, 1f);
|
||||
if (hit.Value.HasBackBonus) return new Color(1f, .55f, .12f, 1f);
|
||||
if (hit.Value.HasPositionBonus) return new Color(.65f, 1f, .3f, 1f);
|
||||
return new Color(.78f, .72f, 1f, 1f);
|
||||
}
|
||||
|
||||
private void RequestHitStop(float duration)
|
||||
{
|
||||
if (duration <= 0f) return;
|
||||
if (!ownsTimeFreeze)
|
||||
{
|
||||
if (Time.timeScale <= 0f) return;
|
||||
capturedTimeScale = Time.timeScale;
|
||||
ownsTimeFreeze = true;
|
||||
Time.timeScale = 0f;
|
||||
}
|
||||
hitStopUntil = Mathf.Max(hitStopUntil, Time.unscaledTime + duration);
|
||||
}
|
||||
|
||||
private void RequestCameraShake(float pixels, Vector2 direction)
|
||||
{
|
||||
if (pixels <= 0f || world.View == null) return;
|
||||
shakePixels = pixels;
|
||||
shakeDirection = direction.sqrMagnitude > 0f ? direction.normalized : Vector2.zero;
|
||||
cameraShakeDuration = GameplayConstants.Current.CombatFeedback.CameraShakeDuration;
|
||||
cameraShakeUntil = Time.unscaledTime + cameraShakeDuration;
|
||||
directionalPunch = false;
|
||||
}
|
||||
|
||||
private void ClearChargeVisuals()
|
||||
{
|
||||
ClearEffect(ref aura);
|
||||
ClearEffect(ref readyAura);
|
||||
ClearEffect(ref chargeFlash);
|
||||
ClearEffect(ref switchPulse);
|
||||
}
|
||||
|
||||
private void ClearEffect(ref SpriteEffect effect)
|
||||
{
|
||||
if (effect == null) return;
|
||||
if (effect.Root != null) Destroy(effect.Root);
|
||||
effects.Remove(effect);
|
||||
effect = null;
|
||||
}
|
||||
|
||||
private void ClearAllEffects()
|
||||
{
|
||||
foreach (SpriteEffect effect in effects)
|
||||
if (effect != null && effect.Root != null) Destroy(effect.Root);
|
||||
effects.Clear();
|
||||
aura = readyAura = chargeFlash = switchPulse = null;
|
||||
guardImpact = blockedBump = null;
|
||||
}
|
||||
|
||||
private Sprite[] LoadFrames(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) return Array.Empty<Sprite>();
|
||||
if (!frameCache.TryGetValue(path, out Sprite[] frames))
|
||||
{
|
||||
frames = Resources.LoadAll<Sprite>(path);
|
||||
Array.Sort(frames, (a, b) => string.CompareOrdinal(a.name, b.name));
|
||||
frameCache.Add(path, frames);
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
private Vector2 PlayerVisualGroundPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (world == null || world.Player == null || world.Player.Visual == null)
|
||||
return world != null && world.Player != null ? world.Player.Center : Vector2.zero;
|
||||
Vector3 foot = world.Player.Visual.FootPosition;
|
||||
return new Vector2(foot.x, foot.z);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 ToWorld(Vector2 point) => new(point.x, world.Settings.GroundY + .015f, point.y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a149c72e9544596b216326c1b27fbe4
|
||||
@@ -0,0 +1,971 @@
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
// An independent Physics3D experiment. No RunManager, PlayerController or 2D proxy.
|
||||
public sealed partial class Defense25DComparison : MonoBehaviour
|
||||
{
|
||||
private const int PortalLaneCount = 2;
|
||||
[SerializeField] private Defense25DComparisonSettings settings;
|
||||
[SerializeField] private Defense25DView view;
|
||||
private readonly List<Defense25DEnemy> enemies = new();
|
||||
private readonly List<Defense25DSummon> summons = new();
|
||||
private readonly Collider[] overlap = new Collider[128];
|
||||
private readonly HashSet<Defense25DActor> unique = new();
|
||||
private readonly List<Defense25DActor> candidates = new();
|
||||
private readonly Dictionary<Defense25DActor, float> nextBump = new();
|
||||
private readonly List<Transform> adjustedArenaTransforms = new();
|
||||
private readonly List<Vector3> originalArenaPositions = new();
|
||||
private readonly List<Vector3> originalArenaScales = new();
|
||||
private readonly DefenseWaveLedger[] portalLedgers = new DefenseWaveLedger[PortalLaneCount];
|
||||
private readonly int[] suppliedPerLane = new int[PortalLaneCount];
|
||||
private readonly bool[] portalSupplyEnded = new bool[PortalLaneCount];
|
||||
private readonly GameObject[] portalRewards = new GameObject[PortalLaneCount];
|
||||
private readonly Vector2[][] routes = new Vector2[PortalLaneCount][];
|
||||
private readonly List<ActiveArtifactDefinition> startupArtifactOptions = new();
|
||||
private Defense25DCycloneVisual cycloneVisual;
|
||||
private Defense25DCombatFeedback combatFeedback;
|
||||
private Defense25DPlayerArt playerArt;
|
||||
private float previousChargeProgress = -1f;
|
||||
private MeshFilter adjustedSouthBoundary;
|
||||
private Mesh originalSouthBoundaryMesh;
|
||||
private Mesh adjustedSouthBoundaryMesh;
|
||||
private Vector2 input;
|
||||
private Vector2 aim = Vector2.right;
|
||||
private Vector2 cycloneDirection;
|
||||
private float nextSpawn, reviveAt, cycloneDuration, cycloneInterval;
|
||||
private float cycloneDistance, cycloneRange, buffUntil;
|
||||
private bool cycloneCharged, cycloneBlocked, initialized;
|
||||
private int nextCycloneTick, scheduledCycloneTicks;
|
||||
private int nextSupplyLane;
|
||||
private int startupSelectionsRemaining, startupSelectionTotal, startupSelectionIndex;
|
||||
private ArtifactColor startupArtifactColor;
|
||||
private bool startupSelectionPending;
|
||||
private float fallbackGauge;
|
||||
private const string CycloneBuff = "defense-25d-cyclone";
|
||||
|
||||
public Defense25DComparisonSettings Settings => settings;
|
||||
public Vector2 ArenaHalfExtents { get; private set; }
|
||||
public Defense25DView View => view;
|
||||
public Defense25DActor Player { get; private set; }
|
||||
public IReadOnlyList<Defense25DEnemy> Enemies => enemies;
|
||||
public IReadOnlyList<Defense25DSummon> Summons => summons;
|
||||
public DefenseWaveLedger Ledger { get; private set; }
|
||||
public Defense25DTowerManager TowerManager { get; private set; }
|
||||
public Defense25DLoadout Loadout { get; private set; }
|
||||
public Defense25DArtifactExecutor ArtifactExecutor { get; private set; }
|
||||
public Defense25DRelicRuntime RelicRuntime { get; private set; }
|
||||
public float GameTime { get; private set; }
|
||||
public float Gauge => Loadout != null ? Loadout.Gauge : fallbackGauge;
|
||||
public float BaseHealth { get; private set; }
|
||||
public int KillCurrency { get; private set; }
|
||||
public bool IsRunning { get; private set; }
|
||||
public bool RunCleared { get; private set; }
|
||||
public bool RunFailed { get; private set; }
|
||||
public bool Paused { get; private set; }
|
||||
public bool AutoSpawn { get; set; } = true;
|
||||
public bool ReadInput { get; set; } = true;
|
||||
public bool ScreenRelativeMovement { get; private set; } = true;
|
||||
public bool CycloneActive { get; private set; }
|
||||
public float CycloneElapsed { get; private set; }
|
||||
public int CycloneTicks { get; private set; }
|
||||
public int CycloneDamageEvents { get; private set; }
|
||||
public int BumpEvents { get; private set; }
|
||||
public bool ShowBoundaries { get; set; } = true;
|
||||
public bool TowersOperational => !Paused && DefensePrototypeRules.CanOperateTowers(
|
||||
Player != null && Player.IsAlive, IsRunning);
|
||||
public float TowerEfficiency
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Player == null || TowerManager == null) return 0f;
|
||||
float best = 0f;
|
||||
foreach (Defense25DTower tower in TowerManager.Towers)
|
||||
if (tower != null) best = Mathf.Max(best, tower.SightEfficiency);
|
||||
return best;
|
||||
}
|
||||
}
|
||||
public Defense25DEnemy CommandTarget { get; private set; }
|
||||
public Defense25DCycloneVisual CycloneVisual => cycloneVisual;
|
||||
public Defense25DCombatFeedback CombatFeedback => combatFeedback;
|
||||
public Defense25DPlayerArt PlayerArt => playerArt;
|
||||
private DefenseConstants Tuning => GameplayConstants.Current.Defense;
|
||||
public Vector2[] GetRouteForLane(int lane) => routes[Mathf.Clamp(lane, 0, PortalLaneCount - 1)];
|
||||
public bool IsTowerImmuneToEnemy(DefenseTowerKind kind, Defense25DEnemy enemy) =>
|
||||
enemy == null || DefensePrototypeRules.IsTowerImmune(kind, Tuning.GetPortalImmuneTowerKind(enemy.Lane));
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (initialized || settings == null) return;
|
||||
// This scene has no RunManager to resume a paused project's clock.
|
||||
Time.timeScale = 1f;
|
||||
Initialize(settings, view);
|
||||
}
|
||||
|
||||
public void Initialize(Defense25DComparisonSettings configuration, Defense25DView display = null)
|
||||
{
|
||||
if (initialized) return;
|
||||
settings = configuration;
|
||||
view = display;
|
||||
initialized = true;
|
||||
ArenaHalfExtents = settings.MapHalfExtents;
|
||||
ReduceSouthBoundaryRenderHeight();
|
||||
Player = CreateActor(ComparisonActorKind.Player, "Player (Physics3D)", settings.PlayerSpawn);
|
||||
combatFeedback = gameObject.AddComponent<Defense25DCombatFeedback>();
|
||||
combatFeedback.Bind(this);
|
||||
playerArt = gameObject.AddComponent<Defense25DPlayerArt>();
|
||||
playerArt.Bind(this);
|
||||
for (int lane = 0; lane < PortalLaneCount; lane++)
|
||||
{
|
||||
routes[lane] = new[]
|
||||
{
|
||||
Tuning.GetPortalPosition(lane),
|
||||
Tuning.GetWaypointPosition(lane, 0),
|
||||
Tuning.GetWaypointPosition(lane, 1),
|
||||
Tuning.GetBasePosition(lane),
|
||||
};
|
||||
for (int i = 0; i < Tuning.PortalEnemyCount; i++)
|
||||
{
|
||||
int id = lane * Tuning.PortalEnemyCount + i;
|
||||
Defense25DActor actor = CreateActor(ComparisonActorKind.Enemy,
|
||||
$"Portal {lane + 1} Enemy {i + 1}", routes[lane][0]);
|
||||
Defense25DEnemy enemy = actor.gameObject.AddComponent<Defense25DEnemy>();
|
||||
enemy.Configure(this, actor, id, lane);
|
||||
AttachEnemyStatusVisual(enemy);
|
||||
enemies.Add(enemy);
|
||||
}
|
||||
}
|
||||
CreateRuntimeRouteVisuals();
|
||||
TowerManager = gameObject.AddComponent<Defense25DTowerManager>();
|
||||
TowerManager.Configure(this);
|
||||
ArtifactConstants artifactTuning = GameplayConstants.Current.Artifacts;
|
||||
Loadout = new Defense25DLoadout(artifactTuning.StartingGauge, artifactTuning.MaximumGauge,
|
||||
artifactTuning.MaximumOwnedArtifacts, artifactTuning.MaximumArtifactsPerColor);
|
||||
Loadout.ConfigureCatalog(artifactTuning.Definitions);
|
||||
ArtifactExecutor = new Defense25DArtifactExecutor();
|
||||
ArtifactExecutor.Configure(this, Loadout);
|
||||
ConfigureRelicRuntime();
|
||||
ConfigureRelicEffectCallbacks();
|
||||
ConfigureRelicReplayCallbacks();
|
||||
CreatePortalRewards();
|
||||
cycloneVisual = new GameObject("Cyclone comparison").AddComponent<Defense25DCycloneVisual>();
|
||||
cycloneVisual.transform.SetParent(transform, false);
|
||||
cycloneVisual.Configure(this);
|
||||
ResetRun();
|
||||
}
|
||||
|
||||
private void ReduceSouthBoundaryRenderHeight()
|
||||
{
|
||||
GameObject boundary = GameObject.Find("South boundary");
|
||||
if (boundary == null) return;
|
||||
ExpandArenaForSummonSlots(boundary);
|
||||
MeshFilter meshFilter = boundary != null ? boundary.GetComponent<MeshFilter>() : null;
|
||||
if (meshFilter == null || meshFilter.sharedMesh == null) return;
|
||||
originalSouthBoundaryMesh = meshFilter.sharedMesh;
|
||||
adjustedSouthBoundaryMesh = Instantiate(originalSouthBoundaryMesh);
|
||||
adjustedSouthBoundaryMesh.name = originalSouthBoundaryMesh.name + " (comparison render height)";
|
||||
adjustedSouthBoundary = meshFilter;
|
||||
Mesh mesh = adjustedSouthBoundaryMesh;
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
float floor = mesh.bounds.min.y;
|
||||
float fraction = Mathf.Clamp01(settings.SouthBoundaryRenderHeightFraction);
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
vertices[i].y = floor + (vertices[i].y - floor) * fraction;
|
||||
mesh.vertices = vertices;
|
||||
mesh.RecalculateNormals();
|
||||
mesh.RecalculateBounds();
|
||||
meshFilter.sharedMesh = mesh;
|
||||
}
|
||||
|
||||
private void ExpandArenaForSummonSlots(GameObject southBoundary)
|
||||
{
|
||||
BoxCollider collider = southBoundary.GetComponent<BoxCollider>();
|
||||
if (collider == null) return;
|
||||
|
||||
DefenseConstants tuning = GameplayConstants.Current.Defense;
|
||||
int maximumSummons = tuning.SummonUnitCount + 4;
|
||||
int lastSlot = Mathf.Max(0, maximumSummons - 1);
|
||||
float lastSlotOffset = -.55f - .8f * (lastSlot / 3);
|
||||
float southernmostSlot = float.PositiveInfinity;
|
||||
for (int pad = 0; pad < 6; pad++)
|
||||
southernmostSlot = Mathf.Min(southernmostSlot,
|
||||
tuning.GetBuildPadPosition(pad).y + lastSlotOffset);
|
||||
|
||||
float maximumVariantRadius = Defense25DComparisonSettings.GiantSlimeBodyCoreDiameterPixels
|
||||
* 1.55f / Defense25DComparisonSettings.SummonBodyPixelsPerUnit * .5f;
|
||||
float requiredNorthFace = southernmostSlot - maximumVariantRadius - settings.MovementSkin;
|
||||
float shiftSouth = Mathf.Min(0f, requiredNorthFace - collider.bounds.max.z);
|
||||
if (shiftSouth >= 0f) return;
|
||||
|
||||
float extension = -shiftSouth;
|
||||
float wallThickness = collider.bounds.size.z;
|
||||
AdjustArenaTransform(southBoundary.transform,
|
||||
southBoundary.transform.position + Vector3.forward * shiftSouth,
|
||||
southBoundary.transform.localScale);
|
||||
AdjustArenaObject("North boundary", Vector3.forward * extension, 0f);
|
||||
float perimeterScaleOffset = extension * 2f + wallThickness;
|
||||
AdjustArenaObject("XZ ground", Vector3.zero, perimeterScaleOffset);
|
||||
AdjustArenaObject("East boundary", Vector3.zero, perimeterScaleOffset);
|
||||
AdjustArenaObject("West boundary", Vector3.zero, perimeterScaleOffset);
|
||||
ArenaHalfExtents += Vector2.up * extension;
|
||||
}
|
||||
|
||||
private void AdjustArenaObject(string objectName, Vector3 positionOffset, float zScaleOffset)
|
||||
{
|
||||
GameObject target = GameObject.Find(objectName);
|
||||
if (target == null) return;
|
||||
Vector3 scale = target.transform.localScale;
|
||||
scale.z += zScaleOffset;
|
||||
AdjustArenaTransform(target.transform, target.transform.position + positionOffset, scale);
|
||||
}
|
||||
|
||||
private void AdjustArenaTransform(Transform target, Vector3 position, Vector3 scale)
|
||||
{
|
||||
int index = adjustedArenaTransforms.IndexOf(target);
|
||||
if (index < 0)
|
||||
{
|
||||
adjustedArenaTransforms.Add(target);
|
||||
originalArenaPositions.Add(target.position);
|
||||
originalArenaScales.Add(target.localScale);
|
||||
}
|
||||
target.position = position;
|
||||
target.localScale = scale;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
for (int i = 0; i < adjustedArenaTransforms.Count; i++)
|
||||
{
|
||||
Transform target = adjustedArenaTransforms[i];
|
||||
if (target == null) continue;
|
||||
target.position = originalArenaPositions[i];
|
||||
target.localScale = originalArenaScales[i];
|
||||
}
|
||||
if (adjustedSouthBoundary != null
|
||||
&& adjustedSouthBoundary.sharedMesh == adjustedSouthBoundaryMesh)
|
||||
adjustedSouthBoundary.sharedMesh = originalSouthBoundaryMesh;
|
||||
if (adjustedSouthBoundaryMesh != null) Destroy(adjustedSouthBoundaryMesh);
|
||||
}
|
||||
|
||||
private Defense25DActor CreateActor(ComparisonActorKind kind, string label, Vector2 position)
|
||||
{
|
||||
GameObject go = new(label);
|
||||
go.transform.SetParent(transform, false);
|
||||
go.transform.position = Defense25DActor.OnGround(position, settings.GroundY);
|
||||
Defense25DActor actor = go.AddComponent<Defense25DActor>();
|
||||
actor.Configure(this, kind);
|
||||
return actor;
|
||||
}
|
||||
|
||||
public void RegisterSummon(Defense25DSummon summon)
|
||||
{
|
||||
if (summon != null && !summons.Contains(summon)) summons.Add(summon);
|
||||
}
|
||||
|
||||
public void UnregisterSummon(Defense25DSummon summon) => summons.Remove(summon);
|
||||
|
||||
internal static void AttachEnemyStatusVisual(Defense25DEnemy enemy)
|
||||
{
|
||||
if (enemy == null) return;
|
||||
Defense25DStatusVisual visual = enemy.GetComponent<Defense25DStatusVisual>();
|
||||
if (visual == null) visual = enemy.gameObject.AddComponent<Defense25DStatusVisual>();
|
||||
visual.Bind(enemy);
|
||||
}
|
||||
|
||||
private void CreatePortalRewards()
|
||||
{
|
||||
for (int lane = 0; lane < PortalLaneCount; lane++)
|
||||
{
|
||||
GameObject reward = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
reward.name = $"Portal {lane + 1} Construction Reward";
|
||||
reward.transform.SetParent(transform, false);
|
||||
reward.transform.localScale = Vector3.one * .38f;
|
||||
reward.transform.position = Defense25DActor.OnGround(
|
||||
Tuning.GetPortalPosition(lane) + Vector2.up * 1.2f, settings.GroundY + .3f);
|
||||
Destroy(reward.GetComponent<Collider>());
|
||||
reward.GetComponent<Renderer>().material.color = new Color32(255, 224, 84, 255);
|
||||
reward.SetActive(false);
|
||||
portalRewards[lane] = reward;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRuntimeRouteVisuals()
|
||||
{
|
||||
for (int segment = 1; segment <= 4; segment++)
|
||||
GameObject.Find($"Route segment {segment}")?.SetActive(false);
|
||||
GameObject.Find("Portal")?.SetActive(false);
|
||||
GameObject.Find("Base - arrival marker")?.SetActive(false);
|
||||
GameObject.Find("South obstacle")?.SetActive(false);
|
||||
|
||||
Transform root = new GameObject("Defense routes and build pads").transform;
|
||||
root.SetParent(transform, false);
|
||||
void Box(string label, Vector2 center, Vector3 size, Material material, float height)
|
||||
{
|
||||
GameObject item = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
item.name = label;
|
||||
item.transform.SetParent(root, false);
|
||||
item.transform.position = Defense25DActor.OnGround(center, settings.GroundY + height);
|
||||
item.transform.localScale = size;
|
||||
item.GetComponent<Renderer>().sharedMaterial = material;
|
||||
Destroy(item.GetComponent<Collider>());
|
||||
}
|
||||
|
||||
for (int lane = 0; lane < PortalLaneCount; lane++)
|
||||
{
|
||||
for (int i = 0; i < routes[lane].Length - 1; i++)
|
||||
{
|
||||
Vector2 start = routes[lane][i], end = routes[lane][i + 1];
|
||||
Vector2 delta = end - start;
|
||||
GameObject segment = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
segment.name = $"Portal {lane + 1} route segment {i + 1}";
|
||||
segment.transform.SetParent(root, false);
|
||||
segment.transform.position = Defense25DActor.OnGround((start + end) * .5f, settings.GroundY - .005f);
|
||||
segment.transform.localScale = new Vector3(.65f, .015f, delta.magnitude);
|
||||
segment.transform.rotation = Quaternion.LookRotation(Defense25DActor.OnGround(delta.normalized));
|
||||
segment.GetComponent<Renderer>().sharedMaterial = settings.PathMaterial;
|
||||
Destroy(segment.GetComponent<Collider>());
|
||||
}
|
||||
Box($"Portal {lane + 1} marker", routes[lane][0], new Vector3(1f, .12f, 1f), settings.PathMaterial, .06f);
|
||||
}
|
||||
|
||||
Box("Shared base marker", Tuning.GetBasePosition(0), new Vector3(1.25f, .4f, 1.25f), settings.BaseMaterial, .2f);
|
||||
for (int pad = 0; pad < 6; pad++)
|
||||
Box($"Tower build pad {pad + 1}", Tuning.GetBuildPadPosition(pad),
|
||||
new Vector3(.8f, .08f, .8f), settings.BaseMaterial, .035f);
|
||||
}
|
||||
|
||||
public void ResetRun()
|
||||
{
|
||||
if (!initialized) return;
|
||||
ResetPresentationRewards();
|
||||
combatFeedback?.ResetFeedback();
|
||||
playerArt?.ResetVisuals();
|
||||
previousChargeProgress = -1f;
|
||||
ArtifactExecutor.Cancel();
|
||||
Loadout.ResetRun();
|
||||
RelicRuntime?.BeginRun();
|
||||
if (view != null) BeginStartupArtifactSelection();
|
||||
CancelCyclone();
|
||||
GameTime = nextSpawn = reviveAt = buffUntil = 0f;
|
||||
BaseHealth = Tuning.BaseMaximumHealth;
|
||||
fallbackGauge = GameplayConstants.Current.Artifacts.StartingGauge;
|
||||
BumpEvents = CycloneTicks = CycloneDamageEvents = KillCurrency = 0;
|
||||
input = Vector2.zero;
|
||||
aim = Vector2.right;
|
||||
Paused = startupSelectionPending;
|
||||
IsRunning = true;
|
||||
RunCleared = RunFailed = false;
|
||||
CommandTarget = null;
|
||||
nextBump.Clear();
|
||||
nextSupplyLane = 0;
|
||||
Ledger = new DefenseWaveLedger(enemies.Count);
|
||||
for (int lane = 0; lane < PortalLaneCount; lane++)
|
||||
{
|
||||
portalLedgers[lane] = new DefenseWaveLedger(Tuning.PortalEnemyCount);
|
||||
suppliedPerLane[lane] = 0;
|
||||
portalSupplyEnded[lane] = false;
|
||||
portalRewards[lane]?.SetActive(false);
|
||||
}
|
||||
TowerManager.ResetForRun();
|
||||
Player.ResetAt(settings.PlayerSpawn);
|
||||
foreach (Defense25DEnemy enemy in enemies) enemy.ResetForRun();
|
||||
foreach (Defense25DSummon summon in summons) summon.ResetForRun();
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!initialized || !ReadInput || Keyboard.current == null) return;
|
||||
Keyboard k = Keyboard.current;
|
||||
if (k.rKey.wasPressedThisFrame) ResetRun();
|
||||
if (k.pKey.wasPressedThisFrame && !startupSelectionPending
|
||||
&& !HasPendingFieldRelicReward && !showingArtifactSelection)
|
||||
SetPaused(!Paused);
|
||||
if (k.escapeKey.wasPressedThisFrame) CancelCyclone();
|
||||
if (k.bKey.wasPressedThisFrame) ShowBoundaries = !ShowBoundaries;
|
||||
if (k.cKey.wasPressedThisFrame) ScreenRelativeMovement = !ScreenRelativeMovement;
|
||||
if (k.tabKey.wasPressedThisFrame) cycloneVisual.Ribbon = !cycloneVisual.Ribbon;
|
||||
if (k.mKey.wasPressedThisFrame) cycloneVisual.Transparent = !cycloneVisual.Transparent;
|
||||
if (k.hKey.wasPressedThisFrame) cycloneVisual.Height = cycloneVisual.Height < .4f ? .65f : .15f;
|
||||
if (k.vKey.wasPressedThisFrame) cycloneVisual.DecorationSpeed = cycloneVisual.DecorationSpeed < 2f ? 4f : 1f;
|
||||
if (k.nKey.wasPressedThisFrame) cycloneVisual.RadiusScale = cycloneVisual.RadiusScale < 1.1f ? 1.35f : 1f;
|
||||
if (k.gKey.wasPressedThisFrame && IsRunning && !Paused) DebugFillGauge();
|
||||
if (startupSelectionPending)
|
||||
{
|
||||
if (k.leftArrowKey.wasPressedThisFrame || k.upArrowKey.wasPressedThisFrame)
|
||||
startupSelectionIndex = (startupSelectionIndex + startupArtifactOptions.Count - 1)
|
||||
% startupArtifactOptions.Count;
|
||||
else if (k.rightArrowKey.wasPressedThisFrame || k.downArrowKey.wasPressedThisFrame)
|
||||
startupSelectionIndex = (startupSelectionIndex + 1) % startupArtifactOptions.Count;
|
||||
if (k.spaceKey.wasPressedThisFrame) ChooseStartupArtifact(startupSelectionIndex);
|
||||
return;
|
||||
}
|
||||
if (IsTowerShowcaseSimulationActive)
|
||||
{
|
||||
input = Vector2.zero;
|
||||
return;
|
||||
}
|
||||
if (!IsRunning || Paused || !Player.IsAlive) { input = Vector2.zero; return; }
|
||||
Mouse mouse = Mouse.current;
|
||||
bool pointerOverUI = mouse != null && IsPointerOverDefenseUI(mouse.position.ReadValue());
|
||||
if (HasPendingFieldRelicReward || showingArtifactSelection
|
||||
|| (pointerOverUI && mouse != null
|
||||
&& (mouse.leftButton.wasPressedThisFrame || mouse.leftButton.wasReleasedThisFrame)))
|
||||
{
|
||||
input = Vector2.zero;
|
||||
return;
|
||||
}
|
||||
if (k.dKey.wasPressedThisFrame) Player.TryActivateGuard();
|
||||
if (k.sKey.wasPressedThisFrame && !ArtifactExecutor.IsExecutingArtifact
|
||||
&& !ArtifactExecutor.IsDashing && Loadout.OwnedArtifactCount > 0)
|
||||
{
|
||||
int current = 0;
|
||||
for (int i = 0; i < Loadout.OwnedArtifacts.Count; i++)
|
||||
if (ReferenceEquals(Loadout.OwnedArtifacts[i], Loadout.CurrentArtifact)) { current = i; break; }
|
||||
if (Loadout.Select((current + 1) % Loadout.OwnedArtifactCount))
|
||||
{
|
||||
combatFeedback?.NotifyArtifactSwitched(Loadout.CurrentArtifact);
|
||||
playerArt?.Bind(this);
|
||||
}
|
||||
}
|
||||
if (k.aKey.wasPressedThisFrame) ArtifactExecutor.OnArtifactButtonPressed();
|
||||
if (k.aKey.wasReleasedThisFrame) ArtifactExecutor.OnArtifactButtonReleased();
|
||||
if (TowersOperational)
|
||||
{
|
||||
if (k.digit1Key.wasPressedThisFrame || k.numpad1Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Direct);
|
||||
else if (k.digit2Key.wasPressedThisFrame || k.numpad2Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Summon);
|
||||
else if (k.digit3Key.wasPressedThisFrame || k.numpad3Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Lightning);
|
||||
else if (k.digit4Key.wasPressedThisFrame || k.numpad4Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Cold);
|
||||
else if (k.digit5Key.wasPressedThisFrame || k.numpad5Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Seismic);
|
||||
else if (k.digit6Key.wasPressedThisFrame || k.numpad6Key.wasPressedThisFrame)
|
||||
TryBuildNearestTower(DefenseTowerKind.Empowering);
|
||||
else if (k.uKey.wasPressedThisFrame)
|
||||
TryUpgradeNearestTower(DefenseTowerBranch.None);
|
||||
else if (k.jKey.wasPressedThisFrame)
|
||||
TryUpgradeNearestTower(DefenseTowerBranch.First);
|
||||
else if (k.kKey.wasPressedThisFrame)
|
||||
TryUpgradeNearestTower(DefenseTowerBranch.Second);
|
||||
}
|
||||
// D is the original guard binding; movement remains on the original arrow-key layout.
|
||||
Vector2 keys = new((k.rightArrowKey.isPressed ? 1 : 0)
|
||||
- (k.leftArrowKey.isPressed ? 1 : 0),
|
||||
(k.wKey.isPressed || k.upArrowKey.isPressed ? 1 : 0)
|
||||
- (k.downArrowKey.isPressed ? 1 : 0));
|
||||
input = view != null && ScreenRelativeMovement ? view.KeyboardToGround(keys) : Vector2.ClampMagnitude(keys, 1f);
|
||||
if (view != null && mouse != null && view.TryScreenToGround(mouse.position.ReadValue(), settings.GroundY, out Vector2 point))
|
||||
{
|
||||
Vector2 direction = point - Player.Position;
|
||||
if (direction.sqrMagnitude > .0001f) aim = direction.normalized;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryBuildNearestTower(DefenseTowerKind kind)
|
||||
{
|
||||
int nearestPad = -1;
|
||||
float nearestDistance = Tuning.BuildInteractionRadius * Tuning.BuildInteractionRadius;
|
||||
for (int pad = 0; pad < 6; pad++)
|
||||
{
|
||||
if (TowerManager.GetTowerAtPad(pad) != null) continue;
|
||||
float distance = (Player.Position - Tuning.GetBuildPadPosition(pad)).sqrMagnitude;
|
||||
if (distance > nearestDistance) continue;
|
||||
nearestDistance = distance;
|
||||
nearestPad = pad;
|
||||
}
|
||||
return nearestPad >= 0 && TowerManager.TryBuildTowerAtPad(nearestPad, kind);
|
||||
}
|
||||
|
||||
private void BeginStartupArtifactSelection()
|
||||
{
|
||||
startupSelectionsRemaining = Mathf.Min(3, GameplayConstants.Current.Artifacts.MaximumOwnedArtifacts);
|
||||
startupSelectionTotal = startupSelectionsRemaining;
|
||||
startupSelectionPending = startupSelectionsRemaining > 0;
|
||||
startupSelectionIndex = 0;
|
||||
if (startupSelectionPending) PopulateStartupArtifactOptions();
|
||||
}
|
||||
|
||||
private void PopulateStartupArtifactOptions()
|
||||
{
|
||||
startupArtifactOptions.Clear();
|
||||
int selectionIndex = Mathf.Min(3 - startupSelectionsRemaining, 2);
|
||||
startupArtifactColor = selectionIndex switch
|
||||
{
|
||||
0 => ArtifactColor.Green,
|
||||
1 => ArtifactColor.Red,
|
||||
_ => ArtifactColor.Blue,
|
||||
};
|
||||
for (int i = 0; i < Loadout.Catalog.Count; i++)
|
||||
{
|
||||
ActiveArtifactDefinition definition = Loadout.Catalog[i];
|
||||
if (definition != null && definition.ArtifactColor == startupArtifactColor
|
||||
&& !Loadout.OwnsArtifact(definition.Effect))
|
||||
startupArtifactOptions.Add(definition);
|
||||
}
|
||||
if (startupArtifactOptions.Count == 0) startupSelectionPending = false;
|
||||
}
|
||||
|
||||
private bool ChooseStartupArtifact(int index)
|
||||
{
|
||||
if (!startupSelectionPending || index < 0 || index >= startupArtifactOptions.Count
|
||||
|| !Loadout.TryAddArtifact(startupArtifactOptions[index])) return false;
|
||||
startupSelectionsRemaining--;
|
||||
if (startupSelectionsRemaining <= 0)
|
||||
{
|
||||
startupSelectionPending = false;
|
||||
Paused = false;
|
||||
startupArtifactOptions.Clear();
|
||||
return true;
|
||||
}
|
||||
PopulateStartupArtifactOptions();
|
||||
startupSelectionIndex = 0;
|
||||
if (!startupSelectionPending) Paused = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryUpgradeNearestTower(DefenseTowerBranch branch)
|
||||
{
|
||||
Defense25DTower nearest = null;
|
||||
float nearestDistance = Tuning.BuildInteractionRadius * Tuning.BuildInteractionRadius;
|
||||
foreach (Defense25DTower tower in TowerManager.TowersInConstructionOrder)
|
||||
{
|
||||
if (tower == null) continue;
|
||||
float distance = (Player.Position - tower.Position).sqrMagnitude;
|
||||
if (distance > nearestDistance) continue;
|
||||
nearestDistance = distance;
|
||||
nearest = tower;
|
||||
}
|
||||
return nearest != null && TowerManager.TryUpgradeTowerAtPad(nearest.PadIndex, branch);
|
||||
}
|
||||
|
||||
public void SetMovementInput(Vector2 direction) => input = Vector2.ClampMagnitude(direction, 1f);
|
||||
public void DebugFillGauge()
|
||||
{
|
||||
if (Loadout != null) Loadout.AddGauge(Loadout.MaximumGauge);
|
||||
else fallbackGauge = GameplayConstants.Current.Artifacts.MaximumGauge;
|
||||
}
|
||||
public void SetPaused(bool value) { Paused = value; input = Vector2.zero; }
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!initialized || !IsRunning || Paused) return;
|
||||
float dt = Time.fixedDeltaTime;
|
||||
if (IsTowerShowcaseSimulationActive)
|
||||
{
|
||||
AdvanceTowerShowcase(dt);
|
||||
TowerManager.Step(dt);
|
||||
return;
|
||||
}
|
||||
GameTime += dt;
|
||||
RelicRuntime?.Advance(GameTime, Player.CurrentHealth);
|
||||
if (buffUntil > 0f && GameTime >= buffUntil)
|
||||
{
|
||||
Player.RemoveModifiersFromSource(CycloneBuff);
|
||||
buffUntil = 0f;
|
||||
}
|
||||
if (!Player.IsAlive && GameTime >= reviveAt)
|
||||
{
|
||||
Player.ResetAt(settings.PlayerSpawn);
|
||||
Player.GrantInvulnerability(Tuning.PlayerReviveInvulnerabilitySeconds);
|
||||
}
|
||||
if (AutoSpawn && GameTime >= nextSpawn && Ledger.SuppliedCount < enemies.Count)
|
||||
{
|
||||
SpawnNextEnemy();
|
||||
nextSpawn = GameTime + Tuning.PortalSpawnInterval;
|
||||
}
|
||||
if (Player.IsAlive)
|
||||
{
|
||||
if (CycloneActive) StepCyclone(dt);
|
||||
else if (Player.HasKnockback || Player.IsHurt) Player.Move(Player.ConsumeKnockback(dt));
|
||||
else
|
||||
{
|
||||
Vector2 velocity = input * Player.MoveSpeed;
|
||||
ResolveBumps(velocity);
|
||||
if (Player.IsAlive && !Player.IsHurt)
|
||||
{
|
||||
Vector2 moved = Player.Move(velocity * dt);
|
||||
if (moved.sqrMagnitude > .0000001f)
|
||||
{
|
||||
AddGauge(GameplayConstants.Current.Artifacts.MovementGaugeGainPerSecond
|
||||
* Mathf.Min(dt, moved.magnitude / Mathf.Max(.001f, Player.MoveSpeed)));
|
||||
RelicRuntime?.AddVoluntaryMovement(moved.magnitude,
|
||||
Player.Radius * 2f, Player.CurrentHealth);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.sqrMagnitude > .001f) Player.Facing = input.normalized;
|
||||
}
|
||||
CommandTarget = null;
|
||||
ArtifactExecutor.Step(dt);
|
||||
float chargeProgress = Loadout != null && Loadout.IsCharging ? Loadout.ChargeProgress : 0f;
|
||||
if (Loadout?.CurrentArtifact != null)
|
||||
combatFeedback?.NotifyChargeChanged(Loadout.CurrentArtifact, chargeProgress,
|
||||
chargeProgress >= 1f && previousChargeProgress < 1f);
|
||||
previousChargeProgress = chargeProgress;
|
||||
StepRelicHealingOrbs();
|
||||
TowerManager.Step(dt);
|
||||
foreach (Defense25DEnemy enemy in enemies) enemy.Step(dt);
|
||||
foreach (Defense25DSummon summon in summons) summon.Step(dt);
|
||||
OfferPortalRewards();
|
||||
TryCollectPortalRewards();
|
||||
TryFinishRunClear();
|
||||
}
|
||||
|
||||
public Defense25DEnemy SpawnNextEnemy()
|
||||
{
|
||||
if (Ledger.SuppliedCount >= enemies.Count) return null;
|
||||
for (int attempt = 0; attempt < PortalLaneCount; attempt++)
|
||||
{
|
||||
int lane = (nextSupplyLane + attempt) % PortalLaneCount;
|
||||
if (suppliedPerLane[lane] >= Tuning.PortalEnemyCount) continue;
|
||||
Defense25DEnemy enemy = null;
|
||||
foreach (Defense25DEnemy candidate in enemies)
|
||||
{
|
||||
if (candidate.Lane == lane && candidate.CanSpawn) { enemy = candidate; break; }
|
||||
}
|
||||
if (enemy == null || !Ledger.RegisterSupply(enemy.Id)
|
||||
|| !portalLedgers[lane].RegisterSupply(enemy.Id)) return null;
|
||||
suppliedPerLane[lane]++;
|
||||
enemy.Spawn(routes[lane][0]);
|
||||
if (suppliedPerLane[lane] >= Tuning.PortalEnemyCount)
|
||||
{
|
||||
portalSupplyEnded[lane] = true;
|
||||
portalLedgers[lane].MarkSupplyEnded();
|
||||
}
|
||||
nextSupplyLane = (lane + 1) % PortalLaneCount;
|
||||
if (Ledger.SuppliedCount >= enemies.Count) Ledger.MarkSupplyEnded();
|
||||
return enemy;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryCollectRewardAtLane(int lane)
|
||||
{
|
||||
if (lane < 0 || lane >= PortalLaneCount || !IsRunning || Paused
|
||||
|| Player == null || !Player.IsAlive || portalRewards[lane] == null
|
||||
|| !portalLedgers[lane].IsRewardOffered
|
||||
|| Vector2.Distance(Player.Position, Tuning.GetPortalPosition(lane) + Vector2.up * 1.2f)
|
||||
> Tuning.RewardPickupRadius
|
||||
|| !portalLedgers[lane].TryCollectReward()) return false;
|
||||
portalRewards[lane].SetActive(false);
|
||||
TowerManager.AddCurrency(Tuning.PortalRewardCurrency);
|
||||
QueueFieldRelicRewardForCollectedPortal(lane);
|
||||
TryFinishRunClear();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OfferPortalRewards()
|
||||
{
|
||||
for (int lane = 0; lane < PortalLaneCount; lane++)
|
||||
{
|
||||
if (!portalSupplyEnded[lane] || !portalLedgers[lane].TryOfferReward()) continue;
|
||||
if (portalRewards[lane] == null)
|
||||
{
|
||||
GameObject reward = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
reward.name = $"Portal {lane + 1} Construction Reward";
|
||||
reward.transform.SetParent(transform, false);
|
||||
reward.transform.localScale = Vector3.one * .38f;
|
||||
reward.transform.position = Defense25DActor.OnGround(
|
||||
Tuning.GetPortalPosition(lane) + Vector2.up * 1.2f, settings.GroundY + .3f);
|
||||
Destroy(reward.GetComponent<Collider>());
|
||||
reward.GetComponent<Renderer>().material.color = new Color32(255, 224, 84, 255);
|
||||
portalRewards[lane] = reward;
|
||||
}
|
||||
portalRewards[lane].SetActive(true);
|
||||
}
|
||||
if (portalSupplyEnded[0] && portalSupplyEnded[1]) Ledger.TryOfferReward();
|
||||
}
|
||||
|
||||
private void TryCollectPortalRewards()
|
||||
{
|
||||
for (int lane = 0; lane < PortalLaneCount; lane++)
|
||||
if (portalRewards[lane] != null && portalRewards[lane].activeSelf)
|
||||
TryCollectRewardAtLane(lane);
|
||||
}
|
||||
|
||||
private void TryFinishRunClear()
|
||||
{
|
||||
if (!IsRunning || RunFailed || Ledger.ResolvedCount < enemies.Count
|
||||
|| !portalSupplyEnded[0] || !portalSupplyEnded[1]
|
||||
|| !portalLedgers[0].IsRewardCollected || !portalLedgers[1].IsRewardCollected
|
||||
|| !CanFinishRunClearPresentation) return;
|
||||
Ledger.TryOfferReward();
|
||||
Ledger.TryCollectReward();
|
||||
RunCleared = true;
|
||||
EndRun();
|
||||
}
|
||||
|
||||
private void AddGauge(float gain)
|
||||
{
|
||||
if (CycloneActive || !Player.IsAlive || !IsRunning || Paused) return;
|
||||
float adjusted = Player.Evaluate(CharacterStat.ArtifactGaugeGain, gain);
|
||||
if (Loadout != null) Loadout.AddGauge(adjusted);
|
||||
else fallbackGauge = Mathf.Clamp(fallbackGauge + adjusted,
|
||||
0f, GameplayConstants.Current.Artifacts.MaximumGauge);
|
||||
}
|
||||
|
||||
// A Physics3D broadphase only. The final footprint/ellipse checks below use XZ.
|
||||
public IReadOnlyList<Defense25DActor> QueryEnemies(Vector2 center, Vector2 radii)
|
||||
{
|
||||
candidates.Clear();
|
||||
unique.Clear();
|
||||
Vector3 low = Defense25DActor.OnGround(center, settings.GroundY + .05f);
|
||||
Vector3 high = low + Vector3.up * settings.ActorHeight;
|
||||
int count = Physics.OverlapBoxNonAlloc((low + high) * .5f,
|
||||
new Vector3(radii.x + 1f, settings.ActorHeight, radii.y + 1f), overlap,
|
||||
Quaternion.identity, Defense25DComparisonSettings.ActorMask, QueryTriggerInteraction.Ignore);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Defense25DActor actor = overlap[i].GetComponentInParent<Defense25DActor>();
|
||||
if (actor != null && actor.Kind == ComparisonActorKind.Enemy && actor.IsAlive && unique.Add(actor))
|
||||
candidates.Add(actor);
|
||||
}
|
||||
candidates.Sort((a, b) => (a.Position - center).sqrMagnitude.CompareTo((b.Position - center).sqrMagnitude));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
public void ResolveBumps(Vector2 attackVelocity)
|
||||
{
|
||||
CombatConstants combat = GameplayConstants.Current.Combat;
|
||||
if (!IsRunning || Paused || !Player.IsAlive || Player.IsHurt || Player.HasKnockback || CycloneActive
|
||||
|| attackVelocity.magnitude < combat.MinimumAttackSpeed) return;
|
||||
QueryEnemies(Player.Center, Vector2.one * (Player.Radius + .5f));
|
||||
var bumpTargets = new List<Defense25DActor>(candidates);
|
||||
foreach (Defense25DActor enemy in bumpTargets)
|
||||
{
|
||||
if (Player.GapTo(enemy) > settings.ContactSensorMargin
|
||||
|| (nextBump.TryGetValue(enemy, out float next) && GameTime < next)) continue;
|
||||
Vector2 toward = (enemy.Position - Player.Position).normalized;
|
||||
if (Vector2.Dot(attackVelocity.normalized, toward) < combat.MinimumApproachDot) continue;
|
||||
HitSide side = BumpCombatMath.ClassifySide(enemy.Facing, Player.Position - enemy.Position);
|
||||
bool offset = BumpCombatMath.IsOffsetHit(side, attackVelocity, Player.Position, enemy.Position, combat.MinimumOffsetDistance);
|
||||
float baseDamage = Player.CalculateDamage(combat.BaseDamage, DamageTag.Collision);
|
||||
float damage = BumpCombatMath.ApplySideMultiplier(baseDamage, side, offset);
|
||||
damage = RelicRuntime?.ApplyTrophyBumpDamage(
|
||||
RelicRuntime.ApplyOrdinaryBumpDamage(damage, true, false), true) ?? damage;
|
||||
float force = combat.KnockbackForce * (side == HitSide.Front ? combat.FrontKnockbackMultiplier : 1f);
|
||||
if (!enemy.TakeDamage(damage, toward, force,
|
||||
BumpCombat.Enemies.EnemyDamageSource.Player, ordinaryBump: true))
|
||||
{
|
||||
combatFeedback?.NotifyBlockedBump(enemy.ClosestGroundPoint(Player.Center),
|
||||
attackVelocity.normalized);
|
||||
continue;
|
||||
}
|
||||
nextBump[enemy] = GameTime + combat.NormalHitInterval;
|
||||
BumpEvents++;
|
||||
AddGauge(GameplayConstants.Current.Artifacts.NormalBumpGaugeGain);
|
||||
Player.Visual.ShowAttack(side == HitSide.Back);
|
||||
if (side == HitSide.Back) enemy.GetComponent<Defense25DEnemy>()?.InterruptAttackFromRearHit();
|
||||
if (side == HitSide.Front && !offset && combat.EnableFrontalBumpSelfDamage)
|
||||
Player.TakeDamage(settings.Enemy.AttackDamage, -toward, GameplayConstants.Current.Enemies.NormalPlayerKnockbackDistance);
|
||||
CombatEvents.RaiseValidHit(new CombatHitResult(
|
||||
Player.gameObject,
|
||||
enemy.gameObject,
|
||||
false,
|
||||
side,
|
||||
damage,
|
||||
toward,
|
||||
force,
|
||||
enemy.ClosestGroundPoint(Player.Center),
|
||||
offset,
|
||||
damageTag: DamageTag.Collision,
|
||||
sourceId: null,
|
||||
baseBumpDamage: damage));
|
||||
RelicRuntime?.NotifyHit(CreateRelicHit(enemy.GetComponent<Defense25DEnemy>(), damage,
|
||||
toward, baseDamage, true, false), Player.CurrentHealth, Player.MaxHealth);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryUseCyclone(bool charged, Vector2 direction)
|
||||
{
|
||||
if (!IsRunning || Paused || !Player.IsAlive || Player.IsHurt || CycloneActive) return false;
|
||||
var definition = settings.Cyclone;
|
||||
float cost = charged ? definition.ChargedGaugeCost : definition.NormalGaugeCost;
|
||||
if (Gauge < cost) return false;
|
||||
if (Loadout != null) Loadout.AddGauge(-cost);
|
||||
else fallbackGauge = Mathf.Max(0f, fallbackGauge - cost);
|
||||
CycloneActive = true;
|
||||
cycloneCharged = charged;
|
||||
cycloneBlocked = false;
|
||||
CycloneElapsed = 0f;
|
||||
CycloneTicks = CycloneDamageEvents = nextCycloneTick = 0;
|
||||
cycloneDuration = Mathf.Max(Time.fixedDeltaTime, charged ? definition.ChargedDuration : definition.NormalDuration);
|
||||
cycloneInterval = Mathf.Max(Time.fixedDeltaTime, definition.HitInterval);
|
||||
cycloneDistance = charged ? definition.ChargedMoveDistance : definition.NormalMoveDistance;
|
||||
cycloneRange = charged ? definition.ChargedRange : definition.NormalRange;
|
||||
cycloneDirection = direction.sqrMagnitude > .0001f ? direction.normalized : Player.Facing;
|
||||
scheduledCycloneTicks = CycloneGeometry.ScheduledHitCount(cycloneDuration, cycloneInterval);
|
||||
float invulnerability = charged ? definition.ChargedInvulnerabilityDuration : definition.NormalInvulnerabilityDuration;
|
||||
if (invulnerability > 0f) Player.GrantInvulnerability(invulnerability);
|
||||
cycloneVisual.SetVisible(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Vector2 CycloneCenter => Player.Position + CycloneGeometry.CenterOffset(cycloneRange, cycloneCharged, settings.PlayerVisualScale);
|
||||
public Vector2 CycloneRadii => CycloneGeometry.Radii(cycloneRange, cycloneCharged);
|
||||
public bool IsCycloneCharged => cycloneCharged;
|
||||
|
||||
private void StepCyclone(float dt)
|
||||
{
|
||||
while (nextCycloneTick < scheduledCycloneTicks && CycloneElapsed + .0001f >= nextCycloneTick * cycloneInterval)
|
||||
{
|
||||
DealCycloneTick(nextCycloneTick == scheduledCycloneTicks - 1);
|
||||
nextCycloneTick++;
|
||||
CycloneTicks++;
|
||||
}
|
||||
if (CycloneElapsed >= cycloneDuration)
|
||||
{
|
||||
// Includes a tick exactly on the duration boundary, without losing it to a frame step.
|
||||
while (nextCycloneTick < scheduledCycloneTicks)
|
||||
{
|
||||
DealCycloneTick(nextCycloneTick == scheduledCycloneTicks - 1);
|
||||
nextCycloneTick++;
|
||||
CycloneTicks++;
|
||||
}
|
||||
CycloneActive = false;
|
||||
cycloneVisual.SetVisible(false);
|
||||
Player.RemoveModifiersFromSource(CycloneBuff);
|
||||
float increase = cycloneCharged ? settings.Cyclone.ChargedMoveSpeedIncrease : settings.Cyclone.NormalMoveSpeedIncrease;
|
||||
Player.AddModifier(new StatModifier(CycloneBuff, CharacterStat.MoveSpeed, ModifierOperation.Increased, increase));
|
||||
buffUntil = GameTime + (cycloneCharged ? settings.Cyclone.ChargedBuffDuration : settings.Cyclone.NormalBuffDuration);
|
||||
return;
|
||||
}
|
||||
float nextTime = Mathf.Min(cycloneDuration, CycloneElapsed + dt);
|
||||
if (!cycloneBlocked)
|
||||
{
|
||||
Player.Move(cycloneDirection * (cycloneDistance * (nextTime - CycloneElapsed) / cycloneDuration), false);
|
||||
cycloneBlocked = Player.MovementBlocked;
|
||||
}
|
||||
CycloneElapsed = nextTime;
|
||||
}
|
||||
|
||||
private void DealCycloneTick(bool final)
|
||||
{
|
||||
Vector2 center = CycloneCenter;
|
||||
Vector2 radii = CycloneRadii;
|
||||
QueryEnemies(center, radii);
|
||||
int cap = cycloneCharged ? settings.Cyclone.ChargedMaxTargets : settings.Cyclone.NormalMaxTargets;
|
||||
int hit = 0;
|
||||
foreach (Defense25DActor enemy in candidates)
|
||||
{
|
||||
if (hit >= cap) break;
|
||||
if (!CycloneGeometry.Overlaps(center, radii, enemy.ClosestPointFunction)) continue;
|
||||
float damage = Player.CalculateDamage(cycloneCharged ? settings.Cyclone.ChargedDamage : settings.Cyclone.NormalDamage,
|
||||
settings.Cyclone.DamageTag);
|
||||
float knockback = final ? (cycloneCharged ? settings.Cyclone.ChargedKnockback : settings.Cyclone.NormalKnockback) : 0f;
|
||||
if (!enemy.TakeDamage(damage, enemy.Position - center, knockback)) continue;
|
||||
hit++;
|
||||
CycloneDamageEvents++;
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelCyclone()
|
||||
{
|
||||
CycloneActive = false;
|
||||
cycloneBlocked = false;
|
||||
if (cycloneVisual != null) cycloneVisual.SetVisible(false);
|
||||
}
|
||||
|
||||
public void NotifyDeath(Defense25DActor actor)
|
||||
{
|
||||
if (actor == Player)
|
||||
{
|
||||
CancelCyclone();
|
||||
Player.RemoveModifiersFromSource(CycloneBuff);
|
||||
buffUntil = 0f;
|
||||
reviveAt = GameTime + Tuning.PlayerReviveDelaySeconds;
|
||||
RelicRuntime?.Advance(GameTime, 0f);
|
||||
foreach (Defense25DSummon summon in summons) summon.SuspendAttack();
|
||||
CommandTarget = null;
|
||||
}
|
||||
else if (actor.Kind == ComparisonActorKind.Enemy)
|
||||
{
|
||||
Defense25DEnemy enemy = actor.GetComponent<Defense25DEnemy>();
|
||||
if (enemy != null && enemy.IsShowcaseDummy) NotifyTowerShowcaseDummyDeath(enemy);
|
||||
else ResolveEnemy(enemy, DefenseEnemyResolution.Death);
|
||||
}
|
||||
else actor.GetComponent<Defense25DSummon>()?.BeginRespawn();
|
||||
}
|
||||
|
||||
public void ResolveEnemy(Defense25DEnemy enemy, DefenseEnemyResolution resolution)
|
||||
{
|
||||
if (enemy == null || !Ledger.TryResolve(enemy.Id, resolution)) return;
|
||||
if (resolution == DefenseEnemyResolution.Death)
|
||||
RelicRuntime?.NotifyEnemyDeath(CreateRelicTarget(enemy), Player.CurrentHealth);
|
||||
portalLedgers[Mathf.Clamp(enemy.Lane, 0, PortalLaneCount - 1)]?.TryResolve(enemy.Id, resolution);
|
||||
if (resolution == DefenseEnemyResolution.Death) KillCurrency += Tuning.EnemyKillCurrency;
|
||||
if (resolution == DefenseEnemyResolution.Death) TowerManager.AddCurrency(Tuning.EnemyKillCurrency);
|
||||
else BaseHealth = Mathf.Max(0f, BaseHealth - Tuning.ArrivalBaseDamage);
|
||||
if (resolution == DefenseEnemyResolution.Death) enemy.RetireForDeathVisual();
|
||||
else enemy.Retire();
|
||||
if (DefensePrototypeRules.IsBaseDestroyed(BaseHealth))
|
||||
{
|
||||
RunFailed = true;
|
||||
EndRun();
|
||||
}
|
||||
}
|
||||
|
||||
public void EndRun()
|
||||
{
|
||||
IsRunning = false;
|
||||
ArtifactExecutor?.Cancel();
|
||||
RelicRuntime?.EndRun();
|
||||
CancelCyclone();
|
||||
input = Vector2.zero;
|
||||
CommandTarget = null;
|
||||
Player.RemoveModifiersFromSource(CycloneBuff);
|
||||
TowerManager?.CancelAll();
|
||||
Player.StopBody();
|
||||
foreach (Defense25DEnemy enemy in enemies) enemy.Actor.StopBody();
|
||||
foreach (Defense25DSummon summon in summons) { summon.SuspendAttack(); summon.Actor.StopBody(); }
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
CleanupPresentationDamagePopups();
|
||||
combatFeedback?.ResetFeedback();
|
||||
if (combatFeedback != null) combatFeedback.enabled = false;
|
||||
if (initialized) EndRun();
|
||||
}
|
||||
|
||||
private void RestoreRuntimeFeedbackAfterEnable()
|
||||
{
|
||||
if (!initialized) return;
|
||||
if (combatFeedback != null)
|
||||
{
|
||||
if (!combatFeedback.enabled) combatFeedback.enabled = true;
|
||||
else if (combatFeedback.TransparentEffectMaterial == null) combatFeedback.Bind(this);
|
||||
}
|
||||
if (playerArt != null)
|
||||
{
|
||||
playerArt.ResetVisuals();
|
||||
playerArt.Bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyPlayerArtifactUseVisual(Defense25DArtifactUse use)
|
||||
{
|
||||
if (Player == null || use.Definition == null) return;
|
||||
if (use.Effect == ActiveArtifactEffect.Dash) Player.Visual.ShowDash();
|
||||
else Player.Visual.ShowArtifactUse();
|
||||
combatFeedback?.NotifyArtifactReleased(use.Definition,
|
||||
Player.Position, use.Charged);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
DrawHud(Defense25DView.GetViewport(Screen.width, Screen.height));
|
||||
}
|
||||
|
||||
private void DrawHud(Rect viewport) => DrawDefensePresentationGUI(viewport);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3da342f87d5c43a5905256f7b5515c7e
|
||||
@@ -0,0 +1,731 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BumpCombat.Combat;
|
||||
using System.Text.RegularExpressions;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Defense;
|
||||
using BumpCombat.Player;
|
||||
using BumpCombat.Progression;
|
||||
using BumpCombat.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public sealed partial class Defense25DComparison
|
||||
{
|
||||
private static readonly Rect RunStatusRect = new(7, 5, 626, 20);
|
||||
private static readonly Rect ArtifactBarRect = new(7, 28, 626, 25);
|
||||
private static readonly Rect BottomControlsRect = new(7, 320, 626, 37);
|
||||
private static readonly Rect FieldRelicPanelRect = new(112, 96, 416, 168);
|
||||
private static readonly Rect StartupArtifactPanelRect = new(107, 88, 426, 184);
|
||||
private static readonly Rect ArtifactPickerRect = new(378, 61, 250, 151);
|
||||
private static readonly Rect[] PersistentDefenseUiRects =
|
||||
{ RunStatusRect, ArtifactBarRect, BottomControlsRect };
|
||||
private static readonly DefenseTowerKind[] PresentationTowerKinds =
|
||||
{
|
||||
DefenseTowerKind.Direct, DefenseTowerKind.Summon, DefenseTowerKind.Lightning,
|
||||
DefenseTowerKind.Cold, DefenseTowerKind.Seismic, DefenseTowerKind.Empowering,
|
||||
};
|
||||
|
||||
private DefenseTowerKind selectedBuildKind = DefenseTowerKind.Direct;
|
||||
private int selectedBuildPad = -1;
|
||||
private int startupArtifactPage;
|
||||
private readonly Queue<PassiveRelicId[]> earnedRelicChoices = new();
|
||||
private readonly bool[] relicChoiceQueuedForLane = new bool[2];
|
||||
private bool resumeAfterRelicChoice;
|
||||
private Func<bool> enterValidationShowcase;
|
||||
private Action exitValidationShowcase;
|
||||
private bool validationShowcaseActive;
|
||||
private readonly CombatDamagePopup[] combatDamagePopups =
|
||||
new CombatDamagePopup[Defense25DCombatPopupConstants.MaximumConcurrentPopups];
|
||||
private int activeCombatDamagePopupCount;
|
||||
private Defense25DCombatPopupLifecycle combatPopupLifecycle;
|
||||
private bool combatPopupEventsSubscribed;
|
||||
private bool hasPresentationMessage;
|
||||
private string presentationMessage;
|
||||
private float presentationMessageUntil;
|
||||
private GUIStyle compactStatusStyle;
|
||||
private GUIStyle combatDamagePopupStyle;
|
||||
|
||||
private struct CombatDamagePopup
|
||||
{
|
||||
public string Text;
|
||||
public Color Color;
|
||||
public Vector3 Position;
|
||||
public float StartedAt;
|
||||
public bool IsTowerPopup;
|
||||
|
||||
public void Set(string text, Color color, Vector3 position, bool isTowerPopup)
|
||||
{
|
||||
Text = text;
|
||||
Color = color;
|
||||
Position = position;
|
||||
StartedAt = Time.unscaledTime;
|
||||
IsTowerPopup = isTowerPopup;
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<DefenseTowerKind> TowerShowcaseKinds => PresentationTowerKinds;
|
||||
public int SelectedBuildPad => selectedBuildPad;
|
||||
public DefenseTowerKind SelectedBuildKind => selectedBuildKind;
|
||||
public IReadOnlyList<PassiveRelicId> PendingFieldRelicChoices =>
|
||||
earnedRelicChoices.Count == 0 ? Array.Empty<PassiveRelicId>() : earnedRelicChoices.Peek();
|
||||
public bool HasPendingFieldRelicReward => earnedRelicChoices.Count > 0;
|
||||
public bool CanFinishRunClearPresentation => !HasPendingFieldRelicReward;
|
||||
public bool IsValidationShowcaseActive => validationShowcaseActive;
|
||||
public int ActiveTowerDamagePopupCount
|
||||
{
|
||||
get
|
||||
{
|
||||
PruneCombatDamagePopups();
|
||||
int count = 0;
|
||||
for (int i = 0; i < activeCombatDamagePopupCount; i++)
|
||||
if (combatDamagePopups[i].IsTowerPopup) count++;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
public int ActiveCombatDamagePopupCount
|
||||
{
|
||||
get
|
||||
{
|
||||
PruneCombatDamagePopups();
|
||||
return activeCombatDamagePopupCount;
|
||||
}
|
||||
}
|
||||
|
||||
public Rect[] GetPersistentDefenseUiHitRects() => (Rect[])PersistentDefenseUiRects.Clone();
|
||||
|
||||
public Rect GetSelectedPadDetailsRect()
|
||||
{
|
||||
if (selectedBuildPad < 0 || TowerManager == null) return Rect.zero;
|
||||
float x = 8f;
|
||||
if (view != null && view.Camera != null)
|
||||
{
|
||||
Vector2 pad = GameplayConstants.Current.Defense.GetBuildPadPosition(selectedBuildPad);
|
||||
Vector3 projected = view.Camera.WorldToViewportPoint(
|
||||
new Vector3(pad.x, settings.GroundY, pad.y));
|
||||
if (projected.x < .5f) x = 422f;
|
||||
}
|
||||
return new Rect(x, 119, 210, 122);
|
||||
}
|
||||
|
||||
public void RecordTowerDamagePopup(float actualDamage, Color color, Vector3 worldPosition)
|
||||
{
|
||||
if (actualDamage > 0f)
|
||||
AddCombatDamagePopup($"-{DamageCalculator.FormatDamage(actualDamage)}",
|
||||
color, worldPosition, isTowerPopup: true);
|
||||
}
|
||||
|
||||
public void NotifyPlayerDamagePopup(float actualDamage, Vector3 worldPosition)
|
||||
{
|
||||
if (actualDamage > 0f)
|
||||
AddCombatDamagePopup($"-{DamageCalculator.FormatDamage(actualDamage)}",
|
||||
Defense25DCombatPopupConstants.PlayerDamageColor, worldPosition,
|
||||
isTowerPopup: false);
|
||||
}
|
||||
|
||||
private void HandleComparisonValidHit(CombatHitResult result)
|
||||
{
|
||||
if (Player == null || result.Attacker != Player.gameObject || result.Target == null) return;
|
||||
Defense25DEnemy target = result.Target.GetComponentInParent<Defense25DEnemy>();
|
||||
if (target == null || !enemies.Contains(target)) return;
|
||||
|
||||
string damageText = DamageCalculator.FormatDamage(result.Damage);
|
||||
string popupText = !string.IsNullOrEmpty(result.SourceId)
|
||||
? $"{GetArtifactHitDisplayName(result.SourceId)} {damageText}"
|
||||
: result.HasBackBonus
|
||||
? $"BACK ×1.5 {damageText}"
|
||||
: result.IsOffsetHit
|
||||
? $"OFFSET ×1.2 {damageText}"
|
||||
: result.HasPositionBonus
|
||||
? $"SIDE ×1.2 {damageText}"
|
||||
: damageText;
|
||||
if (result.LaunchSucceeded) popupText = $"LAUNCH {popupText}";
|
||||
|
||||
Color color = TryGetArtifactPopupColor(result, out Color artifactColor)
|
||||
? artifactColor
|
||||
: result.LaunchSucceeded
|
||||
? Defense25DCombatPopupConstants.LaunchColor
|
||||
: result.HasBackBonus
|
||||
? Defense25DCombatPopupConstants.BackHitColor
|
||||
: result.HasPositionBonus
|
||||
? Defense25DCombatPopupConstants.SideHitColor
|
||||
: Color.white;
|
||||
AddCombatDamagePopup(popupText, color, result.Target.transform.position,
|
||||
isTowerPopup: false);
|
||||
}
|
||||
|
||||
private static bool TryGetArtifactPopupColor(CombatHitResult result, out Color color)
|
||||
{
|
||||
if (result.IsArtifactHit)
|
||||
{
|
||||
color = GetArtifactPopupColor(result.ArtifactEffect);
|
||||
return true;
|
||||
}
|
||||
if (TryGetLegacyArtifactEffect(result.SourceId, out ActiveArtifactEffect effect))
|
||||
{
|
||||
color = GetArtifactPopupColor(effect);
|
||||
return true;
|
||||
}
|
||||
color = Color.white;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Color GetArtifactPopupColor(ActiveArtifactEffect effect)
|
||||
{
|
||||
if (effect < ActiveArtifactEffect.Dash || effect > ActiveArtifactEffect.ChainLightning)
|
||||
return Color.white;
|
||||
return ActiveArtifactDefinition.GetArtifactPaletteColor(
|
||||
ActiveArtifactDefinition.GetArtifactColor(effect));
|
||||
}
|
||||
|
||||
private static bool TryGetLegacyArtifactEffect(string sourceId, out ActiveArtifactEffect effect)
|
||||
{
|
||||
if (string.Equals(sourceId, "dash", StringComparison.OrdinalIgnoreCase))
|
||||
effect = ActiveArtifactEffect.Dash;
|
||||
else if (string.Equals(sourceId, "pulse", StringComparison.OrdinalIgnoreCase))
|
||||
effect = ActiveArtifactEffect.Pulse;
|
||||
else if (string.Equals(sourceId, "phoenix", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(sourceId, "scorching_ray", StringComparison.OrdinalIgnoreCase))
|
||||
effect = ActiveArtifactEffect.Phoenix;
|
||||
else if (string.Equals(sourceId, "cyclone", StringComparison.OrdinalIgnoreCase))
|
||||
effect = ActiveArtifactEffect.Cyclone;
|
||||
else if (string.Equals(sourceId, "thunder_crash", StringComparison.OrdinalIgnoreCase))
|
||||
effect = ActiveArtifactEffect.ThunderCrash;
|
||||
else if (string.Equals(sourceId, "chain_lightning", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(sourceId, "arc", StringComparison.OrdinalIgnoreCase))
|
||||
effect = ActiveArtifactEffect.ChainLightning;
|
||||
else
|
||||
{
|
||||
effect = default;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetArtifactHitDisplayName(string sourceId)
|
||||
{
|
||||
if (Loadout != null)
|
||||
foreach (ActiveArtifactDefinition definition in Loadout.Catalog)
|
||||
if (string.Equals(definition.ArtifactId, sourceId,
|
||||
StringComparison.OrdinalIgnoreCase)) return definition.DisplayName;
|
||||
return sourceId.ToUpperInvariant();
|
||||
}
|
||||
|
||||
private void AddCombatDamagePopup(string text, Color color, Vector3 worldPosition,
|
||||
bool isTowerPopup)
|
||||
{
|
||||
if (!isActiveAndEnabled) return;
|
||||
PruneCombatDamagePopups();
|
||||
if (activeCombatDamagePopupCount >= combatDamagePopups.Length) return;
|
||||
combatDamagePopups[activeCombatDamagePopupCount++].Set(
|
||||
text, color, worldPosition, isTowerPopup);
|
||||
}
|
||||
|
||||
private void PruneCombatDamagePopups()
|
||||
{
|
||||
float now = Time.unscaledTime;
|
||||
for (int i = activeCombatDamagePopupCount - 1; i >= 0; i--)
|
||||
{
|
||||
if (now - combatDamagePopups[i].StartedAt
|
||||
< Defense25DCombatPopupConstants.LifetimeSeconds) continue;
|
||||
activeCombatDamagePopupCount--;
|
||||
combatDamagePopups[i] = combatDamagePopups[activeCombatDamagePopupCount];
|
||||
combatDamagePopups[activeCombatDamagePopupCount] = default;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializePresentationDamagePopups()
|
||||
{
|
||||
if (combatPopupLifecycle == null)
|
||||
{
|
||||
combatPopupLifecycle = GetComponent<Defense25DCombatPopupLifecycle>();
|
||||
if (combatPopupLifecycle == null)
|
||||
combatPopupLifecycle = gameObject.AddComponent<Defense25DCombatPopupLifecycle>();
|
||||
}
|
||||
combatPopupLifecycle.Bind(this);
|
||||
SetCombatDamagePopupEventsSubscribed(true);
|
||||
}
|
||||
|
||||
private void CleanupPresentationDamagePopups()
|
||||
{
|
||||
SetCombatDamagePopupEventsSubscribed(false);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (!initialized) return;
|
||||
InitializePresentationDamagePopups();
|
||||
RestoreRuntimeFeedbackAfterEnable();
|
||||
}
|
||||
|
||||
internal void SetCombatDamagePopupEventsSubscribed(bool subscribed)
|
||||
{
|
||||
if (combatPopupEventsSubscribed == subscribed) return;
|
||||
if (subscribed)
|
||||
CombatEvents.OnValidHit += HandleComparisonValidHit;
|
||||
else
|
||||
{
|
||||
CombatEvents.OnValidHit -= HandleComparisonValidHit;
|
||||
activeCombatDamagePopupCount = 0;
|
||||
Array.Clear(combatDamagePopups, 0, combatDamagePopups.Length);
|
||||
}
|
||||
combatPopupEventsSubscribed = subscribed;
|
||||
}
|
||||
|
||||
public bool IsPointerOverDefenseUI(Vector2 screenPosition)
|
||||
{
|
||||
Rect viewport = Defense25DView.GetViewport(Screen.width, Screen.height);
|
||||
if (viewport.width <= 0f) return false;
|
||||
float scale = viewport.width / Defense25DView.Width;
|
||||
Vector2 gui = new((screenPosition.x - viewport.x) / scale,
|
||||
(Screen.height - screenPosition.y - viewport.y) / scale);
|
||||
if (startupSelectionPending) return StartupArtifactPanelRect.Contains(gui);
|
||||
if (HasPendingFieldRelicReward) return FieldRelicPanelRect.Contains(gui);
|
||||
if (IsValidationShowcaseActive) return new Rect(7, 5, 626, 28).Contains(gui);
|
||||
if (RunStatusRect.Contains(gui) || ArtifactBarRect.Contains(gui)
|
||||
|| BottomControlsRect.Contains(gui)) return true;
|
||||
Rect details = GetSelectedPadDetailsRect();
|
||||
return details.width > 0f && details.Contains(gui)
|
||||
|| showingArtifactSelection && ArtifactPickerRect.Contains(gui);
|
||||
}
|
||||
|
||||
public void BindValidationShowcase(Func<bool> enter, Action exit)
|
||||
{
|
||||
enterValidationShowcase = enter;
|
||||
exitValidationShowcase = exit;
|
||||
}
|
||||
|
||||
public bool EnterValidationShowcase()
|
||||
{
|
||||
if (validationShowcaseActive || enterValidationShowcase == null
|
||||
|| HasPendingFieldRelicReward || !enterValidationShowcase()) return false;
|
||||
validationShowcaseActive = true;
|
||||
showingArtifactSelection = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ExitValidationShowcase()
|
||||
{
|
||||
if (!validationShowcaseActive || exitValidationShowcase == null) return false;
|
||||
exitValidationShowcase();
|
||||
validationShowcaseActive = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Sprite GetTowerShowcaseSprite(DefenseTowerKind kind)
|
||||
{
|
||||
if (!PresentationTowerKinds.Contains(kind)) return null;
|
||||
Sprite[] frames = DefenseTowerArt.Load(DefenseTowerArt.BodyKey(kind, 1, "Idle"));
|
||||
return frames.Length == 0 ? null : frames[0];
|
||||
}
|
||||
|
||||
public bool SelectBuildKind(DefenseTowerKind kind)
|
||||
{
|
||||
if (!PresentationTowerKinds.Contains(kind)) return false;
|
||||
selectedBuildKind = kind;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SelectBuildPad(int padIndex)
|
||||
{
|
||||
if (TowerManager == null || padIndex < 0 || padIndex >= TowerManager.Towers.Count) return false;
|
||||
selectedBuildPad = padIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool BuildSelectedTower()
|
||||
{
|
||||
bool built = selectedBuildPad >= 0
|
||||
&& TowerManager.TryBuildTowerAtPad(selectedBuildPad, selectedBuildKind);
|
||||
SetPresentationMessage(built ? $"{TowerName(selectedBuildKind)} built" : "Move closer to the selected pad");
|
||||
return built;
|
||||
}
|
||||
|
||||
public bool UpgradeSelectedTower(DefenseTowerBranch branch)
|
||||
{
|
||||
bool upgraded = selectedBuildPad >= 0
|
||||
&& TowerManager.TryUpgradeTowerAtPad(selectedBuildPad, branch);
|
||||
SetPresentationMessage(upgraded ? "Tower upgraded" : "Upgrade unavailable at this pad");
|
||||
return upgraded;
|
||||
}
|
||||
|
||||
public bool SelectOwnedArtifact(int index)
|
||||
{
|
||||
if (Loadout == null || !Loadout.Select(index)) return false;
|
||||
ArtifactExecutor?.Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool QueueFieldRelicRewardForCollectedPortal(int lane)
|
||||
{
|
||||
if (lane < 0 || lane >= relicChoiceQueuedForLane.Length || relicChoiceQueuedForLane[lane]
|
||||
|| Loadout == null || portalLedgers[lane] == null || !portalLedgers[lane].IsRewardCollected)
|
||||
return false;
|
||||
PassiveRelicId[] available = Loadout.GetAvailableRelics();
|
||||
if (available.Length == 0) return false;
|
||||
for (int i = available.Length - 1; i > 0; i--)
|
||||
{
|
||||
int swap = UnityEngine.Random.Range(0, i + 1);
|
||||
(available[i], available[swap]) = (available[swap], available[i]);
|
||||
}
|
||||
earnedRelicChoices.Enqueue(available.Take(Mathf.Min(3, available.Length)).ToArray());
|
||||
relicChoiceQueuedForLane[lane] = true;
|
||||
if (earnedRelicChoices.Count == 1)
|
||||
{
|
||||
resumeAfterRelicChoice = IsRunning && !Paused && !startupSelectionPending;
|
||||
SetPaused(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ResolveFieldRelicChoice(PassiveRelicId id)
|
||||
{
|
||||
if (!HasPendingFieldRelicReward || !earnedRelicChoices.Peek().Contains(id)
|
||||
|| !Loadout.TryAcquireRelic(id)) return false;
|
||||
earnedRelicChoices.Dequeue();
|
||||
if (earnedRelicChoices.Count == 0)
|
||||
{
|
||||
if (resumeAfterRelicChoice && IsRunning && Player != null && Player.IsAlive)
|
||||
SetPaused(false);
|
||||
resumeAfterRelicChoice = false;
|
||||
TryFinishRunClear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResetPresentationRewards()
|
||||
{
|
||||
InitializePresentationDamagePopups();
|
||||
activeCombatDamagePopupCount = 0;
|
||||
Array.Clear(combatDamagePopups, 0, combatDamagePopups.Length);
|
||||
earnedRelicChoices.Clear();
|
||||
Array.Clear(relicChoiceQueuedForLane, 0, relicChoiceQueuedForLane.Length);
|
||||
resumeAfterRelicChoice = false;
|
||||
if (validationShowcaseActive) ExitValidationShowcase();
|
||||
}
|
||||
|
||||
public void DrawDefensePresentationGUI(Rect viewport)
|
||||
{
|
||||
if (!initialized || viewport.width <= 0f || viewport.height <= 0f) return;
|
||||
float scale = viewport.width / Defense25DView.Width;
|
||||
Matrix4x4 previous = GUI.matrix;
|
||||
try
|
||||
{
|
||||
GUI.matrix = Matrix4x4.TRS(new Vector3(viewport.x, viewport.y, 0f),
|
||||
Quaternion.identity, new Vector3(scale, scale, 1f));
|
||||
DrawRunStatus();
|
||||
DrawCombatDamagePopups(scale);
|
||||
if (IsValidationShowcaseActive)
|
||||
{
|
||||
GUI.Box(new Rect(7, 5, 626, 28), "Six-family tower combat showcase");
|
||||
if (GUI.Button(new Rect(548, 7, 76, 24), "General run")) ExitValidationShowcase();
|
||||
return;
|
||||
}
|
||||
DrawArtifactBar();
|
||||
DrawTowerCatalog();
|
||||
DrawPadAndUpgradePanel();
|
||||
DrawFieldRelicReward();
|
||||
DrawStartupArtifactPicker();
|
||||
if (hasPresentationMessage && GameTime < presentationMessageUntil)
|
||||
GUI.Label(new Rect(245, 317, 150, 20), presentationMessage, CenteredStyle());
|
||||
if (Paused && !startupSelectionPending && !HasPendingFieldRelicReward)
|
||||
GUI.Box(new Rect(264, 159, 112, 32), "PAUSED");
|
||||
if (RunFailed || RunCleared)
|
||||
GUI.Box(new Rect(224, 151, 192, 42), RunCleared ? "DEFENSE CLEARED" : "BASE DESTROYED");
|
||||
}
|
||||
finally { GUI.matrix = previous; }
|
||||
}
|
||||
|
||||
private void DrawCombatDamagePopups(float scale)
|
||||
{
|
||||
PruneCombatDamagePopups();
|
||||
if (view == null || view.Camera == null) return;
|
||||
Color previousColor = GUI.color;
|
||||
try
|
||||
{
|
||||
combatDamagePopupStyle ??= new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
font = PresentationUiStyle.GetGalmuriFont(),
|
||||
fontSize = Mathf.RoundToInt(Defense25DCombatPopupConstants.FontPixelsAt1080p
|
||||
* Defense25DCombatPopupConstants.ReferenceInternalHeight
|
||||
/ Defense25DCombatPopupConstants.ReferenceScreenHeight),
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
wordWrap = false,
|
||||
clipping = TextClipping.Overflow,
|
||||
};
|
||||
for (int i = 0; i < activeCombatDamagePopupCount; i++)
|
||||
{
|
||||
CombatDamagePopup popup = combatDamagePopups[i];
|
||||
Vector3 projected = view.Camera.WorldToViewportPoint(popup.Position);
|
||||
if (projected.z <= 0f || projected.x < 0f || projected.x > 1f
|
||||
|| projected.y < 0f || projected.y > 1f) continue;
|
||||
float age = Mathf.Max(0f, Time.unscaledTime - popup.StartedAt);
|
||||
float x = projected.x * Defense25DView.Width;
|
||||
float y = (1f - projected.y) * Defense25DView.Height
|
||||
- age * Defense25DCombatPopupConstants.RisePixelsAt1080p
|
||||
* Defense25DCombatPopupConstants.ReferenceInternalHeight
|
||||
/ Defense25DCombatPopupConstants.ReferenceScreenHeight;
|
||||
Rect rect = new(
|
||||
x - Defense25DCombatPopupConstants.PopupWidthInternal * 0.5f,
|
||||
y - Defense25DCombatPopupConstants.PopupHeightInternal * 0.5f,
|
||||
Defense25DCombatPopupConstants.PopupWidthInternal,
|
||||
Defense25DCombatPopupConstants.PopupHeightInternal);
|
||||
Color faded = popup.Color;
|
||||
faded.a *= 1f - age / Defense25DCombatPopupConstants.LifetimeSeconds;
|
||||
Color outline = Defense25DCombatPopupConstants.OutlineColor;
|
||||
outline.a *= faded.a;
|
||||
GUI.color = outline;
|
||||
float outlineOffset = Defense25DCombatPopupConstants.OutlinePixels
|
||||
/ Mathf.Max(1f, scale);
|
||||
GUI.Label(new Rect(rect.x - outlineOffset, rect.y, rect.width, rect.height),
|
||||
popup.Text, combatDamagePopupStyle);
|
||||
GUI.Label(new Rect(rect.x + outlineOffset, rect.y, rect.width, rect.height),
|
||||
popup.Text, combatDamagePopupStyle);
|
||||
GUI.Label(new Rect(rect.x, rect.y - outlineOffset, rect.width, rect.height),
|
||||
popup.Text, combatDamagePopupStyle);
|
||||
GUI.Label(new Rect(rect.x, rect.y + outlineOffset, rect.width, rect.height),
|
||||
popup.Text, combatDamagePopupStyle);
|
||||
GUI.color = faded;
|
||||
GUI.Label(rect, popup.Text, combatDamagePopupStyle);
|
||||
}
|
||||
}
|
||||
finally { GUI.color = previousColor; }
|
||||
}
|
||||
|
||||
private void DrawRunStatus()
|
||||
{
|
||||
GUI.Box(RunStatusRect, "");
|
||||
if (Player == null || TowerManager == null || Ledger == null) return;
|
||||
compactStatusStyle ??= new GUIStyle(GUI.skin.label) { fontSize = 9 };
|
||||
GUI.Label(new Rect(14, 6, 612, 18),
|
||||
$"HP {Player.CurrentHealth:0}/{Player.MaxHealth:0} Base {BaseHealth:0} " +
|
||||
$"Wave {Ledger.ResolvedCount}/{enemies.Count} Build {TowerManager.ConstructionCurrency} " +
|
||||
$"{GameTime:0.0}s Relics {Loadout.OwnedRelicCount}/20 " +
|
||||
$"Efficiency {TowerEfficiency:P0} {(IsRunning ? "DEFENDING" : "RUN ENDED")}", compactStatusStyle);
|
||||
}
|
||||
|
||||
private void DrawArtifactBar()
|
||||
{
|
||||
GUI.Box(ArtifactBarRect, "");
|
||||
GUI.Label(new Rect(13, 29, 105, 23), $"Gauge {Gauge:0}");
|
||||
if (Loadout.CurrentArtifact == null)
|
||||
{
|
||||
GUI.Label(new Rect(120, 29, 220, 23), "No artifact equipped");
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(new Rect(120, 29, 225, 23), Loadout.CurrentArtifact.DisplayName);
|
||||
GUI.Label(new Rect(347, 29, 113, 23), Loadout.IsCharging
|
||||
? $"Charge {Loadout.ChargeProgress:P0}" : "Ready");
|
||||
}
|
||||
if (GUI.Button(new Rect(466, 29, 76, 23), "Artifacts")) showingArtifactSelection = !showingArtifactSelection;
|
||||
if (HasPendingFieldRelicReward) GUI.Label(new Rect(548, 29, 77, 23), "RELIC REWARD");
|
||||
else if (GUI.Button(new Rect(548, 29, 77, 23), IsValidationShowcaseActive ? "General run" : "Showcase"))
|
||||
{
|
||||
if (IsValidationShowcaseActive) ExitValidationShowcase();
|
||||
else EnterValidationShowcase();
|
||||
}
|
||||
if (showingArtifactSelection) DrawOwnedArtifactPicker();
|
||||
}
|
||||
|
||||
private bool showingArtifactSelection;
|
||||
|
||||
private void DrawOwnedArtifactPicker()
|
||||
{
|
||||
GUI.Box(ArtifactPickerRect, "Choose artifact");
|
||||
for (int i = 0; i < Loadout.OwnedArtifacts.Count; i++)
|
||||
{
|
||||
ActiveArtifactDefinition artifact = Loadout.OwnedArtifacts[i];
|
||||
if (GUI.Button(new Rect(390, 83 + i * 28, 226, 24), artifact.DisplayName)
|
||||
&& SelectOwnedArtifact(i)) showingArtifactSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTowerCatalog()
|
||||
{
|
||||
GUI.Box(BottomControlsRect, "");
|
||||
for (int i = 0; i < PresentationTowerKinds.Length; i++)
|
||||
{
|
||||
DefenseTowerKind kind = PresentationTowerKinds[i];
|
||||
Rect button = new(12 + i * 51, 326, 49, 25);
|
||||
string label = selectedBuildKind == kind ? $"*{TowerShortName(kind)}" : TowerShortName(kind);
|
||||
if (GUI.Button(button, label))
|
||||
SelectBuildKind(kind);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPadAndUpgradePanel()
|
||||
{
|
||||
for (int pad = 0; pad < 6; pad++)
|
||||
{
|
||||
Defense25DTower tower = TowerManager.GetTowerAtPad(pad);
|
||||
string label = tower == null ? $"P{pad + 1}" : $"P{pad + 1} T{tower.Tier}";
|
||||
if (GUI.Button(new Rect(322 + pad * 51, 326, 49, 25), label)) SelectBuildPad(pad);
|
||||
}
|
||||
|
||||
Rect panel = GetSelectedPadDetailsRect();
|
||||
if (panel.width <= 0f) return;
|
||||
Defense25DTower selected = selectedBuildPad >= 0 ? TowerManager.GetTowerAtPad(selectedBuildPad) : null;
|
||||
GUI.Box(panel, "");
|
||||
if (GUI.Button(new Rect(panel.xMax - 24, panel.y + 3, 20, 19), "×"))
|
||||
{
|
||||
selectedBuildPad = -1;
|
||||
return;
|
||||
}
|
||||
if (selected == null)
|
||||
{
|
||||
int cost = BuildCost(selectedBuildKind);
|
||||
GUI.Label(new Rect(panel.x + 10, panel.y + 20, panel.width - 20, 23),
|
||||
$"Pad {selectedBuildPad + 1}: {TowerName(selectedBuildKind)}");
|
||||
if (GUI.Button(new Rect(panel.x + 10, panel.y + 54, 112, 27), $"Build {cost}")
|
||||
&& selectedBuildPad >= 0)
|
||||
BuildSelectedTower();
|
||||
return;
|
||||
}
|
||||
|
||||
GUI.Label(new Rect(panel.x + 10, panel.y + 20, panel.width - 20, 24),
|
||||
$"Pad {selectedBuildPad + 1}: {TowerName(selected.Kind)} T{selected.Tier}");
|
||||
if (selected.Tier < 3)
|
||||
{
|
||||
if (GUI.Button(new Rect(panel.x + 10, panel.y + 54, 180, 27),
|
||||
$"Upgrade {UpgradeCost(selected.Tier)}"))
|
||||
UpgradeSelectedTower(DefenseTowerBranch.None);
|
||||
}
|
||||
else if (selected.Tier == 3)
|
||||
{
|
||||
string first = DefenseTower.GetBranchName(selected.Kind, DefenseTowerBranch.First);
|
||||
string second = DefenseTower.GetBranchName(selected.Kind, DefenseTowerBranch.Second);
|
||||
GUI.Label(new Rect(panel.x + 10, panel.y + 45, panel.width - 20, 27),
|
||||
$"I {first} II {second}", compactStatusStyle);
|
||||
if (GUI.Button(new Rect(panel.x + 10, panel.y + 76, 86, 27), $"I {UpgradeCost(3)}"))
|
||||
UpgradeSelectedTower(DefenseTowerBranch.First);
|
||||
if (GUI.Button(new Rect(panel.x + 104, panel.y + 76, 86, 27), $"II {UpgradeCost(3)}"))
|
||||
UpgradeSelectedTower(DefenseTowerBranch.Second);
|
||||
}
|
||||
else GUI.Label(new Rect(panel.x + 10, panel.y + 54, 190, 42),
|
||||
$"{TowerName(selected.Kind)} IV\n{DefenseTower.GetBranchName(selected.Kind, selected.Branch)}");
|
||||
}
|
||||
|
||||
private void DrawFieldRelicReward()
|
||||
{
|
||||
if (!HasPendingFieldRelicReward) return;
|
||||
PassiveRelicId[] choices = earnedRelicChoices.Peek();
|
||||
GUI.Box(FieldRelicPanelRect, $"Portal field reward {Loadout.OwnedRelicCount}/20");
|
||||
for (int i = 0; i < choices.Length; i++)
|
||||
{
|
||||
Rect button = new(132, 126 + i * 36, 376, 30);
|
||||
if (GUI.Button(button, RelicName(choices[i]))) ResolveFieldRelicChoice(choices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawStartupArtifactPicker()
|
||||
{
|
||||
if (!startupSelectionPending) return;
|
||||
GUI.Box(StartupArtifactPanelRect,
|
||||
$"Choose {startupArtifactColor} artifact {Loadout.OwnedArtifactCount + 1}/{startupSelectionTotal}");
|
||||
int pageCount = Mathf.Max(1, Mathf.CeilToInt(startupArtifactOptions.Count / 4f));
|
||||
startupArtifactPage = Mathf.Clamp(startupArtifactPage, 0, pageCount - 1);
|
||||
int firstChoice = startupArtifactPage * 4;
|
||||
int lastChoice = Mathf.Min(startupArtifactOptions.Count, firstChoice + 4);
|
||||
for (int i = firstChoice; i < lastChoice; i++)
|
||||
{
|
||||
Rect button = new(132, 119 + (i - firstChoice) * 32, 376, 28);
|
||||
if (GUI.Button(button, startupArtifactOptions[i].DisplayName)) ChooseStartupArtifact(i);
|
||||
}
|
||||
if (startupArtifactPage > 0 && GUI.Button(new Rect(132, 250, 70, 21), "Previous")) startupArtifactPage--;
|
||||
if (startupArtifactPage + 1 < pageCount && GUI.Button(new Rect(438, 250, 70, 21), "Next")) startupArtifactPage++;
|
||||
}
|
||||
|
||||
private void SetPresentationMessage(string message)
|
||||
{
|
||||
presentationMessage = message;
|
||||
presentationMessageUntil = GameTime + 2f;
|
||||
hasPresentationMessage = true;
|
||||
}
|
||||
|
||||
private int BuildCost(DefenseTowerKind kind)
|
||||
{
|
||||
DefenseConstants tuning = GameplayConstants.Current.Defense;
|
||||
return kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => tuning.DirectTowerCost,
|
||||
DefenseTowerKind.Summon => tuning.SummonTowerCost,
|
||||
DefenseTowerKind.Lightning => tuning.LightningTowerCost,
|
||||
DefenseTowerKind.Cold => tuning.ColdTowerCost,
|
||||
DefenseTowerKind.Seismic => tuning.SeismicTowerCost,
|
||||
DefenseTowerKind.Empowering => tuning.EmpoweringTowerCost,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
private int UpgradeCost(int tier) => tier switch
|
||||
{
|
||||
1 => GameplayConstants.Current.Defense.Tier2UpgradeCost,
|
||||
2 => GameplayConstants.Current.Defense.Tier3UpgradeCost,
|
||||
_ => GameplayConstants.Current.Defense.Tier4UpgradeCost,
|
||||
};
|
||||
|
||||
private static string TowerName(DefenseTowerKind kind) => kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => "Fire",
|
||||
DefenseTowerKind.Summon => "Summon",
|
||||
DefenseTowerKind.Lightning => "Lightning",
|
||||
DefenseTowerKind.Cold => "Cold",
|
||||
DefenseTowerKind.Seismic => "Seismic",
|
||||
DefenseTowerKind.Empowering => "Support",
|
||||
_ => kind.ToString(),
|
||||
};
|
||||
|
||||
private static string TowerShortName(DefenseTowerKind kind) => kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => "Fire",
|
||||
DefenseTowerKind.Summon => "Summon",
|
||||
DefenseTowerKind.Lightning => "Storm",
|
||||
DefenseTowerKind.Cold => "Cold",
|
||||
DefenseTowerKind.Seismic => "Earth",
|
||||
DefenseTowerKind.Empowering => "Support",
|
||||
_ => kind.ToString(),
|
||||
};
|
||||
|
||||
private static string RelicName(PassiveRelicId id) =>
|
||||
Regex.Replace(id.ToString(), "(?<=[a-z])(?=[A-Z])", " ");
|
||||
|
||||
private static GUIStyle CenteredStyle()
|
||||
{
|
||||
GUIStyle style = new(GUI.skin.label) { alignment = TextAnchor.MiddleCenter };
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Defense25DCombatPopupLifecycle : MonoBehaviour
|
||||
{
|
||||
private Defense25DComparison owner;
|
||||
|
||||
public void Bind(Defense25DComparison comparison)
|
||||
{
|
||||
if (owner == comparison) return;
|
||||
if (owner != null) owner.SetCombatDamagePopupEventsSubscribed(false);
|
||||
owner = comparison;
|
||||
if (isActiveAndEnabled && owner != null)
|
||||
owner.SetCombatDamagePopupEventsSubscribed(true);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (owner != null) owner.SetCombatDamagePopupEventsSubscribed(true);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (owner != null) owner.SetCombatDamagePopupEventsSubscribed(false);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (owner != null) owner.SetCombatDamagePopupEventsSubscribed(false);
|
||||
owner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f8db4b736f6478fb580f7fe926c70c1
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public sealed partial class Defense25DComparison
|
||||
{
|
||||
private void ConfigureRelicReplayCallbacks()
|
||||
{
|
||||
relicEffects.EmitEcho = (use, origin, direction, multiplier) =>
|
||||
ArtifactExecutor.TryReplay(use, origin, direction, multiplier);
|
||||
relicEffects.EmitRepeat = (use, origin, direction, multiplier) =>
|
||||
ArtifactExecutor.TryReplay(use, origin, direction, multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b7c43d5582674f77a9befa672a24bd6c
|
||||
@@ -0,0 +1,212 @@
|
||||
using System.Collections.Generic;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Defense;
|
||||
using BumpCombat.Enemies;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public sealed partial class Defense25DComparison
|
||||
{
|
||||
private sealed class RelicHealingOrb
|
||||
{
|
||||
public GameObject Object;
|
||||
public float ExpiresAt;
|
||||
}
|
||||
|
||||
private readonly List<RelicHealingOrb> relicHealingOrbs = new();
|
||||
private Defense25DRelicEffects relicEffects;
|
||||
private float relicOrbLastStepTime;
|
||||
|
||||
private void ConfigureRelicRuntime()
|
||||
{
|
||||
relicEffects = new Defense25DRelicEffects();
|
||||
RelicRuntime = new Defense25DRelicRuntime(
|
||||
Loadout, GameplayConstants.Current.PassiveRelics, relicEffects);
|
||||
Loadout.ArtifactUseSucceeded += NotifyRelicArtifactUse;
|
||||
Loadout.ArtifactUseSucceeded += NotifyPlayerArtifactUseVisual;
|
||||
ArtifactExecutor.NotifyRelicHit = NotifyRelicHit;
|
||||
RelicRuntime.BeginRun(GameTime);
|
||||
}
|
||||
|
||||
private void ConfigureRelicEffectCallbacks()
|
||||
{
|
||||
Defense25DRelicEffects effects = relicEffects;
|
||||
effects.QueryEnemies = QueryRelicTargets;
|
||||
effects.DamageEnemy = ApplyRelicDamage;
|
||||
effects.IgniteEnemy = ApplyRelicIgnite;
|
||||
effects.CalculatePlayerDamage = (damage, tag) => Player.CalculateDamage(damage, tag);
|
||||
effects.ApplyVulnerability = (target, duration, increase) =>
|
||||
FindRelicEnemy(target.Identity)?.ApplyBumpVulnerability(duration, increase) == true;
|
||||
effects.ApplySlow = (target, duration, fraction) =>
|
||||
FindRelicEnemy(target.Identity)?.ApplySlow(duration, fraction) == true;
|
||||
effects.HealPlayer = amount => Player.Heal(amount);
|
||||
effects.GrantShield = (amount, duration) => Player.GrantShield(amount, duration);
|
||||
effects.RestoreGauge = fraction =>
|
||||
{
|
||||
if (!IsRunning || !Player.IsAlive || fraction <= 0f) return false;
|
||||
float before = Gauge;
|
||||
Loadout.AddGauge(Loadout.MaximumGauge * fraction);
|
||||
return Gauge > before;
|
||||
};
|
||||
effects.BoostVisibleTowers = BoostVisibleRelicTowers;
|
||||
effects.AwardCurrency = AwardRelicCurrency;
|
||||
effects.SpawnHealingOrb = SpawnRelicHealingOrb;
|
||||
effects.SetTimedPlayerModifier = (modifier, duration) =>
|
||||
{
|
||||
if (Player.IsAlive && duration > 0f) Player.AddModifier(modifier);
|
||||
};
|
||||
effects.RemovePlayerModifier = source => Player.RemoveModifiersFromSource(source);
|
||||
}
|
||||
|
||||
private IReadOnlyList<Defense25DRelicTarget> QueryRelicTargets(Vector3 center, float radius)
|
||||
{
|
||||
var results = new List<Defense25DRelicTarget>();
|
||||
float radiusSquared = radius * radius;
|
||||
foreach (Defense25DEnemy enemy in enemies)
|
||||
{
|
||||
if (enemy == null || !enemy.IsAvailable) continue;
|
||||
Vector2 closest = enemy.Actor.ClosestGroundPoint(new Vector2(center.x, center.z));
|
||||
Vector2 delta = closest - new Vector2(center.x, center.z);
|
||||
if (delta.sqrMagnitude <= radiusSquared) results.Add(CreateRelicTarget(enemy));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private bool ApplyRelicDamage(Defense25DRelicTarget target, float damage,
|
||||
Vector3 direction, EnemyDamageSource source)
|
||||
{
|
||||
Defense25DEnemy enemy = FindRelicEnemy(target.Identity);
|
||||
if (enemy == null || !enemy.IsAvailable || damage <= 0f) return false;
|
||||
return enemy.Actor.TakeDamage(damage, new Vector2(direction.x, direction.z), 0f, source);
|
||||
}
|
||||
|
||||
private bool ApplyRelicIgnite(Defense25DRelicTarget target, float duration,
|
||||
float interval, float damage, EnemyDamageSource source)
|
||||
{
|
||||
return FindRelicEnemy(target.Identity)?.ApplyIgnite(duration, interval, damage, source) == true;
|
||||
}
|
||||
|
||||
private Defense25DEnemy FindRelicEnemy(int identity)
|
||||
{
|
||||
foreach (Defense25DEnemy enemy in enemies)
|
||||
if (enemy != null && enemy.Id == identity) return enemy;
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool BoostVisibleRelicTowers(float duration)
|
||||
{
|
||||
if (!TowersOperational || duration <= 0f) return false;
|
||||
bool applied = false;
|
||||
foreach (Defense25DTower tower in TowerManager.Towers)
|
||||
{
|
||||
if (tower == null || tower.Kind is DefenseTowerKind.Empowering or DefenseTowerKind.Summon
|
||||
|| Vector2.Distance(Player.Position, tower.Position) > Tuning.PlayerSightRadius) continue;
|
||||
tower.ApplyBattleBaton(duration,
|
||||
GameplayConstants.Current.PassiveRelics.BattleBatonTowerDamageIncrease);
|
||||
applied = true;
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
private bool AwardRelicCurrency(int amount)
|
||||
{
|
||||
if (!IsRunning || Player == null || !Player.IsAlive || amount <= 0) return false;
|
||||
TowerManager.AddCurrency(amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SpawnRelicHealingOrb(Vector3 position, float healFraction, float lifetime)
|
||||
{
|
||||
if (!IsRunning || lifetime <= 0f) return;
|
||||
GameObject orb = GameObject.CreatePrimitive(PrimitiveType.Sphere);
|
||||
orb.name = "Pilgrim Healing Orb";
|
||||
orb.transform.SetParent(transform, true);
|
||||
orb.transform.position = position + Vector3.up * .2f;
|
||||
orb.transform.localScale = Vector3.one * .3f;
|
||||
Collider collider = orb.GetComponent<Collider>();
|
||||
if (collider != null) Destroy(collider);
|
||||
Renderer renderer = orb.GetComponent<Renderer>();
|
||||
if (renderer != null) renderer.material.color = new Color(.35f, 1f, .55f);
|
||||
relicHealingOrbs.Add(new RelicHealingOrb
|
||||
{
|
||||
Object = orb,
|
||||
ExpiresAt = GameTime + lifetime,
|
||||
});
|
||||
}
|
||||
|
||||
private void StepRelicHealingOrbs()
|
||||
{
|
||||
if (GameTime < relicOrbLastStepTime)
|
||||
{
|
||||
foreach (RelicHealingOrb oldOrb in relicHealingOrbs)
|
||||
if (oldOrb.Object != null) Destroy(oldOrb.Object);
|
||||
relicHealingOrbs.Clear();
|
||||
RelicRuntime?.NotifyHealingOrbExpired();
|
||||
}
|
||||
relicOrbLastStepTime = GameTime;
|
||||
float pickupRadius = GameplayConstants.Current.Items.ExperienceOrbPickupRadius;
|
||||
for (int i = relicHealingOrbs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RelicHealingOrb orb = relicHealingOrbs[i];
|
||||
if (orb.Object == null || GameTime >= orb.ExpiresAt)
|
||||
{
|
||||
if (orb.Object != null) Destroy(orb.Object);
|
||||
RelicRuntime?.NotifyHealingOrbExpired();
|
||||
relicHealingOrbs.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
if (Player == null || !Player.IsAlive) continue;
|
||||
Vector2 orbPosition = Defense25DActor.Ground(orb.Object.transform.position);
|
||||
if ((Player.Position - orbPosition).sqrMagnitude > pickupRadius * pickupRadius) continue;
|
||||
bool pickedUp = RelicRuntime?.NotifyHealingOrbPickedUp(Player.MaxHealth) == true;
|
||||
Destroy(orb.Object);
|
||||
relicHealingOrbs.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyRelicArtifactUse(Defense25DArtifactUse use)
|
||||
{
|
||||
if (Player == null) return;
|
||||
RelicRuntime?.NotifyArtifactUse(use,
|
||||
Defense25DActor.OnGround(Player.Position, settings.GroundY),
|
||||
Defense25DActor.OnGround(Player.Facing), Player.CurrentHealth);
|
||||
}
|
||||
|
||||
private void NotifyRelicHit(Defense25DRelicHit hit) =>
|
||||
RelicRuntime?.NotifyHit(hit, Player != null ? Player.CurrentHealth : 0f,
|
||||
Player != null ? Player.MaxHealth : 0f);
|
||||
|
||||
internal Defense25DRelicTarget CreateRelicTarget(Defense25DEnemy enemy)
|
||||
{
|
||||
Defense25DActor actor = enemy.Actor;
|
||||
return new Defense25DRelicTarget(enemy.Id,
|
||||
Defense25DActor.OnGround(actor.Position, settings.GroundY), actor.CurrentHealth,
|
||||
actor.MaxHealth, enemy.LastDamageSource, !actor.IsAlive,
|
||||
igniteRemaining: enemy.IgniteRemaining, igniteInterval: enemy.IgniteInterval,
|
||||
igniteTickDamage: enemy.IgniteTickDamage);
|
||||
}
|
||||
|
||||
public void NotifyPlayerDamageTaken(float actualLoss) =>
|
||||
RelicRuntime?.NotifyPlayerDamageTaken(actualLoss, Player.CurrentHealth,
|
||||
Player.MaxHealth, Defense25DActor.OnGround(Player.Position, settings.GroundY));
|
||||
|
||||
private Defense25DRelicHit CreateRelicHit(Defense25DEnemy enemy, float damage,
|
||||
Vector2 direction, float baseBumpDamage, bool ordinaryBump, bool artifactHit)
|
||||
{
|
||||
Defense25DActor actor = enemy.Actor;
|
||||
Defense25DRelicTarget target = new(enemy.Id,
|
||||
Defense25DActor.OnGround(actor.Position, settings.GroundY), actor.CurrentHealth,
|
||||
actor.MaxHealth, enemy.LastDamageSource, !actor.IsAlive,
|
||||
igniteRemaining: enemy.IgniteRemaining, igniteInterval: enemy.IgniteInterval,
|
||||
igniteTickDamage: enemy.IgniteTickDamage);
|
||||
return new Defense25DRelicHit(target,
|
||||
Defense25DActor.OnGround(actor.ClosestGroundPoint(Player.Center), settings.GroundY),
|
||||
Defense25DActor.OnGround(direction), damage, baseBumpDamage, ordinaryBump,
|
||||
artifactHit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71a94f1e2f6e49ca9a4c44b77f51054e
|
||||
@@ -0,0 +1,262 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BumpCombat.Constants;
|
||||
using BumpCombat.Defense;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public sealed partial class Defense25DComparison
|
||||
{
|
||||
private static DefenseTowerKind[] ShowcaseKinds => DefenseTowerHitTestLayout.KindsByPad;
|
||||
|
||||
private readonly Dictionary<Defense25DEnemy, DefenseTowerKind> showcaseDummyKinds = new();
|
||||
private readonly Dictionary<Defense25DEnemy, Vector2> showcaseDummyPositions = new();
|
||||
private readonly Dictionary<Defense25DEnemy, int> showcaseObservedDamageEvents = new();
|
||||
private readonly Dictionary<DefenseTowerKind, int> showcaseHitCounts = new();
|
||||
private readonly HashSet<Defense25DEnemy> showcaseSuspendedEnemies = new();
|
||||
private Defense25DTowerManager runTowerManager;
|
||||
private Defense25DTowerManager showcaseTowerManager;
|
||||
private GameObject showcaseRoot;
|
||||
private Vector2 runPlayerPosition;
|
||||
private float runGameTime;
|
||||
private bool towerShowcaseActive;
|
||||
|
||||
public bool IsTowerShowcaseSimulationActive => towerShowcaseActive;
|
||||
public IReadOnlyCollection<Defense25DEnemy> TowerShowcaseDummies => showcaseDummyKinds.Keys;
|
||||
public int TowerShowcaseCurrency => showcaseTowerManager != null
|
||||
? showcaseTowerManager.ConstructionCurrency : 0;
|
||||
|
||||
public IEnumerable<Defense25DEnemy> GetTowerTargets(DefenseTowerKind sourceKind)
|
||||
{
|
||||
foreach (Defense25DEnemy enemy in enemies)
|
||||
if (enemy != null && !enemy.IsShowcaseDummy)
|
||||
yield return enemy;
|
||||
|
||||
if (!towerShowcaseActive) yield break;
|
||||
foreach (KeyValuePair<Defense25DEnemy, DefenseTowerKind> pair in showcaseDummyKinds)
|
||||
if (pair.Value == sourceKind && pair.Key != null && pair.Key.IsAvailable)
|
||||
yield return pair.Key;
|
||||
}
|
||||
|
||||
public bool IsTowerShowcaseTarget(DefenseTowerKind sourceKind, Defense25DEnemy target)
|
||||
{
|
||||
if (target == null) return false;
|
||||
if (!target.IsShowcaseDummy) return enemies.Contains(target);
|
||||
return towerShowcaseActive
|
||||
&& showcaseDummyKinds.TryGetValue(target, out DefenseTowerKind assignedKind)
|
||||
&& assignedKind == sourceKind;
|
||||
}
|
||||
|
||||
public Defense25DEnemy GetTowerShowcaseDummy(DefenseTowerKind kind)
|
||||
{
|
||||
foreach (KeyValuePair<Defense25DEnemy, DefenseTowerKind> pair in showcaseDummyKinds)
|
||||
if (pair.Value == kind) return pair.Key;
|
||||
return null;
|
||||
}
|
||||
|
||||
public int GetTowerShowcaseHitCount(DefenseTowerKind kind) =>
|
||||
showcaseHitCounts.TryGetValue(kind, out int count) ? count : 0;
|
||||
|
||||
public float GetTowerShowcaseSupportBonus(DefenseTowerKind kind)
|
||||
{
|
||||
if (showcaseTowerManager == null) return 0f;
|
||||
foreach (Defense25DTower tower in showcaseTowerManager.Towers)
|
||||
if (tower != null && tower.Kind == kind) return tower.ShowcaseSupportBonus;
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public bool EnterTowerShowcase()
|
||||
{
|
||||
if (towerShowcaseActive || !IsRunning || Paused || RunCleared || RunFailed
|
||||
|| Player == null || !Player.IsAlive || TowerManager == null
|
||||
|| HasPendingFieldRelicReward || startupSelectionPending || showingArtifactSelection
|
||||
|| CycloneActive || ArtifactExecutor.IsExecutingArtifact || ArtifactExecutor.IsDashing)
|
||||
return false;
|
||||
|
||||
runTowerManager = TowerManager;
|
||||
runPlayerPosition = Player.Position;
|
||||
runGameTime = GameTime;
|
||||
towerShowcaseActive = true;
|
||||
showcaseSuspendedEnemies.Clear();
|
||||
runTowerManager.SetShowcaseSuspended(true);
|
||||
foreach (Defense25DEnemy enemy in enemies)
|
||||
{
|
||||
if (enemy == null || !enemy.gameObject.activeSelf) continue;
|
||||
showcaseSuspendedEnemies.Add(enemy);
|
||||
enemy.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
showcaseRoot = new GameObject("Isolated tower showcase");
|
||||
showcaseRoot.transform.SetParent(transform, false);
|
||||
GameObject managerObject = new("Showcase construction economy");
|
||||
managerObject.transform.SetParent(showcaseRoot.transform, false);
|
||||
showcaseTowerManager = managerObject.AddComponent<Defense25DTowerManager>();
|
||||
showcaseTowerManager.Configure(this);
|
||||
TowerManager = showcaseTowerManager;
|
||||
showcaseHitCounts.Clear();
|
||||
|
||||
int totalBuildCost = 0;
|
||||
foreach (DefenseTowerKind kind in ShowcaseKinds) totalBuildCost += ShowcaseBuildCost(kind);
|
||||
showcaseTowerManager.AddCurrency(totalBuildCost);
|
||||
|
||||
for (int i = 0; i < ShowcaseKinds.Length; i++)
|
||||
{
|
||||
Vector2 pad = Tuning.GetBuildPadPosition(i);
|
||||
MovePlayerForShowcaseBuild(pad);
|
||||
if (!showcaseTowerManager.TryBuildTowerAtPad(i, ShowcaseKinds[i]))
|
||||
{
|
||||
ExitTowerShowcase();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
RestoreShowcasePlayerPosition();
|
||||
|
||||
for (int i = 0; i < ShowcaseKinds.Length; i++)
|
||||
{
|
||||
DefenseTowerKind kind = ShowcaseKinds[i];
|
||||
Vector2 pad = Tuning.GetBuildPadPosition(i);
|
||||
Vector2 position = DefenseTowerHitTestLayout.GetDummyPosition(kind);
|
||||
GameObject actorObject = new($"Showcase Skeleton - {kind}");
|
||||
actorObject.transform.SetParent(transform, false);
|
||||
actorObject.transform.position = Defense25DActor.OnGround(position, settings.GroundY);
|
||||
Defense25DActor actor = actorObject.AddComponent<Defense25DActor>();
|
||||
actor.Configure(this, ComparisonActorKind.Enemy);
|
||||
actor.SetEnemyMaximumHealth(Mathf.Max(1f, settings.Enemy.MaxHealth));
|
||||
Defense25DEnemy dummy = actorObject.AddComponent<Defense25DEnemy>();
|
||||
dummy.Configure(this, actor, int.MinValue + i, FindNonImmuneShowcaseLane(kind));
|
||||
AttachEnemyStatusVisual(dummy);
|
||||
dummy.ConfigureShowcaseDummy();
|
||||
dummy.Spawn(position);
|
||||
showcaseDummyKinds.Add(dummy, kind);
|
||||
showcaseDummyPositions.Add(dummy, position);
|
||||
showcaseObservedDamageEvents.Add(dummy, 0);
|
||||
showcaseHitCounts[kind] = 0;
|
||||
}
|
||||
|
||||
Physics.SyncTransforms();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExitTowerShowcase()
|
||||
{
|
||||
if (!towerShowcaseActive) return;
|
||||
towerShowcaseActive = false;
|
||||
RestoreShowcasePlayerPosition();
|
||||
if (showcaseTowerManager != null)
|
||||
{
|
||||
showcaseTowerManager.CancelAll();
|
||||
showcaseTowerManager.ResetForRun();
|
||||
}
|
||||
TowerManager = runTowerManager;
|
||||
GameTime = runGameTime;
|
||||
runTowerManager?.SetShowcaseSuspended(false);
|
||||
foreach (Defense25DEnemy enemy in showcaseSuspendedEnemies)
|
||||
if (enemy != null)
|
||||
{
|
||||
enemy.gameObject.SetActive(true);
|
||||
enemy.Actor.RestoreLiveCollisionAndVisuals();
|
||||
}
|
||||
foreach (Defense25DEnemy dummy in showcaseDummyKinds.Keys)
|
||||
if (dummy != null)
|
||||
{
|
||||
dummy.gameObject.SetActive(false);
|
||||
Destroy(dummy.gameObject);
|
||||
}
|
||||
if (showcaseRoot != null) Destroy(showcaseRoot);
|
||||
|
||||
showcaseDummyKinds.Clear();
|
||||
showcaseDummyPositions.Clear();
|
||||
showcaseObservedDamageEvents.Clear();
|
||||
showcaseHitCounts.Clear();
|
||||
showcaseSuspendedEnemies.Clear();
|
||||
showcaseTowerManager = null;
|
||||
showcaseRoot = null;
|
||||
runTowerManager = null;
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
public void AdvanceTowerShowcase(float dt)
|
||||
{
|
||||
if (!towerShowcaseActive || dt <= 0f) return;
|
||||
GameTime += dt;
|
||||
foreach (Defense25DEnemy dummy in showcaseDummyKinds.Keys.ToArray())
|
||||
{
|
||||
AccumulateShowcaseDamage(dummy);
|
||||
}
|
||||
if (showcaseTowerManager == null) return;
|
||||
foreach (Defense25DTower tower in showcaseTowerManager.Towers)
|
||||
if (tower != null && tower.Kind == DefenseTowerKind.Summon)
|
||||
tower.StepShowcaseSummons(dt);
|
||||
}
|
||||
|
||||
public void NotifyTowerShowcaseDummyDeath(Defense25DEnemy enemy)
|
||||
{
|
||||
if (!towerShowcaseActive || enemy == null
|
||||
|| !showcaseDummyKinds.ContainsKey(enemy)) return;
|
||||
AccumulateShowcaseDamage(enemy);
|
||||
enemy.ConfigureShowcaseDummy();
|
||||
enemy.Spawn(showcaseDummyPositions[enemy]);
|
||||
showcaseObservedDamageEvents[enemy] = 0;
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
private void AccumulateShowcaseDamage(Defense25DEnemy enemy)
|
||||
{
|
||||
if (!showcaseDummyKinds.TryGetValue(enemy, out DefenseTowerKind kind)) return;
|
||||
int current = enemy.Actor.DamageEvents;
|
||||
int previous = showcaseObservedDamageEvents.TryGetValue(enemy, out int observed) ? observed : 0;
|
||||
if (current >= previous) showcaseHitCounts[kind] += current - previous;
|
||||
showcaseObservedDamageEvents[enemy] = current;
|
||||
}
|
||||
|
||||
private void MovePlayerForShowcaseBuild(Vector2 position)
|
||||
{
|
||||
Player.Body.position = Defense25DActor.OnGround(position, settings.GroundY);
|
||||
Player.transform.position = Player.Body.position;
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
private void RestoreShowcasePlayerPosition()
|
||||
{
|
||||
if (Player == null || Player.Body == null) return;
|
||||
Player.Body.position = Defense25DActor.OnGround(runPlayerPosition, settings.GroundY);
|
||||
Player.transform.position = Player.Body.position;
|
||||
}
|
||||
|
||||
private int ShowcaseBuildCost(DefenseTowerKind kind) => kind switch
|
||||
{
|
||||
DefenseTowerKind.Direct => Tuning.DirectTowerCost,
|
||||
DefenseTowerKind.Summon => Tuning.SummonTowerCost,
|
||||
DefenseTowerKind.Lightning => Tuning.LightningTowerCost,
|
||||
DefenseTowerKind.Cold => Tuning.ColdTowerCost,
|
||||
DefenseTowerKind.Seismic => Tuning.SeismicTowerCost,
|
||||
DefenseTowerKind.Empowering => Tuning.EmpoweringTowerCost,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
private int FindNonImmuneShowcaseLane(DefenseTowerKind kind)
|
||||
{
|
||||
for (int lane = 0; lane < 2; lane++)
|
||||
if (!DefensePrototypeRules.IsTowerImmune(kind, Tuning.GetPortalImmuneTowerKind(lane)))
|
||||
return lane;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class Defense25DEnemy
|
||||
{
|
||||
public void ConfigureShowcaseDummy() => showcaseDummy = true;
|
||||
}
|
||||
|
||||
public sealed partial class Defense25DTower
|
||||
{
|
||||
public float ShowcaseSupportBonus => Tuning.SupportDamageBonusPerTier * supportAuraStrength;
|
||||
|
||||
public void StepShowcaseSummons(float dt)
|
||||
{
|
||||
foreach (Defense25DSummon summon in ownedSummons)
|
||||
if (summon != null) summon.Step(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3408d4c9c47b4639b0c6d601915cc820
|
||||
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Player;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BumpCombat.Defense.Comparison
|
||||
{
|
||||
public sealed class Defense25DCycloneVisual : MonoBehaviour
|
||||
{
|
||||
private Defense25DComparison world;
|
||||
private readonly Sprite[][] back = new Sprite[2][];
|
||||
private readonly Sprite[][] front = new Sprite[2][];
|
||||
private SpriteRenderer backRenderer, frontRenderer;
|
||||
private MeshRenderer ribbonRenderer;
|
||||
private Mesh ribbonMesh;
|
||||
private Material ribbonMaterial;
|
||||
private LineRenderer boundary;
|
||||
private readonly Vector3[] vertices = new Vector3[130];
|
||||
private readonly Vector2[] uv = new Vector2[130];
|
||||
private bool visible;
|
||||
public bool Ribbon { get; set; }
|
||||
public bool Transparent { get; set; }
|
||||
public float Height { get; set; } = .15f;
|
||||
public float DecorationSpeed { get; set; } = 1f;
|
||||
public float RadiusScale { get; set; } = 1f;
|
||||
public bool IsVisible => visible;
|
||||
|
||||
public void Configure(Defense25DComparison owner)
|
||||
{
|
||||
world = owner;
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
string kind = i == 0 ? "Normal" : "Charged";
|
||||
string key = $"Artifacts/ThreeColor-v1/CycloneOrbit-v3/Cyclone-{kind}-";
|
||||
back[i] = Load(key + "Back-v3");
|
||||
front[i] = Load(key + "Front-v3");
|
||||
}
|
||||
backRenderer = CreateSprite("Existing Back frames");
|
||||
frontRenderer = CreateSprite("Existing Front frames");
|
||||
GameObject ribbon = new("Existing texture on XZ ribbon");
|
||||
ribbon.transform.SetParent(transform, false);
|
||||
ribbonMesh = new Mesh { name = "Cyclone visual ribbon (no hitbox)" };
|
||||
ribbonMesh.MarkDynamic();
|
||||
ribbon.AddComponent<MeshFilter>().sharedMesh = ribbonMesh;
|
||||
ribbonRenderer = ribbon.AddComponent<MeshRenderer>();
|
||||
ribbonMaterial = new Material(world.Settings.CutoutMaterial);
|
||||
ribbonRenderer.sharedMaterial = ribbonMaterial;
|
||||
boundary = Defense25DSprite.CreateLine("Actual damage ellipse", world.Settings.HitBoundaryMaterial, transform, .025f);
|
||||
int[] triangles = new int[64 * 6];
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
int v = i * 2, t = i * 6;
|
||||
triangles[t] = v; triangles[t + 1] = v + 1; triangles[t + 2] = v + 2;
|
||||
triangles[t + 3] = v + 1; triangles[t + 4] = v + 3; triangles[t + 5] = v + 2;
|
||||
}
|
||||
ribbonMesh.vertices = vertices;
|
||||
ribbonMesh.triangles = triangles;
|
||||
SetVisible(false);
|
||||
}
|
||||
|
||||
private static Sprite[] Load(string key)
|
||||
{
|
||||
Texture2D texture = Resources.Load<Texture2D>(key);
|
||||
if (!ActiveArtifactController.IsCycloneOrbitTextureValid(texture))
|
||||
throw new InvalidOperationException($"Existing Cyclone frame sheet missing or invalid: {key}");
|
||||
return ActiveArtifactController.CreateCycloneOrbitFrames(texture, key);
|
||||
}
|
||||
|
||||
private SpriteRenderer CreateSprite(string label)
|
||||
{
|
||||
GameObject go = new(label);
|
||||
go.transform.SetParent(transform, false);
|
||||
return go.AddComponent<SpriteRenderer>();
|
||||
}
|
||||
|
||||
public void SetVisible(bool value)
|
||||
{
|
||||
visible = value;
|
||||
if (backRenderer == null) return;
|
||||
backRenderer.enabled = frontRenderer.enabled = value && !Ribbon;
|
||||
ribbonRenderer.enabled = value && Ribbon;
|
||||
boundary.enabled = value && world.ShowBoundaries;
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!visible || !world.CycloneActive) return;
|
||||
SetVisible(true);
|
||||
int kind = world.IsCycloneCharged ? 1 : 0;
|
||||
int frame = Mathf.FloorToInt(world.CycloneElapsed / CycloneGeometry.FrameSeconds);
|
||||
if (front[kind].Length == 0 || back[kind].Length == 0) return;
|
||||
Sprite frontFrame = front[kind][frame % front[kind].Length];
|
||||
Sprite backFrame = back[kind][frame % back[kind].Length];
|
||||
Material material = Transparent ? world.Settings.TransparentMaterial : world.Settings.CutoutMaterial;
|
||||
backRenderer.sharedMaterial = frontRenderer.sharedMaterial = material;
|
||||
backRenderer.sprite = backFrame;
|
||||
frontRenderer.sprite = frontFrame;
|
||||
Vector2 center = world.CycloneCenter;
|
||||
Vector2 radii = world.CycloneRadii;
|
||||
Quaternion rotation = world.View != null ? world.View.transform.rotation : Quaternion.Euler(world.Settings.CameraPitch, 0f, 0f);
|
||||
Vector3 position = Defense25DActor.OnGround(center, world.Settings.GroundY);
|
||||
float scale = CycloneGeometry.VisualScale(world.IsCycloneCharged ? world.Settings.Cyclone.ChargedRange : world.Settings.Cyclone.NormalRange,
|
||||
world.IsCycloneCharged) * RadiusScale;
|
||||
foreach (SpriteRenderer renderer in new[] { backRenderer, frontRenderer })
|
||||
{
|
||||
renderer.transform.rotation = rotation;
|
||||
renderer.transform.localScale = Vector3.one * scale;
|
||||
renderer.transform.position = position + Vector3.up * Height;
|
||||
}
|
||||
// Real depth, not ZTest Always or a high global sorting layer.
|
||||
backRenderer.transform.position += Vector3.forward * .12f;
|
||||
frontRenderer.transform.position -= Vector3.forward * .12f;
|
||||
Defense25DSprite.SetEllipse(boundary, center, radii, world.Settings.GroundY + .035f);
|
||||
if (!Ribbon) return;
|
||||
ribbonMaterial.CopyPropertiesFromMaterial(material);
|
||||
ribbonMaterial.mainTexture = frontFrame.texture;
|
||||
Rect rect = frontFrame.textureRect;
|
||||
Vector2 texel = frontFrame.texture.texelSize;
|
||||
for (int i = 0; i <= 64; i++)
|
||||
{
|
||||
float t = i / 64f;
|
||||
float angle = t * Mathf.PI * 2f + world.CycloneElapsed * DecorationSpeed * Mathf.PI * 2f;
|
||||
Vector2 ground = center + new Vector2(Mathf.Cos(angle) * radii.x, Mathf.Sin(angle) * radii.y) * RadiusScale;
|
||||
float height = world.Settings.GroundY + Height + .075f * Mathf.Sin(angle * 3f);
|
||||
vertices[2 * i] = Defense25DActor.OnGround(ground, height - .08f);
|
||||
vertices[2 * i + 1] = Defense25DActor.OnGround(ground, height + .08f);
|
||||
uv[2 * i] = new Vector2((rect.x + rect.width * t) * texel.x, rect.y * texel.y);
|
||||
uv[2 * i + 1] = new Vector2((rect.x + rect.width * t) * texel.x, rect.yMax * texel.y);
|
||||
}
|
||||
ribbonMesh.vertices = vertices;
|
||||
ribbonMesh.uv = uv;
|
||||
ribbonMesh.RecalculateBounds();
|
||||
}
|
||||
|
||||
private void OnDisable() => SetVisible(false);
|
||||
private void OnDestroy()
|
||||
{
|
||||
foreach (Sprite[] frames in back)
|
||||
if (frames != null) foreach (Sprite frame in frames) Destroy(frame);
|
||||
foreach (Sprite[] frames in front)
|
||||
if (frames != null) foreach (Sprite frame in frames) Destroy(frame);
|
||||
if (ribbonMesh != null) Destroy(ribbonMesh);
|
||||
if (ribbonMaterial != null) Destroy(ribbonMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||