编队系统大更新 基本快搞好了 准备做敌人和分数

This commit is contained in:
FloatGaming
2025-11-10 22:41:13 +08:00
parent 7837c6adf0
commit 941b294fce
98 changed files with 8933 additions and 98 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6af0e7cd2ed52724c9b8b42bf60ef2d8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: bcec634c6e258c44a803ae3ca618b465
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 36799a159f61a5e489dd0963de4bf661
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
+23
View File
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 3dcbff412c1c9ec4d8ca931f2d51ab10
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: fae24344d433d714fadfa0ca8e285f7f
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c036c8921700c58458a4ed5ab3fbea48
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 4ed1f0171d2ac0145a795a7eee628bd2
TextureImporter:
internalIDToNameTable:
- first:
213: 1508358750112011512
second: "\u753B\u677F 75_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u753B\u677F 75_0"
rect:
serializedVersion: 2
x: 0
y: 0
width: 55
height: 55
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 8f45746aa44cee410800000000000000
internalID: 1508358750112011512
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: ef9f747590c0b3541979026af6c82992
TextureImporter:
internalIDToNameTable:
- first:
213: 627408324413374251
second: "\u753B\u677F 76_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u753B\u677F 76_0"
rect:
serializedVersion: 2
x: 0
y: 0
width: 633
height: 9
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: b2f38c10c8005b800800000000000000
internalID: 627408324413374251
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 7ca4a39b4ea72be4aa0b80a698bdd99f
TextureImporter:
internalIDToNameTable:
- first:
213: -2694539428277866217
second: "\u753B\u677F 78_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u753B\u677F 78_0"
rect:
serializedVersion: 2
x: 0
y: 0
width: 633
height: 9
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 711b6309aa21b9ad0800000000000000
internalID: -2694539428277866217
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

@@ -0,0 +1,255 @@
fileFormatVersion: 2
guid: f48684099e472254f881fc44087db10d
TextureImporter:
internalIDToNameTable:
- first:
213: -7416849779330969551
second: "\u753B\u677F 79_0"
- first:
213: -8945369728627073014
second: "\u753B\u677F 79_1"
- first:
213: 4729716212365872202
second: "\u753B\u677F 79_2"
- first:
213: -1166063591256540292
second: "\u753B\u677F 79_3"
- first:
213: 2919094442245778957
second: "\u753B\u677F 79_4"
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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u753B\u677F 79_0"
rect:
serializedVersion: 2
x: 1
y: 5
width: 18
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 1301b8efb6e021990800000000000000
internalID: -7416849779330969551
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u753B\u677F 79_1"
rect:
serializedVersion: 2
x: 31
y: 5
width: 18
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a00a6ac50a9abd380800000000000000
internalID: -8945369728627073014
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u753B\u677F 79_2"
rect:
serializedVersion: 2
x: 60
y: 5
width: 20
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a4c497606d353a140800000000000000
internalID: 4729716212365872202
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u753B\u677F 79_3"
rect:
serializedVersion: 2
x: 92
y: 5
width: 19
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c7b33a1875f41dfe0800000000000000
internalID: -1166063591256540292
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u753B\u677F 79_4"
rect:
serializedVersion: 2
x: 124
y: 5
width: 18
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: d0e60efdde4b28820800000000000000
internalID: 2919094442245778957
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 6a1563afc10f07f4a91dddedc1542f24
TextureImporter:
internalIDToNameTable:
- first:
213: -5639479817726893851
second: "\u753B\u677F 80_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u753B\u677F 80_0"
rect:
serializedVersion: 2
x: 0
y: 0
width: 55
height: 48
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5e0676a2cfa8cb1b0800000000000000
internalID: -5639479817726893851
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 5ad663b00655c784cb765dc4bef1a70a
TextureImporter:
internalIDToNameTable:
- first:
213: -1101522076588880424
second: "\u753B\u677F 81_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u753B\u677F 81_0"
rect:
serializedVersion: 2
x: 0
y: 0
width: 28
height: 25
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 8dd4ba8e18b96b0f0800000000000000
internalID: -1101522076588880424
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 3f52c0334ac12f746ae80e69fa0a7fb8
TextureImporter:
internalIDToNameTable:
- first:
213: -435054604834745923
second: "\u80CC\u666F \u526F\u672C 2_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u80CC\u666F \u526F\u672C 2_0"
rect:
serializedVersion: 2
x: 1
y: 0
width: 1923
height: 1081
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: db15de4542066f9f0800000000000000
internalID: -435054604834745923
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 596b315de75dacd479b79e1e25a5e2e0
TextureImporter:
internalIDToNameTable:
- first:
213: -1843028302627794106
second: "\u80CC\u666F_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u80CC\u666F_0"
rect:
serializedVersion: 2
x: 1
y: 0
width: 1923
height: 1081
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 64311e6068f3c66e0800000000000000
internalID: -1843028302627794106
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: afef27d42a820784a914abd621cb2c09
AudioImporter:
externalObjects: {}
serializedVersion: 8
defaultSettings:
serializedVersion: 2
loadType: 0
sampleRateSetting: 0
sampleRateOverride: 44100
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
assetBundleName:
assetBundleVariant:
@@ -11,11 +11,11 @@ AudioImporter:
compressionFormat: 1
quality: 1
conversionMode: 0
preloadAudioData: 0
preloadAudioData: 1
platformSettingOverrides: {}
forceToMono: 0
normalize: 1
loadInBackground: 0
loadInBackground: 1
ambisonic: 0
3D: 1
userData:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3fc1f3fc8072ab04db6db7e663dd7956
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,401 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
/// <summary>
/// Throttled preload: uses queues and concurrency limits to load scenes and Resources in small time slices
/// to avoid long frames on low-end devices.
/// - scenesToPreload: names of scenes included in Build Settings to preload additively (allowSceneActivation=false)
/// - resourcesToPreload: paths under Resources/ to load via Resources.LoadAsync
/// </summary>
public class preload_allScene : MonoBehaviour
{
[Header("Preload settings")]
public string[] scenesToPreload = new string[0];
public string[] resourcesToPreload = new string[0];
[Header("UI")]
public Text progressText;
[Tooltip("Optional Image whose fillAmount will be driven by the overall progress (0..1)")]
public Image progressFillImage;
[Tooltip("Optional Text to show current loading item: '正在加载:xxx'")]
public Text loadingInfoText;
[Header("Behavior")]
public bool startOnAwake = true;
[Header("Fade-out on complete")]
[Tooltip("GameObject to disable after fade completes")] public GameObject fadeOutObject;
[Tooltip("CanvasGroup to fade (if null and fadeOutObject assigned, will search for one on it)")] public CanvasGroup fadeCanvasGroup;
[Tooltip("Duration of fade-out in seconds")] public float fadeOutDuration = 0.5f;
[Tooltip("Delay before starting fade-out after completion")] public float fadeOutDelay = 0f;
[Header("Throttle")]
[Tooltip("Max concurrent resource loads")]
public int maxConcurrentResourceLoads = 2;
[Tooltip("Max concurrent scene loads")]
public int maxConcurrentSceneLoads = 1;
[Tooltip("Max milliseconds spent per frame on driver work (approx)")]
public int maxMilliSecondsPerFrame = 4;
// internal queues and trackers
private Queue<string> sceneQueue = new Queue<string>();
private Queue<string> resourceQueue = new Queue<string>();
// scenes currently loading (progress < ~0.9)
private List<(string sceneName, AsyncOperation op)> loadingSceneOps = new List<(string, AsyncOperation)>();
// scenes loaded to 0.9 and waiting activation
private List<(string sceneName, AsyncOperation op)> readySceneOps = new List<(string, AsyncOperation)>();
private List<(string path, ResourceRequest rr)> activeResourceReqs = new List<(string, ResourceRequest)>();
private List<Object> loadedResources = new List<Object>();
private Coroutine preloadCoroutine;
void Awake()
{
fadeOutObject.SetActive(true);
// if UI not assigned, try to auto-find Text components
if (progressText == null)
{
progressText = GetComponentInChildren<Text>(true);
}
if (loadingInfoText == null)
{
// try to find a different Text child (avoid using same as progressText)
var texts = GetComponentsInChildren<Text>(true);
foreach (var t in texts)
{
if (t != progressText)
{
loadingInfoText = t;
break;
}
}
}
// If a fade object is set but no CanvasGroup assigned, try to find one on the object
if (fadeOutObject != null && fadeCanvasGroup == null)
{
fadeCanvasGroup = fadeOutObject.GetComponent<CanvasGroup>() ?? fadeOutObject.GetComponentInChildren<CanvasGroup>(true);
}
}
void Start()
{
// fadeOutObject.SetActive(true);
// auto-start if configured OR there are items to preload
if (startOnAwake || (scenesToPreload != null && scenesToPreload.Length > 0) || (resourcesToPreload != null && resourcesToPreload.Length > 0))
{
StartPreload();
}
}
public void StartPreload()
{
if (preloadCoroutine != null) return;
// fill queues
sceneQueue.Clear();
resourceQueue.Clear();
if (scenesToPreload != null)
{
foreach (var s in scenesToPreload)
if (!string.IsNullOrWhiteSpace(s)) sceneQueue.Enqueue(s);
}
if (resourcesToPreload != null)
{
foreach (var r in resourcesToPreload)
if (!string.IsNullOrWhiteSpace(r)) resourceQueue.Enqueue(r);
}
Debug.Log($"Preload starting. Scenes queued: {sceneQueue.Count}, Resources queued: {resourceQueue.Count}");
// ensure UI shows 0%
UpdateProgressText(0f);
UpdateLoadingInfo("");
preloadCoroutine = StartCoroutine(PreloadRoutine());
}
public void StopPreload()
{
if (preloadCoroutine != null)
{
StopCoroutine(preloadCoroutine);
preloadCoroutine = null;
}
}
private IEnumerator PreloadRoutine()
{
// Start initial batch
while (sceneQueue.Count > 0 || resourceQueue.Count > 0 || loadingSceneOps.Count > 0 || activeResourceReqs.Count > 0)
{
float frameStart = Time.realtimeSinceStartup;
// start scene ops up to concurrency
while (loadingSceneOps.Count < maxConcurrentSceneLoads && sceneQueue.Count > 0)
{
string sceneName = sceneQueue.Dequeue();
UpdateLoadingInfo($"正在加载:{sceneName}");
AsyncOperation op = null;
try
{
op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
}
catch (System.Exception ex)
{
Debug.LogWarning($"Preload: failed to start loading scene '{sceneName}': {ex}");
}
if (op != null)
{
op.allowSceneActivation = false;
loadingSceneOps.Add((sceneName, op));
Debug.Log($"Preload: started scene load '{sceneName}'");
}
if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break;
}
// start resource reqs up to concurrency
while (activeResourceReqs.Count < maxConcurrentResourceLoads && resourceQueue.Count > 0)
{
string path = resourceQueue.Dequeue();
UpdateLoadingInfo($"正在加载:{path}");
ResourceRequest rr = null;
try
{
rr = Resources.LoadAsync<Object>(path);
}
catch (System.Exception ex)
{
Debug.LogWarning($"Preload: failed to start loading resource '{path}': {ex}");
}
if (rr != null)
{
activeResourceReqs.Add((path, rr));
Debug.Log($"Preload: started resource load '{path}'");
}
if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break;
}
// Poll progress and collect finished resources
for (int i = activeResourceReqs.Count - 1; i >= 0; i--)
{
var tuple = activeResourceReqs[i];
var rr = tuple.rr;
if (rr.isDone)
{
if (rr.asset != null)
loadedResources.Add(rr.asset);
Debug.Log($"Preload: resource loaded '{tuple.path}'");
activeResourceReqs.RemoveAt(i);
}
if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break;
}
// Poll loading scenes and move to ready list when reach ~0.9
for (int i = loadingSceneOps.Count - 1; i >= 0; i--)
{
var (sceneName, op) = loadingSceneOps[i];
if (op.progress >= 0.89f)
{
// move to ready list so we free a slot for next scene
readySceneOps.Add((sceneName, op));
loadingSceneOps.RemoveAt(i);
Debug.Log($"Preload: scene '{sceneName}' reached 0.9 and moved to ready list");
}
if ((Time.realtimeSinceStartup - frameStart) * 1000f > maxMilliSecondsPerFrame) break;
}
// Update progress calculation
float overall = CalculateOverallProgress();
UpdateProgressText(overall);
// If there are no active resource requests, no loading scenes, no queued scenes/resources, and ready scenes are present (they are considered loaded), we're done
bool anyLoadingScenes = loadingSceneOps.Count > 0 || sceneQueue.Count > 0;
bool anyActiveResources = activeResourceReqs.Count > 0 || resourceQueue.Count > 0;
if (!anyLoadingScenes && !anyActiveResources)
{
// All loading work started and finished; readySceneOps contains scenes at 0.9 waiting activation
UpdateProgressText(1f);
UpdateLoadingInfo("加载完成");
preloadCoroutine = null;
Debug.Log("Preload complete (throttled). Scenes ready: " + readySceneOps.Count + ", resources cached: " + loadedResources.Count);
// start fade+disable if configured
if (fadeOutObject != null)
{
StartCoroutine(FadeAndDisableCoroutine());
}
yield break;
}
// yield one frame to respect budget
yield return null;
}
// if we exit loop, also mark complete
UpdateProgressText(1f);
UpdateLoadingInfo("加载完成");
preloadCoroutine = null;
if (fadeOutObject != null)
{
StartCoroutine(FadeAndDisableCoroutine());
}
}
private float CalculateOverallProgress()
{
float sum = 0f;
int items = 0;
// queued scenes count as 0
items += sceneQueue.Count;
// loading scenes contribute normalized progress
items += loadingSceneOps.Count;
// ready scenes count as completed (1)
items += readySceneOps.Count;
items += resourceQueue.Count + activeResourceReqs.Count;
if (items == 0) return 1f;
foreach (var (sceneName, op) in loadingSceneOps)
{
float p = Mathf.Clamp01(op.progress / 0.9f);
sum += p;
}
// ready scenes are counted as full
sum += readySceneOps.Count * 1f;
foreach (var (path, rr) in activeResourceReqs)
{
sum += rr.progress; // 0..1
}
return Mathf.Clamp01(sum / (float)items);
}
private void UpdateProgressText(float t)
{
int percent = Mathf.RoundToInt(t * 100f);
// update fill image if present
if (progressFillImage != null)
{
// clamp to 0..1
progressFillImage.fillAmount = Mathf.Clamp01(t);
}
if (progressText != null)
{
progressText.text = percent + "%";
return;
}
// If no UI assigned, try to find any in children and cache it
var tcomp = GetComponentInChildren<Text>(true);
if (tcomp != null)
{
progressText = tcomp;
progressText.text = percent + "%";
}
}
// helper to update loading info text
private void UpdateLoadingInfo(string message)
{
if (loadingInfoText != null)
{
loadingInfoText.text = message;
return;
}
// try to find and cache a Text child that is not the progress text
var texts = GetComponentsInChildren<Text>(true);
foreach (var t in texts)
{
if (t != progressText)
{
loadingInfoText = t;
loadingInfoText.text = message;
return;
}
}
}
private IEnumerator FadeAndDisableCoroutine()
{
// ensure we have a CanvasGroup reference
if (fadeOutObject == null) yield break;
if (fadeCanvasGroup == null)
{
fadeCanvasGroup = fadeOutObject.GetComponent<CanvasGroup>() ?? fadeOutObject.GetComponentInChildren<CanvasGroup>(true);
}
// if there's no CanvasGroup, just disable after delay
if (fadeCanvasGroup == null)
{
if (fadeOutDelay > 0f) yield return new WaitForSeconds(fadeOutDelay);
fadeOutObject.SetActive(false);
yield break;
}
// optionally wait before fading
if (fadeOutDelay > 0f) yield return new WaitForSeconds(fadeOutDelay);
float startAlpha = fadeCanvasGroup.alpha;
float elapsed = 0f;
while (elapsed < fadeOutDuration)
{
elapsed += Time.deltaTime;
float frac = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, fadeOutDuration));
fadeCanvasGroup.alpha = Mathf.Lerp(startAlpha, 0f, frac);
yield return null;
}
fadeCanvasGroup.alpha = 0f;
// disable raycasts and interaction
fadeCanvasGroup.blocksRaycasts = false;
fadeCanvasGroup.interactable = false;
// finally disable the GameObject
fadeOutObject.SetActive(false);
}
/// <summary>
/// Activate a previously preloaded additive scene by name (sets allowSceneActivation=true)
/// </summary>
public void ActivatePreloadedScene(string sceneName)
{
for (int i = 0; i < readySceneOps.Count; i++)
{
if (readySceneOps[i].sceneName == sceneName)
{
readySceneOps[i].op.allowSceneActivation = true;
// remove from ready list so further activations don't re-trigger
readySceneOps.RemoveAt(i);
return;
}
}
// fallback: if scene loaded and already in SceneManager, nothing to do
Debug.LogWarning($"ActivatePreloadedScene: scene '{sceneName}' not found among ready preloaded ops.");
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: da506281e4a56b749a0cf5b32871ff7b
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -0,0 +1,546 @@
fileFormatVersion: 2
guid: 707a9f01ed3cb7f49a8e857e282bbff6
TextureImporter:
internalIDToNameTable:
- first:
213: 618455519012696024
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_0"
- first:
213: -183108937958106121
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_1"
- first:
213: -2747514551019907105
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_2"
- first:
213: 4051328773356679532
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_3"
- first:
213: 683359258392107949
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_4"
- first:
213: -6163899235062800083
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_5"
- first:
213: 1303736164742555898
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_6"
- first:
213: -584840067683796417
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_7"
- first:
213: 2726702000583275308
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_8"
- first:
213: 5085403667284274677
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_9"
- first:
213: -4973876490265236732
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_10"
- first:
213: -877950679927288166
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_11"
- first:
213: -7274499578471264150
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_12"
- first:
213: -4190085041594533794
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_13"
- first:
213: -7912233805109702527
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_14"
- first:
213: -40987154313807340
second: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_15"
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: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_0"
rect:
serializedVersion: 2
x: 242
y: 248
width: 270
height: 343
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 8dfa1209402359800800000000000000
internalID: 618455519012696024
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_1"
rect:
serializedVersion: 2
x: 17
y: 169
width: 48
height: 22
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 7fbdecd1467757df0800000000000000
internalID: -183108937958106121
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_2"
rect:
serializedVersion: 2
x: 68
y: 169
width: 109
height: 22
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: fd7dd33a21eded9d0800000000000000
internalID: -2747514551019907105
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_3"
rect:
serializedVersion: 2
x: 212
y: 154
width: 73
height: 23
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c617e34fd06393830800000000000000
internalID: 4051328773356679532
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_4"
rect:
serializedVersion: 2
x: 266
y: 18
width: 114
height: 179
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: dab9f6aff97cb7900800000000000000
internalID: 683359258392107949
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_5"
rect:
serializedVersion: 2
x: 400
y: 166
width: 41
height: 23
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: d21a7cead4e657aa0800000000000000
internalID: -6163899235062800083
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_6"
rect:
serializedVersion: 2
x: 447
y: 166
width: 40
height: 23
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: af86f44622dc71210800000000000000
internalID: 1303736164742555898
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_7"
rect:
serializedVersion: 2
x: 503
y: 154
width: 74
height: 43
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: f3aba4b9e0b32e7f0800000000000000
internalID: -584840067683796417
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_8"
rect:
serializedVersion: 2
x: 589
y: 18
width: 176
height: 179
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c23fc19d50137d520800000000000000
internalID: 2726702000583275308
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_9"
rect:
serializedVersion: 2
x: 0
y: 18
width: 57
height: 121
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5fda2f711bbf29640800000000000000
internalID: 5085403667284274677
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_10"
rect:
serializedVersion: 2
x: 56
y: 18
width: 130
height: 110
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 40be2115fad39fab0800000000000000
internalID: -4973876490265236732
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_11"
rect:
serializedVersion: 2
x: 138
y: 91
width: 23
height: 72
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a9e0f981284e0d3f0800000000000000
internalID: -877950679927288166
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_12"
rect:
serializedVersion: 2
x: 199
y: 44
width: 93
height: 90
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a60dd72ec29cb0b90800000000000000
internalID: -7274499578471264150
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_13"
rect:
serializedVersion: 2
x: 392
y: 18
width: 40
height: 121
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e58741dcad3d9d5c0800000000000000
internalID: -4190085041594533794
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_14"
rect:
serializedVersion: 2
x: 427
y: 18
width: 74
height: 145
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 180599a6649123290800000000000000
internalID: -7912233805109702527
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_15"
rect:
serializedVersion: 2
x: 497
y: 18
width: 83
height: 121
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 41a75ca76626e6ff0800000000000000
internalID: -40987154313807340
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_0": 618455519012696024
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_1": -183108937958106121
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_10": -4973876490265236732
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_11": -877950679927288166
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_12": -7274499578471264150
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_13": -4190085041594533794
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_14": -7912233805109702527
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_15": -40987154313807340
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_2": -2747514551019907105
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_3": 4051328773356679532
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_4": 683359258392107949
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_5": -6163899235062800083
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_6": 1303736164742555898
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_7": -584840067683796417
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_8": 2726702000583275308
"\u6D6E\u52A8\u6E38\u620FFloatGamingAlpha_9": 5085403667284274677
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+442
View File
@@ -0,0 +1,442 @@
fileFormatVersion: 2
guid: 53623fb5a5a76a04b824351e90b8bb49
TextureImporter:
internalIDToNameTable:
- first:
213: 234875645205025765
second: "\u8D44\u6E90 2@4x_0"
- first:
213: -1984723200111323927
second: "\u8D44\u6E90 2@4x_1"
- first:
213: -3748009902473246706
second: "\u8D44\u6E90 2@4x_2"
- first:
213: -1682124729092548499
second: "\u8D44\u6E90 2@4x_3"
- first:
213: 8035669634750892275
second: "\u8D44\u6E90 2@4x_4"
- first:
213: 5531755780828052498
second: "\u8D44\u6E90 2@4x_5"
- first:
213: 4622229789299838445
second: "\u8D44\u6E90 2@4x_6"
- first:
213: 3325956370787332270
second: "\u8D44\u6E90 2@4x_7"
- first:
213: 2495785389425328509
second: "\u8D44\u6E90 2@4x_8"
- first:
213: 8032133374648562538
second: "\u8D44\u6E90 2@4x_9"
- first:
213: -6379279424289322708
second: "\u8D44\u6E90 2@4x_10"
- first:
213: 4610055813025803884
second: "\u8D44\u6E90 2@4x_11"
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: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u8D44\u6E90 2@4x_0"
rect:
serializedVersion: 2
x: 148
y: 155
width: 41
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 5e316d6a722724300800000000000000
internalID: 234875645205025765
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_1"
rect:
serializedVersion: 2
x: 190
y: 155
width: 92
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 9e453e7d3c8d474e0800000000000000
internalID: -1984723200111323927
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_2"
rect:
serializedVersion: 2
x: 310
y: 142
width: 63
height: 22
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e088f6cf9d46cfbc0800000000000000
internalID: -3748009902473246706
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_3"
rect:
serializedVersion: 2
x: 354
y: 30
width: 98
height: 150
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: d68d98e9874e7a8e0800000000000000
internalID: -1682124729092548499
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_4"
rect:
serializedVersion: 2
x: 467
y: 153
width: 36
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 3f848bf35fe648f60800000000000000
internalID: 8035669634750892275
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_5"
rect:
serializedVersion: 2
x: 505
y: 153
width: 36
height: 21
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 21c387cf29eb4cc40800000000000000
internalID: 5531755780828052498
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_6"
rect:
serializedVersion: 2
x: 551
y: 142
width: 64
height: 38
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: ded0275f085752040800000000000000
internalID: 4622229789299838445
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_7"
rect:
serializedVersion: 2
x: 622
y: 30
width: 155
height: 150
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: ea0f93392bb282e20800000000000000
internalID: 3325956370787332270
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_8"
rect:
serializedVersion: 2
x: 133
y: 30
width: 158
height: 101
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: d718e44a69fc2a220800000000000000
internalID: 2495785389425328509
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_9"
rect:
serializedVersion: 2
x: 248
y: 90
width: 22
height: 63
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a63bc788fbed77f60800000000000000
internalID: 8032133374648562538
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_10"
rect:
serializedVersion: 2
x: 299
y: 50
width: 79
height: 79
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c29c648992f3877a0800000000000000
internalID: -6379279424289322708
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: "\u8D44\u6E90 2@4x_11"
rect:
serializedVersion: 2
x: 458
y: 30
width: 160
height: 123
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c6ea02a36553aff30800000000000000
internalID: 4610055813025803884
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
"\u8D44\u6E90 2@4x_0": 234875645205025765
"\u8D44\u6E90 2@4x_1": -1984723200111323927
"\u8D44\u6E90 2@4x_10": -6379279424289322708
"\u8D44\u6E90 2@4x_11": 4610055813025803884
"\u8D44\u6E90 2@4x_2": -3748009902473246706
"\u8D44\u6E90 2@4x_3": -1682124729092548499
"\u8D44\u6E90 2@4x_4": 8035669634750892275
"\u8D44\u6E90 2@4x_5": 5531755780828052498
"\u8D44\u6E90 2@4x_6": 4622229789299838445
"\u8D44\u6E90 2@4x_7": 3325956370787332270
"\u8D44\u6E90 2@4x_8": 2495785389425328509
"\u8D44\u6E90 2@4x_9": 8032133374648562538
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 211bb2c8eaae7ad4684c90d668d27835
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: de45872e854592b4f9fe42c729492dac
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fc984c4f2c669d94cadcf53239965b8a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 991800b03b1fd7f44ad3b3e8eb63d7e0
TextureImporter:
internalIDToNameTable:
- first:
213: -7736830374416659762
second: Ayano_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: Ayano_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 323
height: 63
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: eca417514c141a490800000000000000
internalID: -7736830374416659762
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
%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: bdd198d96872f3b42bdfc7e262ff2eed, type: 3}
m_Name: 30201_ayano
m_EditorClassIdentifier:
ally_heroName: Ayano
ally_heroID: 30201
ally_heroImage: {fileID: -7736830374416659762, guid: 991800b03b1fd7f44ad3b3e8eb63d7e0, type: 3}
ally_heroProfile: {fileID: 0}
ally_heroIcon: {fileID: 0}
levelStats:
- levelName: Level 1
levelID: 1
attack: 0
maxHP: 100
damageResistance: 0
maxMana: 100
scoreEfficiency: 0
requiredEXP: 0
manaGainGood: 1
manaGainGreat: 2
manaGainPerfect: 3
manaGainOnMiss: 5
damageMultiplierGood: 0.5
damageMultiplierGreat: 0.75
damageMultiplierPerfect: 1
missHpLossBase: 10
- levelName: Level 2
levelID: 2
attack: 0
maxHP: 100
damageResistance: 0
maxMana: 0
scoreEfficiency: 0
requiredEXP: 1
manaGainGood: 1
manaGainGreat: 2
manaGainPerfect: 3
manaGainOnMiss: 5
damageMultiplierGood: 0.5
damageMultiplierGreat: 0.75
damageMultiplierPerfect: 1
missHpLossBase: 10
ally_currentEXP: 0
availableSkills:
- {fileID: 11400000, guid: 9c2d57fabf3a506499779b3d430f595b, type: 2}
selectedSkillIndices: 00000000
primarySkillIndex: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 390a8f8f210be2e489aea46cf4403e24
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7f6754e451e8b8c40a22bac6cdf45ce2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 5f93d010e49316d4594c1065ce5c6d85
TextureImporter:
internalIDToNameTable:
- first:
213: 2718893390870541660
second: YueTao_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: YueTao_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 323
height: 63
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: c55fef832237bb520800000000000000
internalID: 2718893390870541660
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
%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: bdd198d96872f3b42bdfc7e262ff2eed, type: 3}
m_Name: 30202_yuetao
m_EditorClassIdentifier:
ally_heroName: Yue Tao
ally_heroID: 30202
ally_heroImage: {fileID: 2718893390870541660, guid: 5f93d010e49316d4594c1065ce5c6d85, type: 3}
ally_heroProfile: {fileID: 0}
ally_heroIcon: {fileID: 0}
levelStats:
- levelName: Level 1
levelID: 1
attack: 6
maxHP: 114514
damageResistance: 0
maxMana: 101
scoreEfficiency: 0
requiredEXP: 0
manaGainGood: 1
manaGainGreat: 2
manaGainPerfect: 3
manaGainOnMiss: 5
damageMultiplierGood: 0.5
damageMultiplierGreat: 0.75
damageMultiplierPerfect: 1
missHpLossBase: 10
- levelName: Level 2
levelID: 2
attack: 100
maxHP: 200
damageResistance: 0
maxMana: 666
scoreEfficiency: 0
requiredEXP: 11
manaGainGood: 1
manaGainGreat: 2
manaGainPerfect: 3
manaGainOnMiss: 5
damageMultiplierGood: 0.5
damageMultiplierGreat: 0.75
damageMultiplierPerfect: 1
missHpLossBase: 10
ally_currentEXP: 12
availableSkills:
- {fileID: 11400000, guid: 75f453e0c37644343897f37b063bbfd4, type: 2}
selectedSkillIndices:
primarySkillIndex: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b483e2401e5f6284fb91953e2e83111e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3843d751564d952429b1c2f88aecf636
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

@@ -0,0 +1,155 @@
fileFormatVersion: 2
guid: 893e9ceec19c4cc4d9d0326a6a48e2bd
TextureImporter:
internalIDToNameTable:
- first:
213: 390925012957057715
second: Moyuri_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: Moyuri_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 323
height: 63
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 3b2de136a38dc6500800000000000000
internalID: 390925012957057715
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,57 @@
%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: bdd198d96872f3b42bdfc7e262ff2eed, type: 3}
m_Name: 30303_moyuri
m_EditorClassIdentifier:
ally_heroName: Moyuri
ally_heroID: 30303
ally_heroImage: {fileID: 390925012957057715, guid: 893e9ceec19c4cc4d9d0326a6a48e2bd, type: 3}
ally_heroProfile: {fileID: 0}
ally_heroIcon: {fileID: 0}
levelStats:
- levelName: Level 1
levelID: 1
attack: 6
maxHP: 114514
damageResistance: 0
maxMana: 101
scoreEfficiency: 0
requiredEXP: 0
manaGainGood: 1
manaGainGreat: 2
manaGainPerfect: 3
manaGainOnMiss: 5
damageMultiplierGood: 0.5
damageMultiplierGreat: 0.75
damageMultiplierPerfect: 1
missHpLossBase: 10
- levelName: Level 2
levelID: 2
attack: 100
maxHP: 123
damageResistance: 0
maxMana: 100
scoreEfficiency: 0
requiredEXP: 11
manaGainGood: 1
manaGainGreat: 2
manaGainPerfect: 3
manaGainOnMiss: 5
damageMultiplierGood: 0.5
damageMultiplierGreat: 0.75
damageMultiplierPerfect: 1
missHpLossBase: 10
ally_currentEXP: 12
availableSkills:
- {fileID: 11400000, guid: bdc66177d5ae680438237bf717dd26d8, type: 2}
selectedSkillIndices:
primarySkillIndex: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f7b88d014e0b54342848b47aedca49c3
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

+156
View File
@@ -0,0 +1,156 @@
fileFormatVersion: 2
guid: a8c42276e289c3f4d8df7ebc510e67a4
TextureImporter:
internalIDToNameTable:
- first:
213: -8618468658663994465
second: "\u8F68\u9053\u7F6E\u7A7A\u89D2\u8272\uFF08\u5360\u4F4D\u7B26\uFF09_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: 1
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: 100
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: 1
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
- serializedVersion: 4
buildTarget: Android
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: "\u8F68\u9053\u7F6E\u7A7A\u89D2\u8272\uFF08\u5360\u4F4D\u7B26\uFF09_0"
rect:
serializedVersion: 2
x: 0
y: 0
width: 323
height: 63
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: f9bce74ea6c056880800000000000000
internalID: -8618468658663994465
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
"\u8F68\u9053\u7F6E\u7A7A\u89D2\u8272\uFF08\u5360\u4F4D\u7B26\uFF09_0": -8618468658663994465
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 30cb68bde8fe7e94893865d055e79797
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e872e5fb91e13ba4381a94ed48c58962
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
%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: 265d1552ebf55c34a98934707b2c20ce, type: 3}
m_Name: 44001_yaoxueyin
m_EditorClassIdentifier:
skillId: 44001
displayName: yaoxueyin_skill01
summaryInfo:
effectType: 4
defaultSelector: 3
formula: 0
defaultDuration: 0
defaultTickInterval: 0
requiresSpecificTarget: 0
operateDirectly: 0
description:
triggerCondition: 0
onNoteHitMinThreshold: 1
noteTriggerType: 2
onNoteHitCooldown: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c2d57fabf3a506499779b3d430f595b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
%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: 265d1552ebf55c34a98934707b2c20ce, type: 3}
m_Name: 44002_yuetao
m_EditorClassIdentifier:
skillId: 44002
displayName: increaseMana_yuetao
summaryInfo: "44002\u8DC3\u6843\uFF1A\u76F8\u90BB\u53CB\u65B9\u6301\u7EED\u56DE\u84DD"
effectType: 4
defaultSelector: 3
formula: 20
defaultDuration: 5
defaultTickInterval: 1
requiresSpecificTarget: 0
operateDirectly: 0
description: "44002 \u8DC3\u6843\uFF1A\n\n\u6761\u4EF6\uFF1A\u5728\u6CD5\u529B\u503C\u6EE1\u65F6\u91CA\u653E\u3002\n\u6570\u636E\uFF1A\u516C\u5F0F\u975E\u5B9A\u503C\u8BA1\u7B97\n\n\u5728\u6CD5\u529B\u503C\u6EE1\u65F6\uFF0C\u4E3A\u76F8\u90BB\u7684\u53CB\u519B\u6BCF1\u79D2\u56DE\u590D(\u57FA\u7840\u653B\u51FB\u529B)*0.5+5\u7684\u6CD5\u529B\uFF0C\u6301\u7EED3\u79D2\u3002"
triggerCondition: 2
onNoteHitMinThreshold: 0
noteTriggerType: 2
onNoteHitCooldown: 1
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75f453e0c37644343897f37b063bbfd4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,29 @@
%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: 265d1552ebf55c34a98934707b2c20ce, type: 3}
m_Name: 44003_moyuri
m_EditorClassIdentifier:
skillId:
displayName:
summaryInfo:
effectType: 0
defaultSelector: 4
formula:
defaultDuration: 0
defaultTickInterval: 1
requiresSpecificTarget: 0
operateDirectly: 0
description:
triggerCondition: 0
onNoteHitMinThreshold: 1
noteTriggerType: 2
onNoteHitCooldown: 0
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bdc66177d5ae680438237bf717dd26d8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
+567 -2
View File
@@ -273,6 +273,83 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 73146554}
m_CullTransparentMesh: 1
--- !u!1 &182328454
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 182328455}
- component: {fileID: 182328457}
- component: {fileID: 182328456}
m_Layer: 0
m_Name: blackBottom
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &182328455
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 182328454}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1872466268}
- {fileID: 1419788765}
m_Father: {fileID: 849894528}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 1920, y: 1080}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &182328456
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 182328454}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &182328457
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 182328454}
m_CullTransparentMesh: 1
--- !u!1 &224223031
GameObject:
m_ObjectHideFlags: 0
@@ -671,6 +748,85 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &623677385
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 623677386}
- component: {fileID: 623677388}
- component: {fileID: 623677387}
m_Layer: 0
m_Name: loadingWhat
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &623677386
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 623677385}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 849894528}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 344.1, y: -384}
m_SizeDelta: {x: 497.7007, y: 140.77}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &623677387
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 623677385}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.9056604, g: 0.9056604, b: 0.9056604, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: ec651258a51214d47953d3316e0ce983, type: 3}
m_FontSize: 24
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 2
m_MaxSize: 59
m_Alignment: 5
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: loadingObject
--- !u!222 &623677388
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 623677385}
m_CullTransparentMesh: 1
--- !u!1 &736871618
GameObject:
m_ObjectHideFlags: 0
@@ -897,8 +1053,8 @@ AudioSource:
m_Enabled: 1
serializedVersion: 4
OutputAudioMixerGroup: {fileID: 0}
m_audioClip: {fileID: 8300000, guid: 8ed30022249917f4fb5c65d026b896d6, type: 3}
m_Resource: {fileID: 8300000, guid: 8ed30022249917f4fb5c65d026b896d6, type: 3}
m_audioClip: {fileID: 8300000, guid: afef27d42a820784a914abd621cb2c09, type: 3}
m_Resource: {fileID: 8300000, guid: afef27d42a820784a914abd621cb2c09, type: 3}
m_PlayOnAwake: 0
m_Volume: 1
m_Pitch: 1
@@ -1074,6 +1230,123 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 816275736}
m_CullTransparentMesh: 1
--- !u!1 &849894527
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 849894528}
- component: {fileID: 849894531}
- component: {fileID: 849894530}
- component: {fileID: 849894529}
- component: {fileID: 849894532}
m_Layer: 0
m_Name: thisCanvas
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &849894528
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 849894527}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0, y: 0, z: 0}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 182328455}
- {fileID: 1401636780}
- {fileID: 623677386}
m_Father: {fileID: 1903432995}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!114 &849894529
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 849894527}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3}
m_Name:
m_EditorClassIdentifier:
m_IgnoreReversedGraphics: 1
m_BlockingObjects: 0
m_BlockingMask:
serializedVersion: 2
m_Bits: 4294967295
--- !u!114 &849894530
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 849894527}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3}
m_Name:
m_EditorClassIdentifier:
m_UiScaleMode: 1
m_ReferencePixelsPerUnit: 100
m_ScaleFactor: 1
m_ReferenceResolution: {x: 1920, y: 1080}
m_ScreenMatchMode: 0
m_MatchWidthOrHeight: 0
m_PhysicalUnit: 3
m_FallbackScreenDPI: 96
m_DefaultSpriteDPI: 96
m_DynamicPixelsPerUnit: 1
m_PresetInfoIsWorld: 0
--- !u!223 &849894531
Canvas:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 849894527}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 0
m_Camera: {fileID: 0}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 1
m_AdditionalShaderChannelsFlag: 0
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 32767
m_TargetDisplay: 0
--- !u!225 &849894532
CanvasGroup:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 849894527}
m_Enabled: 1
m_Alpha: 1
m_Interactable: 1
m_BlocksRaycasts: 1
m_IgnoreParentGroups: 0
--- !u!1 &958271890
GameObject:
m_ObjectHideFlags: 0
@@ -1633,6 +1906,160 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1401636779
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1401636780}
- component: {fileID: 1401636782}
- component: {fileID: 1401636781}
m_Layer: 0
m_Name: jindu
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1401636780
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1401636779}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 849894528}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: -348.63, y: -384}
m_SizeDelta: {x: 497.7007, y: 140.77}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1401636781
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1401636779}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.9056604, g: 0.9056604, b: 0.9056604, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 12800000, guid: ec651258a51214d47953d3316e0ce983, type: 3}
m_FontSize: 24
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 2
m_MaxSize: 59
m_Alignment: 3
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: loadingPercent%
--- !u!222 &1401636782
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1401636779}
m_CullTransparentMesh: 1
--- !u!1 &1419788764
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1419788765}
- component: {fileID: 1419788767}
- component: {fileID: 1419788766}
m_Layer: 0
m_Name: loadingPanel
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1419788765
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1419788764}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 182328455}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: -408.32}
m_SizeDelta: {x: 1192, y: 12}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1419788766
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1419788764}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 9099771840580648462, guid: 6bf71d2cb6281dc4c994526868b3379d, type: 3}
m_Type: 3
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 0
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1419788767
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1419788764}
m_CullTransparentMesh: 1
--- !u!1 &1610130039
GameObject:
m_ObjectHideFlags: 0
@@ -2051,6 +2478,143 @@ Canvas:
m_SortingLayerID: 0
m_SortingOrder: 1
m_TargetDisplay: 0
--- !u!1 &1872466267
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1872466268}
- component: {fileID: 1872466270}
- component: {fileID: 1872466269}
m_Layer: 0
m_Name: logo_fdLogo
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1872466268
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1872466267}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.25, y: 0.25, z: 0.25}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 182328455}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 765, y: 638}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1872466269
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1872466267}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 21300000, guid: 707a9f01ed3cb7f49a8e857e282bbff6, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1872466270
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1872466267}
m_CullTransparentMesh: 1
--- !u!1 &1903432994
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1903432995}
- component: {fileID: 1903432996}
m_Layer: 0
m_Name: logo_and_load
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1903432995
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1903432994}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 659.97345, y: 501.6619, z: -9.434833}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 849894528}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1903432996
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1903432994}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: da506281e4a56b749a0cf5b32871ff7b, type: 3}
m_Name:
m_EditorClassIdentifier:
scenesToPreload:
- gamePlay_gamePlay
- selectYourSongFirst
- Songs_Select
- UI_UI
resourcesToPreload: []
progressText: {fileID: 1401636781}
progressFillImage: {fileID: 1419788766}
loadingInfoText: {fileID: 623677387}
startOnAwake: 1
fadeOutObject: {fileID: 849894527}
fadeCanvasGroup: {fileID: 849894532}
fadeOutDuration: 2
fadeOutDelay: 0.5
maxConcurrentResourceLoads: 2
maxConcurrentSceneLoads: 1
maxMilliSecondsPerFrame: 4
--- !u!1 &1906290265
GameObject:
m_ObjectHideFlags: 0
@@ -2233,3 +2797,4 @@ SceneRoots:
- {fileID: 1313638918}
- {fileID: 1735952444}
- {fileID: 777304730}
- {fileID: 1903432995}
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 718e488bcee5b6a4c8eedc073040d3a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+680
View File
@@ -0,0 +1,680 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
[ExecuteAlways]
/// <summary>
/// AllyCombatant 绑定到场上友军的运行时 GameObject(命名为 ally_01..ally_05),
/// 实现 ICombatant 接口以供 EffectSystem 调用。
/// - 从 teamUIController 获取 UI 引用并同步显示血量/法力
/// - 启动时从 TeamCharacterDataInfo 或 AllyHero_SO 填充最大生命/法力等基础属性
/// - 初始生命值为最大生命值,初始法力为 0
/// </summary>
public class AllyCombatant : MonoBehaviour, ICombatant
{
public int slotIndex = 0; // 0..4 对应 ally_01..ally_05
// runtime stats
public int maxHP = 100;
public int currentHP = 100;
public int maxMana = 100;
public int currentMana = 0;
public float damageResistance = 0f; // 0..1
public float scoreEfficiency = 1f;
private bool isDead = false; // new: dead flag
// add attack stat to be available at runtime
[Tooltip("基础攻击力(运行时使用),会从 AllyHero_SO.levelStats[0].attack ")]
public int attack = 0;
// scoring fields
[Header("Scoring")]
[Tooltip("Base score value for a perfect hit on this track")]
public int baseTrackScore = 1000;
[Tooltip("Score multiplier for Great (e.g. 0.75)")]
public float greatRatio = 0.75f;
[Tooltip("Score multiplier for Good (e.g. 0.5)")]
public float goodRatio = 0.5f;
[Tooltip("Score multiplier for Miss (e.g. 0)")]
public float missRatio = 0f;
[Tooltip("角色当前轨道得分(累加),显示于 UI 的 currentScore 文本)")]
public int currentScore = 0;
[Tooltip("轨道分数上限,暂设为 int.MaxValue - 1")]
public int maxTrackScore = int.MaxValue - 1;
// When true, InitializeStatsFromData() will overwrite inspector values on Start.
// Set to false if you want to keep manual inspector values when entering Play mode.
public bool allowOverwriteFromSO = true;
// UI refs (resolved from teamUIController)
private Image characterImage;
private Image healthImage;
private Image fadeHealthImage;
private Image manaImage;
private Image fadeManaImage;
private TextMeshProUGUI nameText;
private TextMeshProUGUI healthRateText;
private TextMeshProUGUI currentScoreText;
private Image hurtRedImage;
private Coroutine fadeHealthCoroutine;
private Coroutine fadeManaCoroutine;
private List<Buff> activeBuffs = new List<Buff>();
private void Awake()
{
// ensure GameObject name matches pattern so EffectSystem can find it
gameObject.name = $"ally_0{Mathf.Clamp(slotIndex + 1, 1, 5)}";
}
private void Start()
{
// Resolve UI and optionally pull data from SO
ResolveUIReferences();
if (allowOverwriteFromSO)
InitializeStatsFromData();
// Ensure runtime invariants
currentHP = Mathf.Clamp(currentHP, 0, Mathf.Max(1, maxHP));
currentMana = Mathf.Clamp(currentMana, 0, Mathf.Max(1, maxMana));
UpdateUIImmediate();
// Ensure score UI shows initial value
UpdateScoreUI();
}
// Allow editing in inspector to immediately reflect on UI (editor only)
private void OnValidate()
{
// Avoid heavy editor-only operations, but resolve UI references so inspector changes show
ResolveUIReferences();
// Clamp values to valid ranges
if (maxHP < 1) maxHP = 1;
if (maxMana < 1) maxMana = 1;
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
currentMana = Mathf.Clamp(currentMana, 0, maxMana);
// Do not start coroutines from OnValidate - update immediately
UpdateUIImmediate();
// If user adjusts currentMana in inspector while in Play mode, attempt to trigger mana-full behaviors
// Guard to avoid calling TryCastOnFullMana before singletons are initialized
if (Application.isPlaying && SkillBuilder.Instance != null)
{
TryCastOnFullMana();
}
}
private void ResolveUIReferences()
{
// Try singleton first, fallback to scene search (works in editor)
var ui = teamUIController.Instance ?? FindObjectOfType<teamUIController>();
if (ui == null) return;
switch (slotIndex)
{
case 0:
characterImage = ui.teammate01_characterImage;
healthImage = ui.teammate01_healthImage;
fadeHealthImage = ui.teammate01_fadehealthImage;
manaImage = ui.teammate01_manaImage;
fadeManaImage = ui.teammate01_fadeManaImage;
nameText = ui.teammate01_nameText;
healthRateText = ui.teammate01_healthRate;
currentScoreText = ui.teammate01_current_scoreText;
hurtRedImage = ui.teammate01_hurtRedImage;
break;
case 1:
characterImage = ui.teammate02_characterImage;
healthImage = ui.teammate02_healthImage;
fadeHealthImage = ui.teammate02_fadehealthImage;
manaImage = ui.teammate02_manaImage;
fadeManaImage = ui.teammate02_fadeManaImage;
nameText = ui.teammate02_nameText;
healthRateText = ui.teammate02_healthRate;
currentScoreText = ui.teammate02_current_scoreText;
hurtRedImage = ui.teammate02_hurtRedImage;
break;
case 2:
characterImage = ui.teammate03_characterImage;
healthImage = ui.teammate03_healthImage;
fadeHealthImage = ui.teammate03_fadehealthImage;
manaImage = ui.teammate03_manaImage;
fadeManaImage = ui.teammate03_fadeManaImage;
nameText = ui.teammate03_nameText;
healthRateText = ui.teammate03_healthRate;
currentScoreText = ui.teammate03_current_scoreText;
hurtRedImage = ui.teammate03_hurtRedImage;
break;
case 3:
characterImage = ui.teammate04_characterImage;
healthImage = ui.teammate04_healthImage;
fadeHealthImage = ui.teammate04_fadehealthImage;
manaImage = ui.teammate04_manaImage;
fadeManaImage = ui.teammate04_fadeManaImage;
nameText = ui.teammate04_nameText;
healthRateText = ui.teammate04_healthRate;
currentScoreText = ui.teammate04_current_scoreText;
hurtRedImage = ui.teammate04_hurtRedImage;
break;
case 4:
characterImage = ui.teammate05_characterImage;
healthImage = ui.teammate05_healthImage;
fadeHealthImage = ui.teammate05_fadehealthImage;
manaImage = ui.teammate05_manaImage;
fadeManaImage = ui.teammate05_fadeManaImage;
nameText = ui.teammate05_nameText;
healthRateText = ui.teammate05_healthRate;
currentScoreText = ui.teammate05_current_scoreText;
hurtRedImage = ui.teammate05_hurtRedImage;
break;
}
// If the assigned TMP field is null, attempt to resolve from the UI parent's object (teamUIController keeps objectFather references)
if (currentScoreText == null)
{
GameObject parent = null;
switch (slotIndex)
{
case 0: parent = ui.objectFather_ally01; break;
case 1: parent = ui.objectFather_ally02; break;
case 2: parent = ui.objectFather_ally03; break;
case 3: parent = ui.objectFather_ally04; break;
case 4: parent = ui.objectFather_ally05; break;
}
if (parent != null)
{
var tmp = parent.GetComponentInChildren<TextMeshProUGUI>(true);
if (tmp != null)
{
currentScoreText = tmp;
// also assign back to teamUIController so other systems can find it
switch (slotIndex)
{
case 0: ui.teammate01_current_scoreText = tmp; break;
case 1: ui.teammate02_current_scoreText = tmp; break;
case 2: ui.teammate03_current_scoreText = tmp; break;
case 3: ui.teammate04_current_scoreText = tmp; break;
case 4: ui.teammate05_current_scoreText = tmp; break;
}
Debug.Log($"[AllyCombatant] Resolved currentScoreText for slot {slotIndex+1} from parent {parent.name} -> {tmp.gameObject.name}");
}
else
{
// legacy UnityEngine.UI.Text fallback
var legacy = parent.GetComponentInChildren<Text>(true);
if (legacy != null)
{
var go = legacy.gameObject;
var created = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
created.text = legacy.text;
currentScoreText = created;
switch (slotIndex)
{
case 0: ui.teammate01_current_scoreText = created; break;
case 1: ui.teammate02_current_scoreText = created; break;
case 2: ui.teammate03_current_scoreText = created; break;
case 3: ui.teammate04_current_scoreText = created; break;
case 4: ui.teammate05_current_scoreText = created; break;
}
Debug.LogWarning($"[AllyCombatant] Found legacy Text for slot {slotIndex+1} under {parent.name}. Added/used TMP component on {go.name} and copied text.");
}
}
}
}
}
private void InitializeStatsFromData()
{
// Try TeamCharacterDataInfo first
var arr = teamUIController.Instance?.GetCurrentAllySOs();
if (arr != null && slotIndex >= 0 && slotIndex < arr.Length)
{
var so = arr[slotIndex];
if (so != null)
{
// TeamCharacterDataInfo has limited fields; use CharacterMaxHealth if present
if (so.CharacterMaxHealth > 0)
{
maxHP = so.CharacterMaxHealth;
}
if (!string.IsNullOrEmpty(so.CharacterName) && nameText != null)
{
nameText.text = so.CharacterName;
}
if (characterImage != null && so.CharacterImage_gameplay != null)
{
characterImage.sprite = so.CharacterImage_gameplay.sprite;
}
}
}
// If more detailed info exists in AllyHero_SO, try to load by ally ID
int id = -1;
if (teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null && slotIndex < teamUIController.Instance.allySlotIds.Count)
id = teamUIController.Instance.allySlotIds[slotIndex];
if (id > 0)
{
var all = Resources.LoadAll<AllyHero_SO>("");
foreach (var a in all)
{
if (a != null && a.ally_heroID == id)
{
// pick effective level based on current EXP if available
if (a.levelStats != null && a.levelStats.Count > 0)
{
var eff = a.GetEffectiveLevelForCurrentEXP();
if (eff != null)
{
maxHP = eff.maxHP;
maxMana = eff.maxMana;
damageResistance = eff.damageResistance;
scoreEfficiency = eff.scoreEfficiency;
attack = eff.attack; // populate attack from SO effective level
}
else
{
var lvl = a.levelStats[0];
maxHP = lvl.maxHP;
maxMana = lvl.maxMana;
damageResistance = lvl.damageResistance;
scoreEfficiency = lvl.scoreEfficiency;
attack = lvl.attack;
}
}
if (nameText != null) nameText.text = a.ally_heroName;
if (characterImage != null && a.ally_heroProfile != null) characterImage.sprite = a.ally_heroProfile;
break;
}
}
}
}
// Scoring API
public void AddScoreForJudge(string judge)
{
int add = 0;
switch (judge)
{
case "Perfect":
add = baseTrackScore;
break;
case "Great":
add = Mathf.FloorToInt(baseTrackScore * greatRatio);
break;
case "Good":
add = Mathf.FloorToInt(baseTrackScore * goodRatio);
break;
case "Miss":
add = Mathf.FloorToInt(baseTrackScore * missRatio);
break;
default:
add = 0;
break;
}
// apply scoreEfficiency or other modifiers here
add = Mathf.FloorToInt(add * scoreEfficiency);
currentScore = Mathf.Clamp(currentScore + add, 0, maxTrackScore);
UpdateScoreUI();
// update global total
ScoreManager.Instance?.RecalculateTotal();
}
// Add an arbitrary score delta directly (used by skills to modify single judge or grant/penalize score)
public void AddScoreDirect(int delta)
{
currentScore = Mathf.Clamp(currentScore + delta, 0, maxTrackScore);
UpdateScoreUI();
ScoreManager.Instance?.RecalculateTotal();
}
private void UpdateScoreUI()
{
string value = $"{currentScore}/{maxTrackScore}";
if (currentScoreText != null)
{
currentScoreText.text = value;
Debug.Log($"[AllyCombatant] Updated slot {slotIndex+1} score UI -> {value} (target {currentScoreText.gameObject.name})");
return;
}
// As a fallback, try to write directly to teamUIController.Instance fields if available
var ui = teamUIController.Instance;
if (ui != null)
{
switch (slotIndex)
{
case 0:
if (ui.teammate01_current_scoreText != null) { ui.teammate01_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate01_current_scoreText: {value}"); }
break;
case 1:
if (ui.teammate02_current_scoreText != null) { ui.teammate02_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate02_current_scoreText: {value}"); }
break;
case 2:
if (ui.teammate03_current_scoreText != null) { ui.teammate03_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate03_current_scoreText: {value}"); }
break;
case 3:
if (ui.teammate04_current_scoreText != null) { ui.teammate04_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate04_current_scoreText: {value}"); }
break;
case 4:
if (ui.teammate05_current_scoreText != null) { ui.teammate05_current_scoreText.text = value; Debug.Log($"[AllyCombatant] Fallback wrote to ui.teammate05_current_scoreText: {value}"); }
break;
}
}
}
// Public setters that update UI and optionally animate fade bars
public void SetCurrentHP(int hp, bool animateFade = true)
{
if (isDead) return; // dead characters cannot change HP
int old = currentHP;
currentHP = Mathf.Clamp(hp, 0, maxHP);
if (currentHP <= 0) isDead = true;
UpdateHealthVisuals(old, animateFade);
}
public void ModifyHP(int delta, bool animateFade = true)
{
if (isDead && delta > 0)
{
// cannot heal a dead unit
return;
}
SetCurrentHP(currentHP + delta, animateFade);
}
public void SetMaxHP(int newMax, bool keepCurrentRatio = true)
{
if (newMax < 1) newMax = 1;
float ratio = maxHP > 0 ? (float)currentHP / maxHP : 1f;
maxHP = newMax;
if (keepCurrentRatio)
currentHP = Mathf.Clamp(Mathf.RoundToInt(ratio * maxHP), 0, maxHP);
else
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
UpdateUIImmediate();
}
public void SetCurrentMana(int mana, bool animateFade = true)
{
if (isDead) return; // dead characters cannot change mana
int old = currentMana;
currentMana = Mathf.Clamp(mana, 0, maxMana);
UpdateManaVisuals(old, animateFade);
TryCastOnFullMana();
}
public void ModifyMana(int delta, bool animateFade = true)
{
if (isDead && delta > 0)
{
// dead units cannot gain mana
return;
}
SetCurrentMana(currentMana + delta, animateFade);
}
public void SetMaxMana(int newMax, bool keepCurrentRatio = true)
{
if (newMax < 1) newMax = 1;
float ratio = maxMana > 0 ? (float)currentMana / maxMana : 1f;
maxMana = newMax;
if (keepCurrentRatio)
currentMana = Mathf.Clamp(Mathf.RoundToInt(ratio * maxMana), 0, maxMana);
else
currentMana = Mathf.Clamp(currentMana, 0, maxMana);
UpdateUIImmediate();
}
private void UpdateHealthVisuals(int oldHP, bool animateFade)
{
// main bar should snap quickly to new value, while fade bar lerps towards it
if (healthImage != null)
{
float newFill = maxHP > 0 ? (float)currentHP / maxHP : 0f;
// snap main bar quickly for visual pop
healthImage.fillAmount = Mathf.MoveTowards(healthImage.fillAmount, newFill, 1f);
}
if (healthRateText != null)
healthRateText.text = $"{currentHP}/{maxHP}";
if (fadeHealthCoroutine != null) StopCoroutine(fadeHealthCoroutine);
if (fadeHealthImage != null)
{
float target = healthImage != null ? healthImage.fillAmount : maxHP > 0 ? (float)currentHP / maxHP : 0f;
if (animateFade && Application.isPlaying)
fadeHealthCoroutine = StartCoroutine(FadeHealthCoroutine(target));
else
fadeHealthImage.fillAmount = target;
}
// if HP decreased, show hurt flash; if dead, keep hurt overlay visible
if (currentHP < oldHP && hurtRedImage != null && Application.isPlaying)
{
StopCoroutine("HurtFlash");
StartCoroutine(HurtFlash());
}
if (isDead && hurtRedImage != null)
{
Color c = hurtRedImage.color;
c.a = 1f;
hurtRedImage.color = c; // keep overlay visible when dead
}
}
private void UpdateManaVisuals(int oldMana, bool animateFade)
{
// main bar snaps, fade bar lerps to new value for visual effect
if (manaImage != null)
{
float newFill = maxMana > 0 ? (float)currentMana / maxMana : 0f;
manaImage.fillAmount = Mathf.MoveTowards(manaImage.fillAmount, newFill, 1f);
}
if (fadeManaCoroutine != null) StopCoroutine(fadeManaCoroutine);
if (fadeManaImage != null)
{
float target = manaImage != null ? manaImage.fillAmount : maxMana > 0 ? (float)currentMana / maxMana : 0f;
if (animateFade && Application.isPlaying)
fadeManaCoroutine = StartCoroutine(FadeManaCoroutine(target));
else
fadeManaImage.fillAmount = target;
}
}
private void UpdateUIImmediate()
{
if (healthImage != null)
{
healthImage.fillAmount = maxHP > 0 ? (float)currentHP / maxHP : 0f;
}
if (fadeHealthImage != null)
{
// keep fade bar equal to main bar immediately
fadeHealthImage.fillAmount = healthImage != null ? healthImage.fillAmount : maxHP > 0 ? (float)currentHP / maxHP : 0f;
}
if (manaImage != null)
{
manaImage.fillAmount = maxMana > 0 ? (float)currentMana / maxMana : 0f;
}
if (fadeManaImage != null)
{
fadeManaImage.fillAmount = manaImage != null ? manaImage.fillAmount : maxMana > 0 ? (float)currentMana / maxMana : 0f;
}
if (healthRateText != null)
{
healthRateText.text = $"{currentHP}/{maxHP}";
}
}
private IEnumerator FadeHealthCoroutine(float targetFill)
{
if (fadeHealthImage == null) yield break;
float start = fadeHealthImage.fillAmount;
float duration = 0.6f;
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
fadeHealthImage.fillAmount = Mathf.Lerp(start, targetFill, t / duration);
yield return null;
}
fadeHealthImage.fillAmount = targetFill;
}
private IEnumerator FadeManaCoroutine(float targetFill)
{
if (fadeManaImage == null) yield break;
float start = fadeManaImage.fillAmount;
float duration = 0.4f;
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
fadeManaImage.fillAmount = Mathf.Lerp(start, targetFill, t / duration);
yield return null;
}
fadeManaImage.fillAmount = targetFill;
}
public void ReceiveDamage(float amount, GameObject source)
{
float effective = amount * (1f - damageResistance);
int delta = Mathf.CeilToInt(effective);
ModifyHP(-delta, true);
}
private IEnumerator HurtFlash()
{
if (hurtRedImage == null) yield break;
Color c = hurtRedImage.color;
c.a = 1f;
hurtRedImage.color = c;
// when injured, remain visible a bit longer; if dead, keep it on
if (isDead)
{
yield break;
}
yield return new WaitForSeconds(0.2f);
float dur = 0.4f;
float t = 0f;
while (t < dur)
{
t += Time.deltaTime;
c.a = Mathf.Lerp(1f, 0f, t / dur);
hurtRedImage.color = c;
yield return null;
}
c.a = 0f;
hurtRedImage.color = c;
}
public void ReceiveHeal(float amount, GameObject source)
{
int delta = Mathf.CeilToInt(amount);
ModifyHP(delta, true);
}
public void ApplyBuff(Buff buff, GameObject source)
{
if (buff == null) return;
activeBuffs.Add(buff);
// simple immediate apply: multiply stats
if (buff.attackMultiplier != 1f) { attack = Mathf.RoundToInt(attack * buff.attackMultiplier); }
if (buff.scoreMultiplier != 1f) scoreEfficiency *= buff.scoreMultiplier;
// UI/visual could be added
}
public void RemoveBuff(string buffId)
{
var b = activeBuffs.Find(x => x.buffId == buffId);
if (b != null)
{
activeBuffs.Remove(b);
if (b.scoreMultiplier != 1f) scoreEfficiency /= b.scoreMultiplier;
// cannot easily restore attack without storing original; keep simple
}
}
private void TryCastOnFullMana()
{
if (currentMana >= maxMana && Application.isPlaying)
{
// Ensure SkillBuilder singleton exists before attempting to cast.
if (SkillBuilder.Instance == null)
{
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1} cannot cast right now: SkillBuilder.Instance is null. Will not attempt cast.");
return;
}
int prevMana = currentMana;
// Attempt to cast primary skill configured in SO via SkillBuilder
CastSkill();
// Reset mana after casting
currentMana = 0;
// Update visuals using previous mana as old value so fade works
UpdateManaVisuals(prevMana, true);
}
}
private void CastSkill()
{
// Use SkillBuilder to invoke the primary skill defined in the AllyHero_SO for this slot
if (SkillBuilder.Instance == null)
{
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1} cannot cast: SkillBuilder.Instance is null.");
return;
}
var so = SkillBuilder.Instance.GetAllyHeroSOBySlot(slotIndex);
if (so == null)
{
// Can't locate SO for this slot; attempt best-effort: call UsePrimarySkillForSlot which will log details
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1}: AllyHero_SO not found for slot. Falling back to UsePrimarySkillForSlot.");
SkillBuilder.Instance.UsePrimarySkillForSlot(slotIndex, -1f, null);
return;
}
var def = so.GetPrimarySkill();
if (def == null)
{
Debug.LogWarning($"[AllyCombatant] Slot {slotIndex + 1} cannot cast: primary skill not set in SO.");
return;
}
// Only auto-cast here if the skill's trigger condition matches ManaFull
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnManaFull)
{
// Use the SkillDefinition API to ensure selector/resolution/operateDirectly are respected
SkillBuilder.Instance.UseSkillDefinition(def, slotIndex, -1f, null);
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} cast primary skill '{def.skillId}' due to ManaFull.");
}
else
{
Debug.Log($"[AllyCombatant] Slot {slotIndex + 1} primary skill '{def.skillId}' not set to trigger on ManaFull (trigger={def.triggerCondition}); skipping cast.");
}
}
public void TriggerOnManaFull()
{
if (currentMana >= maxMana)
{
TryCastOnFullMana();
}
}
[ContextMenu("Test Fill Mana")]
private void TestFillMana()
{
SetCurrentMana(maxMana, true);
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d966ccc05a974124baf07b34117841e8
+32
View File
@@ -0,0 +1,32 @@
using System;
using UnityEngine;
[Serializable]
public class Buff
{
// Unique identifier for this buff instance
public string buffId;
// Duration in seconds
public float duration = 0f;
// Optional human-readable description/type
public string description = string.Empty;
// Multipliers / modifiers that systems may use
public float attackMultiplier = 1f; // multiply outgoing attack
public float scoreMultiplier = 1f; // multiply scoring efficiency
public float healReceivedMultiplier = 1f; // multiply heals received
// Flat modifications (if needed)
public int flatHP = 0;
public int flatMana = 0;
// Convenience constructor
public Buff() { buffId = Guid.NewGuid().ToString(); }
public Buff(string id)
{
buffId = id;
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 25fcea503b24fdc4e89adbc8dd2bd409
+37
View File
@@ -0,0 +1,37 @@
using System;
using UnityEngine;
// Shared types used by combat/effect/skill systems
public enum Selector
{
Self,
AllAllies,
AllAlliesExceptSelf,
AdjacentAllies,
CurrentEnemies,
AllEntities
}
public enum EffectType
{
DamageSingleEnemy,
DamageOverTimeEnemy,
HealSingleSelf,
HealOverTimeSelf,
HealGroupSingle,
HealGroupOverTime,
BuffDuration,
DebuffDuration,
IncreaseManaOverTime,
ReduceEnemyHealOverTime,
ScoreMultiplier,
AddScore
}
public interface ICombatant
{
void ReceiveDamage(float amount, GameObject source);
void ReceiveHeal(float amount, GameObject source);
void ApplyBuff(Buff buff, GameObject source);
void RemoveBuff(string buffId);
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b949a850de06357409a6342e542319b2
+453
View File
@@ -0,0 +1,453 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// EffectSystem 提供技能效果的统一分发接口。
/// - 选择器范围(Selector)决定目标集合(自己、全体友军、除自己外友军、相邻友军、场上敌人、所有人)
/// - 效果类型(EffectType)定义了即时/持续的伤害/治疗/增益/减益等。
///
/// 目标实体应实现 ICombatant 接口以便 EffectSystem 能调用统一方法来处理伤害/治疗/增益等;如果没有实现,会在日志中提醒。
///
/// 该脚本为框架:不包含具体技能配置,只提供 ApplyEffect(...) 等方法供其他脚本调用。
/// </summary>
public class EffectSystem : MonoBehaviour
{
public static EffectSystem Instance { get; private set; }
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
/// <summary>
/// ApplyEffect: 对指定选择器范围内目标应用效果。
/// - selector: 目标选择器
/// - effectType: 效果类型(即时/持续等)
/// - amount: 数值(伤害或治疗总量或效果强度)
/// - duration: 持续时间(对持续效果或 Buff/Debuff 有效),即时效果可为 0
/// - source: 发起者(用于 Self/AlliesExceptSelf 判断或记录)
/// - specificTarget: 当 selector 为 CurrentEnemies 且只想指定单体时可传入具体目标
/// - tickInterval: 持续效果的 tick 间隔(秒),默认 1 秒
/// </summary>
public void ApplyEffect(Selector selector, EffectType effectType, float amount, float duration = 0f, GameObject source = null, GameObject specificTarget = null, float tickInterval = 1f)
{
List<GameObject> targets = ResolveTargets(selector, source, specificTarget);
if (targets == null || targets.Count == 0)
{
Debug.LogWarning($"[EffectSystem] No targets resolved for selector={selector}");
return;
}
switch (effectType)
{
case EffectType.DamageSingleEnemy:
// apply instantaneous damage to first applicable enemy in targets
if (targets.Count > 0)
ApplyInstantDamage(targets[0], amount, source);
break;
case EffectType.DamageOverTimeEnemy:
foreach (var t in targets)
StartCoroutine(ApplyDamageOverTimeCoroutine(t, amount, duration, tickInterval, source));
break;
case EffectType.HealSingleSelf:
if (targets.Count > 0)
ApplyInstantHeal(targets[0], amount, source);
break;
case EffectType.HealOverTimeSelf:
foreach (var t in targets)
StartCoroutine(ApplyHealOverTimeCoroutine(t, amount, duration, tickInterval, source));
break;
case EffectType.HealGroupSingle:
foreach (var t in targets)
ApplyInstantHeal(t, amount, source);
break;
case EffectType.HealGroupOverTime:
foreach (var t in targets)
StartCoroutine(ApplyHealOverTimeCoroutine(t, amount, duration, tickInterval, source));
break;
case EffectType.BuffDuration:
// create a simple buff with id+duration; additional fields can be filled by caller when using Buff instances directly
Buff buff = new Buff { buffId = Guid.NewGuid().ToString(), duration = duration };
foreach (var t in targets)
StartCoroutine(ApplyTimedBuffCoroutine(t, buff, source));
break;
case EffectType.DebuffDuration:
Buff debuff = new Buff { buffId = Guid.NewGuid().ToString(), duration = duration };
foreach (var t in targets)
StartCoroutine(ApplyTimedDebuffCoroutine(t, debuff, source));
break;
case EffectType.IncreaseManaOverTime:
foreach (var t in targets)
StartCoroutine(ApplyIncreaseManaOverTimeCoroutine(t, amount, duration, tickInterval, source));
break;
case EffectType.ReduceEnemyHealOverTime:
// create a debuff that marks heal reduction in its description; enforcement depends on ICombatant.ReceiveHeal implementation
Buff healReductionDebuff = new Buff { buffId = Guid.NewGuid().ToString(), duration = duration, description = "ReduceHeal" };
foreach (var t in targets)
StartCoroutine(ApplyTimedDebuffCoroutine(t, healReductionDebuff, source));
break;
case EffectType.ScoreMultiplier:
// create a buff that multiplies scoring efficiency
Buff scoreBuff = new Buff { buffId = Guid.NewGuid().ToString(), duration = duration, scoreMultiplier = amount };
foreach (var t in targets)
StartCoroutine(ApplyTimedBuffCoroutine(t, scoreBuff, source));
break;
case EffectType.AddScore:
// amount interpreted as integer delta to add immediately to ally tracks
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.AddScoreDirect(Mathf.CeilToInt(amount));
}
else
{
// if target does not have AllyCombatant, try to find ICombatant and warn
var ic = t.GetComponent<ICombatant>();
if (ic != null)
{
Debug.LogWarning($"[EffectSystem] AddScore: target {t.name} implements ICombatant but is not AllyCombatant; cannot AddScoreDirect");
}
else
{
Debug.LogWarning($"[EffectSystem] AddScore: target {t.name} does not support AddScore");
}
}
}
break;
default:
Debug.LogWarning($"[EffectSystem] Unhandled EffectType {effectType}");
break;
}
}
// Resolve targets based on selector. Tries several strategies:
// - If selector == Self -> source only
// - AllAllies / AllAlliesExceptSelf -> find GameObjects named ally_01..ally_05, and also fall back to objects with tag "Ally"
// - AdjacentAllies -> use AllyCombatant.slotIndex (from source) and teamUIController helpers
// - CurrentEnemies -> specificTarget if provided else all objects with tag "Enemy"
// - AllEntities -> find all enemies + allies
private List<GameObject> ResolveTargets(Selector selector, GameObject source, GameObject specificTarget)
{
List<GameObject> list = new List<GameObject>();
switch (selector)
{
case Selector.Self:
if (source != null) list.Add(source);
break;
case Selector.AllAllies:
list.AddRange(FindAllAllies());
break;
case Selector.AllAlliesExceptSelf:
list.AddRange(FindAllAllies());
if (source != null)
list.RemoveAll(g => g == null || g == source || g.gameObject == source.gameObject);
list.RemoveAll(g => g == null || g == source);
break;
case Selector.AdjacentAllies:
// find adjacent allies of the source (left/right). Use teamUIController if available.
if (source == null)
{
Debug.LogWarning("[EffectSystem] AdjacentAllies selector requires a non-null source GameObject");
break;
}
// try to use AllyCombatant.slotIndex if present
var allyComp = source.GetComponent<AllyCombatant>();
int slotIndex = -1;
if (allyComp != null)
{
slotIndex = allyComp.slotIndex;
}
else
{
// try to parse name like "ally_01"
var n = source.name;
for (int i = 1; i <= 5; i++)
{
if (n.Contains($"ally_0{i}")) { slotIndex = i - 1; break; }
}
}
if (slotIndex >= 0)
{
var ui = teamUIController.Instance;
if (ui != null)
{
try
{
var adj = ui.GetAdjacentAllyIndices(slotIndex);
foreach (var idx in adj)
{
var go = ui.GetAllyObjectBySlot(idx);
if (go != null) list.Add(go);
else
{
var named = GameObject.Find($"ally_0{idx + 1}");
if (named != null) list.Add(named);
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"[EffectSystem] teamUIController does not expose adjacent helpers: {ex.Message}");
int left = slotIndex - 1;
int right = slotIndex + 1;
if (left >= 0)
{
var g = GameObject.Find($"ally_0{left + 1}"); if (g != null) list.Add(g);
}
if (right <= 4)
{
var g = GameObject.Find($"ally_0{right + 1}"); if (g != null) list.Add(g);
}
}
}
else
{
int left = slotIndex - 1;
int right = slotIndex + 1;
if (left >= 0)
{
var g = GameObject.Find($"ally_0{left + 1}"); if (g != null) list.Add(g);
}
if (right <= 4)
{
var g = GameObject.Find($"ally_0{right + 1}"); if (g != null) list.Add(g);
}
}
}
else
{
Debug.LogWarning("[EffectSystem] Could not determine slot index for source when resolving AdjacentAllies");
}
break;
case Selector.CurrentEnemies:
if (specificTarget != null) list.Add(specificTarget);
else list.AddRange(FindAllEnemies());
break;
case Selector.AllEntities:
list.AddRange(FindAllAllies());
list.AddRange(FindAllEnemies());
break;
}
// remove nulls and duplicates
list.RemoveAll(x => x == null);
var uniq = new List<GameObject>();
foreach (var g in list)
if (!uniq.Contains(g)) uniq.Add(g);
return uniq;
}
private IEnumerable<GameObject> FindAllAllies()
{
List<GameObject> allies = new List<GameObject>();
// try names ally_01..ally_05
for (int i = 1; i <= 5; i++)
{
string name = $"ally_0{i}"; // ally_01..ally_05
var go = GameObject.Find(name);
if (go != null) allies.Add(go);
}
// also include objects tagged "Ally"
try
{
var tagged = GameObject.FindGameObjectsWithTag("Ally");
foreach (var g in tagged)
if (!allies.Contains(g)) allies.Add(g);
}
catch { /* tag might not exist; ignore */ }
return allies;
}
private IEnumerable<GameObject> FindAllEnemies()
{
List<GameObject> enemies = new List<GameObject>();
// try to find object named "thisEnemy" (single) or many by tag
var single = GameObject.Find("thisEnemy");
if (single != null) enemies.Add(single);
try
{
var tagged = GameObject.FindGameObjectsWithTag("Enemy");
foreach (var g in tagged)
if (!enemies.Contains(g)) enemies.Add(g);
}
catch { }
return enemies;
}
#region Instant / OverTime Helpers
private void ApplyInstantDamage(GameObject target, float amount, GameObject source)
{
if (target == null) return;
var comp = target.GetComponent<ICombatant>();
if (comp != null)
{
comp.ReceiveDamage(amount, source);
}
else
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply damage");
}
}
private void ApplyInstantHeal(GameObject target, float amount, GameObject source)
{
if (target == null) return;
var comp = target.GetComponent<ICombatant>();
if (comp != null)
{
comp.ReceiveHeal(amount, source);
}
else
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply heal");
}
}
private IEnumerator ApplyDamageOverTimeCoroutine(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
{
if (target == null) yield break;
var comp = target.GetComponent<ICombatant>();
if (comp == null)
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply DOT");
yield break;
}
if (duration <= 0f)
{
comp.ReceiveDamage(totalAmount, source);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
comp.ReceiveDamage(perTick, source);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
private IEnumerator ApplyHealOverTimeCoroutine(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
{
if (target == null) yield break;
var comp = target.GetComponent<ICombatant>();
if (comp == null)
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply HOT");
yield break;
}
if (duration <= 0f)
{
comp.ReceiveHeal(totalAmount, source);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
comp.ReceiveHeal(perTick, source);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
private IEnumerator ApplyIncreaseManaOverTimeCoroutine(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
{
if (target == null) yield break;
var comp = target.GetComponent<ICombatant>();
// If target implements a method to add mana, call it; otherwise try to find AllyCombatant component
var ally = target.GetComponent<AllyCombatant>();
if (ally == null && comp == null)
{
Debug.LogWarning($"[EffectSystem] Target {target.name} cannot receive mana -> no AllyCombatant or ICombatant found");
yield break;
}
if (duration <= 0f)
{
// immediate add totalAmount to mana if ally present
if (ally != null) ally.ModifyMana(Mathf.CeilToInt(totalAmount), true);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
if (ally != null) ally.ModifyMana(Mathf.CeilToInt(perTick), true);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
private IEnumerator ApplyTimedBuffCoroutine(GameObject target, Buff buff, GameObject source)
{
if (target == null) yield break;
var comp = target.GetComponent<ICombatant>();
if (comp == null)
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply Buff");
yield break;
}
comp.ApplyBuff(buff, source);
yield return new WaitForSeconds(buff.duration);
comp.RemoveBuff(buff.buffId);
}
private IEnumerator ApplyTimedDebuffCoroutine(GameObject target, Buff debuff, GameObject source)
{
// For now debuff is same as buff but may be handled differently by ICombatant implementation
if (target == null) yield break;
var comp = target.GetComponent<ICombatant>();
if (comp == null)
{
Debug.LogWarning($"[EffectSystem] Target {target.name} does not implement ICombatant -> cannot apply Debuff");
yield break;
}
comp.ApplyBuff(debuff, source);
yield return new WaitForSeconds(debuff.duration);
comp.RemoveBuff(debuff.buffId);
}
#endregion
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ac6185c2505b3ec47af065d12a0dd2b6
+127
View File
@@ -0,0 +1,127 @@
using UnityEngine;
using TMPro;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
public int totalScore = 0;
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
public void RecalculateTotal()
{
int sum = 0;
int[] currents = new int[5];
int[] maxes = new int[5];
for (int i = 1; i <= 5; i++)
{
var go = GameObject.Find($"ally_0{i}");
if (go == null) { currents[i - 1] = 0; maxes[i - 1] = 0; continue; }
var ally = go.GetComponent<AllyCombatant>();
if (ally != null)
{
currents[i - 1] = ally.currentScore;
maxes[i - 1] = ally.maxTrackScore;
}
else
{
currents[i - 1] = 0;
maxes[i - 1] = 0;
}
sum += currents[i - 1];
}
totalScore = sum;
// update UI if available
if (teamUIController.Instance != null)
{
var ui = teamUIController.Instance;
// helper lambda to set text on TMP or legacy Text under a parent fallback
void SetScoreText(TextMeshProUGUI assignedTmp, GameObject parent, int cur, int max, string slotName)
{
string value = cur + "/" + max;
if (assignedTmp != null)
{
assignedTmp.text = value;
Debug.Log($"[ScoreManager] Wrote to assigned TMP for {slotName}: '{value}' -> {assignedTmp.gameObject.name}");
return;
}
// try find TMP in parent
if (parent != null)
{
var tmp = parent.GetComponentInChildren<TextMeshProUGUI>(true);
if (tmp != null)
{
tmp.text = value;
Debug.Log($"[ScoreManager] Wrote to TMP in parent {parent.name} for {slotName}: '{value}' -> {tmp.gameObject.name}");
return;
}
var legacy = parent.GetComponentInChildren<Text>(true);
if (legacy != null)
{
legacy.text = value;
Debug.Log($"[ScoreManager] Wrote to legacy Text in parent {parent.name} for {slotName}: '{value}' -> {legacy.gameObject.name}");
return;
}
}
Debug.LogWarning($"[ScoreManager] Could not find UI to write score for {slotName}. assignedTmp is null and parent '{parent?.name}' has no TMP/Text children.");
}
// Update each slot: use the explicit TMP fields if set; otherwise try parent object fields
SetScoreText(ui.teammate01_current_scoreText, ui.objectFather_ally01, currents[0], maxes[0], "teammate01");
SetScoreText(ui.teammate02_current_scoreText, ui.objectFather_ally02, currents[1], maxes[1], "teammate02");
SetScoreText(ui.teammate03_current_scoreText, ui.objectFather_ally03, currents[2], maxes[2], "teammate03");
SetScoreText(ui.teammate04_current_scoreText, ui.objectFather_ally04, currents[3], maxes[3], "teammate04");
SetScoreText(ui.teammate05_current_scoreText, ui.objectFather_ally05, currents[4], maxes[4], "teammate05");
// update total score (try TMP first, then legacy Text)
if (ui.currentTotalScore != null)
{
var go = ui.currentTotalScore.gameObject;
if (go != null)
{
var tmpComp = go.GetComponent<TextMeshProUGUI>();
if (tmpComp != null)
{
tmpComp.text = totalScore.ToString();
Debug.Log($"[ScoreManager] Wrote total to TMP: {totalScore} -> {tmpComp.gameObject.name}");
}
else
{
var legacyComp = go.GetComponent<Text>();
if (legacyComp != null)
{
legacyComp.text = totalScore.ToString();
Debug.Log($"[ScoreManager] Wrote total to legacy Text: {totalScore} -> {legacyComp.gameObject.name}");
}
else
{
ui.currentTotalScore.text = totalScore.ToString(); // fallback
Debug.Log($"[ScoreManager] Wrote total to currentTotalScore field fallback: {totalScore}");
}
}
}
else
{
ui.currentTotalScore.text = totalScore.ToString();
Debug.Log($"[ScoreManager] Wrote total to currentTotalScore fallback (no go): {totalScore}");
}
}
else
{
Debug.LogWarning("[ScoreManager] teamUIController.currentTotalScore is null; cannot display total score.");
}
}
else
{
Debug.LogWarning("[ScoreManager] teamUIController.Instance is null; cannot update UI.");
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1e98dd46b2f96dd42a078225abf0f31a
+938
View File
@@ -0,0 +1,938 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// SkillBuilder: 集中定义可快速调用的技能函数。
/// - 每个技能为一个独立的 public 方法,接受必要参数(施法者 source, 目标 specificTarget 等)
/// - 内部调用 EffectSystem.Instance.ApplyEffect(...),统一使用 Selector 与 EffectType
/// - 提供常用参数(amount, duration, tickInterval)并对参数做简单校验
///
/// 用法示例:
/// SkillBuilder.Instance.ExecuteSkill("Fireball", EffectType.DamageSingleEnemy, 120f, Selector.CurrentEnemies, caster, target);
/// SkillBuilder.Instance.ApplyScoreMultiplier(caster, Selector.AllAllies, 1.5f, 5f); // 5 秒内队伍得分乘 1.5
/// </summary>
public class SkillBuilder : MonoBehaviour
{
public static SkillBuilder Instance { get; private set; }
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
// ----------------------------- 基础参数模板 -----------------------------
public float defaultDamage = 100f;
public float defaultHeal = 80f;
public float defaultDuration = 5f;
public float defaultTickInterval = 1f;
[Header("Shared note-hit auto-skill settings")]
// These defaults remain as a global fallback but the preferred source is per-level fields in AllyHero_SO
[Tooltip("(Fallback) Mana gained on Good judge if SO level value not present")] public int manaGainGood = 1;
[Tooltip("(Fallback) Mana gained on Great judge if SO level value not present")] public int manaGainGreat = 2;
[Tooltip("(Fallback) Mana gained on Perfect judge if SO level value not present")] public int manaGainPerfect = 3;
[Tooltip("(Fallback) Mana gained on Miss judge if SO level value not present")] public int manaGainOnMiss = 5;
[Tooltip("(Fallback) Damage multiplier applied to ally base attack for Good hits if SO level value not present")] public float damageMultiplierGood = 0.5f;
[Tooltip("(Fallback) Damage multiplier applied to ally base attack for Great hits if SO level value not present")] public float damageMultiplierGreat = 0.75f;
[Tooltip("(Fallback) Damage multiplier applied to ally base attack for Perfect hits if SO level value not present")] public float damageMultiplierPerfect = 1f;
[Tooltip("(Fallback) Base HP loss on Miss will be (missHpLossBase * (1 - damageResistance)) if SO level value not present")] public float missHpLossBase = 10f;
// Placeholder: if you want to route damage through EffectSystem later, use this.
public void DealDamageFromAllyToEnemies(int slotIndex, float amount)
{
Debug.Log($"[SkillBuilder] DealDamageFromAllyToEnemies (placeholder): slot={slotIndex} amount={amount}");
// Future: integrate with EffectSystem/Enemy system here. Example (commented):
// var caster = GetAllyObjectBySlot(slotIndex);
// global::EffectSystem.Instance?.ApplyEffect(Selector.CurrentEnemies, EffectType.DamageSingleEnemy, amount, 0f, caster, null);
}
private void ApplySharedOnNoteHit(int trackIndex, string judgeResult)
{
if (trackIndex < 0) return;
var allyGO = GetAllyObjectBySlot(trackIndex);
if (allyGO == null) return;
var ally = allyGO.GetComponent<AllyCombatant>();
var so = GetAllyHeroSOBySlot(trackIndex);
// try to obtain per-level params from SO if present
AllyHero_SO.AllyLevelInfo levelInfo = so != null ? (so.GetEffectiveLevelForCurrentEXP()) : null;
int manaGain = 0;
float damageMult = 0f;
if (levelInfo != null)
{
switch (judgeResult)
{
case "Perfect": manaGain = levelInfo.manaGainPerfect; damageMult = levelInfo.damageMultiplierPerfect; break;
case "Great": manaGain = levelInfo.manaGainGreat; damageMult = levelInfo.damageMultiplierGreat; break;
case "Good": manaGain = levelInfo.manaGainGood; damageMult = levelInfo.damageMultiplierGood; break;
default: manaGain = levelInfo.manaGainOnMiss; break;
}
}
else
{
switch (judgeResult)
{
case "Perfect": manaGain = manaGainPerfect; damageMult = damageMultiplierPerfect; break;
case "Great": manaGain = manaGainGreat; damageMult = damageMultiplierGreat; break;
case "Good": manaGain = manaGainGood; damageMult = damageMultiplierGood; break;
default: manaGain = manaGainOnMiss; break;
}
}
if (ally != null && manaGain != 0)
{
ally.ModifyMana(manaGain, true);
Debug.Log($"[SkillBuilder] Applied mana gain {manaGain} to slot {trackIndex} due to judge {judgeResult}");
}
if (judgeResult == "Miss")
{
if (ally != null)
{
float missBase = (levelInfo != null) ? levelInfo.missHpLossBase : missHpLossBase;
// apply damage reduction by damageResistance: final loss = missBase * (1 - damageResistance)
float loss = missBase * (1f - ally.damageResistance);
ally.ModifyHP(-Mathf.CeilToInt(loss), true);
Debug.Log($"[SkillBuilder] Applied miss HP loss {loss} to slot {trackIndex} (damageResistance={ally.damageResistance})");
}
}
else
{
if (damageMult > 0f)
{
int baseAtk = GetAllyBaseAttack(so);
// damage should scale with (1 - damageResistance) of enemies when applied; here we pass raw amount = baseAtk * damageMult
float dmg = baseAtk * damageMult;
DealDamageFromAllyToEnemies(trackIndex, dmg);
}
}
}
// ----------------------------- 通用技能执行接口 -----------------------------
// 通用执行:技能名, 效果类型, 数值, 目标选择器, 施法者, 可选具体目标, 持续时间和 tick
public void ExecuteSkill(string skillName, EffectType effectType, float amount, Selector selector, GameObject caster, GameObject specificTarget = null, float duration = 0f, float tickInterval = 1f)
{
if (global::EffectSystem.Instance == null)
{
Debug.LogWarning($"ExecuteSkill failed: EffectSystem.Instance is null");
return;
}
// basic logging
Debug.Log($"[SkillBuilder] ExecuteSkill: {skillName} type={effectType} amount={amount} selector={selector} caster={(caster?caster.name:"null")} target={(specificTarget?specificTarget.name:"null")} duration={duration}");
// call EffectSystem using GameObject API (keeps compatibility with existing EffectSystem)
global::EffectSystem.Instance.ApplyEffect(selector, effectType, amount, duration, caster, specificTarget, tickInterval);
}
// New overload: execute by slot index (no GameObject required externally)
public void ExecuteSkillBySlot(string skillName, EffectType effectType, float amount, Selector selector, int casterSlotIndex = -1, int specificTargetSlot = -1, float duration = 0f, float tickInterval = 1f)
{
GameObject casterGO = casterSlotIndex >= 0 ? GetAllyObjectBySlot(casterSlotIndex) : null;
GameObject targetGO = specificTargetSlot >= 0 ? GetAllyObjectBySlot(specificTargetSlot) : null;
ExecuteSkill(skillName, effectType, amount, selector, casterGO, targetGO, duration, tickInterval);
}
// ----------------------------- 目标解析(供本脚本使用,和 EffectSystem 的解析一致) -----------------------------
// 返回符合 selector 的 GameObject 列表,方便技能脚本自行操作(例如直接给 ICombatant.Buff
public List<GameObject> ResolveTargetsLocal(Selector selector, GameObject source = null, GameObject specificTarget = null)
{
List<GameObject> list = new List<GameObject>();
switch (selector)
{
case Selector.Self:
if (source != null) list.Add(source);
break;
case Selector.AllAllies:
for (int i = 1; i <= 5; i++) { var go = GameObject.Find($"ally_0{i}"); if (go != null) list.Add(go); }
try { var tagged = GameObject.FindGameObjectsWithTag("Ally"); foreach (var g in tagged) if (!list.Contains(g)) list.Add(g); } catch { }
break;
case Selector.AllAlliesExceptSelf:
for (int i = 1; i <= 5; i++) { var go = GameObject.Find($"ally_0{i}"); if (go != null) list.Add(go); }
try { var tagged = GameObject.FindGameObjectsWithTag("Ally"); foreach (var g in tagged) if (!list.Contains(g)) list.Add(g); } catch { }
if (source != null) list.RemoveAll(g => g == null || g == source);
break;
case Selector.AdjacentAllies:
if (source == null) break;
int slotIndex = -1;
var ac = source.GetComponent<AllyCombatant>();
if (ac != null) slotIndex = ac.slotIndex;
else
{
var n = source.name;
for (int i = 1; i <= 5; i++) if (n.Contains($"ally_0{i}")) { slotIndex = i - 1; break; }
}
var ui = teamUIController.Instance;
// Additional attempt: if source isn't AllyCombatant and name parse failed, try to match source to UI slots (or child of slot object)
if (slotIndex < 0 && ui != null)
{
for (int i = 0; i < 5; i++)
{
var slotObj = ui.GetAllyObjectBySlot(i);
if (slotObj == null) continue;
if (slotObj == source) { slotIndex = i; break; }
// if source is a child of the slot object
if (source.transform.IsChildOf(slotObj.transform)) { slotIndex = i; break; }
}
}
if (slotIndex >= 0)
{
if (ui != null)
{
var adj = ui.GetAdjacentAllyIndices(slotIndex);
foreach (var idx in adj)
{
var go = ui.GetAllyObjectBySlot(idx);
if (go != null) list.Add(go);
else { var named = GameObject.Find($"ally_0{idx + 1}"); if (named != null) list.Add(named); }
}
}
else
{
int left = slotIndex - 1, right = slotIndex + 1;
if (left >= 0) { var g = GameObject.Find($"ally_0{left + 1}"); if (g != null) list.Add(g); }
if (right <= 4) { var g = GameObject.Find($"ally_0{right + 1}"); if (g != null) list.Add(g); }
}
}
else
{
Debug.LogWarning("[SkillBuilder] ResolveTargetsLocal: could not determine slot index for AdjacentAllies for source=" + (source?source.name:"null"));
}
break;
case Selector.CurrentEnemies:
if (specificTarget != null) list.Add(specificTarget);
else { try { var tagged = GameObject.FindGameObjectsWithTag("Enemy"); foreach (var g in tagged) if (!list.Contains(g)) list.Add(g); } catch { var single = GameObject.Find("thisEnemy"); if (single != null) list.Add(single); } }
break;
case Selector.AllEntities:
for (int i = 1; i <= 5; i++) { var go = GameObject.Find($"ally_0{i}"); if (go != null) list.Add(go); }
try { var tagged = GameObject.FindGameObjectsWithTag("Ally"); foreach (var g in tagged) if (!list.Contains(g)) list.Add(g); } catch { }
try { var taggedE = GameObject.FindGameObjectsWithTag("Enemy"); foreach (var g in taggedE) if (!list.Contains(g)) list.Add(g); } catch { var single = GameObject.Find("thisEnemy"); if (single != null && !list.Contains(single)) list.Add(single); }
break;
}
list.RemoveAll(x => x == null);
var uniq = new List<GameObject>();
foreach (var g in list) if (!uniq.Contains(g)) uniq.Add(g);
return uniq;
}
// ----------------------------- 分数影响 API -----------------------------
// 直接修改全局总分(立即生效)
public void ModifyTotalScore(int delta)
{
if (ScoreManager.Instance == null) { Debug.LogWarning("ModifyTotalScore: ScoreManager.Instance == null"); return; }
// adjust total and attempt to update UI
ScoreManager.Instance.totalScore += delta;
// try to force UI update via ScoreManager.RecalculateTotal() is not appropriate because it recalculates from allies
// so instead update displayed total directly if teamUIController present
if (teamUIController.Instance != null && teamUIController.Instance.currentTotalScore != null)
{
teamUIController.Instance.currentTotalScore.text = ScoreManager.Instance.totalScore.ToString();
}
}
// 修改单个友军的分数(即时)
public void ModifySingleAllyScoreDirect(GameObject allyObject, int delta)
{
if (allyObject == null) return;
var ally = allyObject.GetComponent<AllyCombatant>();
if (ally != null)
{
ally.AddScoreDirect(delta);
}
}
// Slot-based: modify single ally by slot index (no GameObject required)
public void ModifySingleAllyScoreDirectBySlot(int slotIndex, int delta)
{
var go = GetAllyObjectBySlot(slotIndex);
if (go == null) return;
var ally = go.GetComponent<AllyCombatant>();
if (ally != null) ally.AddScoreDirect(delta);
}
// 对一组目标应用一次性的分数奖励/惩罚(按集合应用)
public void ModifyGroupScoreDirect(Selector selector, GameObject caster, int delta)
{
var targets = ResolveTargetsLocal(selector, caster);
foreach (var t in targets)
{
var ally = t.GetComponent<AllyCombatant>();
if (ally != null) ally.AddScoreDirect(delta);
}
}
// 临时提高(或降低)目标的分数倍率,使用 EffectSystem.ScoreMultiplier(推荐)
public void ApplyScoreMultiplier(Selector selector, GameObject caster, float multiplier, float duration)
{
if (multiplier <= 0f) { Debug.LogWarning("ApplyScoreMultiplier: invalid multiplier"); return; }
global::EffectSystem.Instance.ApplyEffect(selector, EffectType.ScoreMultiplier, multiplier, duration, caster, null);
}
// Slot-based helper: apply score multiplier to a single slot
public void ApplyScoreMultiplierToSlot(int slotIndex, float multiplier, float duration)
{
if (multiplier <= 0f) { Debug.LogWarning("ApplyScoreMultiplierToSlot: invalid multiplier"); return; }
var go = GetAllyObjectBySlot(slotIndex);
if (go == null) { Debug.LogWarning($"ApplyScoreMultiplierToSlot: no ally object for slot {slotIndex}"); return; }
global::EffectSystem.Instance.ApplyEffect(Selector.Self, EffectType.ScoreMultiplier, multiplier, duration, go, null);
}
// Slot-based helper: add flat score to a single slot (immediate)
public void AddScoreToSlot(int slotIndex, int delta)
{
var go = GetAllyObjectBySlot(slotIndex);
if (go == null) { Debug.LogWarning($"AddScoreToSlot: no ally object for slot {slotIndex}"); return; }
global::EffectSystem.Instance.ApplyEffect(Selector.Self, EffectType.AddScore, delta, 0f, go, null);
}
private IEnumerator ApplyBuffToTargetsCoroutine(Buff buff, List<GameObject> targets, GameObject source)
{
if (buff == null) yield break;
foreach (var t in targets)
{
if (t == null) continue;
var comp = t.GetComponent<ICombatant>();
if (comp != null) comp.ApplyBuff(buff, source);
else Debug.LogWarning($"ApplyBuffToTargets: target {t.name} has no ICombatant");
}
if (buff.duration > 0f)
yield return new WaitForSeconds(buff.duration);
else
yield break;
foreach (var t in targets)
{
if (t == null) continue;
var comp = t.GetComponent<ICombatant>();
if (comp != null) comp.RemoveBuff(buff.buffId);
}
}
// ----------------------------- 现有便捷技能示例 -----------------------------
public void DealSingleEnemyDamage(GameObject caster, GameObject enemyTarget, float amount)
{
if (enemyTarget == null) { Debug.LogWarning("DealSingleEnemyDamage: enemyTarget == null"); return; }
ExecuteSkill("DealSingleEnemyDamage", EffectType.DamageSingleEnemy, amount, Selector.CurrentEnemies, caster, enemyTarget);
}
public void ApplyDamageOverTimeToEnemies(GameObject caster, Selector selector, float totalAmount, float duration, float tickInterval = 1f, GameObject specificTarget = null)
{
if (duration <= 0f) duration = defaultDuration;
if (tickInterval <= 0f) tickInterval = defaultTickInterval;
ExecuteSkill("DamageOverTime", EffectType.DamageOverTimeEnemy, totalAmount, selector, caster, specificTarget, duration, tickInterval);
}
public void HealSelf(GameObject caster, float amount)
{
ExecuteSkill("HealSelf", EffectType.HealSingleSelf, amount, Selector.Self, caster, caster);
}
public void HealSelfOverTime(GameObject caster, float totalAmount, float duration, float tickInterval = 1f)
{
ExecuteSkill("HealSelfOverTime", EffectType.HealOverTimeSelf, totalAmount, Selector.Self, caster, caster, duration, tickInterval);
}
public void HealGroupSingle(GameObject caster, float amount, bool includeSelf = true)
{
var sel = includeSelf ? Selector.AllAllies : Selector.AllAlliesExceptSelf;
ExecuteSkill("HealGroupSingle", EffectType.HealGroupSingle, amount, sel, caster);
}
public void HealGroupOverTime(GameObject caster, float totalAmount, float duration, float tickInterval = 1f, bool includeSelf = true)
{
var sel = includeSelf ? Selector.AllAllies : Selector.AllAlliesExceptSelf;
ExecuteSkill("HealGroupOverTime", EffectType.HealGroupOverTime, totalAmount, sel, caster, null, duration, tickInterval);
}
public void IncreaseManaOverTime(GameObject caster, Selector selector, float totalAmount, float duration, float tickInterval = 1f, GameObject specificTarget = null)
{
ExecuteSkill("IncreaseManaOverTime", EffectType.IncreaseManaOverTime, totalAmount, selector, caster, specificTarget, duration, tickInterval);
}
public void ReduceEnemyHealOverTime(GameObject caster, Selector selector, float duration, GameObject specificTarget = null)
{
ExecuteSkill("ReduceEnemyHealOverTime", EffectType.ReduceEnemyHealOverTime, 0f, selector, caster, specificTarget, duration);
}
public void HealAdjacentAllies(GameObject caster, float amount)
{
ExecuteSkill("HealAdjacentAllies", EffectType.HealGroupSingle, amount, Selector.AdjacentAllies, caster);
}
public void IncreaseManaAdjacentAllies(GameObject caster, float totalAmount, float duration, float tickInterval = 1f)
{
ExecuteSkill("IncreaseManaAdjacentAllies", EffectType.IncreaseManaOverTime, totalAmount, Selector.AdjacentAllies, caster, null, duration, tickInterval);
}
public void Hero_A_Ultimate(GameObject caster, GameObject enemyTarget)
{
DealSingleEnemyDamage(caster, enemyTarget, defaultDamage * 2f);
HealAdjacentAllies(caster, defaultHeal * 0.5f);
}
public void MassHeal(GameObject caster)
{
HealGroupOverTime(caster, defaultHeal * 5f, 4f, 1f, true);
}
// ----------------------------- 基于 SO 的数值计算 helpers -----------------------------
// 获取某个槽位的 ally GameObject0-based slot index
public GameObject GetAllyObjectBySlot(int slotIndex)
{
var ui = teamUIController.Instance;
if (ui != null) return ui.GetAllyObjectBySlot(slotIndex);
var named = GameObject.Find($"ally_0{slotIndex + 1}");
return named;
}
// 根据槽位解析 AllyHero_SO(运行时从 Resources 中查找 ally_heroID
public AllyHero_SO GetAllyHeroSOBySlot(int slotIndex)
{
if (teamUIController.Instance == null || teamUIController.Instance.allySlotIds == null) return null;
if (slotIndex < 0 || slotIndex >= teamUIController.Instance.allySlotIds.Count) return null;
int id = teamUIController.Instance.allySlotIds[slotIndex];
if (id <= 0) return null;
var all = Resources.LoadAll<AllyHero_SO>("");
foreach (var a in all)
{
if (a != null && a.ally_heroID == id) return a;
}
return null;
}
// 根据当前经验选择生效的等级信息(返回 null 表示未找到)
private AllyHero_SO.AllyLevelInfo GetEffectiveLevelInfo(AllyHero_SO so)
{
if (so == null || so.levelStats == null || so.levelStats.Count == 0) return null;
// choose the highest requiredEXP that is <= ally_currentEXP
AllyHero_SO.AllyLevelInfo best = null;
int currentExp = so.ally_currentEXP;
foreach (var lvl in so.levelStats)
{
if (best == null)
{
if (currentExp >= lvl.requiredEXP) best = lvl;
}
else
{
if (currentExp >= lvl.requiredEXP && lvl.requiredEXP >= best.requiredEXP) best = lvl;
}
}
// if no level matched (all requiredEXP > currentExp), fallback to lowest level (index 0)
if (best == null) return so.levelStats[0];
return best;
}
// 取得英雄基础攻击力(使用当前经验匹配的等级的 attack),若没有则返回 0
public int GetAllyBaseAttack(AllyHero_SO so)
{
if (so == null) return 0;
var eff = GetEffectiveLevelInfo(so);
if (eff != null) return eff.attack;
if (so.levelStats != null && so.levelStats.Count > 0) return so.levelStats[0].attack;
return 0;
}
// 统一计算技能造成数值:当 inputValue == -1 时使用 skillStatic + allyAttack
// 否则直接使用 inputValue(按传入的 EffectType 做必要转换)
public float ComputeSkillValue(GameObject caster, int slotIndex, float inputValue, int skillStatic)
{
if (inputValue != -1f) return inputValue;
var so = GetAllyHeroSOBySlot(slotIndex);
int allyAtk = GetAllyBaseAttack(so);
return skillStatic + allyAtk;
}
// ----------------------------- 槽位技能通用接口 -----------------------------
// 通常技能在被槽位的角色释放时调用此函数:
// slotIndex: 0-based 槽位索引;skillId: 自定义技能标识;effectType,value,selector,duration等参数同前述约定
public void AllySlotSkill(int slotIndex, string skillId, EffectType effectType, float value, Selector selector, float duration = 0f, GameObject specificTarget = null, float tickInterval = 1f)
{
GameObject caster = GetAllyObjectBySlot(slotIndex);
if (caster == null)
{
Debug.LogWarning($"AllySlotSkill: no caster found for slot {slotIndex}");
return;
}
// determine skill-specific static values (示例:skill01 的固定伤害为 50)
int skillStaticDamage = 0;
switch (skillId)
{
case "skill01": skillStaticDamage = 50; break;
case "skill_heal_small": skillStaticDamage = 30; break;
// 在此添加更多技能静态值
default: skillStaticDamage = 0; break;
}
float finalValue = ComputeSkillValue(caster, slotIndex, value, skillStaticDamage);
// 特殊处理:若 effectType 是单次伤害但我们希望对所有敌人使用 CurrentEnemiesselector 参数已由调用方传入
ExecuteSkill($"slot{slotIndex + 1}_{skillId}", effectType, finalValue, selector, caster, specificTarget, duration, tickInterval);
}
// New: invoke a SkillDefinition directly (respects staticValue and SO attack when value == -1)
public void UseSkillDefinition(SkillDefinition def, int slotIndex, float inputValue = -1f, GameObject specificTarget = null)
{
if (def == null) { Debug.LogWarning("UseSkillDefinition: null def"); return; }
GameObject caster = GetAllyObjectBySlot(slotIndex);
if (caster == null)
{
Debug.LogWarning($"UseSkillDefinition: no caster GameObject found for slot {slotIndex}. Proceeding with null caster.");
}
float amount = 0f;
// If caller provided explicit inputValue use it
if (inputValue != -1f)
{
amount = inputValue;
}
else
{
// Build variables from SO and caster; formula can be a plain number too
var so = GetAllyHeroSOBySlot(slotIndex);
var vars = new Dictionary<string, float>();
vars["slot"] = slotIndex;
vars["attack"] = GetAllyBaseAttack(so);
if (so != null && so.levelStats != null && so.levelStats.Count > 0)
{
var eff = GetEffectiveLevelInfo(so);
var lvl = eff ?? so.levelStats[0];
vars["maxHP"] = lvl.maxHP;
vars["maxMana"] = lvl.maxMana;
vars["damageResistance"] = lvl.damageResistance;
vars["scoreEfficiency"] = lvl.scoreEfficiency;
vars["attack"] = lvl.attack; // ensure attack from SO levelStats is available
}
else
{
vars["maxHP"] = 0f;
vars["maxMana"] = 0f;
vars["damageResistance"] = 0f;
vars["scoreEfficiency"] = 1f;
}
if (so != null) vars["ally_currentEXP"] = so.ally_currentEXP;
else vars["ally_currentEXP"] = 0f;
if (string.IsNullOrWhiteSpace(def.formula))
{
Debug.LogWarning($"UseSkillDefinition: skill {def.skillId} has empty formula, defaulting to 0");
amount = 0f;
}
else
{
if (!SkillDefinition.TryEvaluateFormula(def.formula, vars, out float fresult))
{
Debug.LogWarning($"UseSkillDefinition: formula evaluation failed for skill {def.skillId}, defaulting to 0");
amount = 0f;
}
else
{
amount = fresult;
}
}
}
// Convert formula result (interpreted as per-tick when skill is sustained) to total amount
// amount now represents the formula result (per-tick for sustained effects, instant for single-instance)
float amountPerTick = amount;
float amountTotal = amountPerTick;
if (!def.IsSingleInstance)
{
// compute ticks based on def duration and effective tick interval
float tickInterval = def.GetEffectiveTickInterval();
int ticks = Mathf.Max(1, Mathf.CeilToInt(def.defaultDuration / tickInterval));
amountTotal = amountPerTick * ticks;
}
// If this skill is intended to be triggered by note hits, treat it as a one-time application
// (do not multiply by duration ticks). This prevents OnNoteHit triggers from unexpectedly
// applying sustained totals (e.g. 5s * perTick) when the designer expects a single effect.
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnNoteHit)
{
amountTotal = amountPerTick;
}
if (def.operateDirectly)
{
// Resolve targets as GameObjects but operate on their AllyCombatant / ICombatant data directly
List<GameObject> targets = ResolveTargetsLocal(def.defaultSelector, caster, specificTarget);
if (targets == null || targets.Count == 0)
{
Debug.LogWarning($"UseSkillDefinition: no targets for direct operation skill {def.skillId}");
return;
}
foreach (var t in targets)
{
if (t == null) continue;
var ally = t.GetComponent<AllyCombatant>();
var ic = t.GetComponent<ICombatant>();
switch (def.effectType)
{
case EffectType.DamageSingleEnemy:
// treat as damage to an enemy; if target has AllyCombatant, apply negative HP
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(amountTotal), true);
else if (ic != null) ic.ReceiveDamage(amountTotal, caster);
break;
case EffectType.DamageOverTimeEnemy:
// sustained: start coroutine to apply per-tick damage rather than instant total
if (def.defaultDuration <= 0f)
{
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(amountPerTick), true);
else if (ic != null) ic.ReceiveDamage(amountPerTick, caster);
}
else
{
StartCoroutine(ApplyDamageOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
}
break;
case EffectType.HealSingleSelf:
case EffectType.HealGroupSingle:
if (ally != null) ally.ModifyHP(Mathf.CeilToInt(amountTotal), true);
else if (ic != null) ic.ReceiveHeal(amountTotal, caster);
break;
case EffectType.HealOverTimeSelf:
case EffectType.HealGroupOverTime:
if (def.defaultDuration <= 0f)
{
if (ally != null) ally.ModifyHP(Mathf.CeilToInt(amountPerTick), true);
else ic?.ReceiveHeal(amountPerTick, caster);
}
else
{
StartCoroutine(ApplyHealOverTimeDirect(t, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval(), caster));
}
break;
case EffectType.IncreaseManaOverTime:
if (ally != null)
{
if (def.defaultDuration <= 0f)
{
// single instance - amountPerTick equals immediate amount
ally.ModifyMana(Mathf.CeilToInt(amountPerTick), true);
}
else
{
// for sustained, pass total amount (amountTotal) to coroutine which expects total
StartCoroutine(ApplyIncreaseManaOverTimeDirect(ally, amountTotal, def.defaultDuration, def.GetEffectiveTickInterval()));
}
}
break;
case EffectType.ReduceEnemyHealOverTime:
var deb = new Buff { buffId = System.Guid.NewGuid().ToString(), duration = def.defaultDuration, healReceivedMultiplier = 0.5f };
if (ic != null) ic.ApplyBuff(deb, caster);
break;
case EffectType.BuffDuration:
case EffectType.DebuffDuration:
var b = new Buff { buffId = System.Guid.NewGuid().ToString(), duration = def.defaultDuration };
if (def.effectType == EffectType.BuffDuration) b.scoreMultiplier = 1.5f;
if (ic != null) ic.ApplyBuff(b, caster);
break;
default:
// non-direct path handled below, but keep compatibility
ExecuteSkill(def.displayName ?? def.skillId, def.effectType, amountTotal, def.defaultSelector, caster, specificTarget, def.defaultDuration, def.defaultTickInterval);
break;
}
}
}
else
{
// For non-direct path, EffectSystem expects 'amount' to be total amount for sustained effects
ExecuteSkill(def.displayName ?? def.skillId, def.effectType, amountTotal, def.defaultSelector, caster, specificTarget, def.defaultDuration, def.defaultTickInterval);
}
}
// Use the selected skill index from an AllyHero_SO for a given slot (calls UseSkillDefinition)
public void UseSelectedSkillForSlot(int slotIndex, int selectedSkillIndex, float inputValue = -1f, GameObject specificTarget = null)
{
var so = GetAllyHeroSOBySlot(slotIndex);
if (so == null || so.availableSkills == null || selectedSkillIndex < 0 || selectedSkillIndex >= so.availableSkills.Length)
{
Debug.LogWarning($"UseSelectedSkillForSlot: invalid selection for slot {slotIndex}");
return;
}
UseSkillDefinition(so.availableSkills[selectedSkillIndex], slotIndex, inputValue, specificTarget);
}
// Use the primary skill configured in the AllyHero_SO for this slot (primarySkillIndex dropdown)
public void UsePrimarySkillForSlot(int slotIndex, float inputValue = -1f, GameObject specificTarget = null)
{
var so = GetAllyHeroSOBySlot(slotIndex);
if (so == null)
{
Debug.LogWarning($"UsePrimarySkillForSlot: no SO for slot {slotIndex}");
return;
}
int idx = so.primarySkillIndex;
if (idx < 0) { Debug.LogWarning($"UsePrimarySkillForSlot: primarySkillIndex not set for slot {slotIndex}"); return; }
UseSelectedSkillForSlot(slotIndex, idx, inputValue, specificTarget);
}
// Called at game start to trigger any allies whose primary skill is set to trigger on game start
public void TriggerOnGameStart()
{
if (teamUIController.Instance == null || teamUIController.Instance.allySlotIds == null) return;
int slots = teamUIController.Instance.allySlotIds.Count;
for (int i = 0; i < slots; i++)
{
var so = GetAllyHeroSOBySlot(i);
if (so == null) continue;
var def = so.GetPrimarySkill();
if (def == null) continue;
if (def.triggerCondition == SkillDefinition.SkillTrigger.OnGameStart)
{
Debug.Log($"[SkillBuilder] TriggerOnGameStart: casting primary skill {def.skillId} for slot {i}");
UsePrimarySkillForSlot(i, -1f, null);
}
}
}
private Dictionary<string, float> _lastOnNoteHitTriggerTime = new Dictionary<string, float>();
// Called when a note on a particular track is hit (judgeResult e.g. "Perfect"/"Great"/"Good").
// Will cast the primary skill for the ally in that track if its primary skill is configured to trigger on note hit.
// noteType indicates whether the hit event came from a Tap or Hold (tail)
public bool NotifyNoteHit(int trackIndex, string judgeResult, SkillDefinition.NoteTypeTrigger noteType = SkillDefinition.NoteTypeTrigger.Tap)
{
// Always apply shared per-note behavior (mana gain / miss penalty / reserved damage) regardless of SO-defined primary skill
try { ApplySharedOnNoteHit(trackIndex, judgeResult); } catch (System.Exception ex) { Debug.LogError($"[SkillBuilder] ApplySharedOnNoteHit threw: {ex}"); }
Debug.Log($"[SkillBuilder] NotifyNoteHit RECEIVED: track={trackIndex} judge={judgeResult} noteType={noteType}");
if (trackIndex < 0) { Debug.Log("[SkillBuilder] NotifyNoteHit: invalid trackIndex"); return false; }
var so = GetAllyHeroSOBySlot(trackIndex);
if (so == null)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: no SO found for slot {trackIndex}, attempting fallback resolution...");
// Dump current UI slot ids for diagnostics
if (teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null)
{
Debug.Log($"[SkillBuilder] teamUIController.allySlotIds = [{string.Join(",", teamUIController.Instance.allySlotIds)}]");
}
// Try to find ally GameObject named by convention
var foundGO = GameObject.Find($"ally_0{trackIndex + 1}");
if (foundGO != null)
{
Debug.Log($"[SkillBuilder] Found GameObject by name ally_0{trackIndex + 1}: {foundGO.name}");
// try to map to UI slot
var ui = teamUIController.Instance;
if (ui != null && ui.allySlotIds != null)
{
for (int i = 0; i < ui.allySlotIds.Count; i++)
{
var slotObj = ui.GetAllyObjectBySlot(i);
if (slotObj == null) continue;
if (slotObj == foundGO || foundGO.transform.IsChildOf(slotObj.transform))
{
Debug.Log($"[SkillBuilder] Resolved fallback slot {i} for GameObject {foundGO.name}");
trackIndex = i; // override
so = GetAllyHeroSOBySlot(trackIndex);
break;
}
}
}
}
if (so == null)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: still no SO for resolved slot {trackIndex}, aborting NotifyNoteHit");
return false;
}
}
var def = so.GetPrimarySkill();
if (def == null) { Debug.Log($"[SkillBuilder] NotifyNoteHit: no primary skill for slot {trackIndex}"); return false; }
Debug.Log($"[SkillBuilder] NotifyNoteHit: skill={def.skillId} noteTriggerType={def.noteTriggerType} onNoteHitMinThreshold={def.onNoteHitMinThreshold} cooldown={def.onNoteHitCooldown}");
// Treat Hold events as Tap-equivalent by default to improve compatibility: many skills expect Tap/Either
var effectiveNoteType = noteType;
if (noteType == SkillDefinition.NoteTypeTrigger.Hold)
{
// Only convert to Tap-equivalent for checking; keep original in logs
effectiveNoteType = SkillDefinition.NoteTypeTrigger.Tap;
Debug.Log($"[SkillBuilder] NotifyNoteHit: treating incoming Hold event as Tap-equivalent for skill checks (track={trackIndex})");
}
// Respect note type (Tap/Hold/Either)
switch (def.noteTriggerType)
{
case SkillDefinition.NoteTypeTrigger.Tap:
// accept Tap (and treat Hold as Tap-equivalent)
if (effectiveNoteType != SkillDefinition.NoteTypeTrigger.Tap)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: skill {def.skillId} requires Tap but event is {noteType}, rejecting");
return false;
}
break;
case SkillDefinition.NoteTypeTrigger.Hold:
if (noteType != SkillDefinition.NoteTypeTrigger.Hold)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: skill {def.skillId} requires Hold but event is {noteType}, rejecting");
return false;
}
break;
case SkillDefinition.NoteTypeTrigger.Either:
// accept both
break;
}
// Map judgeResult to numeric quality
int quality = JudgeQualityFromString(judgeResult);
Debug.Log($"[SkillBuilder] NotifyNoteHit: judgeResult='{judgeResult}' mappedQuality={quality}");
// Special case: if threshold == Miss, only trigger on Miss (quality == 0)
if (def.onNoteHitMinThreshold == SkillDefinition.NoteTriggerThreshold.Miss)
{
if (quality != 0)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: skill {def.skillId} is Miss-only but quality={quality}, rejecting");
return false;
}
}
else
{
int required = (int)def.onNoteHitMinThreshold; // Good=1, Great=2, Perfect=3
if (quality < required)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: skill {def.skillId} requires quality>={required} but got {quality}, rejecting");
// not high enough
return false;
}
}
// cooldown: per slot+skill
string key = $"{trackIndex}:{def.skillId}";
float now = Time.time;
if (def.onNoteHitCooldown > 0f)
{
if (_lastOnNoteHitTriggerTime.TryGetValue(key, out float last))
{
if (now - last < def.onNoteHitCooldown)
{
Debug.Log($"[SkillBuilder] NotifyNoteHit: skill {def.skillId} on cooldown for slot {trackIndex}");
// still cooling down
return false;
}
}
}
// Passed checks -> invoke primary skill for slot
Debug.Log($"[SkillBuilder] NotifyNoteHit: triggering primary skill {def.skillId} for slot {trackIndex} due to note hit {judgeResult} (noteType={noteType})");
UsePrimarySkillForSlot(trackIndex, -1f, null);
_lastOnNoteHitTriggerTime[key] = now;
return true;
}
private int JudgeQualityFromString(string judge)
{
switch (judge)
{
case "Perfect": return 3;
case "Great": return 2;
case "Good": return 1;
default: return 0; // Miss or unknown
}
}
/*
Explanation of caster and enemyTarget:
- GameObject caster: the GameObject that performs (casts) the skill. For ally skills this should be the in-scene ally GameObject (usually name ally_01..ally_05) and should have AllyCombatant / ICombatant components. SkillBuilder uses slotIndex -> GetAllyObjectBySlot to resolve this.
- GameObject specificTarget / enemyTarget: when a skill targets a specific unit (single enemy or ally), pass that GameObject here. If null, the selected Selector determines the set of targets (e.g. CurrentEnemies without specificTarget will resolve all enemies). Use Enemy GameObjects found by tag 'Enemy' or name 'thisEnemy', or keep a reference to the spawned enemy instance you want.
How to assign/choose:
- Caster: when you call from gameplay code, pass the caster GameObject (or call UseSelectedSkillForSlot with slot index so SkillBuilder resolves caster for you).
- EnemyTarget: if your skill should hit a specific enemy instance, supply that enemy's GameObject (for example from Enemy spawn manager or from collision detection). If you want to apply to all enemies, pass null and use Selector.CurrentEnemies.
*/
// ----------------------------- 示例:为轨道3 封装一个快捷函数(用户示例) -----------------------------
// 轨道3(索引2) 的技能1,遵循用户所需签名:_ally03skill01(EffectType, value, Selector, duration)
public void _ally03skill01(EffectType effectType, float value, Selector selector, float duration = 0f, GameObject specificTarget = null)
{
AllySlotSkill(2, "skill01", effectType, value, selector, duration, specificTarget);
}
// Coroutine helper for IncreaseManaOverTime
private IEnumerator ApplyIncreaseManaOverTimeDirect(AllyCombatant ally, float totalAmount, float duration, float tickInterval)
{
if (ally == null) yield break;
if (duration <= 0f || tickInterval <= 0f)
{
ally.ModifyMana(Mathf.CeilToInt(totalAmount), true);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
ally.ModifyMana(Mathf.CeilToInt(perTick), true);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
// Coroutine helpers for direct DoT/HoT on GameObject targets
private IEnumerator ApplyDamageOverTimeDirect(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
{
if (target == null) yield break;
var ic = target.GetComponent<ICombatant>();
var ally = target.GetComponent<AllyCombatant>();
if (ally == null && ic == null)
{
Debug.LogWarning($"ApplyDamageOverTimeDirect: target {target.name} cannot receive damage");
yield break;
}
if (duration <= 0f || tickInterval <= 0f)
{
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(totalAmount), true);
else ic?.ReceiveDamage(totalAmount, source);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
if (ally != null) ally.ModifyHP(-Mathf.CeilToInt(perTick), true);
else ic?.ReceiveDamage(perTick, source);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
private IEnumerator ApplyHealOverTimeDirect(GameObject target, float totalAmount, float duration, float tickInterval, GameObject source)
{
if (target == null) yield break;
var ic = target.GetComponent<ICombatant>();
var ally = target.GetComponent<AllyCombatant>();
if (ally == null && ic == null)
{
Debug.LogWarning($"ApplyHealOverTimeDirect: target {target.name} cannot receive heal");
yield break;
}
if (duration <= 0f || tickInterval <= 0f)
{
if (ally != null) ally.ModifyHP(Mathf.CeilToInt(totalAmount), true);
else ic?.ReceiveHeal(totalAmount, source);
yield break;
}
int ticks = Mathf.Max(1, Mathf.CeilToInt(duration / tickInterval));
float perTick = totalAmount / ticks;
float elapsed = 0f;
while (elapsed < duration)
{
if (ally != null) ally.ModifyHP(Mathf.CeilToInt(perTick), true);
else ic?.ReceiveHeal(perTick, source);
yield return new WaitForSeconds(tickInterval);
elapsed += tickInterval;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 53ea14de0f25c18469332fda60802db9
+263
View File
@@ -0,0 +1,263 @@
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Globalization;
[CreateAssetMenu(fileName = "NewSkillDefinition", menuName = "Combat/SkillDefinition")]
public class SkillDefinition : ScriptableObject
{
public enum SkillTrigger
{
None,
OnGameStart,
OnNoteHit,
OnManaFull
}
[Header("标识 (Identity)")]
[Tooltip("技能的内部唯一 ID,用于代码中识别此技能")]
public string skillId;
[Tooltip("技能在 UI 中显示的名称")]
public string displayName;
[Tooltip("简短注释/摘要,仅供设计时参考,不影响运行")]
[TextArea(1,2)]
public string summaryInfo;
[Header("效果 (Effect)")]
[Tooltip("技能的效果类型,例如即时伤害、持续伤害、治疗、增益/减益等")]
public EffectType effectType = EffectType.DamageSingleEnemy;
[Tooltip("默认目标选择器:决定技能默认作用的目标范围(自身/所有友军/敌方等)")]
public Selector defaultSelector = Selector.CurrentEnemies;
[Tooltip("当选择 FromFormula 时使用的表达式字符串。支持使用 AllyHero_SO 中同名数值字段作为变量,例如: attack, maxHP, maxMana, damageResistance, scoreEfficiency, ally_currentEXP, slot。示例: 'attack * 1.5 + maxHP * 0.1 + 20'。如果只填写一个数字(例如 '120'),则等同于指定固定数值。")]
[TextArea(2,4)]
public string formula = "";
[Tooltip("默认持续时间(秒),用于持续效果或 Buff 的持续时长。若为 0 则视为一次性/即时技能(不会按 tick 分发)")]
public float defaultDuration = 0f;
[Tooltip("默认打点间隔(秒),用于持续效果的每次触发间隔(例如每秒扣血)。若为 0 则视为一次性/即时技能")]
public float defaultTickInterval = 1f;
[Header("标志 (Flags)")]
[Tooltip("是否必须指定单体目标(true 表示此技能通常需要传入 specificTarget")]
public bool requiresSpecificTarget = false; // if true, skill usually targets a specific GameObject
[Tooltip("是否直接作用于目标的数据(true 表示 SkillBuilder 会直接调用 ICombatant/AllyCombatant,而非通过 EffectSystem 分发)")]
public bool operateDirectly = false; // if true, SkillBuilder will operate on ICombatant/AllyCombatant fields directly and update UI, instead of calling EffectSystem
[Tooltip("技能的描述或备注,便于设计时填写说明")]
[TextArea(8,4)]
public string description;
[Header("触发条件 (Trigger)")]
[Tooltip("技能触发条件:进入游戏时、击打音符时、法力值满时等")]
public SkillTrigger triggerCondition = SkillTrigger.None;
[Tooltip("仅当 triggerCondition == OnNoteHit 时生效:达到该判定或更高时触发(可选 Miss/Good/Great/Perfect)。特别说明:选择 Miss 表示“仅在判定为 Miss 时触发”,其它判定(Good/Great/Perfect)不会触发。)")]
public NoteTriggerThreshold onNoteHitMinThreshold = NoteTriggerThreshold.Good;
[Tooltip("仅当 triggerCondition == OnNoteHit 时生效:指定此技能由 Tap(点击)/Hold(长按尾段)或 Either(任意)触发。若选择 Hold,则只有长音符尾段会触发;若选择 Tap,则仅短按触发;Either 则两者均可(但 Hold 仍可被显式禁用)。")]
public NoteTypeTrigger noteTriggerType = NoteTypeTrigger.Either;
[Tooltip("仅当 triggerCondition == OnNoteHit 时生效:触发后的冷却时间(秒),在冷却时间内再次满足判定不会触发技能")]
public float onNoteHitCooldown = 0f;
// Minimum judge quality required to trigger on note hit
public enum NoteTriggerThreshold
{
// 当设置为 Miss 时,技能仅在判定为 Missquality == 0)时触发
Miss = 0,
Good = 1,
Great = 2,
Perfect = 3
}
// 新增:指定技能应由 Tap/ Hold/ Either 触发
public enum NoteTypeTrigger
{
Tap,
Hold,
Either
}
// 运行时辅助属性:当 defaultDuration 或 defaultTickInterval 为 0 时,视作一次性即时技能
public bool IsSingleInstance => defaultDuration <= 0f || defaultTickInterval <= 0f;
// 返回实际用于分发打点的间隔(若为一次性技能则返回 0)
public float GetEffectiveTickInterval()
{
return IsSingleInstance ? 0f : Mathf.Max(0.0001f, defaultTickInterval);
}
private void OnValidate()
{
// 保证数值为非负
if (defaultDuration < 0f) defaultDuration = 0f;
if (defaultTickInterval < 0f) defaultTickInterval = 0f;
if (onNoteHitCooldown < 0f) onNoteHitCooldown = 0f;
}
// --- Simple expression evaluator used when valueMode == FromFormula ---
// Supports variables (alphanumeric), floats, operators + - * / and parentheses.
public static bool TryEvaluateFormula(string expr, Dictionary<string, float> variables, out float result)
{
result = 0f;
if (string.IsNullOrWhiteSpace(expr)) return false;
try
{
var tokens = Tokenize(expr);
var rpn = ToRPN(tokens);
result = EvalRPN(rpn, variables);
return true;
}
catch (Exception e)
{
Debug.LogWarning($"SkillDefinition: failed to evaluate formula '{expr}': {e.Message}");
return false;
}
}
// tokenization
private enum TokType { Number, Ident, Op, LParen, RParen }
private struct Token { public TokType type; public string text; }
private static List<Token> Tokenize(string s)
{
var outp = new List<Token>();
int i = 0;
while (i < s.Length)
{
char c = s[i];
if (char.IsWhiteSpace(c)) { i++; continue; }
if (c == '(') { outp.Add(new Token { type = TokType.LParen, text = "(" }); i++; continue; }
if (c == ')') { outp.Add(new Token { type = TokType.RParen, text = ")" }); i++; continue; }
if ("+-*/".IndexOf(c) >= 0)
{
outp.Add(new Token { type = TokType.Op, text = c.ToString() }); i++; continue;
}
if (char.IsDigit(c) || c == '.')
{
int start = i;
while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '.')) i++;
outp.Add(new Token { type = TokType.Number, text = s.Substring(start, i - start) });
continue;
}
if (char.IsLetter(c) || c == '_')
{
int start = i;
while (i < s.Length && (char.IsLetterOrDigit(s[i]) || s[i] == '_')) i++;
outp.Add(new Token { type = TokType.Ident, text = s.Substring(start, i - start) });
continue;
}
throw new Exception($"Invalid char in formula: '{c}'");
}
// Handle unary plus/minus: when + or - appears at start or after operator or left paren
var fixedTokens = new List<Token>();
for (int idx = 0; idx < outp.Count; idx++)
{
var t = outp[idx];
if (t.type == TokType.Op && (t.text == "-" || t.text == "+"))
{
bool unary = (idx == 0) || (outp[idx - 1].type == TokType.Op) || (outp[idx - 1].type == TokType.LParen);
if (unary)
{
// insert 0 before unary +/- to convert unary into binary (0 +/- x)
fixedTokens.Add(new Token { type = TokType.Number, text = "0" });
}
}
fixedTokens.Add(t);
}
return fixedTokens;
}
private static int Precedence(string op)
{
if (op == "+" || op == "-") return 1;
if (op == "*" || op == "/") return 2;
return 0;
}
private static List<Token> ToRPN(List<Token> tokens)
{
var output = new List<Token>();
var stack = new Stack<Token>();
foreach (var t in tokens)
{
if (t.type == TokType.Number || t.type == TokType.Ident)
{
output.Add(t);
}
else if (t.type == TokType.Op)
{
while (stack.Count > 0 && stack.Peek().type == TokType.Op && Precedence(stack.Peek().text) >= Precedence(t.text))
{
output.Add(stack.Pop());
}
stack.Push(t);
}
else if (t.type == TokType.LParen)
{
stack.Push(t);
}
else if (t.type == TokType.RParen)
{
while (stack.Count > 0 && stack.Peek().type != TokType.LParen)
{
output.Add(stack.Pop());
}
if (stack.Count == 0) throw new Exception("Mismatched parentheses");
stack.Pop(); // pop LParen
}
}
while (stack.Count > 0)
{
var tk = stack.Pop();
if (tk.type == TokType.LParen || tk.type == TokType.RParen) throw new Exception("Mismatched parentheses");
output.Add(tk);
}
return output;
}
private static float EvalRPN(List<Token> rpn, Dictionary<string, float> vars)
{
var st = new Stack<float>();
foreach (var t in rpn)
{
if (t.type == TokType.Number)
{
if (!float.TryParse(t.text, NumberStyles.Float, CultureInfo.InvariantCulture, out float v)) throw new Exception($"Invalid number '{t.text}'");
st.Push(v);
}
else if (t.type == TokType.Ident)
{
if (vars != null && vars.TryGetValue(t.text, out float v)) st.Push(v);
else
{
// unknown identifiers treat as 0 but warn
Debug.LogWarning($"SkillDefinition: unknown variable '{t.text}' in formula, treated as 0");
st.Push(0f);
}
}
else if (t.type == TokType.Op)
{
if (st.Count < 2) throw new Exception("Insufficient values for operator");
float b = st.Pop(); float a = st.Pop();
switch (t.text)
{
case "+": st.Push(a + b); break;
case "-": st.Push(a - b); break;
case "*": st.Push(a * b); break;
case "/": st.Push(a / b); break;
default: throw new Exception($"Unsupported operator {t.text}");
}
}
else throw new Exception("Invalid token in RPN");
}
if (st.Count != 1) throw new Exception("Invalid expression evaluation");
return st.Pop();
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 265d1552ebf55c34a98934707b2c20ce
+107
View File
@@ -0,0 +1,107 @@
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "NewAllyHero", menuName = "SO_Data/AllyHero")]
public class AllyHero_SO : ScriptableObject
{
[Header("友方英雄基本信息")]
public string ally_heroName;
public int ally_heroID;
[Header("英雄立绘")]
public Sprite ally_heroImage;
[Header("英雄局内头像")]
public Sprite ally_heroProfile;
[Header("英雄轨道icon")]
public Sprite ally_heroIcon;
[Header("等级属性列表")]
public List<AllyLevelInfo> levelStats = new List<AllyLevelInfo>();
[System.Serializable]
public class AllyLevelInfo
{
[Tooltip("等级名称或标识")]
public string levelName;
public int levelID;
[Tooltip("基础攻击力")]
public int attack = 0;
[Tooltip("最大生命值")]
public int maxHP = 100;
[Tooltip("伤害抗性 (0..1), 用于减少所受伤害")]
public float damageResistance = 0f;
[Tooltip("最大法力值")]
public int maxMana = 100;
[Tooltip("得分效率")]
public float scoreEfficiency = 1f;
[Tooltip("升到本等级所需经验")]
public int requiredEXP;
// --- 新增:每等级的共享音符击中参数(用于将来调整) ---
[Header("Note-hit shared parameters")]
[Tooltip("Good 判定时获得的法力值(整数)")]
public int manaGainGood = 1;
[Tooltip("Great 判定时获得的法力值(整数)")]
public int manaGainGreat = 2;
[Tooltip("Perfect 判定时获得的法力值(整数)")]
public int manaGainPerfect = 3;
[Tooltip("Miss 判定时获得的法力值(整数)")]
public int manaGainOnMiss = 5;
[Tooltip("Good 判定时基于英雄攻击力的伤害倍率,例如 0.5 表示造成 50% 的基础攻击力伤害")]
public float damageMultiplierGood = 0.5f;
[Tooltip("Great 判定时基于英雄攻击力的伤害倍率,例如 0.75 表示造成 75% 的基础攻击力伤害")]
public float damageMultiplierGreat = 0.75f;
[Tooltip("Perfect 判定时基于英雄攻击力的伤害倍率,例如 1.0 表示造成 100% 的基础攻击力伤害")]
public float damageMultiplierPerfect = 1f;
[Tooltip("Miss 时造成的基础生命损失基数。最终扣血将按 (missHpLossBase * (1 - damageResistance)) 计算,以考虑抗性")]
public float missHpLossBase = 10f;
}
[Header("当前经验")]
public int ally_currentEXP;
[Header("Skills")]
public SkillDefinition[] availableSkills;
[Tooltip("Indexes into availableSkills for preselected skills; empty means none selected")]
public int[] selectedSkillIndices = new int[0];
[Tooltip("Primary selected skill index into availableSkills (-1 = none)")]
public int primarySkillIndex = -1;
public SkillDefinition GetPrimarySkill()
{
if (availableSkills == null || primarySkillIndex < 0 || primarySkillIndex >= availableSkills.Length) return null;
return availableSkills[primarySkillIndex];
}
// Return the AllyLevelInfo that corresponds to the current ally_currentEXP.
// Picks the highest level whose requiredEXP <= ally_currentEXP. If none match, returns levelStats[0] if present, otherwise null.
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
{
if (levelStats == null || levelStats.Count == 0) return null;
AllyLevelInfo best = null;
int currentExp = ally_currentEXP;
foreach (var lvl in levelStats)
{
if (lvl == null) continue;
if (best == null)
{
if (currentExp >= lvl.requiredEXP) best = lvl;
}
else
{
if (currentExp >= lvl.requiredEXP && lvl.requiredEXP >= best.requiredEXP) best = lvl;
}
}
if (best == null) return levelStats[0];
return best;
}
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bdd198d96872f3b42bdfc7e262ff2eed
+9
View File
@@ -0,0 +1,9 @@
using UnityEngine;
[CreateAssetMenu(fileName = "NewBossData", menuName = "SO_Data/BossData")]
public class BossData_SO : EnemyData_SO
{
[Header("boss特有信息")]
[Tooltip("最大法力值")]
public int enemy_maxMana;
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d5b254d6c08a7d94f99406da53f22341
+24
View File
@@ -0,0 +1,24 @@
using UnityEngine;
[CreateAssetMenu(fileName = "NewEnemyData", menuName = "SO_Data/EnemyData")]
public class EnemyData_SO : ScriptableObject
{
[Header("基本信息")]
[Tooltip("敌人名称")]
public string enemyName;
public Sprite enemy_Image;
public Sprite enemy_Profile;
[Tooltip("敌人ID")]
public int enemyID;
[Header("Stats")]
[Tooltip("最大生命值")]
public int enemy_maxHP;
[Tooltip("敌人伤害抗性")]
public float enemy_damageResistance;
[Tooltip("基础攻击力")]
public int enemy_baseAttack;
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a8cdb6ddbbe019f498476071245f3237
+12
View File
@@ -0,0 +1,12 @@
using UnityEngine;
[CreateAssetMenu(fileName = "PlayerDefaultSO", menuName = "SO_Data/PlayerSO")]
public class Player_SO : ScriptableObject
{
[Header("玩家信息")]
public string player_name;
public int player_ID; // steam ID
public int player_EXP;
}
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 27e379f45b1bb88449756219caee2f89
+2 -1
View File
@@ -8,7 +8,8 @@ public class TeamSetting
{
public TeamSetting(List<CharacterView> teamList, int leaderId)
{
this.teamIdList = teamIdList;
// assign passed-in list to the backing field
this.teamIdList = teamList ?? new List<CharacterView>();
LeaderId = leaderId;
}
+24
View File
@@ -1,5 +1,9 @@
using UnityEngine;
using System;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
/// <summary>
/// Simple pause manager. Call Pause(true) to pause or Pause(false) to resume.
@@ -32,10 +36,30 @@ public class PauseManager : MonoBehaviour
void Update()
{
// Delete = immediate quit (stop play in editor or quit app)
if (Input.GetKeyDown(KeyCode.Delete))
{
#if UNITY_EDITOR
EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
return;
}
if (Input.GetKeyDown(KeyCode.Escape))
{
TogglePause();
}
// 当处于暂停状态且按下 Backspace 时返回主界面
if (IsPaused && Input.GetKeyDown(KeyCode.Backspace))
{
// 恢复时间流并触发事件,然后加载主场景
Pause(false);
// 确保场景名称与项目中的主场景匹配
SceneManager.LoadScene("Main_main");
}
}
/// <summary>
+29 -11
View File
@@ -21,22 +21,39 @@ public class GameManager : MonoBehaviour
[Header("Debug / Test")]
public Text banflagTextUI;
private bool pauseSubscribed = false;
private void SubscribeToPauseManager()
{
if (pauseSubscribed) return;
// prefer the singleton, but try to find in scene if null
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
if (pm != null)
{
pm.OnPauseStateChanged += HandlePauseStateChanged;
pauseSubscribed = true;
}
}
private void UnsubscribeFromPauseManager()
{
if (!pauseSubscribed) return;
var pm = PauseManager.Instance ?? FindObjectOfType<PauseManager>();
if (pm != null)
{
pm.OnPauseStateChanged -= HandlePauseStateChanged;
}
pauseSubscribed = false;
}
private void OnEnable()
{
// 订阅暂停管理器的事件
if (PauseManager.Instance != null)
{
PauseManager.Instance.OnPauseStateChanged += HandlePauseStateChanged;
}
SubscribeToPauseManager();
}
private void OnDisable()
{
// 取消订阅暂停管理器的事件
if (PauseManager.Instance != null)
{
PauseManager.Instance.OnPauseStateChanged -= HandlePauseStateChanged;
}
UnsubscribeFromPauseManager();
}
private void HandlePauseStateChanged(bool isPaused)
@@ -282,7 +299,8 @@ public class GameManager : MonoBehaviour
}
// Pause the system until Space pressed
// Use PauseManager instead of directly setting Time.timeScale
// Ensure we are subscribed before invoking pause so we track audio state
SubscribeToPauseManager();
PauseManager.Instance?.Pause(true);
UpdateStatusOnConsole("Paused: press Space to start playback");
@@ -1,4 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum NoteSegment { None, Start, Middle, End }
@@ -40,6 +41,8 @@ public class HoldNote : BaseNote
[Header("判定区间配置")]
public NoteJudgeConfig judgeConfig; // 判定区间配置,需在预制体或生成时赋值
private bool _hasTriggeredOnThisHold = false; // ensure single trigger per hold note
private void Awake()
{
controller = GetComponent<HoldNoteController>();
@@ -92,6 +95,8 @@ public class HoldNote : BaseNote
{
this.id = id;
this.trackIndex = trackIndex;
// also set BaseNote.TrackIndex so other systems using TrackIndex property work consistently
this.TrackIndex = trackIndex;
this.speed = speed;
this.startTime = time;
this.delay = delay;
@@ -346,6 +351,28 @@ public class HoldNote : BaseNote
JudgeManager.Instance.RegisterStartJudged(noteID, true);
isHoldActive = true; // 成功判定Start,长按状态激活
PlayHitAnimation();
// Trigger skills configured to fire on note hits for this slot when the Start is successfully hit.
if (!_hasTriggeredOnThisHold)
{
try
{
Debug.Log($"[HoldNote] Triggering SkillBuilder on START for slot {trackIndex} with result={result}");
// Prefer Hold type notification so skills expecting Hold can react. If it fails, fallback to Tap.
bool triggered = SkillBuilder.Instance?.NotifyNoteHit(this.trackIndex, result, SkillDefinition.NoteTypeTrigger.Hold) ?? false;
if (!triggered)
{
Debug.Log($"[HoldNote] START Hold notify did not trigger skill for slot {trackIndex}, trying Tap fallback");
triggered = SkillBuilder.Instance?.NotifyNoteHit(this.trackIndex, result, SkillDefinition.NoteTypeTrigger.Tap) ?? false;
}
Debug.Log($"[HoldNote] START skill trigger result for slot {trackIndex}: {triggered}");
}
catch (System.Exception ex)
{
Debug.LogError($"[HoldNote] NotifyNoteHit(Start) threw: {ex}");
}
_hasTriggeredOnThisHold = true; // avoid triggering again at End
}
}
else
{
@@ -353,8 +380,26 @@ public class HoldNote : BaseNote
Debug.Log($"[HoldNote] START Miss(偏差={offset:F2}秒): {noteColor}");
JudgeManager.Instance.RegisterStartJudged(noteID, false); // 明确标记未判定
isHoldActive = false; // Start Miss,长按状态不激活
// For Miss we should still notify shared logic once so it behaves like short notes
try
{
// Treat as Tap-type miss for compatibility
bool triggered = SkillBuilder.Instance?.NotifyNoteHit(this.trackIndex, result, SkillDefinition.NoteTypeTrigger.Tap) ?? false;
Debug.Log($"[HoldNote] START Miss NotifyNoteHit fired for slot {trackIndex}: {triggered}");
}
catch (System.Exception ex)
{
Debug.LogError($"[HoldNote] NotifyNoteHit(Start Miss) threw: {ex}");
}
_hasTriggeredOnThisHold = true; // ensure we don't trigger again at End
}
// show judge result and play sound / update combo like short notes
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
JudgeSoundManager.Instance?.PlayJudgeSound(result);
teamUIController.Instance?.OnJudgeResult(result);
StartCoroutine(DelayedReturn());
}
@@ -442,6 +487,88 @@ public class HoldNote : BaseNote
}
InputManager.Instance?.ShowJudgeResult(trackIndex, result);
teamUIController.Instance?.OnJudgeResult(result); // 新增:更新combo计数
// Notify SkillBuilder so Hold notes can trigger skills configured for Hold
if (!_hasTriggeredOnThisHold)
{
var sb = SkillBuilder.Instance;
if (sb == null)
{
Debug.LogWarning($"[HoldNote] SkillBuilder.Instance is null when trying to notify for track {TrackIndex}");
}
else
{
var so = sb.GetAllyHeroSOBySlot(TrackIndex);
var def = so?.GetPrimarySkill();
Debug.Log($"[HoldNote] About to NotifyNoteHit: TrackIndex={TrackIndex} trackIndexField={trackIndex} so={(so!=null?so.name:"null")} skill={(def!=null?def.skillId:"null")} trigger={(def!=null?def.triggerCondition.ToString():"-")}");
}
bool triggeredHold = false;
bool triggeredTap = false;
// Build candidate slot indices to try: prefer instance field, then BaseNote.TrackIndex, then try to match UI slots
var candidates = new List<int>();
candidates.Add(this.trackIndex);
if (!candidates.Contains(this.TrackIndex)) candidates.Add(this.TrackIndex);
// try to resolve by matching this object to UI slots
var ui = teamUIController.Instance;
if (ui != null)
{
for (int i = 0; i < ui.allySlotIds.Count; i++)
{
var slotObj = ui.GetAllyObjectBySlot(i);
if (slotObj == null) continue;
if (slotObj == this.gameObject || this.gameObject.transform.IsChildOf(slotObj.transform))
{
if (!candidates.Contains(i)) candidates.Add(i);
break;
}
}
}
Debug.Log($"[HoldNote] NotifyNoteHit candidate slots: {string.Join(",", candidates)}");
// Try each candidate: prefer Hold notification. Stop on first successful trigger.
foreach (var slot in candidates)
{
try
{
triggeredHold = SkillBuilder.Instance?.NotifyNoteHit(slot, result, SkillDefinition.NoteTypeTrigger.Hold) ?? false;
}
catch (System.Exception ex)
{
Debug.LogError($"[HoldNote] NotifyNoteHit(Hold) threw for slot {slot}: {ex}");
}
if (triggeredHold)
{
Debug.Log($"[HoldNote] NotifyNoteHit succeeded (Hold) for slot {slot}");
break;
}
}
// If no candidate triggered via Hold, try Tap fallback on same candidate list
if (!triggeredHold)
{
foreach (var slot in candidates)
{
try
{
triggeredTap = SkillBuilder.Instance?.NotifyNoteHit(slot, result, SkillDefinition.NoteTypeTrigger.Tap) ?? false;
}
catch (System.Exception ex)
{
Debug.LogError($"[HoldNote] NotifyNoteHit(Tap) threw for slot {slot}: {ex}");
}
if (triggeredTap)
{
Debug.Log($"[HoldNote] NotifyNoteHit succeeded (Tap fallback) for slot {slot}");
break;
}
}
}
_hasTriggeredOnThisHold = true;
Debug.Log($"[HoldNote] Hold end NotifyNoteHit called: TrackIndex={TrackIndex} trackIndexField={trackIndex} result={result} triggeredHold={triggeredHold} triggeredTap={triggeredTap}");
}
Debug.Log($"[HoldNote] END判定结果: {noteColor} {result} (release={releaseTime:F2}, target={scheduledEndTime:F2})");
ReturnToPool();
}
@@ -507,6 +634,7 @@ public class HoldNote : BaseNote
isJudged = false;
isHoldActive = false; // 重置长按标记
hasBeenHeldFromStart = false; // 重置
_hasTriggeredOnThisHold = false; // 重置
if (autoReturnCoroutine != null)
{
@@ -0,0 +1,123 @@
using UnityEngine;
/// <summary>
/// Centralized judge sound player. Assign four AudioSources (one per judgement) in the Inspector.
/// Designed to be low-latency: each judgement has a dedicated AudioSource and PlayOneShot is used.
/// Place this component on a persistent gameobject in the scene (or your audio container).
/// </summary>
public class JudgeSoundManager : MonoBehaviour
{
public static JudgeSoundManager Instance { get; private set; }
[Header("AudioSources (assign dedicated AudioSource for each judgement)")]
public AudioSource perfectSource;
public AudioSource greatSource;
public AudioSource goodSource;
public AudioSource missSource;
[Header("Enable/Disable per-judgement playback")]
[Tooltip("Toggle whether Perfect sounds are played")]
public bool enablePerfect = true;
[Tooltip("Toggle whether Great sounds are played")]
public bool enableGreat = true;
[Tooltip("Toggle whether Good sounds are played")]
public bool enableGood = true;
[Tooltip("Toggle whether Miss sounds are played")]
public bool enableMiss = true;
[Header("Fallback clips (optional, used if the assigned AudioSource has no clip)")]
public AudioClip perfectClip;
public AudioClip greatClip;
public AudioClip goodClip;
public AudioClip missClip;
private void Awake()
{
if (Instance == null)
{
Instance = this;
// Do not automatically destroy; leave lifecycle to scene management
}
else
{
Destroy(gameObject);
return;
}
// Ensure AudioSources are configured for low latency
ConfigureSource(perfectSource);
ConfigureSource(greatSource);
ConfigureSource(goodSource);
ConfigureSource(missSource);
}
private void ConfigureSource(AudioSource src)
{
if (src == null) return;
src.playOnAwake = false;
// keep 3D off for UI/flat sounds
src.spatialBlend = 0f;
// ensure not looping
src.loop = false;
}
public enum JudgeResult { Perfect, Great, Good, Miss }
public void PlayJudgeSound(string result)
{
if (string.IsNullOrEmpty(result)) return;
switch (result)
{
case "Perfect": PlayJudgeSound(JudgeResult.Perfect); break;
case "Great": PlayJudgeSound(JudgeResult.Great); break;
case "Good": PlayJudgeSound(JudgeResult.Good); break;
case "Miss": PlayJudgeSound(JudgeResult.Miss); break;
default: break;
}
}
public void PlayJudgeSound(JudgeResult r)
{
// honor inspector toggles to allow disabling specific judgement sounds
switch (r)
{
case JudgeResult.Perfect:
if (!enablePerfect) return;
PlayOnSource(perfectSource, perfectClip);
break;
case JudgeResult.Great:
if (!enableGreat) return;
PlayOnSource(greatSource, greatClip);
break;
case JudgeResult.Good:
if (!enableGood) return;
PlayOnSource(goodSource, goodClip);
break;
case JudgeResult.Miss:
if (!enableMiss) return;
PlayOnSource(missSource, missClip);
break;
}
}
private void PlayOnSource(AudioSource src, AudioClip fallbackClip)
{
if (src != null)
{
// Prefer PlayOneShot so sources can overlap and we don't need to manage clip state
if (src.clip != null)
{
src.PlayOneShot(src.clip);
}
else if (fallbackClip != null)
{
src.PlayOneShot(fallbackClip);
}
}
else if (fallbackClip != null)
{
// no dedicated source provided: play via a temporary one-shot using AudioSource.PlayClipAtPoint at camera position
AudioSource.PlayClipAtPoint(fallbackClip, Camera.main != null ? Camera.main.transform.position : Vector3.zero);
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a7692a74982af114ca6d79307fc43676
+13 -12
View File
@@ -76,22 +76,18 @@ public class Note : BaseNote
{
Debug.Log($"{keyToPress}: Miss");
judgeResult = "Miss";
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
JudgeMiss();
return;
// Do not return here - let shared post-judge logic run so NotifyNoteHit is invoked for Miss as well
}
else
{
Debug.Log($"{keyToPress}: Miss");
judgeResult = "Miss";
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
JudgeMiss();
return;
// Do not return here - let shared post-judge logic run so NotifyNoteHit is invoked for Miss as well
}
}
else
{
// 兼容旧逻辑
// fallback timing
if (timeDifference <= 0.08f)
{
Debug.Log($"{keyToPress}: Perfect");
@@ -106,17 +102,18 @@ public class Note : BaseNote
{
Debug.Log($"{keyToPress}: Miss");
judgeResult = "Miss";
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
JudgeMiss();
return;
// Do not return here - let shared post-judge logic run so NotifyNoteHit is invoked for Miss as well
}
}
// 显示判定文本
// 显示判定信息
if (!string.IsNullOrEmpty(judgeResult))
{
InputManager.Instance?.ShowJudgeResult(TrackIndex, judgeResult);
teamUIController.Instance?.OnJudgeResult(judgeResult); // 新增:更新combo计数
JudgeSoundManager.Instance?.PlayJudgeSound(judgeResult);
teamUIController.Instance?.OnJudgeResult(judgeResult); // 更新combo计数
// Notify SkillBuilder about this note hit so OnNoteHit skills may trigger (tap notes)
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, judgeResult, SkillDefinition.NoteTypeTrigger.Tap); } catch { }
}
Judge();
}
@@ -152,7 +149,10 @@ public class Note : BaseNote
Debug.Log($"{keyToPress} Miss");
// Ensure UI shows Miss and combo is updated when a note auto-misses
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
teamUIController.Instance?.OnJudgeResult("Miss");
// Notify SkillBuilder about Miss so OnNoteHit skills configured for Miss can trigger
try { SkillBuilder.Instance?.NotifyNoteHit(TrackIndex, "Miss", SkillDefinition.NoteTypeTrigger.Tap); } catch { }
// InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss"); // 已在HandlePress中调用 for manual presses
ReturnToPool();
}
@@ -188,6 +188,7 @@ public class Note : BaseNote
{
// Show Miss and update combo
InputManager.Instance?.ShowJudgeResult(TrackIndex, "Miss");
JudgeSoundManager.Instance?.PlayJudgeSound("Miss");
teamUIController.Instance?.OnJudgeResult("Miss");
JudgeMiss();
}
@@ -7,12 +7,12 @@ using UnityEngine;
public class NoteJudgeConfig : ScriptableObject
{
[Header("判定区间(单位:秒)")]
[Tooltip("Perfect 判定最大时间偏差")]
[Tooltip("Perfect")]
public float perfectRange = 0.05f;
[Tooltip("Great 判定最大时间偏差")]
[Tooltip("Great")]
public float greatRange = 0.1f;
[Tooltip("Good 判定最大时间偏差")]
[Tooltip("Good")]
public float goodRange = 0.2f;
[Tooltip("Miss 判定最大时间偏差")]
[Tooltip("Miss")]
public float missRange = 0.3f;
}
@@ -18,6 +18,10 @@ public class NoteSpawner : MonoBehaviour
public float spawnOffset = 0f; // 生成音符时的时间偏移量
public float bpm;
[Header("Global timing adjustments")]
[Tooltip("Global additional realtime offset (seconds) added to hit times for all notes. Use to test input latency or adjust judgement timing. Default 0. Can be negative to make notes arrive earlier.")]
public float globalHitDelay = 0f;
public NoteJudgeConfig judgeConfig; // 判定区间配置,需在 Inspector 赋值
private Beatmap beatmap;
@@ -108,8 +112,9 @@ public class NoteSpawner : MonoBehaviour
if (noteScript != null)
{
float noteSpawnTime = Time.time;
// pass realtime hit time to Note.Setup
float realtimeHit = startTime + noteData.time;
// pass realtime hit time to Note.Setup, include globalHitDelay
float rawHit = startTime + noteData.time + globalHitDelay;
float realtimeHit = Mathf.Max(0f, rawHit); // clamp to non-negative
noteScript.Setup(key, noteData.trackIndex, CalculateSpeed(), realtimeHit, noteData.color, judgeConfig);
if (GameConfig.verboseLogs) Debug.Log($"到达时间:{noteSpawnTime + (60f / bpm) * 4}");
}
@@ -152,7 +157,8 @@ public class NoteSpawner : MonoBehaviour
Transform spawnPoint = spawnPoints[noteData.trackIndex];
// scheduledEndTime 使用谱面时间(note.time + length),但转换为实时
float scheduledEndTime = startTime + (noteData.time + noteData.length);
float rawScheduledEnd = startTime + (noteData.time + noteData.length) + globalHitDelay;
float scheduledEndTime = Mathf.Max(0f, rawScheduledEnd); // clamp to non-negative
// 生成唯一 id(改为自增计数器,避免随机冲突)
int holdNoteId = ++holdNoteIdCounter;
@@ -170,8 +176,10 @@ public class NoteSpawner : MonoBehaviour
HoldNote holdNote = startObj.GetComponent<HoldNote>();
if (holdNote != null)
{
// pass realtime hit time (startTime + note.time) and delay 0
holdNote.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), startTime + noteData.time, 0f, false, scheduledEndTime, noteData.color, key, "start", judgeConfig);
// pass realtime hit time (startTime + note.time) and delay 0, include globalHitDelay
float rawStartHit = startTime + noteData.time + globalHitDelay;
float startHit = Mathf.Max(0f, rawStartHit);
holdNote.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), startHit, 0f, false, scheduledEndTime, noteData.color, key, "start", judgeConfig);
}
else
{
@@ -198,7 +206,9 @@ public class NoteSpawner : MonoBehaviour
if (holdSeg != null)
{
// 中段都标记为 middlepass realtime base time and segmentDelay
holdSeg.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), startTime + noteData.time, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", judgeConfig);
float rawBase = startTime + noteData.time + globalHitDelay;
float baseHit = Mathf.Max(0f, rawBase);
holdSeg.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), baseHit, segmentDelay, false, scheduledEndTime, noteData.color, key, "middle", judgeConfig);
}
else
{
@@ -226,7 +236,9 @@ public class NoteSpawner : MonoBehaviour
{
// 将 delay 设置为整个 hold 长度 (或 segmentCount * actualSegmentInterval),确保尾部在最后中段之后
float endDelay = segmentCount * actualSegmentInterval; // 通常等于 noteData.length
holdEnd.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), startTime + noteData.time, endDelay, true, scheduledEndTime, noteData.color, key, "end", judgeConfig);
float rawBase = startTime + noteData.time + globalHitDelay;
float baseHit = Mathf.Max(0f, rawBase);
holdEnd.Setup(holdNoteId, noteData.trackIndex, CalculateSpeed(), baseHit, endDelay, true, scheduledEndTime, noteData.color, key, "end", judgeConfig);
}
else
{
@@ -1,6 +1,10 @@
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class teamUIController : MonoBehaviour
{
@@ -9,6 +13,480 @@ public class teamUIController : MonoBehaviour
private set;
}
// --- 新增:队伍槽 ID 列表(从上到下 5 个友军) ---
[Header("Runtime ally configuration")]
[Tooltip("IDs for the 5 ally slots (top to bottom). These are used by PopulateAllySOsFromIds to resolve SOs into currentAllySOs.")]
public List<int> allySlotIds = new List<int> { 0, 0, 0, 0, 0 };
// --- 新增:解析后的当前五个 SO 引用(按顺序) ---
private TeamCharacterDataInfo[] currentAllySOs = new TeamCharacterDataInfo[5];
// previous active flags for detecting external changes
private bool[] prevAllyActive = new bool[5];
// 两个路径字段:一个用于编辑器查找(可以填写 Assets/Resources/so/ally 或磁盘绝对路径),
// 一个用于运行时代码查找(Resources 相对路径,例如 so/ally
[Header("SO folder paths")]
[Tooltip("Editor-only: project path or absolute disk path to the folder that contains SOs. Example: Assets/Resources/so/ally")]
public string editorSOFolderPath = "Assets/Resources/so/ally";
[Tooltip("Runtime: Resources subfolder path (no 'Resources/' prefix). Example: so/ally")]
public string runtimeResourcesFolderPath = "so/ally";
// Optional per-ally object to toggle together with isAllyX_active. If null falls back to objectFather_allyXX.
[Header("Ally runtime objects (optional)")]
public GameObject ally01_object;
public GameObject ally02_object;
public GameObject ally03_object;
public GameObject ally04_object;
public GameObject ally05_object;
// 兼容旧字段(可选)
[Header("Editor folder load (Editor only)")]
[Tooltip("Deprecated: kept for compatibility. Use editorSOFolderPath or runtimeResourcesFolderPath instead.")]
public string selectedProjectFolderPath = string.Empty;
/// <summary>
/// 在运行/编辑器中调用:根据 allySlotIds(5 个 int)在指定文件夹中搜索对应的 TeamCharacterDataInfo 资产。
/// Editor: 使用 editorSOFolderPath / selectedProjectFolderPathRuntime: 使用 runtimeResourcesFolderPath / selectedProjectFolderPath。
/// </summary>
public void PopulateAllySOsFromIds()
{
if (allySlotIds == null || allySlotIds.Count < 5)
{
Debug.LogWarning("allySlotIds 未正确设置,自动填充为 5 个占位 ID");
allySlotIds = new List<int> { 0, 0, 0, 0, 0 };
}
#if UNITY_EDITOR
// Choose editor folder path preferentially from editorSOFolderPath, fallback to selectedProjectFolderPath
string folderToUse = !string.IsNullOrEmpty(editorSOFolderPath) ? editorSOFolderPath : selectedProjectFolderPath;
// Ensure folderToUse points inside the project Assets folder when possible
string projectRelative = null;
if (!string.IsNullOrEmpty(folderToUse))
{
string projectPath = folderToUse.Replace("\\", "/");
if (projectPath.StartsWith(Application.dataPath))
{
projectRelative = "Assets" + projectPath.Substring(Application.dataPath.Length);
}
else if (projectPath.StartsWith("Assets/"))
{
projectRelative = projectPath;
}
else
{
Debug.LogWarning("editorSOFolderPath 必须位于项目的 Assets 文件夹下以便进行查询(建议填写 Assets/Resources/so/ally)。回退到全局列表查找。路径也可填写为磁盘绝对路径。");
}
}
// Collect TeamCharacterDataInfo and AllyHero_SO assets under the folder (or project-wide)
List<Object> discovered = new List<Object>();
if (!string.IsNullOrEmpty(projectRelative))
{
string[] guids = AssetDatabase.FindAssets("t:TeamCharacterDataInfo", new[] { projectRelative });
foreach (var g in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(g);
var so = AssetDatabase.LoadAssetAtPath<TeamCharacterDataInfo>(assetPath);
if (so != null) discovered.Add(so);
}
string[] guids2 = AssetDatabase.FindAssets("t:AllyHero_SO", new[] { projectRelative });
foreach (var g in guids2)
{
string assetPath = AssetDatabase.GUIDToAssetPath(g);
var so = AssetDatabase.LoadAssetAtPath<AllyHero_SO>(assetPath);
if (so != null) discovered.Add(so);
}
}
else
{
// no valid folder chosen -> search entire project
string[] guids = AssetDatabase.FindAssets("t:TeamCharacterDataInfo");
foreach (var g in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(g);
var so = AssetDatabase.LoadAssetAtPath<TeamCharacterDataInfo>(assetPath);
if (so != null) discovered.Add(so);
}
string[] guids2 = AssetDatabase.FindAssets("t:AllyHero_SO");
foreach (var g in guids2)
{
string assetPath = AssetDatabase.GUIDToAssetPath(g);
var so = AssetDatabase.LoadAssetAtPath<AllyHero_SO>(assetPath);
if (so != null) discovered.Add(so);
}
}
TeamCharacterList globalList = null;
if (teamSettingPanel.Instance != null)
globalList = teamSettingPanel.Instance.teamCharacterList;
for (int i = 0; i < 5; i++)
{
int id = allySlotIds[i];
TeamCharacterDataInfo found = null;
// 如果 slot ID 为 0 -> 关闭对应 ally UI 并跳过
if (id == 0)
{
currentAllySOs[i] = null;
switch (i)
{
case 0: isAlly01_active = false; break;
case 1: isAlly02_active = false; break;
case 2: isAlly03_active = false; break;
case 3: isAlly04_active = false; break;
case 4: isAlly05_active = false; break;
}
ToggleAllyObject(i, false);
Debug.Log($"[teamUIController] slot {i+1} id=0 -> disabled");
continue;
}
// 只要 asset 的路径或名称包含 id 即可引用(Editor)
for (int j = 0; j < discovered.Count; j++)
{
var obj = discovered[j];
if (obj == null) continue;
string path = AssetDatabase.GetAssetPath(obj);
if (!string.IsNullOrEmpty(path) && path.Contains(id.ToString()))
{
// if it's already TeamCharacterDataInfo, use it
if (obj is TeamCharacterDataInfo tc)
{
found = tc;
Debug.Log($"[teamUIController] (Editor) matched asset by path: {path} for id={id} slot={i+1}");
break;
}
// if it's AllyHero_SO, map to a runtime TeamCharacterDataInfo instance
if (obj is AllyHero_SO ah)
{
var mapped = ScriptableObject.CreateInstance<TeamCharacterDataInfo>();
mapped.CharacterID = ah.ally_heroID;
mapped.CharacterName = ah.ally_heroName;
mapped.CharacterCardSprite = ah.ally_heroImage;
mapped.CharacterTeamSprite = ah.ally_heroProfile;
mapped.name = ah.name;
found = mapped;
Debug.Log($"[teamUIController] (Editor) matched AllyHero_SO by path: {path} for id={id} slot={i+1}");
break;
}
}
}
// 回退到全局 teamCharacterList
if (found == null && globalList != null && globalList.characters != null)
{
for (int j = 0; j < globalList.characters.Count; j++)
{
var so = globalList.characters[j];
if (so == null) continue;
// globalList 中也以 name 包含 id 为准
if (!string.IsNullOrEmpty(so.name) && so.name.Contains(id.ToString()))
{
found = so;
break;
}
}
}
currentAllySOs[i] = found;
if (found == null)
{
Debug.LogWarning($"[teamUIController] 未找到 id={id} 对应的 SO (slot {i + 1})");
// disable UI slot when not found
switch (i)
{
case 0: isAlly01_active = false; break;
case 1: isAlly02_active = false; break;
case 2: isAlly03_active = false; break;
case 3: isAlly04_active = false; break;
case 4: isAlly05_active = false; break;
}
ToggleAllyObject(i, false);
}
else
{
Debug.Log($"[teamUIController] (Editor) slot {i + 1} resolved to SO: {found.name} (id={found.CharacterID})");
// 确保对应 UI slot 被开启
switch (i)
{
case 0: isAlly01_active = true; break;
case 1: isAlly02_active = true; break;
case 2: isAlly03_active = true; break;
case 3: isAlly04_active = true; break;
case 4: isAlly05_active = true; break;
}
ToggleAllyObject(i, true);
DebugCharacterSO(found, i+1, true);
// Also attempt to set the teammate character image from an underlying AllyHero_SO if available
try
{
// first try to find AllyHero_SO by the same id
var ahList = Resources.LoadAll<AllyHero_SO>("");
AllyHero_SO matched = null;
foreach (var a in ahList) if (a != null && a.ally_heroID == id) { matched = a; break; }
if (matched != null)
{
var sprite = matched.ally_heroProfile != null ? matched.ally_heroProfile : matched.ally_heroImage;
SetTeammateCharacterImage(i, sprite);
}
else
{
// fallback: if current TeamCharacterDataInfo has a TeamSprite, use that
var tc = found as TeamCharacterDataInfo;
if (tc != null && tc.CharacterTeamSprite != null)
SetTeammateCharacterImage(i, tc.CharacterTeamSprite);
}
}
catch { }
}
}
#else
// Runtime: prefer runtimeResourcesFolderPath, fallback to selectedProjectFolderPath
string resourcesPath = ResolveResourcesRelativePath(!string.IsNullOrEmpty(runtimeResourcesFolderPath) ? runtimeResourcesFolderPath : selectedProjectFolderPath);
List<Object> runtimeDiscovered = new List<Object>();
if (!string.IsNullOrEmpty(resourcesPath))
{
resourcesPath = resourcesPath.Trim('/');
try
{
var arr = Resources.LoadAll(resourcesPath);
if (arr != null && arr.Length > 0)
runtimeDiscovered.AddRange(arr);
// If nothing loaded from the specific folder, fallback to loading all and filter by name later
if (runtimeDiscovered.Count == 0)
{
var arrAll = Resources.LoadAll("");
if (arrAll != null && arrAll.Length > 0)
{
runtimeDiscovered.AddRange(arrAll);
Debug.LogWarning($"[teamUIController] (Runtime) Resources.LoadAll returned 0 in '{resourcesPath}', fell back to loading all ScriptableObjects ({arrAll.Length})");
}
}
Debug.Log($"[teamUIcontroller] (Runtime) Loaded {runtimeDiscovered.Count} objects from Resources/{resourcesPath}");
}
catch (System.Exception ex)
{
Debug.LogWarning($"[teamUIController] (Runtime) Resources.LoadAll failed for '{resourcesPath}': {ex.Message}");
var arrAll = Resources.LoadAll("");
if (arrAll != null && arrAll.Length > 0)
runtimeDiscovered.AddRange(arrAll);
}
}
else
{
var arr = Resources.LoadAll("");
if (arr != null && arr.Length > 0)
runtimeDiscovered.AddRange(arr);
Debug.Log($"[teamUIController] (Runtime) Loaded {arr?.Length ?? 0} objects from Resources (project-wide)");
}
TeamCharacterList globalList = null;
if (teamSettingPanel.Instance != null)
globalList = teamSettingPanel.Instance.teamCharacterList;
for (int i = 0; i < 5; i++)
{
int id = allySlotIds[i];
TeamCharacterDataInfo found = null;
// 如果 slot ID 为 0 -> 关闭对应 ally UI 并跳过
if (id == 0)
{
currentAllySOs[i] = null;
switch (i)
{
case 0: isAlly01_active = false; break;
case 1: isAlly02_active = false; break;
case 2: isAlly03_active = false; break;
case 3: isAlly04_active = false; break;
case 4: isAlly05_active = false; break;
}
// also toggle associated object
ToggleAllyObject(i, false);
Debug.Log($"[teamUIController] (Runtime) slot {i+1} id=0 -> disabled");
continue;
}
// 只要资源名包含 id 即可引用(Runtime)。支持 TeamCharacterDataInfo 与 AllyHero_SO
string runtimeSourceInfo = string.Empty;
for (int j = 0; j < runtimeDiscovered.Count; j++)
{
var obj = runtimeDiscovered[j];
if (obj == null) continue;
// name match first
if (!string.IsNullOrEmpty(obj.name) && obj.name.Contains(id.ToString()))
{
if (obj is TeamCharacterDataInfo tc)
{
found = tc; break;
}
if (obj is AllyHero_SO ah)
{
var mapped = ScriptableObject.CreateInstance<TeamCharacterDataInfo>();
mapped.CharacterID = ah.ally_heroID;
mapped.CharacterName = ah.ally_heroName;
mapped.CharacterCardSprite = ah.ally_heroImage;
mapped.CharacterTeamSprite = ah.ally_heroProfile;
found = mapped; break;
}
}
// fallback: if object is TeamCharacterDataInfo, check its CharacterID
if (obj is TeamCharacterDataInfo tco && tco.CharacterID == id)
{
found = tco; break;
}
// fallback: AllyHero_SO CharacterID equivalent
if (obj is AllyHero_SO ah2 && ah2.ally_heroID == id)
{
var mapped2 = ScriptableObject.CreateInstance<TeamCharacterDataInfo>();
mapped2.CharacterID = ah2.ally_heroID;
mapped2.CharacterName = ah2.ally_heroName;
mapped2.CharacterCardSprite = ah2.ally_heroImage;
mapped2.CharacterTeamSprite = ah2.ally_heroProfile;
found = mapped2; break;
}
}
// 回退到全局 teamCharacterList
if (found == null && globalList != null && globalList.characters != null)
{
for (int j = 0; j < globalList.characters.Count; j++)
{
var so = globalList.characters[j];
if (so == null) continue;
if (!string.IsNullOrEmpty(so.name) && so.name.Contains(id.ToString()))
{
found = so;
break;
}
if (so.CharacterID == id)
{
found = so;
break;
}
}
}
// final fallback: try loading by resource path/id if resourcesPath provided
if (found == null && !string.IsNullOrEmpty(resourcesPath))
{
try
{
var byId = Resources.Load<TeamCharacterDataInfo>($"{resourcesPath}/{id}");
if (byId != null) found = byId;
if (found != null) runtimeSourceInfo = $"Resources.Load: {resourcesPath}/{id}";
}
catch { }
}
currentAllySOs[i] = found;
if (found == null)
{
Debug.LogWarning($"[teamUIController] (Runtime) 未找到 id={id} 对应的 SO (slot {i + 1})");
switch (i)
{
case 0: isAlly01_active = false; break;
case 1: isAlly02_active = false; break;
case 2: isAlly03_active = false; break;
case 3: isAlly04_active = false; break;
case 4: isAlly05_active = false; break;
}
ToggleAllyObject(i, false);
}
else
{
// try to get a readable source for the matched object
if (string.IsNullOrEmpty(runtimeSourceInfo))
{
runtimeSourceInfo = found.name + " (mapped)";
}
Debug.Log($"[teamUIcontroller] (Runtime) slot {i + 1} resolved to SO: {found.name} (id={found.CharacterID}) Source={runtimeSourceInfo}");
switch (i)
{
case 0: isAlly01_active = true; break;
case 1: isAlly02_active = true; break;
case 2: isAlly03_active = true; break;
case 3: isAlly04_active = true; break;
case 4: isAlly05_active = true; break;
}
ToggleAllyObject(i, true);
DebugCharacterSO(found, i+1, false);
// attempt to set teammate image from underlying AllyHero_SO if possible
try
{
var ahList = Resources.LoadAll<AllyHero_SO>("");
AllyHero_SO matched = null;
foreach (var a in ahList) if (a != null && a.ally_heroID == id) { matched = a; break; }
if (matched != null)
{
var sprite = matched.ally_heroProfile != null ? matched.ally_heroProfile : matched.ally_heroImage;
SetTeammateCharacterImage(i, sprite);
}
else
{
var tc = found as TeamCharacterDataInfo;
if (tc != null && tc.CharacterTeamSprite != null)
SetTeammateCharacterImage(i, tc.CharacterTeamSprite);
}
}
catch { }
}
}
#endif
}
// Resolve various forms of selectedProjectFolderPath into a Resources.Load relative path (no 'Resources/' prefix, no extension)
private string ResolveResourcesRelativePath(string selectedPath)
{
if (string.IsNullOrEmpty(selectedPath)) return string.Empty;
string p = selectedPath.Replace("\\", "/");
// If user provided a path starting with "Assets/Resources/", strip to get relative
int idx = p.IndexOf("Assets/Resources/");
if (idx >= 0)
{
return p.Substring(idx + "Assets/Resources/".Length).Trim('/');
}
// If user provided an absolute path that contains Application.dataPath and Resources
string appData = Application.dataPath.Replace("\\", "/");
idx = p.IndexOf(appData + "/Resources/");
if (idx >= 0)
{
return p.Substring((appData + "/Resources/").Length).Trim('/');
}
// If user already provided a Resources relative path (like "so/ally"), return it trimmed
if (p.StartsWith("Resources/")) p = p.Substring("Resources/".Length);
return p.Trim('/');
}
// Debug helper to print key fields of a TeamCharacterDataInfo
private void DebugCharacterSO(TeamCharacterDataInfo so, int slotIndex, bool isEditor)
{
if (so == null) return;
string mode = isEditor ? "Editor" : "Runtime";
string spriteCard = so.CharacterCardSprite != null ? so.CharacterCardSprite.name : "(null)";
string spriteTeam = so.CharacterTeamSprite != null ? so.CharacterTeamSprite.name : "(null)";
Debug.Log($"[teamUIController] ({mode}) Slot {slotIndex} -> CharacterID={so.CharacterID}, Name={so.CharacterName}, Skill={so.CharacterSkillName}, MaxHealth={so.CharacterMaxHealth}, CardSprite={spriteCard}, TeamSprite={spriteTeam}");
}
/// <summary>
/// 返回当前解析到的 SO 列表(数组副本)
/// </summary>
public TeamCharacterDataInfo[] GetCurrentAllySOs()
{
return (TeamCharacterDataInfo[])currentAllySOs.Clone();
}
[Header("基本文本")]
[Tooltip("title")]
public TextMeshProUGUI songNametitle;
@@ -22,7 +500,7 @@ public class teamUIController : MonoBehaviour
[SerializeField] private int enemyCounterMax; // 本局游戏敌人最大数
[SerializeField] private int enemyCurrentCount; // 现在的敌人顺序
[Tooltip("当前总分")]
public Text currentTotalScore;
public TextMeshProUGUI currentTotalScore;
[Header("Combo判定最低要求")]
public ComboJudgeType comboJudgeType = ComboJudgeType.Perfect;
@@ -232,13 +710,32 @@ public class teamUIController : MonoBehaviour
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
PopulateAllySOsFromIds();
// initialize previous flags so Update can detect changes
prevAllyActive[0] = isAlly01_active;
prevAllyActive[1] = isAlly02_active;
prevAllyActive[2] = isAlly03_active;
prevAllyActive[3] = isAlly04_active;
prevAllyActive[4] = isAlly05_active;
// Ensure score text references are resolved so ScoreManager and AllyCombatant can write to them
ResolveScoreTextReferences();
}
// Update is called once per frame
void Update()
{
// detect external changes to isAllyX_active and toggle objects accordingly
bool[] current = new bool[] { isAlly01_active, isAlly02_active, isAlly03_active, isAlly04_active, isAlly05_active };
for (int i = 0; i < 5; i++)
{
if (prevAllyActive[i] != current[i])
{
ToggleAllyObject(i, current[i]);
prevAllyActive[i] = current[i];
}
}
}
private void FixedUpdate()
@@ -289,5 +786,108 @@ public class teamUIController : MonoBehaviour
}
}
// helper to toggle ally object by slot index
private void ToggleAllyObject(int slotIndex, bool active)
{
GameObject target = null;
switch (slotIndex)
{
case 0: target = ally01_object ?? objectFather_ally01; break;
case 1: target = ally02_object ?? objectFather_ally02; break;
case 2: target = ally03_object ?? objectFather_ally03; break;
case 3: target = ally04_object ?? objectFather_ally04; break;
case 4: target = ally05_object ?? objectFather_ally05; break;
}
if (target != null)
target.SetActive(active);
}
// Try to ensure the teammateXX_current_scoreText fields are assigned; if null, search the corresponding parent for TMP/Text
private void ResolveScoreTextReferences()
{
void TryResolve(ref TextMeshProUGUI field, GameObject parent, string slotName)
{
if (field != null) return;
if (parent != null)
{
var tmp = parent.GetComponentInChildren<TextMeshProUGUI>(true);
if (tmp != null)
{
field = tmp;
Debug.Log($"[teamUIController] Resolved {slotName} TMP from parent {parent.name} -> {tmp.gameObject.name}");
return;
}
var legacy = parent.GetComponentInChildren<Text>(true);
if (legacy != null)
{
// if only legacy Text exists, try to create a TMP component to avoid type mismatch
var go = legacy.gameObject;
var created = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
created.text = legacy.text;
field = created;
Debug.LogWarning($"[teamUIController] Found legacy Text for {slotName} under {parent.name}. Added/used TMP component on {go.name} and copied text.");
return;
}
}
Debug.LogWarning($"[teamUIController] Could not resolve {slotName} TMP (parent '{parent?.name}')");
}
TryResolve(ref teammate01_current_scoreText, objectFather_ally01, "teammate01_current_scoreText");
TryResolve(ref teammate02_current_scoreText, objectFather_ally02, "teammate02_current_scoreText");
TryResolve(ref teammate03_current_scoreText, objectFather_ally03, "teammate03_current_scoreText");
TryResolve(ref teammate04_current_scoreText, objectFather_ally04, "teammate04_current_scoreText");
TryResolve(ref teammate05_current_scoreText, objectFather_ally05, "teammate05_current_scoreText");
// total score: if assigned field is null, try to find any TMP in scene named "currentTotalScore" or under this object
if (currentTotalScore == null)
{
var tmp = GetComponentInChildren<TextMeshProUGUI>(true);
if (tmp != null)
{
currentTotalScore = tmp;
Debug.Log($"[teamUIController] Resolved currentTotalScore from child {tmp.gameObject.name}");
}
}
}
/// <summary>
/// 返回相邻的友军槽索引(左右),边侧只有一侧
/// </summary>
public int[] GetAdjacentAllyIndices(int slotIndex)
{
var list = new List<int>();
if (slotIndex - 1 >= 0) list.Add(slotIndex - 1);
if (slotIndex + 1 <= 4) list.Add(slotIndex + 1);
return list.ToArray();
}
/// <summary>
/// 返回指定槽位对应的友军 GameObject(优先返回 allyX_object,否则返回 objectFather_allyXX),可能为 null
/// </summary>
public GameObject GetAllyObjectBySlot(int slotIndex)
{
switch (slotIndex)
{
case 0: return ally01_object ?? objectFather_ally01;
case 1: return ally02_object ?? objectFather_ally02;
case 2: return ally03_object ?? objectFather_ally03;
case 3: return ally04_object ?? objectFather_ally04;
case 4: return ally05_object ?? objectFather_ally05;
default: return null;
}
}
// helper to set teammate character image based on slot index
private void SetTeammateCharacterImage(int slotIndex, Sprite sprite)
{
if (sprite == null) return;
switch (slotIndex)
{
case 0: if (teammate01_characterImage != null) teammate01_characterImage.sprite = sprite; break;
case 1: if (teammate02_characterImage != null) teammate02_characterImage.sprite = sprite; break;
case 2: if (teammate03_characterImage != null) teammate03_characterImage.sprite = sprite; break;
case 3: if (teammate04_characterImage != null) teammate04_characterImage.sprite = sprite; break;
case 4: if (teammate05_characterImage != null) teammate05_characterImage.sprite = sprite; break;
}
}
}