Integrate COM logging HUD fixes and hopper dispensing

Подробный список изменений:
- Добавлено файловое логирование игровых сессий в game.log и расширена диагностика COM-подключений.
- Подключен native COM transport с fallback-логикой для IL2CPP и добавлен плагин ComNative.dll.
- Исправлена логика one-way платформ, чтобы игрок проходил сквозь платформы при нахождении ниже их верхней точки.
- Переработана компоновка gameplay HUD: верхние плашки выровнены, левая уменьшена, центральная плашка очков центрирована и масштабирована.
- Добавлена общая выдача призов через хоппер с командами запуска/остановки мотора и ожиданием датчика.
- Подключен сервисный тест хоппера в настройках и обновлены связанные тесты.
- Добавлены скриншоты визуальной проверки HUD.
This commit is contained in:
Данил Ярош 2026-07-06 16:47:27 +03:00
parent 9b9f57871a
commit c3bb250769
73 changed files with 5482 additions and 262 deletions

View File

@ -84,14 +84,6 @@ MonoBehaviour:
volume: 0.55
pitchRange: {x: 0.96, y: 1.04}
spatial: 1
- eventId: 31
channel: 2
outputGroupOverride: {fileID: 0}
clips:
- {fileID: 8300000, guid: e3dc6a0fbf543b64388f7b60615f4145, type: 3}
volume: 0.7
pitchRange: {x: 0.96, y: 1.04}
spatial: 1
- eventId: 32
channel: 2
outputGroupOverride: {fileID: 0}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8480e16cd88119a4083023fb213dffce
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 265471db36c70b54180317c720985aad

View File

@ -260,7 +260,7 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::DanroJump.UI.Gameplay.GameplayHudController
visualTreeAsset: {fileID: 9197481963319205126, guid: e5019473f4474704f8a8e8c93608eafc, type: 3}
panelSettings: {fileID: 11400000, guid: d0960c9338bdb86469da533ffbbbc1fb, type: 2}
sortingOrder: 10
sortingOrder: 5000
scoreLabelName: ScoreValue
heightLabelName: HeightValue
bestHeightLabelName: BestHeightValue

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: e5281feb3e8fadf4195b417283d66194
TextureImporter:
internalIDToNameTable:
- first:
213: -6613811265633659565
second: gameplay-hud-aligned-no-overlap_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-aligned-no-overlap_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 355dd6402b50734a0800000000000000
internalID: -6613811265633659565
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-aligned-no-overlap_0: -6613811265633659565
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 33859e507258e7f4bb6888557906181e
TextureImporter:
internalIDToNameTable:
- first:
213: -88504916756509590
second: gameplay-hud-final-aligned_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-final-aligned_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a6c4ad34f3195cef0800000000000000
internalID: -88504916756509590
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-final-aligned_0: -88504916756509590
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 18810d73ea59a4b449afdc1c8e6f5863
TextureImporter:
internalIDToNameTable:
- first:
213: 1204845118186171250
second: gameplay-hud-layout-check-1_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-layout-check-1_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 1080
height: 1920
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 27ba9de504878b010800000000000000
internalID: 1204845118186171250
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-layout-check-1_0: 1204845118186171250
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 387580a6fdae7ff42900412e409ef078
TextureImporter:
internalIDToNameTable:
- first:
213: -7993827095130345330
second: gameplay-hud-layout-check_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-layout-check_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 1080
height: 1920
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: e80fcde2c98301190800000000000000
internalID: -7993827095130345330
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-layout-check_0: -7993827095130345330
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 7ab9dc1160c053340b1ba0dd9805f456
TextureImporter:
internalIDToNameTable:
- first:
213: -2984284117762596666
second: gameplay-hud-left-padding-center-true-center_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-left-padding-center-true-center_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 6c0abe30861b596d0800000000000000
internalID: -2984284117762596666
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-left-padding-center-true-center_0: -2984284117762596666
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 2c74775832b90654dac4c794c1d0e647
TextureImporter:
internalIDToNameTable:
- first:
213: 192430222544730116
second: gameplay-hud-left-smaller-center-bigger-final_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-left-smaller-center-bigger-final_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 408f3d29446aba200800000000000000
internalID: 192430222544730116
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-left-smaller-center-bigger-final_0: 192430222544730116
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 61863c92cb6f70f4f824aaa5b38a05e0
TextureImporter:
internalIDToNameTable:
- first:
213: -5553616215781586425
second: gameplay-hud-left-smaller-center-bigger_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-left-smaller-center-bigger_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 70a4d7fd9779de2b0800000000000000
internalID: -5553616215781586425
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-left-smaller-center-bigger_0: -5553616215781586425
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 1000adfacd6f4bf4b972e3c18b1abd29
TextureImporter:
internalIDToNameTable:
- first:
213: -7200965495179829446
second: gameplay-hud-runtime-check_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-runtime-check_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: a3f16f3d808011c90800000000000000
internalID: -7200965495179829446
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-runtime-check_0: -7200965495179829446
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: e2b4729c8644bc5439846cb05511ba42
TextureImporter:
internalIDToNameTable:
- first:
213: -1398601438865487644
second: gameplay-hud-runtime-ui-check-final-fixed_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-runtime-ui-check-final-fixed_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 4e88216c66b279ce0800000000000000
internalID: -1398601438865487644
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-runtime-ui-check-final-fixed_0: -1398601438865487644
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 2885c47a0ecb53e47b356f30dd10bd5b
TextureImporter:
internalIDToNameTable:
- first:
213: 8421283597357811696
second: gameplay-hud-runtime-ui-check-final_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-runtime-ui-check-final_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 0f7065d50d86ed470800000000000000
internalID: 8421283597357811696
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-runtime-ui-check-final_0: 8421283597357811696
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 0ce49b63caafebb4d877b2bfd5421507
TextureImporter:
internalIDToNameTable:
- first:
213: 8905311186297662870
second: gameplay-hud-runtime-ui-check-fixed_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-runtime-ui-check-fixed_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 69da5917755069b70800000000000000
internalID: 8905311186297662870
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-runtime-ui-check-fixed_0: 8905311186297662870
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: de42086cba591cd4fa8265deb11c9809
TextureImporter:
internalIDToNameTable:
- first:
213: 4112799035560915661
second: gameplay-hud-runtime-ui-check-sorting_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-runtime-ui-check-sorting_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: dc27d2baee8931930800000000000000
internalID: 4112799035560915661
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-runtime-ui-check-sorting_0: 4112799035560915661
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: b707582a4e3e16c4182a4f982fd90ea8
TextureImporter:
internalIDToNameTable:
- first:
213: 7529376751934918786
second: gameplay-hud-runtime-ui-check_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-runtime-ui-check_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 2805a277348bd7860800000000000000
internalID: 7529376751934918786
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-runtime-ui-check_0: 7529376751934918786
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: b456d48c0a913544c97ec3620d29f2b6
TextureImporter:
internalIDToNameTable:
- first:
213: -3297423903188290487
second: gameplay-hud-same-height-sub-sprite_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: WebGL
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: WindowsStoreApps
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: gameplay-hud-same-height-sub-sprite_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 94474983e623d32d0800000000000000
internalID: -3297423903188290487
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
gameplay-hud-same-height-sub-sprite_0: -3297423903188290487
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: ee6fd35711026f94abe3fa4d082a89e3
TextureImporter:
internalIDToNameTable:
- first:
213: 5866824435928499895
second: screenshot-20260706-085727_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: WebGL
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: WindowsStoreApps
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: screenshot-20260706-085727_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 7b6891f9db52b6150800000000000000
internalID: 5866824435928499895
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
screenshot-20260706-085727_0: 5866824435928499895
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: e9b8b0aea1c298d42961d9f239378684
TextureImporter:
internalIDToNameTable:
- first:
213: 8860738017832214241
second: screenshot-20260706-085751_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: WebGL
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: WindowsStoreApps
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: screenshot-20260706-085751_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 1ee10b0f74aa7fa70800000000000000
internalID: 8860738017832214241
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
screenshot-20260706-085751_0: 8860738017832214241
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: d9b5b32ece6d79f4c8f07068d4145c73
TextureImporter:
internalIDToNameTable:
- first:
213: -84736916317270727
second: screenshot-20260706-085804_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: WebGL
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: WindowsStoreApps
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: screenshot-20260706-085804_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 93d0c8b3934f2def0800000000000000
internalID: -84736916317270727
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
screenshot-20260706-085804_0: -84736916317270727
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: b27172e5ea1648e41bb3249612633fdf
TextureImporter:
internalIDToNameTable:
- first:
213: -5655167601327757581
second: screenshot-20260706-091834_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: WebGL
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: WindowsStoreApps
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: screenshot-20260706-091834_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 3f6af89670fc481b0800000000000000
internalID: -5655167601327757581
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
screenshot-20260706-091834_0: -5655167601327757581
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

View File

@ -0,0 +1,169 @@
fileFormatVersion: 2
guid: 544707e8a80be704da33766e4e1b7b2d
TextureImporter:
internalIDToNameTable:
- first:
213: 8766466611136954131
second: screenshot-20260706-092024_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: WebGL
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: WindowsStoreApps
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: screenshot-20260706-092024_0
rect:
serializedVersion: 2
x: 0
y: 0
width: 701
height: 1246
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
customData:
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 31ff324afeeb8a970800000000000000
internalID: 8766466611136954131
vertices: []
indices:
edges: []
weights: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable:
screenshot-20260706-092024_0: 8766466611136954131
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,9 @@
using System.IO;
using System.Threading;
using Cysharp.Threading.Tasks;
using DanroJump.Debugging;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
namespace DanroJump.Bootstrap
@ -13,6 +15,7 @@ namespace DanroJump.Bootstrap
public sealed class BootstrapEntryPoint : MonoBehaviour
{
private const float AspectTolerance = 0.0025f;
private const string GameLogFileName = "game.log";
private static BootstrapEntryPoint instance;
[SerializeField] private string mainMenuSceneName = "MainMenu";
@ -26,6 +29,8 @@ namespace DanroJump.Bootstrap
[SerializeField] [Min(0.1f)] private float aspectCheckInterval = 0.5f;
private float nextAspectCheckTime;
private InputAction fullscreenToggleAction;
private FileLogger fileLogger;
/// <summary>
/// Создает singleton bootstrap-объект и применяет разрешение билда до загрузки меню.
@ -50,6 +55,38 @@ namespace DanroJump.Bootstrap
ApplyBuildAspectResolution();
EnforceBuildAspectOnStartupAsync(this.GetCancellationTokenOnDestroy()).Forget(Debug.LogException);
}
// Инициализируем файловый логгер — пишем game.log в корень папки игры.
InitFileLogger();
}
/// <summary>
/// Регистрирует InputAction для переключения полноэкранного режима по F11.
/// </summary>
private void OnEnable()
{
fullscreenToggleAction = new InputAction("FullscreenToggle", InputActionType.Button, "<Keyboard>/f11");
fullscreenToggleAction.performed += OnFullscreenTogglePerformed;
fullscreenToggleAction.Enable();
}
/// <summary>
/// Снимает подписку и отключает InputAction при деактивации объекта.
/// </summary>
private void OnDisable()
{
if (fullscreenToggleAction != null)
{
fullscreenToggleAction.performed -= OnFullscreenTogglePerformed;
fullscreenToggleAction.Disable();
fullscreenToggleAction.Dispose();
fullscreenToggleAction = null;
}
}
private void OnFullscreenTogglePerformed(InputAction.CallbackContext context)
{
ToggleFullscreen();
}
/// <summary>
@ -70,6 +107,33 @@ namespace DanroJump.Bootstrap
}
}
/// <summary>
/// Переключает полноэкранный режим по нажатию F11.
/// В билде учитывает целевой aspect ratio и разрешение монитора.
/// </summary>
private void ToggleFullscreen()
{
bool isCurrentlyFullscreen = Screen.fullScreen;
if (isCurrentlyFullscreen)
{
// Выход из полноэкранного режима — переходим в Windowed с aspect-корректным разрешением
Vector2Int resolution = CalculateTargetResolution();
Screen.SetResolution(resolution.x, resolution.y, FullScreenMode.Windowed);
}
else
{
// Вход в полноэкранный режим — используем native разрешение монитора
Resolution native = Screen.currentResolution;
FullScreenMode targetMode = FullScreenMode.FullScreenWindow;
#if UNITY_STANDALONE_WIN
// На Windows ExclusiveFullScreen даёт минимальный input lag
targetMode = FullScreenMode.FullScreenWindow;
#endif
Screen.SetResolution(native.width, native.height, targetMode);
}
}
/// <summary>
/// Загружает сцену главного меню после bootstrap-настройки.
/// </summary>
@ -138,12 +202,66 @@ namespace DanroJump.Bootstrap
/// </summary>
private void OnDestroy()
{
DisposeFileLogger();
if (instance == this)
{
instance = null;
}
}
/// <summary>
/// Создаёт FileLogger и подписывает его на все сообщения Unity Debug.Log.
/// В билде лог пишется рядом с .exe, в редакторе — в корень проекта.
/// </summary>
private void InitFileLogger()
{
if (fileLogger != null)
{
return;
}
try
{
string logDirectory = ResolveGameRootDirectory();
string logPath = Path.Combine(logDirectory, GameLogFileName);
fileLogger = new FileLogger(logPath, append: true);
Application.logMessageReceived += fileLogger.HandleUnityLog;
Debug.Log($"[BootstrapEntryPoint] Game log writes to: {fileLogger.FilePath}", this);
}
catch (System.Exception ex)
{
Debug.LogError($"[BootstrapEntryPoint] Failed to init FileLogger: {ex.Message}");
}
}
private static string ResolveGameRootDirectory()
{
string dataPath = Application.dataPath;
var dataDirectory = new DirectoryInfo(dataPath);
#if UNITY_STANDALONE_OSX && !UNITY_EDITOR
return dataDirectory.Parent?.Parent?.FullName ?? Path.GetFullPath(".");
#else
return dataDirectory.Parent?.FullName ?? Path.GetFullPath(".");
#endif
}
/// <summary>
/// Корректно отписывается и освобождает FileLogger.
/// </summary>
private void DisposeFileLogger()
{
if (fileLogger == null)
{
return;
}
Application.logMessageReceived -= fileLogger.HandleUnityLog;
fileLogger.Dispose();
fileLogger = null;
}
/// <summary>
/// Несколько кадров подряд повторяет установку разрешения, пока Unity завершает старт окна.
/// </summary>

View File

@ -0,0 +1,395 @@
using System;
using System.IO;
using System.Text;
using UnityEngine;
using Object = UnityEngine.Object;
namespace DanroJump.Debugging
{
/// <summary>
/// Логгер для записи сообщений в файл. Совместим с CompositeLogger.
/// </summary>
public class FileLogger : ILogger, IDisposable
{
public ILogHandler logHandler { get; set; }
public LogType filterLogType { get; set; }
public bool logEnabled { get; set; } = true;
private readonly string _filePath;
private readonly object _lock = new object();
private StreamWriter _writer;
private bool _disposed;
private bool _footerWritten;
/// <summary>
/// Создает файловый логгер с указанным путём к файлу.
/// </summary>
/// <param name="filePath">Полный путь к файлу лога. Если null, используется Application.persistentDataPath/game.log</param>
/// <param name="append">Если true, добавляет к существующему файлу, иначе перезаписывает</param>
public FileLogger(string filePath = null, bool append = true)
{
_filePath = filePath ?? Path.Combine(Application.persistentDataPath, "game.log");
filterLogType = LogType.Exception;
try
{
// Создаем директорию если её нет
string directory = Path.GetDirectoryName(_filePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// Открываем файл для записи
_writer = new StreamWriter(_filePath, append, Encoding.UTF8)
{
AutoFlush = true // Автоматически сбрасывать буфер для надёжности
};
// Записываем заголовок
WriteHeader();
// Подписываемся на завершение приложения для гарантированной записи
Application.quitting += OnApplicationQuitting;
}
catch (Exception ex)
{
Debug.LogError($"[FileLogger] Не удалось открыть файл лога: {_filePath}\n{ex}");
}
}
public string FilePath => _filePath;
private void WriteHeader()
{
if (_writer != null)
{
_writer.WriteLine("================================================================================");
_writer.WriteLine($"Новая сессия логирования начата: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_writer.WriteLine($"Unity версия: {Application.unityVersion}");
_writer.WriteLine($"Платформа: {Application.platform}");
_writer.WriteLine($"Путь к логу: {_filePath}");
_writer.WriteLine("================================================================================");
_writer.WriteLine();
}
}
public bool IsLogTypeAllowed(LogType logType)
{
return logEnabled && logType <= filterLogType;
}
private void WriteToFile(LogType logType, string message)
{
if (!logEnabled || _disposed || _writer == null)
return;
if (!IsLogTypeAllowed(logType))
return;
lock (_lock)
{
try
{
string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
string logTypeStr = GetLogTypeString(logType);
_writer.WriteLine($"[{timestamp}] [{logTypeStr}] {message}");
}
catch (Exception ex)
{
Debug.LogError($"[FileLogger] Ошибка записи в файл: {ex.Message}");
}
}
}
private string GetLogTypeString(LogType logType)
{
return logType switch
{
LogType.Error => "ERROR",
LogType.Assert => "ASSERT",
LogType.Warning => "WARNING",
LogType.Log => "INFO",
LogType.Exception => "EXCEPTION",
_ => "UNKNOWN"
};
}
private string FormatMessage(object message, Object context = null)
{
string msg = message?.ToString() ?? "null";
if (context != null)
{
msg += $" (Context: {context.name})";
}
return msg;
}
private string FormatMessage(string tag, object message, Object context = null)
{
string msg = $"[{tag}] {message?.ToString() ?? "null"}";
if (context != null)
{
msg += $" (Context: {context.name})";
}
return msg;
}
#region ILogger Implementation
public void Log(LogType logType, object message)
{
WriteToFile(logType, FormatMessage(message));
}
public void Log(LogType logType, object message, Object context)
{
WriteToFile(logType, FormatMessage(message, context));
}
public void Log(LogType logType, string tag, object message)
{
WriteToFile(logType, FormatMessage(tag, message));
}
public void Log(LogType logType, string tag, object message, Object context)
{
WriteToFile(logType, FormatMessage(tag, message, context));
}
public void Log(object message)
{
WriteToFile(LogType.Log, FormatMessage(message));
}
public void Log(string tag, object message)
{
WriteToFile(LogType.Log, FormatMessage(tag, message));
}
public void Log(string tag, object message, Object context)
{
WriteToFile(LogType.Log, FormatMessage(tag, message, context));
}
public void LogWarning(string tag, object message)
{
WriteToFile(LogType.Warning, FormatMessage(tag, message));
}
public void LogWarning(string tag, object message, Object context)
{
WriteToFile(LogType.Warning, FormatMessage(tag, message, context));
}
public void LogError(string tag, object message)
{
WriteToFile(LogType.Error, FormatMessage(tag, message));
}
public void LogError(string tag, object message, Object context)
{
WriteToFile(LogType.Error, FormatMessage(tag, message, context));
}
public void LogFormat(LogType logType, string format, params object[] args)
{
try
{
string message = string.Format(format, args);
WriteToFile(logType, message);
}
catch (FormatException ex)
{
WriteToFile(LogType.Error, $"Ошибка форматирования: {ex.Message}");
}
}
public void LogFormat(LogType logType, Object context, string format, params object[] args)
{
try
{
string message = string.Format(format, args);
WriteToFile(logType, FormatMessage(message, context));
}
catch (FormatException ex)
{
WriteToFile(LogType.Error, $"Ошибка форматирования: {ex.Message}");
}
}
public void LogException(Exception exception)
{
if (exception == null)
return;
WriteToFile(LogType.Exception, $"{exception.GetType().Name}: {exception.Message}");
WriteToFile(LogType.Exception, $"StackTrace:\n{exception.StackTrace}");
if (exception.InnerException != null)
{
WriteToFile(LogType.Exception,
$"Inner Exception: {exception.InnerException.GetType().Name}: {exception.InnerException.Message}");
}
}
public void LogException(Exception exception, Object context)
{
if (exception == null)
return;
string contextInfo = context != null ? $" (Context: {context.name})" : "";
WriteToFile(LogType.Exception, $"{exception.GetType().Name}: {exception.Message}{contextInfo}");
WriteToFile(LogType.Exception, $"StackTrace:\n{exception.StackTrace}");
if (exception.InnerException != null)
{
WriteToFile(LogType.Exception,
$"Inner Exception: {exception.InnerException.GetType().Name}: {exception.InnerException.Message}");
}
}
#endregion
/// <summary>
/// Обработчик для Application.logMessageReceived — перенаправляет все Debug.Log в файл.
/// </summary>
public void HandleUnityLog(string logString, string stackTrace, LogType type)
{
if (type == LogType.Exception)
{
WriteToFile(type, $"{logString}\n{stackTrace}");
}
else
{
WriteToFile(type, logString);
}
}
private void OnApplicationQuitting()
{
WriteFooter();
Dispose();
}
private void WriteFooter()
{
if (_footerWritten || _disposed || _writer == null) return;
lock (_lock)
{
if (_footerWritten) return;
_footerWritten = true;
try
{
_writer.WriteLine();
_writer.WriteLine("================================================================================");
_writer.WriteLine($"Сессия логирования завершена: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
_writer.WriteLine("================================================================================");
_writer.Flush();
}
catch (ObjectDisposedException) { }
catch (Exception ex)
{
Debug.LogError($"[FileLogger] Ошибка при записи footer: {ex.Message}");
}
}
}
/// <summary>
/// Принудительно сбрасывает буфер на диск
/// </summary>
public void Flush()
{
lock (_lock)
{
try
{
_writer?.Flush();
}
catch (Exception ex)
{
Debug.LogError($"[FileLogger] Ошибка при сбросе буфера: {ex.Message}");
}
}
}
/// <summary>
/// Очищает файл лога
/// </summary>
public void Clear()
{
lock (_lock)
{
try
{
_writer?.Close();
File.WriteAllText(_filePath, string.Empty);
_writer = new StreamWriter(_filePath, true, Encoding.UTF8) { AutoFlush = true };
WriteHeader();
}
catch (Exception ex)
{
Debug.LogError($"[FileLogger] Ошибка при очистке файла: {ex.Message}");
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed)
return;
lock (_lock)
{
if (_disposed)
return;
try
{
// Гарантированно записываем footer
WriteFooter();
if (disposing)
{
Application.quitting -= OnApplicationQuitting;
}
if (_writer != null)
{
_writer.Dispose();
_writer = null;
}
}
catch (ObjectDisposedException)
{
// Поток уже закрыт — игнорируем.
}
catch (Exception ex)
{
Debug.LogError($"[FileLogger] Ошибка при закрытии файла: {ex.Message}");
}
finally
{
_disposed = true;
}
}
}
~FileLogger()
{
Dispose(false);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d029602fde45ab04f85586e245e48a1a

View File

@ -80,7 +80,6 @@ namespace DanroJump.Gameplay
private bool moveActionWasEnabled;
#endif
private Transform wrapVisualCloneRoot;
private Transform visualRootClone;
private readonly List<SpriteRenderer> sourceRenderers = new();
private readonly List<SpriteRenderer> wrapRendererClones = new();
private readonly List<IPlatformLandingHandler> landingHandlerBuffer = new();
@ -1591,29 +1590,13 @@ namespace DanroJump.Gameplay
sourceRenderers.Clear();
wrapRendererClones.Clear();
if (wrapVisualCloneRoot == null)
if (visualRoot != null)
{
wrapVisualCloneRoot = new GameObject($"{name} Wrap Visuals").transform;
wrapVisualCloneRoot.SetParent(transform, false);
wrapVisualCloneRoot.gameObject.SetActive(false);
sourceRenderers.AddRange(visualRoot.GetComponentsInChildren<SpriteRenderer>(true));
}
if (visualRootClone == null)
else
{
visualRootClone = new GameObject("VisualRoot Clone").transform;
visualRootClone.SetParent(wrapVisualCloneRoot, false);
}
var allRenderers = visualRoot != null
? visualRoot.GetComponentsInChildren<SpriteRenderer>(true)
: GetComponentsInChildren<SpriteRenderer>(true);
foreach (var r in allRenderers)
{
if (r != null && (wrapVisualCloneRoot == null || !r.transform.IsChildOf(wrapVisualCloneRoot)))
{
sourceRenderers.Add(r);
}
sourceRenderers.AddRange(GetComponentsInChildren<SpriteRenderer>(true));
}
if (sourceRenderers.Count == 0)
@ -1621,6 +1604,12 @@ namespace DanroJump.Gameplay
return;
}
if (wrapVisualCloneRoot == null)
{
wrapVisualCloneRoot = new GameObject($"{name} Wrap Visuals").transform;
wrapVisualCloneRoot.gameObject.SetActive(false);
}
for (var index = sourceRenderers.Count - 1; index >= 0; index--)
{
var sourceRenderer = sourceRenderers[index];
@ -1631,62 +1620,9 @@ namespace DanroJump.Gameplay
}
var clone = new GameObject($"{sourceRenderer.name} Wrap Clone").AddComponent<SpriteRenderer>();
clone.transform.SetParent(visualRootClone, false);
clone.transform.SetParent(wrapVisualCloneRoot, false);
wrapRendererClones.Insert(0, clone);
}
RebuildWrapCollider();
}
private void RebuildWrapCollider()
{
if (wrapVisualCloneRoot == null)
{
return;
}
var oldColliders = wrapVisualCloneRoot.GetComponents<Collider2D>();
foreach (var old in oldColliders)
{
if (Application.isPlaying)
{
Destroy(old);
}
else
{
DestroyImmediate(old);
}
}
var sourceCollider = GetComponent<Collider2D>();
if (sourceCollider != null)
{
if (sourceCollider is BoxCollider2D boxCol)
{
var cloneCol = wrapVisualCloneRoot.gameObject.AddComponent<BoxCollider2D>();
cloneCol.size = boxCol.size;
cloneCol.offset = boxCol.offset;
cloneCol.isTrigger = boxCol.isTrigger;
cloneCol.sharedMaterial = boxCol.sharedMaterial;
}
else if (sourceCollider is CircleCollider2D circleCol)
{
var cloneCol = wrapVisualCloneRoot.gameObject.AddComponent<CircleCollider2D>();
cloneCol.radius = circleCol.radius;
cloneCol.offset = circleCol.offset;
cloneCol.isTrigger = circleCol.isTrigger;
cloneCol.sharedMaterial = circleCol.sharedMaterial;
}
else if (sourceCollider is CapsuleCollider2D capsuleCol)
{
var cloneCol = wrapVisualCloneRoot.gameObject.AddComponent<CapsuleCollider2D>();
cloneCol.size = capsuleCol.size;
cloneCol.offset = capsuleCol.offset;
cloneCol.direction = capsuleCol.direction;
cloneCol.isTrigger = capsuleCol.isTrigger;
cloneCol.sharedMaterial = capsuleCol.sharedMaterial;
}
}
}
/// <summary>
@ -1726,20 +1662,11 @@ namespace DanroJump.Gameplay
CopyWrappedVisuals(new Vector3(wrapOffsetX, 0f, 0f));
}
/// <summary>
/// Копирует состояние исходных SpriteRenderer в wrap-клоны.
/// </summary>
private void CopyWrappedVisuals(Vector3 offset)
{
if (wrapVisualCloneRoot != null)
{
wrapVisualCloneRoot.localPosition = offset;
}
if (visualRoot != null && visualRootClone != null)
{
visualRootClone.localPosition = visualRoot.localPosition;
visualRootClone.localRotation = visualRoot.localRotation;
visualRootClone.localScale = visualRoot.localScale;
}
for (var index = 0; index < sourceRenderers.Count; index++)
{
var source = sourceRenderers[index];
@ -1769,9 +1696,8 @@ namespace DanroJump.Gameplay
clone.sortingLayerID = source.sortingLayerID;
clone.sortingOrder = source.sortingOrder;
clone.sharedMaterial = source.sharedMaterial;
clone.transform.localPosition = source.transform.localPosition;
clone.transform.localRotation = source.transform.localRotation;
clone.transform.localScale = source.transform.localScale;
clone.transform.SetPositionAndRotation(source.transform.position + offset, source.transform.rotation);
clone.transform.localScale = source.transform.lossyScale;
clone.gameObject.layer = source.gameObject.layer;
}
catch (MissingReferenceException)

View File

@ -5,7 +5,7 @@ namespace DanroJump.Gameplay
{
/// <summary>
/// Кастомная one-way логика платформы.
/// Игнорирует столкновение, если GroundCheck игрока ниже верхней границы платформы.
/// Игнорирует столкновение, если нижняя точка игрока ниже верхней границы платформы.
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(Collider2D))]
@ -14,10 +14,8 @@ namespace DanroJump.Gameplay
[SerializeField] private JumpPlayerController player;
[SerializeField] private float topBoundaryPadding;
[SerializeField] private float landingVelocityThreshold = 0.05f;
[SerializeField] private float crossingGraceDistance = 0.8f;
private Collider2D platformCollider;
private BoxCollider2D boxPlatformCollider;
private Collider2D playerCollider;
private float cachedTopY;
private Vector3 lastPosition;
@ -33,15 +31,7 @@ namespace DanroJump.Gameplay
/// </summary>
public void UpdateCachedTopY()
{
if (boxPlatformCollider != null)
{
var localTop = boxPlatformCollider.offset + Vector2.up * (boxPlatformCollider.size.y * 0.5f);
cachedTopY = boxPlatformCollider.transform.TransformPoint(localTop).y;
}
else
{
cachedTopY = platformCollider != null ? platformCollider.bounds.max.y : transform.position.y;
}
cachedTopY = platformCollider != null ? platformCollider.bounds.max.y : transform.position.y;
}
/// <summary>
@ -60,7 +50,6 @@ namespace DanroJump.Gameplay
private void Awake()
{
platformCollider = GetComponent<Collider2D>();
boxPlatformCollider = platformCollider as BoxCollider2D;
platformCollider.usedByEffector = false;
if (TryGetComponent(out PlatformEffector2D platformEffector))
@ -139,7 +128,7 @@ namespace DanroJump.Gameplay
/// Проверяет, должен ли игрок столкнуться с платформой в текущем физическом шаге.
/// </summary>
/// <param name="targetPlayer">Игрок, для которого проверяется столкновение.</param>
/// <returns>True, если игрок падает сверху или только что пересек верх платформы сверху вниз.</returns>
/// <returns>True, если нижняя точка игрока не ниже верхней границы платформы.</returns>
public bool ShouldCollideWith(JumpPlayerController targetPlayer)
{
if (targetPlayer == null)
@ -147,17 +136,10 @@ namespace DanroJump.Gameplay
return false;
}
var topY = cachedTopY + topBoundaryPadding;
var topY = cachedTopY + Mathf.Max(0f, topBoundaryPadding);
var playerLowestY = playerCollider != null ? playerCollider.bounds.min.y : targetPlayer.GroundCheckY;
if (targetPlayer.GroundCheckY >= topY)
{
return true;
}
// Grace-зона закрывает быстрые кадры, когда GroundCheck успел проскочить немного ниже верхней границы.
var crossedFromAbove = targetPlayer.PreviousGroundCheckY >= topY &&
targetPlayer.GroundCheckY >= topY - crossingGraceDistance;
return crossedFromAbove;
return playerLowestY >= topY;
}
/// <summary>

View File

@ -53,6 +53,12 @@ namespace DanroJump.Gameplay
return;
}
if (!content.scene.isLoaded)
{
Object.Destroy(content);
return;
}
if (content.TryGetComponent(out PlatformSpawnedContent marker) && marker.SourcePrefab != null)
{
content.SetActive(false);

View File

@ -0,0 +1,87 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine.Events;
namespace DanroJump.Hardware.Com
{
/// <summary>
/// Единая процедура выдачи капсулы через хоппер: запуск мотора, ожидание датчика и остановка.
/// </summary>
public static class ComHopperDispenser
{
public const string StartMotorCommand = "301";
public const string StopMotorCommand = "300";
public const int DefaultTimeoutSeconds = 10;
public static UniTask<ComHopperDispenseResult> DispenseAsync(
IComSystem com,
CancellationToken cancellationToken = default)
{
return DispenseAsync(com, TimeSpan.FromSeconds(DefaultTimeoutSeconds), cancellationToken);
}
public static async UniTask<ComHopperDispenseResult> DispenseAsync(
IComSystem com,
TimeSpan timeout,
CancellationToken cancellationToken = default)
{
if (com == null)
{
return ComHopperDispenseResult.Failure("COM-система недоступна.");
}
if (!com.IsRunning)
{
return ComHopperDispenseResult.Failure("COM-система не запущена.");
}
if (!com.SendToMain(StartMotorCommand))
{
return ComHopperDispenseResult.Failure("Не удалось отправить команду запуска хоппера.");
}
var sensorTriggered = new UniTaskCompletionSource();
UnityAction onTriggered = () => sensorTriggered.TrySetResult();
com.HopperSensorTriggered.AddListener(onTriggered);
try
{
var winner = await UniTask.WhenAny(
sensorTriggered.Task,
UniTask.Delay(timeout, DelayType.Realtime, PlayerLoopTiming.Update, cancellationToken));
return winner == 0
? ComHopperDispenseResult.Success("Капсула выдана успешно.")
: ComHopperDispenseResult.Failure($"Датчик хоппера не сработал за {timeout.TotalSeconds:0.#} сек.");
}
finally
{
com.HopperSensorTriggered.RemoveListener(onTriggered);
com.SendToMain(StopMotorCommand);
}
}
}
public readonly struct ComHopperDispenseResult
{
private ComHopperDispenseResult(bool isSuccess, string message)
{
IsSuccess = isSuccess;
Message = message ?? string.Empty;
}
public bool IsSuccess { get; }
public string Message { get; }
public static ComHopperDispenseResult Success(string message)
{
return new ComHopperDispenseResult(true, message);
}
public static ComHopperDispenseResult Failure(string message)
{
return new ComHopperDispenseResult(false, message);
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3402102c626636f48aa68fbc9154cde6

View File

@ -301,8 +301,8 @@ namespace DanroJump.Hardware.Com
return;
}
portProvider = customPortProvider ?? new ReflectionComPortProvider();
transportFactory = customTransportFactory ?? new ReflectionSerialComTransportFactory();
portProvider = customPortProvider ?? CreateDefaultPortProvider();
transportFactory = customTransportFactory ?? CreateDefaultTransportFactory();
}
/// <summary>
@ -441,10 +441,24 @@ namespace DanroJump.Hardware.Com
ResetDeviceSelections();
ClearQueues();
string[] ports = SelectPorts(portProvider.GetPortNames());
string[] detectedPorts = portProvider.GetPortNames();
if (ShouldLog(DebugLogChannel.ComLifecycle, logLifecycle))
{
LogDebug(DebugLogChannel.ComLifecycle, $"Starting. Selected ports: {(ports.Length > 0 ? string.Join(", ", ports) : "<none>")}");
LogDebug(
DebugLogChannel.ComLifecycle,
$"Detected ports: {(detectedPorts.Length > 0 ? string.Join(", ", detectedPorts) : "<none>")}. " +
$"scanAll={useAllDetectedPorts} explicit=[{FormatList(explicitPorts)}] preferredMain={FormatDebugValue(preferredMainPort)}");
}
string[] ports = SelectPorts(detectedPorts);
if (ShouldLog(DebugLogChannel.ComLifecycle, logLifecycle))
{
LogDebug(
DebugLogChannel.ComLifecycle,
$"Starting. Selected ports: {(ports.Length > 0 ? string.Join(", ", ports) : "<none>")}. " +
$"serial={baudRate}/{dataBits}/{parity}/{stopBits} readTimeoutMs={readTimeoutMs} writeTimeoutMs={writeTimeoutMs} " +
$"encryptedHandshake={useEncryptedHandshake} handshakeTimeoutMs={handshakeTimeoutMs} " +
$"roleRequest={FormatDebugValue(roleRequestCommand)} rolePrefix={FormatDebugValue(roleResponsePrefix)}");
}
if (ports.Length == 0)
{
@ -474,8 +488,22 @@ namespace DanroJump.Hardware.Com
private void EnsureInfrastructure()
{
portProvider ??= new ReflectionComPortProvider();
transportFactory ??= new ReflectionSerialComTransportFactory();
portProvider ??= CreateDefaultPortProvider();
transportFactory ??= CreateDefaultTransportFactory();
}
private static IComPortProvider CreateDefaultPortProvider()
{
return new FallbackComPortProvider(
new ReflectionComPortProvider(),
new NativeComPortProvider());
}
private static IComTransportFactory CreateDefaultTransportFactory()
{
return new FallbackComTransportFactory(
new ReflectionSerialComTransportFactory(),
new NativeComTransportFactory());
}
private void EnsureLightingSystem()
@ -1257,6 +1285,7 @@ namespace DanroJump.Hardware.Com
roleRequestCommand,
roleResponsePrefix,
roleRequestTimeoutMs,
message => LogDebug(DebugLogChannel.ComLifecycle, logLifecycle, message),
message => LogWarning(DebugLogChannel.ComLifecycle, logLifecycle, message));
Subscribe(device);
@ -1611,6 +1640,25 @@ namespace DanroJump.Hardware.Com
return string.IsNullOrWhiteSpace(value) ? "<unknown>" : value;
}
private static string FormatList(IEnumerable<string> values)
{
if (values == null)
{
return "";
}
List<string> filtered = new();
foreach (var value in values)
{
if (!string.IsNullOrWhiteSpace(value))
{
filtered.Add(value.Trim());
}
}
return filtered.Count > 0 ? string.Join(", ", filtered) : "";
}
private static async UniTask RunLoggedTaskAsync(Func<UniTask> taskFactory, CancellationToken token, string operationName)
{
try
@ -1759,6 +1807,7 @@ namespace DanroJump.Hardware.Com
private readonly string roleRequestCommand;
private readonly string roleResponsePrefix;
private readonly int roleRequestTimeoutMs;
private readonly Action<string> logInfo;
private readonly Action<string> logWarning;
private readonly byte[] aesKey;
@ -1798,6 +1847,7 @@ namespace DanroJump.Hardware.Com
string roleRequestCommand,
string roleResponsePrefix,
int roleRequestTimeoutMs,
Action<string> logInfo,
Action<string> logWarning)
{
this.transportFactory = transportFactory ?? throw new ArgumentNullException(nameof(transportFactory));
@ -1817,6 +1867,7 @@ namespace DanroJump.Hardware.Com
this.roleRequestCommand = roleRequestCommand?.Trim() ?? "";
this.roleResponsePrefix = string.IsNullOrWhiteSpace(roleResponsePrefix) ? "ROLE" : roleResponsePrefix.Trim();
this.roleRequestTimeoutMs = Mathf.Max(200, roleRequestTimeoutMs);
this.logInfo = logInfo;
this.logWarning = logWarning;
aesKey = BuildAesKey();
}
@ -1899,14 +1950,17 @@ namespace DanroJump.Hardware.Com
return false;
}
LogInfo($"{PortName} read loop starting.");
RunLoggedTask(() => ReadLoopAsync(localCts.Token), localCts.Token, $"{PortName} read loop");
if (useEncryptedHandshake)
{
LogInfo($"{PortName} encrypted handshake starting.");
RunLoggedTask(() => RunHandshakeAsync(localCts.Token), localCts.Token, $"{PortName} handshake");
}
else
{
LogInfo($"{PortName} encrypted handshake disabled; marking operational.");
MarkOperational();
}
@ -1963,6 +2017,7 @@ namespace DanroJump.Hardware.Com
{
try
{
LogInfo($"{PortName} opening serial port. settings={baudRate}/{dataBits}/{parity}/{stopBits} readTimeoutMs={readTimeoutMs} writeTimeoutMs={writeTimeoutMs}");
lock (ioLock)
{
serialPort = transportFactory.Create(new SerialSettings(
@ -1980,11 +2035,12 @@ namespace DanroJump.Hardware.Com
}
lastCoinTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
LogInfo($"{PortName} serial port opened successfully.");
return true;
}
catch (Exception exception)
{
LogWarning($"Failed to open {PortName}: {exception.Message}");
LogWarning($"Failed to open {PortName}: {exception.GetType().Name}: {exception.Message}");
return false;
}
}
@ -2092,6 +2148,7 @@ namespace DanroJump.Hardware.Com
handshakeCompletion = NewCompletion<bool>();
chalCompletion = NewCompletion<bool>();
requestHex = GenerateRandomBlock16Hex();
LogInfo($"{PortName} handshake: sending challenge request command 07<random16>.");
Send("07" + requestHex, true);
var (chalReceived, _) = await UniTask.WhenAny(
@ -2104,12 +2161,14 @@ namespace DanroJump.Hardware.Com
return;
}
LogInfo($"{PortName} handshake: CHAL received, waiting RAES.");
var (handshakeFinished, handshakeSucceeded) = await UniTask.WhenAny(
handshakeCompletion.Task,
UniTask.Delay(handshakeTimeoutMs, DelayType.Realtime, PlayerLoopTiming.Update, token));
await UniTask.SwitchToThreadPool();
if (handshakeFinished && handshakeSucceeded)
{
LogInfo($"{PortName} handshake: RAES verified.");
MarkOperational();
return;
}
@ -2126,12 +2185,14 @@ namespace DanroJump.Hardware.Com
if (handshakeStage == HandshakeStage.WaitingChal && line.StartsWith("CHAL", StringComparison.Ordinal))
{
LogInfo($"{PortName} handshake line received: CHAL length={line.Length}.");
HandleChal(line);
return;
}
if (handshakeStage == HandshakeStage.WaitingRaes && line.StartsWith("RAES", StringComparison.Ordinal))
{
LogInfo($"{PortName} handshake line received: RAES length={line.Length}.");
HandleRaes(line);
}
}
@ -2207,6 +2268,7 @@ namespace DanroJump.Hardware.Com
isOperational = true;
handshakeStage = HandshakeStage.Completed;
missedInputResponses = 0;
LogInfo($"{PortName} marked operational. Sending startup commands.");
Send("2171", true);
Send("890003", true);
@ -2230,6 +2292,7 @@ namespace DanroJump.Hardware.Com
if (!string.IsNullOrWhiteSpace(roleRequestCommand))
{
roleCompletion = NewCompletion<string>();
LogInfo($"{PortName} identity: requesting role with command {roleRequestCommand}, expecting prefix {roleResponsePrefix}.");
Send(roleRequestCommand);
var (roleReceived, role) = await UniTask.WhenAny(
@ -2239,10 +2302,16 @@ namespace DanroJump.Hardware.Com
if (roleReceived)
{
Role = role;
LogInfo($"{PortName} identity: role received '{Role}'.");
}
else
{
LogInfo($"{PortName} identity: role request timed out after {roleRequestTimeoutMs} ms.");
}
}
macCompletion = NewCompletion<string>();
LogInfo($"{PortName} identity: requesting MAC with command 06.");
Send("06");
var (macReceived, macAddress) = await UniTask.WhenAny(
@ -2252,6 +2321,11 @@ namespace DanroJump.Hardware.Com
if (macReceived)
{
MacAddress = macAddress;
LogInfo($"{PortName} identity: MAC received {MacAddress}.");
}
else
{
LogInfo($"{PortName} identity: MAC request timed out after {roleRequestTimeoutMs} ms.");
}
}
catch (OperationCanceledException)
@ -2528,6 +2602,7 @@ namespace DanroJump.Hardware.Com
}
isOperational = false;
LogInfo($"{PortName} notifying disconnected.");
localCts?.Cancel();
ClosePort();
Disconnected?.Invoke(this);
@ -2559,9 +2634,15 @@ namespace DanroJump.Hardware.Com
serialPort.Dispose();
serialPort = null;
LogInfo($"{PortName} serial port closed.");
}
}
private void LogInfo(string message)
{
logInfo?.Invoke(message);
}
private string EncryptHexBlock16(string plainHex)
{
byte[] plain = HexToBytes(plainHex);

View File

@ -0,0 +1,228 @@
using System;
using UnityEngine;
namespace DanroJump.Hardware.Com
{
/// <summary>
/// Поставщик портов, который переходит на запасной источник, если основной не нашел порты или упал.
/// </summary>
public sealed class FallbackComPortProvider : IComPortProvider
{
private readonly IComPortProvider primaryProvider;
private readonly IComPortProvider fallbackProvider;
public FallbackComPortProvider(IComPortProvider primaryProvider, IComPortProvider fallbackProvider)
{
this.primaryProvider = primaryProvider ?? throw new ArgumentNullException(nameof(primaryProvider));
this.fallbackProvider = fallbackProvider ?? throw new ArgumentNullException(nameof(fallbackProvider));
}
public string[] GetPortNames()
{
try
{
string[] primaryPorts = primaryProvider.GetPortNames() ?? Array.Empty<string>();
if (primaryPorts.Length > 0)
{
return primaryPorts;
}
Debug.LogWarning("[ComSystem] Primary COM port provider returned no ports. Trying native ComNative provider.");
}
catch (Exception exception)
{
Debug.LogWarning($"[ComSystem] Primary COM port provider failed: {exception.GetType().Name}: {exception.Message}. Trying native ComNative provider.");
}
return fallbackProvider.GetPortNames() ?? Array.Empty<string>();
}
}
/// <summary>
/// Фабрика транспорта, которая открывает порт через основной backend и переключается на запасной при ошибке.
/// </summary>
public sealed class FallbackComTransportFactory : IComTransportFactory
{
private readonly IComTransportFactory primaryFactory;
private readonly IComTransportFactory fallbackFactory;
public FallbackComTransportFactory(IComTransportFactory primaryFactory, IComTransportFactory fallbackFactory)
{
this.primaryFactory = primaryFactory ?? throw new ArgumentNullException(nameof(primaryFactory));
this.fallbackFactory = fallbackFactory ?? throw new ArgumentNullException(nameof(fallbackFactory));
}
public IComTransport Create(SerialSettings settings)
{
return new FallbackComTransport(settings, primaryFactory, fallbackFactory);
}
}
internal sealed class FallbackComTransport : IComTransport
{
private const string PrimaryBackendName = "System.IO.Ports";
private const string FallbackBackendName = "ComNative";
private readonly SerialSettings settings;
private readonly IComTransportFactory primaryFactory;
private readonly IComTransportFactory fallbackFactory;
private IComTransport activeTransport;
private string activeBackendName = "";
public FallbackComTransport(
SerialSettings settings,
IComTransportFactory primaryFactory,
IComTransportFactory fallbackFactory)
{
this.settings = settings;
this.primaryFactory = primaryFactory;
this.fallbackFactory = fallbackFactory;
}
public bool IsOpen => activeTransport != null && activeTransport.IsOpen;
public void Open()
{
if (IsOpen)
{
return;
}
Exception primaryException = null;
try
{
OpenWith(primaryFactory, PrimaryBackendName);
return;
}
catch (Exception exception)
{
primaryException = exception;
DisposeActiveTransport();
Debug.LogWarning($"[ComSystem] System.IO.Ports failed for {settings.PortName}: {exception.GetType().Name}: {exception.Message}. Trying ComNative.");
}
try
{
OpenWith(fallbackFactory, FallbackBackendName);
}
catch (Exception fallbackException)
{
DisposeActiveTransport();
throw new InvalidOperationException(
$"Both COM backends failed for {settings.PortName}. " +
$"System.IO.Ports: {primaryException.GetType().Name}: {primaryException.Message}. " +
$"ComNative: {fallbackException.GetType().Name}: {fallbackException.Message}",
fallbackException);
}
}
public void Close()
{
activeTransport?.Close();
}
public void DiscardInBuffer()
{
try
{
RequireActiveTransport().DiscardInBuffer();
}
catch (Exception exception) when (TrySwitchToFallback(exception, "discard input buffer"))
{
RequireActiveTransport().DiscardInBuffer();
}
}
public void DiscardOutBuffer()
{
try
{
RequireActiveTransport().DiscardOutBuffer();
}
catch (Exception exception) when (TrySwitchToFallback(exception, "discard output buffer"))
{
RequireActiveTransport().DiscardOutBuffer();
}
}
public string ReadLine()
{
try
{
return RequireActiveTransport().ReadLine();
}
catch (TimeoutException)
{
throw;
}
catch (Exception exception) when (TrySwitchToFallback(exception, "read"))
{
return RequireActiveTransport().ReadLine();
}
}
public void WriteLine(string value)
{
try
{
RequireActiveTransport().WriteLine(value);
}
catch (Exception exception) when (TrySwitchToFallback(exception, "write"))
{
RequireActiveTransport().WriteLine(value);
}
}
public void Dispose()
{
DisposeActiveTransport();
}
private void OpenWith(IComTransportFactory factory, string backendName)
{
activeTransport = factory.Create(settings);
activeTransport.Open();
activeBackendName = backendName;
Debug.Log($"[ComSystem] {settings.PortName} serial backend selected: {activeBackendName}.");
}
private IComTransport RequireActiveTransport()
{
if (activeTransport == null)
{
throw new InvalidOperationException($"COM port {settings.PortName} has no active transport backend.");
}
return activeTransport;
}
private bool TrySwitchToFallback(Exception exception, string operationName)
{
if (!string.Equals(activeBackendName, PrimaryBackendName, StringComparison.Ordinal))
{
return false;
}
Debug.LogWarning(
$"[ComSystem] {PrimaryBackendName} {operationName} failed for {settings.PortName}: " +
$"{exception.GetType().Name}: {exception.Message}. Switching to {FallbackBackendName}.");
DisposeActiveTransport();
OpenWith(fallbackFactory, FallbackBackendName);
return true;
}
private void DisposeActiveTransport()
{
try
{
activeTransport?.Dispose();
}
finally
{
activeTransport = null;
activeBackendName = "";
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 931f17b413d5e4b4ab7315bb21e810a0

View File

@ -0,0 +1,266 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
namespace DanroJump.Hardware.Com
{
/// <summary>
/// Получает список COM-портов через native-плагин ComNative.
/// </summary>
public sealed class NativeComPortProvider : IComPortProvider
{
public string[] GetPortNames()
{
try
{
string[] ports = ComNativeMethods.ListPorts();
Debug.Log($"[ComSystem] ComNative.ListPorts returned: {(ports.Length > 0 ? string.Join(", ", ports) : "<none>")}");
return ports;
}
catch (Exception exception)
{
Debug.LogWarning($"[ComSystem] ComNative.ListPorts failed: {exception.GetType().Name}: {exception.Message}");
return Array.Empty<string>();
}
}
}
/// <summary>
/// Создает COM-транспорт через native-плагин ComNative.
/// </summary>
public sealed class NativeComTransportFactory : IComTransportFactory
{
public IComTransport Create(SerialSettings settings)
{
return new NativeComTransport(settings);
}
}
/// <summary>
/// Native serial-транспорт для IL2CPP-билдов, где System.IO.Ports.SerialPort может быть недоступен.
/// </summary>
internal sealed class NativeComTransport : IComTransport
{
private const int ReadBufferSize = 4096;
private const int MaxFlushLines = 100;
private readonly SerialSettings settings;
private int handleId;
public NativeComTransport(SerialSettings settings)
{
this.settings = settings;
}
public bool IsOpen => handleId > 0;
public void Open()
{
if (IsOpen)
{
return;
}
handleId = ComNativeMethods.Serial_Open(
settings.PortName,
settings.BaudRate,
settings.DataBits,
ConvertParity(settings.Parity),
ConvertStopBits(settings.StopBits),
settings.ReadTimeoutMs,
settings.WriteTimeoutMs);
if (handleId <= 0)
{
string nativeError = ComNativeMethods.GetLastError();
handleId = 0;
throw new IOException($"ComNative failed to open {settings.PortName}: {nativeError}");
}
Debug.Log($"[ComSystem] ComNative opened {settings.PortName} with handle {handleId}.");
}
public void Close()
{
if (handleId <= 0)
{
return;
}
int closingHandle = handleId;
handleId = 0;
int result = ComNativeMethods.Serial_Close(closingHandle);
if (result < 0)
{
Debug.LogWarning($"[ComSystem] ComNative close failed for handle {closingHandle}: {ComNativeMethods.GetLastError()}");
}
}
public void DiscardInBuffer()
{
EnsureOpen();
byte[] buffer = new byte[ReadBufferSize];
for (int i = 0; i < MaxFlushLines; i++)
{
int read = ComNativeMethods.Serial_ReadLineUTF8_bytes(handleId, buffer, buffer.Length);
if (read <= 0)
{
return;
}
}
}
public void DiscardOutBuffer()
{
// Native plugin has no explicit outgoing-buffer flush API.
}
public string ReadLine()
{
EnsureOpen();
byte[] buffer = new byte[ReadBufferSize];
int read = ComNativeMethods.Serial_ReadLineUTF8_bytes(handleId, buffer, buffer.Length);
if (read == 0)
{
throw new TimeoutException();
}
if (read < 0)
{
throw new IOException($"ComNative read failed on {settings.PortName}: {ComNativeMethods.GetLastError()}");
}
int count = Math.Min(read, buffer.Length);
return Encoding.UTF8.GetString(buffer, 0, count).TrimEnd('\r', '\n');
}
public void WriteLine(string value)
{
EnsureOpen();
int result = ComNativeMethods.Serial_WriteStringUTF8(handleId, value ?? "", appendNewline: 1);
if (result < 0)
{
throw new IOException($"ComNative write failed on {settings.PortName}: {ComNativeMethods.GetLastError()}");
}
}
public void Dispose()
{
Close();
}
private void EnsureOpen()
{
if (!IsOpen)
{
throw new InvalidOperationException($"ComNative port {settings.PortName} is not open.");
}
}
private static int ConvertParity(SerialParity parity)
{
return parity switch
{
SerialParity.Odd => 1,
SerialParity.Even => 2,
SerialParity.Mark => 3,
SerialParity.Space => 4,
_ => 0
};
}
private static int ConvertStopBits(SerialStopBits stopBits)
{
return stopBits switch
{
SerialStopBits.Two => 2,
SerialStopBits.OnePointFive => 3,
_ => 1
};
}
}
internal static class ComNativeMethods
{
private const string Dll = "ComNative";
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int Serial_ListPortsLength();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int Serial_ListPorts(StringBuilder buffer, int bufferChars);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int Serial_Open(
string portName,
int baud,
int dataBits,
int parity,
int stopBits,
int readTimeoutMs,
int writeTimeoutMs);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int Serial_Close(int handleId);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)]
public static extern int Serial_CloseAll();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int Serial_WriteStringUTF8(int handleId, string utf8, int appendNewline);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, EntryPoint = "Serial_ReadLineUTF8")]
public static extern int Serial_ReadLineUTF8_bytes(int handleId, byte[] buffer, int bufferSize);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int Serial_GetLastErrorW(StringBuilder buffer, int bufferChars);
public static string GetLastError()
{
int requiredLength = Serial_GetLastErrorW(null, 0);
if (requiredLength < 0)
{
return $"Serial_GetLastErrorW failed with code {requiredLength}.";
}
var buffer = new StringBuilder(Math.Max(requiredLength, 1));
int result = Serial_GetLastErrorW(buffer, buffer.Capacity);
return result < 0 ? $"Serial_GetLastErrorW failed with code {result}." : buffer.ToString();
}
public static string[] ListPorts()
{
int requiredLength = Serial_ListPortsLength();
if (requiredLength <= 1)
{
return Array.Empty<string>();
}
var buffer = new StringBuilder(requiredLength);
int result = Serial_ListPorts(buffer, buffer.Capacity);
if (result < 0)
{
throw new IOException($"ComNative port listing failed: {GetLastError()}");
}
string joined = buffer.ToString();
if (string.IsNullOrWhiteSpace(joined))
{
return Array.Empty<string>();
}
string[] rawPorts = joined.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < rawPorts.Length; i++)
{
rawPorts[i] = rawPorts[i].Trim();
}
return rawPorts;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 19898e41b209f744f91b180136b8f18c

View File

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
@ -9,29 +10,189 @@ namespace DanroJump.Hardware.Com
/// </summary>
public sealed class ReflectionComPortProvider : IComPortProvider
{
private static readonly Type SerialPortType = Type.GetType("System.IO.Ports.SerialPort, System");
private static readonly Type SerialPortType = FindSerialPortType();
private static bool diagnosticLogged;
/// <summary>
/// Возвращает имена доступных serial-портов или пустой список, если API недоступен.
/// </summary>
public string[] GetPortNames()
{
LogSerialPortDiagnosticOnce();
string[] registryPorts = GetWindowsRegistryPortNames();
if (SerialPortType == null)
{
Debug.LogWarning("[ComSystem] System.IO.Ports.SerialPort is not available in this Unity profile.");
return Array.Empty<string>();
return registryPorts;
}
try
{
return (string[])SerialPortType.GetMethod("GetPortNames", BindingFlags.Public | BindingFlags.Static)
?.Invoke(null, null) ?? Array.Empty<string>();
var getPortNamesMethod = SerialPortType.GetMethod("GetPortNames", BindingFlags.Public | BindingFlags.Static);
if (getPortNamesMethod == null)
{
Debug.LogWarning("[ComSystem] System.IO.Ports.SerialPort.GetPortNames() is not available.");
return registryPorts;
}
var ports = (string[])getPortNamesMethod.Invoke(null, null) ?? Array.Empty<string>();
Debug.Log($"[ComSystem] SerialPort.GetPortNames returned: {(ports.Length > 0 ? string.Join(", ", ports) : "<none>")}");
return ports.Length > 0 ? ports : registryPorts;
}
catch (Exception exception)
{
Debug.LogWarning($"[ComSystem] Failed to list COM ports: {Unwrap(exception).Message}");
Exception unwrapped = Unwrap(exception);
Debug.LogWarning($"[ComSystem] Failed to list COM ports via SerialPort.GetPortNames: {unwrapped.GetType().Name}: {unwrapped.Message}");
return registryPorts;
}
}
private static string[] GetWindowsRegistryPortNames()
{
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
try
{
Type registryType = FindType("Microsoft.Win32.Registry");
Type registryKeyType = FindType("Microsoft.Win32.RegistryKey");
if (registryType == null || registryKeyType == null)
{
Debug.Log("[ComSystem] Windows registry API is not available for COM fallback.");
return Array.Empty<string>();
}
object localMachine = registryType.GetProperty("LocalMachine", BindingFlags.Public | BindingFlags.Static)
?.GetValue(null);
if (localMachine == null)
{
localMachine = registryType.GetField("LocalMachine", BindingFlags.Public | BindingFlags.Static)
?.GetValue(null);
}
if (localMachine == null)
{
Debug.Log("[ComSystem] Windows registry fallback could not access LocalMachine.");
return Array.Empty<string>();
}
MethodInfo openSubKey = registryKeyType.GetMethod(
"OpenSubKey",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(string) },
null);
MethodInfo getValueNames = registryKeyType.GetMethod(
"GetValueNames",
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
MethodInfo getValue = registryKeyType.GetMethod(
"GetValue",
BindingFlags.Public | BindingFlags.Instance,
null,
new[] { typeof(string) },
null);
if (openSubKey == null || getValueNames == null || getValue == null)
{
Debug.Log("[ComSystem] Windows registry fallback methods are not available.");
return Array.Empty<string>();
}
object serialCommKey = openSubKey.Invoke(localMachine, new object[] { @"HARDWARE\DEVICEMAP\SERIALCOMM" });
if (serialCommKey == null)
{
Debug.Log("[ComSystem] Windows registry fallback did not find HARDWARE\\DEVICEMAP\\SERIALCOMM.");
return Array.Empty<string>();
}
using (serialCommKey as IDisposable)
{
var valueNames = (string[])getValueNames.Invoke(serialCommKey, null) ?? Array.Empty<string>();
var ports = new List<string>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (string valueName in valueNames)
{
string portName = getValue.Invoke(serialCommKey, new object[] { valueName }) as string;
if (string.IsNullOrWhiteSpace(portName))
{
continue;
}
portName = portName.Trim();
if (!seen.Add(portName))
{
continue;
}
ports.Add(portName);
}
ports.Sort(StringComparer.OrdinalIgnoreCase);
Debug.Log($"[ComSystem] Windows registry COM fallback returned: {(ports.Count > 0 ? string.Join(", ", ports) : "<none>")}");
return ports.ToArray();
}
}
catch (Exception exception)
{
Exception unwrapped = Unwrap(exception);
Debug.LogWarning($"[ComSystem] Windows registry COM fallback failed: {unwrapped.GetType().Name}: {unwrapped.Message}");
return Array.Empty<string>();
}
#else
return Array.Empty<string>();
#endif
}
private static Type FindSerialPortType()
{
var type = Type.GetType("System.IO.Ports.SerialPort, System", false) ??
Type.GetType("System.IO.Ports.SerialPort, System.IO.Ports", false);
if (type != null)
{
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
type = assembly.GetType("System.IO.Ports.SerialPort", false);
if (type != null)
{
return type;
}
}
return null;
}
private static void LogSerialPortDiagnosticOnce()
{
if (diagnosticLogged)
{
return;
}
diagnosticLogged = true;
string message = SerialPortType == null
? "SerialPort API resolution failed."
: $"SerialPort API resolved from assembly: {SerialPortType.Assembly.FullName}";
Debug.Log($"[ComSystem] {message}");
}
private static Type FindType(string fullName)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type type = assembly.GetType(fullName, false);
if (type != null)
{
return type;
}
}
return null;
}
/// <summary>

View File

@ -25,9 +25,9 @@ namespace DanroJump.Hardware.Com
{
private const BindingFlags InstancePublicFlags = BindingFlags.Public | BindingFlags.Instance;
private static readonly Type SerialPortType = Type.GetType("System.IO.Ports.SerialPort, System");
private static readonly Type ParityType = Type.GetType("System.IO.Ports.Parity, System");
private static readonly Type StopBitsType = Type.GetType("System.IO.Ports.StopBits, System");
private static readonly Type SerialPortType = FindSystemIoPortsType("SerialPort");
private static readonly Type ParityType = FindSystemIoPortsType("Parity");
private static readonly Type StopBitsType = FindSystemIoPortsType("StopBits");
private static readonly PropertyInfo IsOpenProperty = SerialPortType?.GetProperty("IsOpen", InstancePublicFlags);
private static readonly PropertyInfo DataBitsProperty = SerialPortType?.GetProperty("DataBits", InstancePublicFlags);
private static readonly PropertyInfo ParityProperty = SerialPortType?.GetProperty("Parity", InstancePublicFlags);
@ -64,7 +64,9 @@ namespace DanroJump.Hardware.Com
{
if (SerialPortType == null)
{
throw new InvalidOperationException("System.IO.Ports.SerialPort is not available.");
throw new InvalidOperationException(
"System.IO.Ports.SerialPort is not available. " +
"Check Unity API compatibility/scripting backend and whether System.IO.Ports is present in the player.");
}
EnsureRequiredMetadata();
@ -214,5 +216,27 @@ namespace DanroJump.Hardware.Com
return Enum.Parse(enumType, value);
}
private static Type FindSystemIoPortsType(string typeName)
{
string fullName = "System.IO.Ports." + typeName;
var type = Type.GetType(fullName + ", System", false) ??
Type.GetType(fullName + ", System.IO.Ports", false);
if (type != null)
{
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
type = assembly.GetType(fullName, false);
if (type != null)
{
return type;
}
}
return null;
}
}
}

View File

@ -48,8 +48,8 @@ namespace DanroJump.Settings
ServiceSettingsKeys.Currency),
ServiceSettingsKeys.HopperTestButton => ServiceSettingsActionResult.Success(
"Тест выдачи",
"Тест выдачи подготовлен. Аппаратная команда будет подключена через призовой контур.",
"Проверка hopper/выдачи"),
"Запускается тест хоппера: мотор выдачи будет включен до срабатывания датчика или таймаута.",
ServiceSettingsKeys.HopperTestButton),
ServiceSettingsKeys.LevelsPrizesButton => ServiceSettingsActionResult.Success(
"Настройки призов",
PrizeInfoFormatter.Build(settings),

View File

@ -43,6 +43,12 @@ namespace DanroJump.UI.Gameplay
menuButton.RegisterCallback<FocusInEvent>(HandleButtonFocusIn);
menuButton.RegisterCallback<FocusOutEvent>(HandleButtonFocusOut);
}
if (root != null)
{
root.RegisterCallback<KeyDownEvent>(HandleKeyDown, TrickleDown.TrickleDown);
root.RegisterCallback<NavigationMoveEvent>(HandleNavigationMove, TrickleDown.TrickleDown);
}
}
public event Action ContinueRequested;
@ -124,6 +130,12 @@ namespace DanroJump.UI.Gameplay
menuButton.UnregisterCallback<FocusInEvent>(HandleButtonFocusIn);
menuButton.UnregisterCallback<FocusOutEvent>(HandleButtonFocusOut);
}
if (root != null)
{
root.UnregisterCallback<KeyDownEvent>(HandleKeyDown, TrickleDown.TrickleDown);
root.UnregisterCallback<NavigationMoveEvent>(HandleNavigationMove, TrickleDown.TrickleDown);
}
}
private static readonly Color FocusedButtonTint = new Color32(0xCE, 0xFF, 0x1A, 0xFF);
@ -178,5 +190,102 @@ namespace DanroJump.UI.Gameplay
{
return Time.unscaledTime < inputEnabledAtTime;
}
private void HandleKeyDown(KeyDownEvent evt)
{
if (IsInputGuardActive())
{
return;
}
var focusedElement = root?.panel?.focusController?.focusedElement as VisualElement;
var direction = 0;
switch (evt.keyCode)
{
case KeyCode.Tab:
direction = evt.shiftKey ? -1 : 1;
break;
case KeyCode.LeftArrow:
case KeyCode.UpArrow:
case KeyCode.A:
case KeyCode.W:
direction = -1;
break;
case KeyCode.RightArrow:
case KeyCode.DownArrow:
case KeyCode.D:
case KeyCode.S:
direction = 1;
break;
}
if (direction != 0)
{
NavigateFocus(focusedElement, direction);
evt.StopPropagation();
}
}
private void HandleNavigationMove(NavigationMoveEvent evt)
{
if (IsInputGuardActive())
{
return;
}
var focusedElement = root?.panel?.focusController?.focusedElement as VisualElement;
var direction = 0;
switch (evt.direction)
{
case NavigationMoveEvent.Direction.Left:
case NavigationMoveEvent.Direction.Up:
direction = -1;
break;
case NavigationMoveEvent.Direction.Right:
case NavigationMoveEvent.Direction.Down:
direction = 1;
break;
}
if (direction != 0)
{
NavigateFocus(focusedElement, direction);
evt.StopPropagation();
}
}
private void NavigateFocus(VisualElement current, int direction)
{
if (continueButton == null || menuButton == null)
{
return;
}
if (!continueButton.enabledSelf)
{
menuButton.Focus();
return;
}
if (current == continueButton)
{
if (direction > 0)
{
menuButton.Focus();
}
}
else if (current == menuButton)
{
if (direction < 0)
{
continueButton.Focus();
}
}
else
{
FocusDefaultButton();
}
}
}
}

View File

@ -21,7 +21,7 @@ namespace DanroJump.UI.Gameplay
{
[SerializeField] private VisualTreeAsset visualTreeAsset;
[SerializeField] private PanelSettings panelSettings;
[SerializeField] private int sortingOrder = 10;
[SerializeField] private int sortingOrder = 5000;
[SerializeField] private string scoreLabelName = "ScoreValue";
[SerializeField] private string heightLabelName = "HeightValue";
[SerializeField] private string bestHeightLabelName = "BestHeightValue";
@ -59,6 +59,17 @@ namespace DanroJump.UI.Gameplay
private bool isSubscribedToSession;
private bool isSubscribedToPrizeFlow;
private int lastScore = -1;
private int lastHeight = -1;
private int lastRecord = -1;
private int lastLives = -1;
private int lastCoins = -1;
private int lastDeaths = -1;
private int lastCombo = -1;
private GameplaySessionState lastState = (GameplaySessionState)(-1);
private bool isSubscribedToSettings;
private bool enabledStatsInSettings;
/// <summary>
/// Получает игровую сессию из DI-контейнера сцены.
/// </summary>
@ -91,6 +102,7 @@ namespace DanroJump.UI.Gameplay
BindPrizeOverlay();
BindContinueOverlay();
ShowLastPrizeResultIfNeeded();
ResetCache();
Refresh();
}
@ -101,6 +113,7 @@ namespace DanroJump.UI.Gameplay
{
SubscribeToSession();
SubscribeToPrizeFlow();
SubscribeToSettings();
if (refreshCancellation == null)
{
@ -108,6 +121,7 @@ namespace DanroJump.UI.Gameplay
RefreshContinuouslyAsync(refreshCancellation.Token).Forget(Debug.LogException);
}
ResetCache();
Refresh();
}
@ -139,6 +153,7 @@ namespace DanroJump.UI.Gameplay
UnsubscribeFromSession();
UnsubscribeFromPrizeFlow();
UnsubscribeFromSettings();
prizeResultOverlay?.Dispose();
prizeResultOverlay = null;
prizeOverlayRoot = null;
@ -162,9 +177,18 @@ namespace DanroJump.UI.Gameplay
return;
}
document.visualTreeAsset = visualTreeAsset;
document.panelSettings = panelSettings;
document.sortingOrder = sortingOrder;
if (document.visualTreeAsset != visualTreeAsset)
{
document.visualTreeAsset = visualTreeAsset;
}
if (document.panelSettings != panelSettings)
{
document.panelSettings = panelSettings;
}
if (document.sortingOrder != sortingOrder)
{
document.sortingOrder = sortingOrder;
}
}
/// <summary>
@ -191,6 +215,11 @@ namespace DanroJump.UI.Gameplay
private void BindPrizeOverlay()
{
if (prizeResultOverlay != null && prizeOverlayRoot != null && prizeOverlayRoot.panel != null)
{
return;
}
var root = document != null ? document.rootVisualElement : null;
var overlayRoot = root?.Q<VisualElement>("PrizeResultOverlay");
if (overlayRoot == null)
@ -198,12 +227,6 @@ namespace DanroJump.UI.Gameplay
return;
}
if (prizeResultOverlay != null && prizeOverlayRoot == overlayRoot)
{
return;
}
prizeResultOverlay?.Dispose();
prizeOverlayRoot = overlayRoot;
prizeResultOverlay = new PrizeResultOverlayWindow(overlayRoot, audioService);
prizeResultOverlay.ReturnRequested += HandlePrizeReturnRequested;
@ -211,6 +234,11 @@ namespace DanroJump.UI.Gameplay
private void BindContinueOverlay()
{
if (continueGameOverlay != null && continueOverlayRoot != null && continueOverlayRoot.panel != null)
{
return;
}
var root = document != null ? document.rootVisualElement : null;
var overlayRoot = root?.Q<VisualElement>("ContinueGameOverlay");
if (overlayRoot == null)
@ -218,12 +246,6 @@ namespace DanroJump.UI.Gameplay
return;
}
if (continueGameOverlay != null && continueOverlayRoot == overlayRoot)
{
return;
}
continueGameOverlay?.Dispose();
continueOverlayRoot = overlayRoot;
continueGameOverlay = new ContinueGameOverlayWindow(overlayRoot, audioService);
continueGameOverlay.ContinueRequested += HandleContinueRequested;
@ -243,29 +265,76 @@ namespace DanroJump.UI.Gameplay
return;
}
SetLabel(scoreLabel, session.Score.ToString());
SetLabel(heightLabel, $"{session.MaxRunHeight:0} м");
SetLabel(bestHeightLabel, $"{session.RecordHeight:0} м");
SetLabel(livesLabel, session.RemainingLives.ToString());
SetLabel(coinsLabel, session.Coins.ToString());
SetLabel(deathsLabel, session.DeathCount.ToString());
SetLabel(stateLabel, FormatState(session.State));
int currentScore = session.Score;
if (currentScore != lastScore)
{
SetLabel(scoreLabel, currentScore.ToString());
lastScore = currentScore;
}
int currentHeight = Mathf.FloorToInt(session.MaxRunHeight);
if (currentHeight != lastHeight)
{
SetLabel(heightLabel, $"{currentHeight} м");
lastHeight = currentHeight;
}
int currentRecord = Mathf.FloorToInt(session.RecordHeight);
if (currentRecord != lastRecord)
{
SetLabel(bestHeightLabel, $"{currentRecord} м");
lastRecord = currentRecord;
}
int currentLives = session.RemainingLives;
if (currentLives != lastLives)
{
SetLabel(livesLabel, currentLives.ToString());
lastLives = currentLives;
}
int currentCoins = session.Coins;
if (currentCoins != lastCoins)
{
SetLabel(coinsLabel, currentCoins.ToString());
lastCoins = currentCoins;
}
int currentDeaths = session.DeathCount;
if (currentDeaths != lastDeaths)
{
SetLabel(deathsLabel, currentDeaths.ToString());
lastDeaths = currentDeaths;
}
GameplaySessionState currentState = session.State;
if (currentState != lastState)
{
SetLabel(stateLabel, FormatState(currentState));
lastState = currentState;
}
if (comboLabel != null)
{
if (session.CurrentCombo > 1)
int currentCombo = session.CurrentCombo;
if (currentCombo > 1)
{
var comboText = $"x{session.CurrentCombo} COMBO!";
if (comboLabel.text != comboText)
if (currentCombo != lastCombo)
{
var comboText = $"x{currentCombo} COMBO!";
comboLabel.text = comboText;
comboLabel.style.display = DisplayStyle.Flex;
PulseComboLabel();
lastCombo = currentCombo;
}
}
else
{
comboLabel.style.display = DisplayStyle.None;
if (lastCombo != currentCombo)
{
comboLabel.style.display = DisplayStyle.None;
lastCombo = currentCombo;
}
}
}
@ -320,12 +389,14 @@ namespace DanroJump.UI.Gameplay
return;
}
developmentStatsRoot.style.display = ShouldShowDevelopmentStats
? DisplayStyle.Flex
: DisplayStyle.None;
var targetDisplay = ShouldShowDevelopmentStats ? DisplayStyle.Flex : DisplayStyle.None;
if (developmentStatsRoot.style.display != targetDisplay)
{
developmentStatsRoot.style.display = targetDisplay;
}
}
private static bool ShouldShowDevelopmentStats => Debug.isDebugBuild || Application.isEditor;
private bool ShouldShowDevelopmentStats => enabledStatsInSettings && (Debug.isDebugBuild || Application.isEditor);
/// <summary>
/// Подстраховывает HUD от событий, которые могли прийти до подписки.
@ -368,10 +439,60 @@ namespace DanroJump.UI.Gameplay
UnsubscribeFromSession();
session = injectedSession;
ResetCache();
SubscribeToSession();
Refresh();
}
private void ResetCache()
{
lastScore = -1;
lastHeight = -1;
lastRecord = -1;
lastLives = -1;
lastCoins = -1;
lastDeaths = -1;
lastCombo = -1;
lastState = (GameplaySessionState)(-1);
}
private void SubscribeToSettings()
{
if (serviceSettings == null || isSubscribedToSettings)
{
return;
}
serviceSettings.SettingChanged += HandleSettingChanged;
isSubscribedToSettings = true;
UpdateStatsVisibilityCache();
}
private void UnsubscribeFromSettings()
{
if (serviceSettings == null || !isSubscribedToSettings)
{
return;
}
serviceSettings.SettingChanged -= HandleSettingChanged;
isSubscribedToSettings = false;
}
private void HandleSettingChanged(string key, DanroJump.Settings.ServiceSettingValue value)
{
if (key == DanroJump.Settings.ServiceSettingsKeys.StatisticsButton)
{
UpdateStatsVisibilityCache();
RefreshDevelopmentStatsVisibility();
}
}
private void UpdateStatsVisibilityCache()
{
enabledStatsInSettings = serviceSettings != null && serviceSettings.GetBool(DanroJump.Settings.ServiceSettingsKeys.StatisticsButton);
}
private void SubscribeToSession()
{
if (!isActiveAndEnabled || session == null || isSubscribedToSession)
@ -612,10 +733,9 @@ namespace DanroJump.UI.Gameplay
if (result.RewardKind == PrizeRewardKind.Hopper)
{
success = await DispenseHopperAsync(comSystem, token);
detailMessage = success
? "Капсула выдана успешно!"
: "Ошибка выдачи: датчик хоппера не сработал за 10 секунд.";
var hopperResult = await ComHopperDispenser.DispenseAsync(comSystem, token);
success = hopperResult.IsSuccess;
detailMessage = hopperResult.Message;
}
else if (result.RewardKind == PrizeRewardKind.PrizeStand)
{
@ -676,35 +796,5 @@ namespace DanroJump.UI.Gameplay
}
}
private async UniTask<bool> DispenseHopperAsync(IComSystem com, CancellationToken token)
{
if (com == null)
{
return false;
}
if (!com.SendToMain("301"))
{
return false;
}
var tcs = new UniTaskCompletionSource();
UnityEngine.Events.UnityAction onTriggered = () => tcs.TrySetResult();
com.HopperSensorTriggered.AddListener(onTriggered);
try
{
await UniTask.WhenAny(
tcs.Task,
UniTask.Delay(TimeSpan.FromSeconds(10), cancellationToken: token)
);
return tcs.Task.Status == UniTaskStatus.Succeeded;
}
finally
{
com.HopperSensorTriggered.RemoveListener(onTriggered);
com.SendToMain("300");
}
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using DanroJump.Audio;
@ -66,6 +67,7 @@ namespace DanroJump.UI.MainMenu
private IReadOnlyServiceSettings serviceSettings;
private IServiceSettingsService serviceSettingsService;
private ISceneFlowService sceneFlow;
private IComSystem comSystem;
private IComCreditWallet creditWallet;
private IGameAudioService audioService;
private IPrizeSessionStateService prizeSessionState;
@ -92,6 +94,7 @@ namespace DanroJump.UI.MainMenu
serviceSettingsService = injectedSettingsService;
serviceSettings = injectedServiceSettings ?? injectedSettingsService;
sceneFlow = injectedSceneFlow;
comSystem = injectedComSystem;
creditWallet = injectedComSystem;
audioService = injectedAudioService;
prizeSessionState = injectedPrizeSessionState;
@ -253,6 +256,11 @@ namespace DanroJump.UI.MainMenu
return;
}
if (TryRunHopperTest(result))
{
return;
}
if (serviceInfoModal == null || serviceInfoModal.Root == null)
{
return;
@ -262,6 +270,64 @@ namespace DanroJump.UI.MainMenu
modalStack?.Open(serviceInfoModal);
}
private bool TryRunHopperTest(ServiceSettingsActionResult result)
{
if (!string.Equals(result.FocusKey, ServiceSettingsKeys.HopperTestButton, StringComparison.Ordinal))
{
return false;
}
if (serviceInfoModal == null || serviceInfoModal.Root == null)
{
return false;
}
serviceInfoModal.SetContent(
"Тест выдачи",
"Запускается тест хоппера...",
"Хоппер");
modalStack?.Open(serviceInfoModal);
RunHopperTestAsync(this.GetCancellationTokenOnDestroy()).Forget(Debug.LogException);
return true;
}
private async UniTask RunHopperTestAsync(CancellationToken cancellationToken)
{
ResolveComSystemIfNeeded();
if (comSystem == null)
{
audioService?.Play(GameAudioEvent.UiError);
serviceInfoModal?.SetContent(
"Тест выдачи",
"Ошибка: COM-система недоступна. Проверь подключение платы и запуск COM-сервиса.",
"Хоппер");
return;
}
try
{
serviceInfoModal?.SetContent(
"Тест выдачи",
"Хоппер включен. Ожидаю срабатывание датчика выдачи...",
"Хоппер");
var result = await ComHopperDispenser.DispenseAsync(comSystem, cancellationToken);
audioService?.Play(result.IsSuccess ? GameAudioEvent.UiSubmit : GameAudioEvent.UiError);
serviceInfoModal?.SetContent(
"Тест выдачи",
result.Message,
"Хоппер");
}
catch (OperationCanceledException)
{
serviceInfoModal?.SetContent(
"Тест выдачи",
"Тест хоппера отменен.",
"Хоппер");
}
}
private bool TryOpenServiceTextInput(ServiceSettingsActionResult result)
{
if (serviceSettingsService == null ||
@ -659,7 +725,13 @@ namespace DanroJump.UI.MainMenu
if (creditWallet == null && projectContext.Container.HasBinding<IComSystem>())
{
creditWallet = projectContext.Container.Resolve<IComSystem>();
comSystem = projectContext.Container.Resolve<IComSystem>();
creditWallet = comSystem;
}
if (comSystem == null && projectContext.Container.HasBinding<IComSystem>())
{
comSystem = projectContext.Container.Resolve<IComSystem>();
}
if (creditWallet == null && projectContext.Container.HasBinding<IComCreditWallet>())
@ -693,7 +765,18 @@ namespace DanroJump.UI.MainMenu
}
ResolveServiceSettingsIfNeeded();
creditWallet ??= ComSystem.Instance;
creditWallet ??= comSystem ?? ComSystem.Instance;
}
private void ResolveComSystemIfNeeded()
{
if (comSystem != null)
{
return;
}
ResolveServiceSettingsIfNeeded();
comSystem ??= ComSystem.Instance;
}
private void RegisterCreditCallbacks()

View File

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using DanroJump.Hardware.Com;
using NUnit.Framework;
namespace DanroJump.Tests.EditMode
{
public sealed class ComHopperDispenserTests
{
[Test]
public async Task DispenseAsync_StartsAndStopsMotorOnTimeout()
{
var com = new StubComSystem { IsRunningValue = true };
var result = await ComHopperDispenser
.DispenseAsync(com, TimeSpan.Zero, CancellationToken.None)
.AsTask();
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Message, Does.Contain("Датчик хоппера"));
Assert.That(com.SentCommands, Is.EqualTo(new[]
{
ComHopperDispenser.StartMotorCommand,
ComHopperDispenser.StopMotorCommand
}));
}
[Test]
public async Task DispenseAsync_NotRunningComReturnsFailureWithoutCommands()
{
var com = new StubComSystem { IsRunningValue = false };
var result = await ComHopperDispenser
.DispenseAsync(com, TimeSpan.Zero, CancellationToken.None)
.AsTask();
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Message, Does.Contain("не запущена"));
Assert.That(com.SentCommands, Is.Empty);
}
private sealed class StubComSystem : IComSystem
{
public readonly List<string> SentCommands = new();
public bool IsRunningValue { get; set; }
public bool IsRunning => IsRunningValue;
public string MainDeviceId => "main";
public string PrizeStandDeviceId => "stand";
public IReadOnlyCollection<string> ConnectedDeviceIds => new[] { "main" };
public UnityEngine.Events.UnityEvent<string> DeviceAuthorized { get; } = new();
public UnityEngine.Events.UnityEvent<string> DeviceDisconnected { get; } = new();
public UnityEngine.Events.UnityEvent<int> PrizeBoxOpened { get; } = new();
public int Credits => 0;
public UnityEngine.Events.UnityEvent<int> CreditAdded { get; } = new();
public UnityEngine.Events.UnityEvent<int> CreditRemoved { get; } = new();
public UnityEngine.Events.UnityEvent<int> CreditsChanged { get; } = new();
public string LastMainInputBits => string.Empty;
public string LastMainAdcValues => string.Empty;
public UnityEngine.Events.UnityEvent<string> MainInputReceived { get; } = new();
public UnityEngine.Events.UnityEvent<string> MainAdcReceived { get; } = new();
public UnityEngine.Events.UnityEvent HopperSensorTriggered { get; } = new();
public void StartSystem() { }
public void StopSystem(bool sendShutdownCommands) { }
public bool SendToMain(string command)
{
SentCommands.Add(command);
return true;
}
public bool SendToDevice(string deviceId, string command) => true;
public void SendToAll(string command) { }
public string GetDeviceRole(string deviceId) => string.Empty;
public string GetDeviceMac(string deviceId) => string.Empty;
public UniTask<IReadOnlyDictionary<int, PrizeBoxStatus>> GetPrizeBoxStatusesAsync(CancellationToken cancellationToken = default)
{
return UniTask.FromResult<IReadOnlyDictionary<int, PrizeBoxStatus>>(new Dictionary<int, PrizeBoxStatus>());
}
public UniTask<int[]> GetReadyPrizeBoxesAsync(CancellationToken cancellationToken = default)
{
return UniTask.FromResult(Array.Empty<int>());
}
public UniTask<PrizeOpenResult> OpenPrizeBoxAsync(int boxNumber, CancellationToken cancellationToken = default)
{
return UniTask.FromResult(PrizeOpenResult.Fail(boxNumber, "stub"));
}
public UniTask<PrizeOpenResult> ForceOpenPrizeBoxAsync(int boxNumber, CancellationToken cancellationToken = default)
{
return UniTask.FromResult(PrizeOpenResult.Fail(boxNumber, "stub"));
}
public bool SetPrizeStandIdleLighting() => true;
public bool HighlightPrizeBox(int boxNumber) => true;
public void AddCredit(int amount = 1) { }
public bool TrySpendCredits(int amount = 1) => false;
public void SetCredits(int value) { }
public string GetLastInputBits(string deviceId) => string.Empty;
public string GetLastAdcValues(string deviceId) => string.Empty;
public void PlayBoardLight(LightMode mode, float cycles = -1f) { }
public void PlayBoardLightSequence(IReadOnlyList<LightStep> steps, bool loop) { }
public void PlayDefaultBoardLight() { }
public void StopBoardLightSequence() { }
public void StopAllBoardEffects() { }
public void BlinkHat() { }
public void BlinkHat(float intervalSeconds) { }
public void StopHatBlink(bool leaveHatEnabled = true) { }
public void PauseLighting() { }
public void ResumeLighting() { }
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d77880810c968aa408b9b36b0ee7e9ea

View File

@ -0,0 +1,168 @@
using System;
using DanroJump.Hardware.Com;
using NUnit.Framework;
namespace DanroJump.Tests.EditMode
{
public sealed class ComTransportFallbackTests
{
private static readonly SerialSettings TestSettings = new(
"COM_TEST",
115200,
8,
SerialParity.None,
SerialStopBits.One,
50,
100);
[Test]
public void FallbackComTransportFactory_Open_UsesPrimaryWhenPrimarySucceeds()
{
var primaryTransport = new FakeComTransport();
var fallbackTransport = new FakeComTransport();
var factory = new FallbackComTransportFactory(
new FakeComTransportFactory(primaryTransport),
new FakeComTransportFactory(fallbackTransport));
using IComTransport transport = factory.Create(TestSettings);
transport.Open();
Assert.That(primaryTransport.OpenCount, Is.EqualTo(1));
Assert.That(fallbackTransport.OpenCount, Is.EqualTo(0));
Assert.That(transport.IsOpen, Is.True);
}
[Test]
public void FallbackComTransportFactory_Open_UsesFallbackWhenPrimaryOpenFails()
{
var primaryTransport = new FakeComTransport(openException: new InvalidOperationException("SerialPort failed"));
var fallbackTransport = new FakeComTransport();
var factory = new FallbackComTransportFactory(
new FakeComTransportFactory(primaryTransport),
new FakeComTransportFactory(fallbackTransport));
using IComTransport transport = factory.Create(TestSettings);
transport.Open();
Assert.That(primaryTransport.OpenCount, Is.EqualTo(1));
Assert.That(primaryTransport.DisposeCount, Is.EqualTo(1));
Assert.That(fallbackTransport.OpenCount, Is.EqualTo(1));
Assert.That(transport.IsOpen, Is.True);
}
[Test]
public void FallbackComTransportFactory_DiscardInBuffer_UsesFallbackWhenPrimaryOperationFails()
{
var primaryTransport = new FakeComTransport(discardInException: new NotSupportedException("Discard failed"));
var fallbackTransport = new FakeComTransport();
var factory = new FallbackComTransportFactory(
new FakeComTransportFactory(primaryTransport),
new FakeComTransportFactory(fallbackTransport));
using IComTransport transport = factory.Create(TestSettings);
transport.Open();
transport.DiscardInBuffer();
Assert.That(primaryTransport.OpenCount, Is.EqualTo(1));
Assert.That(primaryTransport.DisposeCount, Is.EqualTo(1));
Assert.That(fallbackTransport.OpenCount, Is.EqualTo(1));
Assert.That(fallbackTransport.DiscardInBufferCount, Is.EqualTo(1));
Assert.That(transport.IsOpen, Is.True);
}
[Test]
public void FallbackComPortProvider_GetPortNames_UsesFallbackWhenPrimaryReturnsEmpty()
{
var provider = new FallbackComPortProvider(
new FakeComPortProvider(Array.Empty<string>()),
new FakeComPortProvider(new[] { "COM3" }));
string[] ports = provider.GetPortNames();
Assert.That(ports, Is.EqualTo(new[] { "COM3" }));
}
private sealed class FakeComPortProvider : IComPortProvider
{
private readonly string[] ports;
public FakeComPortProvider(string[] ports)
{
this.ports = ports;
}
public string[] GetPortNames()
{
return ports;
}
}
private sealed class FakeComTransportFactory : IComTransportFactory
{
private readonly IComTransport transport;
public FakeComTransportFactory(IComTransport transport)
{
this.transport = transport;
}
public IComTransport Create(SerialSettings settings)
{
return transport;
}
}
private sealed class FakeComTransport : IComTransport
{
private readonly Exception openException;
private readonly Exception discardInException;
private bool isOpen;
public FakeComTransport(Exception openException = null, Exception discardInException = null)
{
this.openException = openException;
this.discardInException = discardInException;
}
public int OpenCount { get; private set; }
public int DiscardInBufferCount { get; private set; }
public int DisposeCount { get; private set; }
public bool IsOpen => isOpen;
public void Open()
{
OpenCount++;
if (openException != null)
{
throw openException;
}
isOpen = true;
}
public void Close()
{
isOpen = false;
}
public void DiscardInBuffer()
{
DiscardInBufferCount++;
if (discardInException != null)
{
throw discardInException;
}
}
public void DiscardOutBuffer() { }
public string ReadLine() => "";
public void WriteLine(string value) { }
public void Dispose()
{
DisposeCount++;
Close();
}
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 58c3e63a06e9fc04eac935acec081a3c

View File

@ -21,7 +21,7 @@ namespace DanroJump.Tests.EditMode
try
{
var controller = gameObject.AddComponent<GameplayHudController>();
var session = new GameplaySession(null, new StubSettings(canContinue: false));
var session = new GameplaySession(null, new StubSettings(canContinue: true));
TestObjectFactory.SetPrivateField(session, "<State>k__BackingField", GameplaySessionState.GameOver);
var overlayRoot = CreateContinueOverlayRoot();
@ -76,7 +76,7 @@ namespace DanroJump.Tests.EditMode
public IReadOnlyDictionary<string, ServiceSettingValue> Snapshot() => new Dictionary<string, ServiceSettingValue>();
public ServiceSettingValue Get(string key) => default;
public bool GetBool(string key) => key == ServiceSettingsKeys.CanContinueGame && canContinue;
public int GetInt(string key) => 0;
public int GetInt(string key) => key == ServiceSettingsKeys.ContinueGameCost ? 1 : 0;
public float GetFloat(string key) => 0f;
public string GetString(string key) => string.Empty;
public int GetOptionIndex(string key) => 0;
@ -118,6 +118,41 @@ namespace DanroJump.Tests.EditMode
}
}
[Test]
public void RefreshDevelopmentStatsVisibility_RequiresServiceSetting()
{
var gameObject = new GameObject("GameplayHudControllerTest");
try
{
var controller = gameObject.AddComponent<GameplayHudController>();
var statsRoot = new VisualElement { name = "RightStats" };
TestObjectFactory.SetPrivateField(controller, "developmentStatsRoot", statsRoot);
TestObjectFactory.SetPrivateField(controller, "enabledStatsInSettings", false);
InvokeRefreshDevelopmentStatsVisibility(controller);
Assert.That(statsRoot.style.display.value, Is.EqualTo(DisplayStyle.None));
TestObjectFactory.SetPrivateField(controller, "enabledStatsInSettings", true);
InvokeRefreshDevelopmentStatsVisibility(controller);
Assert.That(statsRoot.style.display.value, Is.EqualTo(DisplayStyle.Flex));
}
finally
{
UnityEngine.Object.DestroyImmediate(gameObject);
}
}
private static void InvokeRefreshDevelopmentStatsVisibility(GameplayHudController controller)
{
typeof(GameplayHudController)
.GetMethod("RefreshDevelopmentStatsVisibility", BindingFlags.Instance | BindingFlags.NonPublic)
?.Invoke(controller, null);
}
private sealed class StubComSystem : IComSystem
{
public List<string> SentCommands = new List<string>();

View File

@ -0,0 +1,79 @@
using DanroJump.Gameplay;
using NUnit.Framework;
using UnityEngine;
namespace DanroJump.Tests.EditMode
{
public sealed class OneWayPlatformColliderTests
{
private GameObject playerObject;
private GameObject platformObject;
[TearDown]
public void TearDown()
{
if (playerObject != null)
{
Object.DestroyImmediate(playerObject);
}
if (platformObject != null)
{
Object.DestroyImmediate(platformObject);
}
}
[Test]
public void ShouldCollideWith_ReturnsFalseWhenPlayerBottomIsBelowPlatformTop()
{
var player = CreatePlayer(centerY: 0.45f, previousGroundCheckY: 0.5f);
var oneWayPlatform = CreatePlatform(topY: 0f);
Assert.That(oneWayPlatform.ShouldCollideWith(player), Is.False);
}
[Test]
public void ShouldCollideWith_ReturnsTrueWhenPlayerBottomIsAtPlatformTop()
{
var player = CreatePlayer(centerY: 0.5f, previousGroundCheckY: 0.5f);
var oneWayPlatform = CreatePlatform(topY: 0f);
Assert.That(oneWayPlatform.ShouldCollideWith(player), Is.True);
}
private JumpPlayerController CreatePlayer(float centerY, float previousGroundCheckY)
{
playerObject = new GameObject("OneWay Platform Test Player");
playerObject.transform.position = new Vector3(0f, centerY, 0f);
var groundCheckObject = new GameObject("GroundCheck");
groundCheckObject.transform.SetParent(playerObject.transform, false);
groundCheckObject.transform.localPosition = new Vector3(0f, -0.5f, 0f);
var body = playerObject.AddComponent<Rigidbody2D>();
body.gravityScale = 0f;
body.linearVelocity = Vector2.down;
playerObject.AddComponent<BoxCollider2D>();
var player = playerObject.AddComponent<JumpPlayerController>();
TestObjectFactory.SetPrivateField(player, "<PreviousGroundCheckY>k__BackingField", previousGroundCheckY);
TestObjectFactory.SetPrivateField(player, "<PreviousVerticalVelocity>k__BackingField", -1f);
return player;
}
private OneWayPlatformCollider CreatePlatform(float topY)
{
platformObject = new GameObject("OneWay Platform Test Platform");
platformObject.transform.position = Vector3.zero;
var platformCollider = platformObject.AddComponent<BoxCollider2D>();
platformCollider.size = new Vector2(2f, 0.2f);
platformCollider.offset = new Vector2(0f, topY - platformCollider.size.y * 0.5f);
var oneWayPlatform = platformObject.AddComponent<OneWayPlatformCollider>();
oneWayPlatform.Construct(playerObject.GetComponent<JumpPlayerController>());
oneWayPlatform.UpdateCachedTopY();
return oneWayPlatform;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6eedb3e116ba1504b8694f7757c6f212

View File

@ -140,6 +140,22 @@ namespace DanroJump.Tests.EditMode
Assert.That(result.Status, Does.Contain("малый приз включён"));
}
[Test]
public void HopperTestButton_ReturnsHopperTestFocusKey()
{
var definition = TestObjectFactory.CreateSettingDefinition(
ServiceSettingsKeys.HopperTestButton,
ServiceSettingValueType.Button,
ServiceSettingValue.Button());
var result = ServiceSettingsActionRouter.Execute(definition, new StubReadOnlySettings());
Assert.That(result.Handled, Is.True);
Assert.That(result.Title, Is.EqualTo("Тест выдачи"));
Assert.That(result.FocusKey, Is.EqualTo(ServiceSettingsKeys.HopperTestButton));
Assert.That(result.Status, Does.Contain("тест хоппера"));
}
[Test]
public void UnknownButton_ReturnsSafeFallback()

View File

@ -3,31 +3,52 @@
left: 0;
right: 0;
top: 0;
bottom: 0;
padding-left: 24px;
padding-right: 24px;
padding-top: 20px;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
flex-direction: column;
}
.hud-column {
flex-direction: column;
flex-grow: 0;
flex-shrink: 0;
}
.hud-column-left {
min-width: 460px;
position: absolute;
left: 24px;
top: 20px;
width: 240px;
min-width: 0;
max-width: 240px;
}
.hud-column-right {
min-width: 500px;
position: absolute;
right: 24px;
top: 190px;
width: 360px;
min-width: 0;
max-width: 360px;
align-items: flex-end;
}
.hud-center {
flex-grow: 1;
position: absolute;
left: 50%;
top: 20px;
width: 500px;
height: 130px;
min-width: 0;
max-width: 500px;
margin-left: -250px;
flex-grow: 0;
flex-shrink: 0;
align-items: center;
justify-content: flex-start;
background-size: 100% 100%;
}
.hud-plaque,
@ -45,20 +66,22 @@
}
.hud-plaque-score {
width: 450px;
min-height: 154px;
padding-left: 64px;
padding-right: 64px;
padding-top: 24px;
padding-bottom: 22px;
width: 240px;
min-height: 104px;
padding-left: 14px;
padding-right: 24px;
padding-top: 16px;
padding-bottom: 14px;
background-image: url("project://database/Assets/Sprites/UI/Components/plashka_side.png");
}
.hud-height-plaque {
min-width: 560px;
height: 156px;
padding-left: 68px;
padding-right: 68px;
width: 500px;
min-width: 0;
max-width: 500px;
height: 130px;
padding-left: 82px;
padding-right: 82px;
flex-direction: row;
align-items: center;
justify-content: center;
@ -66,7 +89,10 @@
}
.hud-height-text {
min-width: 280px;
width: 280px;
min-width: 0;
max-width: 280px;
margin-top: 0;
align-items: center;
}
@ -86,45 +112,45 @@
}
.hud-score-value {
margin-top: -6px;
margin-top: -14px;
color: rgb(31, 79, 63);
font-size: 66px;
font-size: 36px;
-unity-font-style: bold;
}
.hud-height-value {
margin-top: -2px;
color: rgb(31, 79, 63);
font-size: 58px;
font-size: 32px;
-unity-font-style: bold;
-unity-text-align: middle-center;
}
.hud-stat-list {
width: 500px;
width: 360px;
align-items: stretch;
}
.hud-stat-row {
height: 86px;
margin-bottom: 10px;
padding-left: 34px;
padding-right: 42px;
height: 72px;
margin-bottom: 8px;
padding-left: 24px;
padding-right: 28px;
flex-direction: row;
align-items: center;
background-image: url("project://database/Assets/Sprites/UI/Components/plashka_small.png");
}
.hud-stat-caption {
width: 156px;
margin-left: 14px;
font-size: 20px;
width: 124px;
margin-left: 10px;
font-size: 18px;
}
.hud-stat-value {
flex-grow: 1;
color: rgb(31, 79, 63);
font-size: 34px;
font-size: 28px;
-unity-font-style: bold;
-unity-text-align: middle-right;
}
@ -149,8 +175,8 @@
}
.hud-mini-icon {
width: 42px;
height: 42px;
width: 36px;
height: 36px;
-unity-background-scale-mode: scale-to-fit;
background-size: contain;
}
@ -209,8 +235,8 @@
.overlay-panel {
max-width: 1180px;
min-width: 760px;
min-height: 430px;
min-width: 900px;
min-height: 520px;
padding-left: 86px;
padding-right: 86px;
padding-top: 72px;
@ -222,7 +248,8 @@
.prize-panel {
max-width: 1280px;
min-height: 500px;
min-width: 1100px;
min-height: 650px;
}
.overlay-title {
@ -299,18 +326,17 @@
}
.overlay-button {
min-width: 360px;
height: 106px;
min-width: 420px;
height: 120px;
color: rgb(31, 79, 63);
font-size: 36px;
font-size: 32px;
-unity-font-style: bold;
background-image: url("project://database/Assets/Sprites/UI/Components/button_white.png");
-unity-font-definition: url("project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&guid=63991fec8a53abe4d9a7907ef7f0db77&type=3#RifficFree-Bold");
}
.overlay-button-primary {
min-width: 430px;
background-image: url("project://database/Assets/Sprites/UI/Components/button.png");
min-width: 480px;
}
.overlay-button-secondary {
@ -319,7 +345,7 @@
.overlay-button-single {
margin-top: 66px;
min-width: 430px;
min-width: 480px;
}
.prize-stand-container {

View File

@ -2,22 +2,22 @@
<ui:Template name="GameplayOverlays" src="project://database/Assets/UI/Gameplay/Templates/GameplayOverlays.uxml?fileID=9197481963319205126&amp;guid=0c975b01026d5194dbc5f5264f0eee67&amp;type=3#GameplayOverlays"/>
<Style src="project://database/Assets/UI/Gameplay/GameplayHud.uss?fileID=7433441132597879392&amp;guid=40e3d2a4ed0a8e84ba40618caa5869d3&amp;type=3#GameplayHud"/>
<ui:VisualElement name="GameplayHudRoot" class="gameplay-hud-root" style="pointer-events: none;">
<ui:VisualElement name="LeftStats" class="hud-column hud-column-left">
<ui:VisualElement name="VisualElement" class="hud-plaque hud-plaque-score" style="background-image: url(&quot;project://database/Assets/Sprites/UI/Components/icon.png?fileID=6934086728263231946&amp;guid=cd717660f24894143bbbd4102e1709e6&amp;type=3#icon_0&quot;); min-width: 25%; max-width: 100%;">
<ui:Label text="ВЫСОТА" class="hud-caption hud-caption-center" style="font-size: 48px; -unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); color: rgb(255, 255, 255); -unity-text-align: middle-left;"/>
<ui:VisualElement name="LeftStats" class="hud-column hud-column-left" style="max-width: 300px;">
<ui:VisualElement name="VisualElement" class="hud-plaque hud-plaque-score" style="background-image: url(&quot;project://database/Assets/Sprites/UI/Components/icon.png?fileID=6934086728263231946&amp;guid=cd717660f24894143bbbd4102e1709e6&amp;type=3#icon_0&quot;); width: 260px; -unity-slice-scale: 0.5px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px;">
<ui:Label text="ВЫСОТА" class="hud-caption hud-caption-center" style="font-size: 30px; -unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); color: rgb(255, 255, 255); -unity-text-align: middle-left;"/>
<ui:Label name="HeightValue" text="0 м" class="hud-height-value" style="-unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); color: rgb(255, 255, 255); -unity-text-align: middle-left;"/>
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="CenterStats" class="hud-center" style="background-image: url(&quot;project://database/Assets/Sprites/UI/Components/plashka_levels.png?fileID=2800000&amp;guid=cc33cc4cae199f847975aec2300a7950&amp;type=3#plashka_levels&quot;); -unity-slice-left: 0; -unity-slice-top: 0; -unity-slice-right: 0; -unity-slice-bottom: 0; -unity-slice-scale: 100px; background-size: 100% auto;">
<ui:VisualElement name="VisualElement" class="hud-height-plaque" style="background-image: none; min-width: 50%; height: auto; -unity-slice-left: 100; -unity-slice-top: 100; -unity-slice-right: 100; -unity-slice-bottom: 100;">
<ui:VisualElement name="CenterStats" class="hud-center" style="background-image: url(&quot;project://database/Assets/Sprites/UI/Components/plashka_levels.png?fileID=8483911246306948391&amp;guid=cc33cc4cae199f847975aec2300a7950&amp;type=3#plashka_levels_1&quot;); -unity-slice-left: 152; -unity-slice-top: 0; -unity-slice-right: 157; -unity-slice-bottom: 0; -unity-slice-scale: 0.5px; background-size: 100% 100%; width: 360px; height: 120px; left: 50%; margin-left: -180px;">
<ui:VisualElement name="VisualElement" class="hud-height-plaque" style="background-image: none; -unity-slice-left: 100; -unity-slice-top: 100; -unity-slice-right: 100; -unity-slice-bottom: 100; -unity-slice-scale: 1px; padding-left: 48px; padding-right: 48px; width: auto; height: auto; padding-top: 10px;">
<ui:VisualElement name="VisualElement" class="hud-icon hud-icon-crystal" style="padding-right: 32px; padding-left: 64px; display: none;"/>
<ui:VisualElement class="hud-height-text" style="min-width: 75%; padding-top: 24px; padding-right: 32px; padding-bottom: 22px; padding-left: 32px;">
<ui:Label text="ОЧКИ" class="hud-caption" style="color: rgb(255, 255, 255); -unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); font-size: 48px;"/>
<ui:VisualElement class="hud-height-text" style="padding-top: 10px; padding-right: 24px; padding-bottom: 6px; padding-left: 24px;">
<ui:Label text="ОЧКИ" class="hud-caption" style="color: rgb(255, 255, 255); -unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); font-size: 30px;"/>
<ui:Label name="ScoreValue" text="0" class="hud-score-value" style="-unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); color: rgb(255, 255, 255);"/>
</ui:VisualElement>
<ui:VisualElement class="hud-icon hud-icon-crystal hud-icon-crystal-right" style="padding-left: 32px; padding-right: 64px; display: none;"/>
</ui:VisualElement>
<ui:Label name="ComboValue" text="x2 COMBO!" style="position: absolute; bottom: -80px; align-self: center; font-size: 54px; -unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); color: rgb(255, 140, 0); -unity-text-outline-width: 3px; -unity-text-outline-color: rgb(0, 0, 0); text-shadow: 3px 3px 0px rgba(0,0,0,0.5); display: none; transition-property: scale; transition-duration: 0.08s; transition-timing-function: ease-out;" />
<ui:Label name="ComboValue" text="x2 COMBO!" style="position: absolute; bottom: -80px; align-self: center; font-size: 54px; -unity-font-definition: url(&quot;project://database/Assets/Fonts/RifficFree-Bold.ttf?fileID=12800000&amp;guid=63991fec8a53abe4d9a7907ef7f0db77&amp;type=3#RifficFree-Bold&quot;); color: rgb(255, 140, 0); -unity-text-outline-width: 3px; -unity-text-outline-color: rgb(0, 0, 0); text-shadow: 3px 3px 0 rgba(0, 0, 0, 0.5); display: none; transition-property: scale; transition-duration: 0.08s; transition-timing-function: ease-out;"/>
</ui:VisualElement>
<ui:VisualElement name="RightStats" class="hud-column hud-column-right">
<ui:VisualElement class="hud-stat-list">

View File

@ -24,10 +24,10 @@ MonoBehaviour:
m_Scale: 1
m_ReferenceDpi: 96
m_FallbackDpi: 96
m_ReferenceResolution: {x: 1920, y: 1080}
m_ReferenceResolution: {x: 1080, y: 1920}
m_ScreenMatchMode: 0
m_Match: 0
m_SortingOrder: 0
m_Match: 0.5
m_SortingOrder: 5000
m_TargetDisplay: 0
m_BindingLogLevel: 0
m_ClearDepthStencil: 1