ADD : 돈주머니 프랍 추가 및 카메라 전환 버그 해결 및 fov 제어기능 추가
This commit is contained in:
parent
ba817cb4e3
commit
6a7deb6e96
8
Assets/Preset.meta
Normal file
8
Assets/Preset.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ecda75af11211dd4ead9d2948b3d3b7e
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Preset/후원.meta
Normal file
8
Assets/Preset/후원.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6d80c97dc0290914c9ccf17efc450e2f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Preset/후원/WefLab.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/Preset/후원/WefLab.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
7
Assets/Preset/후원/WefLab.prefab.meta
Normal file
7
Assets/Preset/후원/WefLab.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9470035ddd1c1aa4bb67805985097d7d
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Preset/후원/던질 프랍.meta
Normal file
8
Assets/Preset/후원/던질 프랍.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7066e932cb18c694594dc51b487741c2
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Preset/후원/던질 프랍/돈주머니.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/Preset/후원/던질 프랍/돈주머니.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
7
Assets/Preset/후원/던질 프랍/돈주머니.prefab.meta
Normal file
7
Assets/Preset/후원/던질 프랍/돈주머니.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b5a0e552e06dfd4a82dfbaa0d8fdac3
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Resources/Settings/BackgroundSceneDatabase.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings/BackgroundSceneDatabase.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/Resources/Settings/PropDatabase.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings/PropDatabase.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/Resources/Settings/Streamingle Render Pipeline Asset_Renderer.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings/Streamingle Render Pipeline Asset_Renderer.asset
(Stored with Git LFS)
Binary file not shown.
8
Assets/ResourcesData/Prop/돈주머니.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b7d1ad4cc4839d408d3d6e39ec6047f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Model.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Model.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 06d22216356ccad42b5434d79bc5ac8c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Materials.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Materials.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2195070c29d0ed04ab72ed53f6780ce0
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
1191
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Materials/material_0.mat
Normal file
1191
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Materials/material_0.mat
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 83c9cb0f42010264f86ac30b1da1432d
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Textures.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Textures.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 60e1891c4dc0c374da17786077d08f80
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Textures/Image_0.png
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.Textures/Image_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,117 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3ded3cb3c3ae6f243b3a449aef14524f
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
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: 2
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
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: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
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: 512
|
||||||
|
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
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.glb
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.glb
(Stored with Git LFS)
Normal file
Binary file not shown.
22
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.glb.meta
Normal file
22
Assets/ResourcesData/Prop/돈주머니/Model/돈주머니.glb.meta
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 61efca492f301314c9b7189c17f6e417
|
||||||
|
ScriptedImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects:
|
||||||
|
- first:
|
||||||
|
type: UnityEngine:Material
|
||||||
|
assembly: UnityEngine.CoreModule
|
||||||
|
name: material_0
|
||||||
|
second: {fileID: 2100000, guid: 83c9cb0f42010264f86ac30b1da1432d, type: 2}
|
||||||
|
- first:
|
||||||
|
type: UnityEngine:Texture
|
||||||
|
assembly: UnityEngine.CoreModule
|
||||||
|
name: Image_0
|
||||||
|
second: {fileID: 2800000, guid: 3ded3cb3c3ae6f243b3a449aef14524f, type: 3}
|
||||||
|
serializedVersion: 2
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
script: {fileID: 11500000, guid: cc45016b844e7624dae3aec10fb443ea, type: 3}
|
||||||
|
reverseAxis: 2
|
||||||
|
renderPipeline: 1
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Particle.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Particle.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 259f604cdbf223b4c85a423dd5d3d63c
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Particle/Coin Particle System.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Particle/Coin Particle System.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 80d0fe3b10048864f93050efd36883e9
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
158
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.mat
Normal file
158
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.mat
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!21 &2100000
|
||||||
|
Material:
|
||||||
|
serializedVersion: 8
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_Name: Coin
|
||||||
|
m_Shader: {fileID: 211, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_Parent: {fileID: 0}
|
||||||
|
m_ModifiedSerializedProperties: 0
|
||||||
|
m_ValidKeywords:
|
||||||
|
- _ALPHATEST_ON
|
||||||
|
m_InvalidKeywords: []
|
||||||
|
m_LightmapFlags: 0
|
||||||
|
m_EnableInstancingVariants: 0
|
||||||
|
m_DoubleSidedGI: 0
|
||||||
|
m_CustomRenderQueue: 2450
|
||||||
|
stringTagMap:
|
||||||
|
RenderType: TransparentCutout
|
||||||
|
disabledShaderPasses:
|
||||||
|
- MOTIONVECTORS
|
||||||
|
- GRABPASS
|
||||||
|
m_LockedProperties:
|
||||||
|
m_SavedProperties:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TexEnvs:
|
||||||
|
- _BaseMap:
|
||||||
|
m_Texture: {fileID: 2800000, guid: 08c6cf6250054064fb85c4f953406a6f, type: 3}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _BumpMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailAlbedoMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailMask:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _DetailNormalMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _EmissionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MainTex:
|
||||||
|
m_Texture: {fileID: 2800000, guid: 08c6cf6250054064fb85c4f953406a6f, type: 3}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _MetallicGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _OcclusionMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _ParallaxMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- _SpecGlossMap:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_Lightmaps:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_LightmapsInd:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
- unity_ShadowMasks:
|
||||||
|
m_Texture: {fileID: 0}
|
||||||
|
m_Scale: {x: 1, y: 1}
|
||||||
|
m_Offset: {x: 0, y: 0}
|
||||||
|
m_Ints: []
|
||||||
|
m_Floats:
|
||||||
|
- _AddPrecomputedVelocity: 0
|
||||||
|
- _AlphaClip: 0
|
||||||
|
- _AlphaToMask: 0
|
||||||
|
- _Blend: 0
|
||||||
|
- _BlendModePreserveSpecular: 1
|
||||||
|
- _BlendOp: 0
|
||||||
|
- _BumpScale: 1
|
||||||
|
- _CameraFadingEnabled: 0
|
||||||
|
- _CameraFarFadeDistance: 2
|
||||||
|
- _CameraNearFadeDistance: 1
|
||||||
|
- _ClearCoatMask: 0
|
||||||
|
- _ClearCoatSmoothness: 0
|
||||||
|
- _ColorMode: 0
|
||||||
|
- _Cull: 0
|
||||||
|
- _Cutoff: 0.5
|
||||||
|
- _DetailAlbedoMapScale: 1
|
||||||
|
- _DetailNormalMapScale: 1
|
||||||
|
- _DistortionBlend: 0.5
|
||||||
|
- _DistortionEnabled: 0
|
||||||
|
- _DistortionStrength: 1
|
||||||
|
- _DistortionStrengthScaled: 0
|
||||||
|
- _DstBlend: 0
|
||||||
|
- _DstBlendAlpha: 0
|
||||||
|
- _EmissionEnabled: 0
|
||||||
|
- _EnvironmentReflections: 1
|
||||||
|
- _FlipbookMode: 0
|
||||||
|
- _GlossMapScale: 0
|
||||||
|
- _Glossiness: 0
|
||||||
|
- _GlossyReflections: 0
|
||||||
|
- _LightingEnabled: 0
|
||||||
|
- _Metallic: 0
|
||||||
|
- _Mode: 1
|
||||||
|
- _OcclusionStrength: 1
|
||||||
|
- _Parallax: 0.005
|
||||||
|
- _QueueOffset: 0
|
||||||
|
- _ReceiveShadows: 1
|
||||||
|
- _Smoothness: 0.5
|
||||||
|
- _SmoothnessTextureChannel: 0
|
||||||
|
- _SoftParticlesEnabled: 0
|
||||||
|
- _SoftParticlesFarFadeDistance: 1
|
||||||
|
- _SoftParticlesNearFadeDistance: 0
|
||||||
|
- _SpecularHighlights: 1
|
||||||
|
- _SrcBlend: 1
|
||||||
|
- _SrcBlendAlpha: 1
|
||||||
|
- _Surface: 0
|
||||||
|
- _WorkflowMode: 1
|
||||||
|
- _XRMotionVectorsPass: 1
|
||||||
|
- _ZWrite: 1
|
||||||
|
m_Colors:
|
||||||
|
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
- _CameraFadeParams: {r: 0, g: Infinity, b: 0, a: 0}
|
||||||
|
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||||
|
- _ColorAddSubDiff: {r: 0, g: 0, b: 0, a: 0}
|
||||||
|
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||||
|
- _SoftParticleFadeParams: {r: 0, g: 0, b: 0, a: 0}
|
||||||
|
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||||
|
m_BuildTextureStacks: []
|
||||||
|
m_AllowLocking: 1
|
||||||
|
--- !u!114 &6738923856525603992
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 11
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion
|
||||||
|
version: 10
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.mat.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.mat.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 77bcf914247406744892f91d7497c54e
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 2100000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.png
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.png
(Stored with Git LFS)
Normal file
Binary file not shown.
117
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.png.meta
Normal file
117
Assets/ResourcesData/Prop/돈주머니/Particle/Coin.png.meta
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 08c6cf6250054064fb85c4f953406a6f
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
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: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
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: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
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
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Prefab.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Prefab.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ac3169f25f136984e9eda821c9b67868
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Prefab/돈주머니.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Prefab/돈주머니.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
7
Assets/ResourcesData/Prop/돈주머니/Prefab/돈주머니.prefab.meta
Normal file
7
Assets/ResourcesData/Prop/돈주머니/Prefab/돈주머니.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4a438ec402234494db1c4597ce20dc41
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Sound.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Sound.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ec9ad2e9fbffb604184a0ee84836d48b
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Sound/돈주머니 효과음.mp3
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Sound/돈주머니 효과음.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
23
Assets/ResourcesData/Prop/돈주머니/Sound/돈주머니 효과음.mp3.meta
Normal file
23
Assets/ResourcesData/Prop/돈주머니/Sound/돈주머니 효과음.mp3.meta
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 483113300e6fec0488ef74bbb771c3da
|
||||||
|
AudioImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 8
|
||||||
|
defaultSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
loadType: 0
|
||||||
|
sampleRateSetting: 0
|
||||||
|
sampleRateOverride: 44100
|
||||||
|
compressionFormat: 1
|
||||||
|
quality: 1
|
||||||
|
conversionMode: 0
|
||||||
|
preloadAudioData: 0
|
||||||
|
platformSettingOverrides: {}
|
||||||
|
forceToMono: 0
|
||||||
|
normalize: 1
|
||||||
|
loadInBackground: 0
|
||||||
|
ambisonic: 0
|
||||||
|
3D: 1
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/ResourcesData/Prop/돈주머니/Thumbnail.meta
Normal file
8
Assets/ResourcesData/Prop/돈주머니/Thumbnail.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0597c595d70f57242b02e1f8ecd7f88e
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/ResourcesData/Prop/돈주머니/Thumbnail/돈주머니_thumbnail.png
(Stored with Git LFS)
Normal file
BIN
Assets/ResourcesData/Prop/돈주머니/Thumbnail/돈주머니_thumbnail.png
(Stored with Git LFS)
Normal file
Binary file not shown.
117
Assets/ResourcesData/Prop/돈주머니/Thumbnail/돈주머니_thumbnail.png.meta
Normal file
117
Assets/ResourcesData/Prop/돈주머니/Thumbnail/돈주머니_thumbnail.png.meta
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 52eff15a79fe4224993692534a200c90
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 13
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
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: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
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: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
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
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
customData:
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
spriteCustomMetadata:
|
||||||
|
entries: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
mipmapLimitGroupName:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Scripts/Streamingle/DonationThrowable.meta
Normal file
8
Assets/Scripts/Streamingle/DonationThrowable.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fd54180499ff72f4bbcb70fe47ba5f8a
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
315
Assets/Scripts/Streamingle/DonationThrowable/ThrowableObject.cs
Normal file
315
Assets/Scripts/Streamingle/DonationThrowable/ThrowableObject.cs
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Streamingle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Component attached to throwable objects.
|
||||||
|
/// Uses parabolic interpolation for smooth arc trajectory.
|
||||||
|
/// On reaching target, enables collision detection - bounces off colliders or falls.
|
||||||
|
/// </summary>
|
||||||
|
public class ThrowableObject : MonoBehaviour
|
||||||
|
{
|
||||||
|
[HideInInspector]
|
||||||
|
public ThrowableObjectLauncher launcher;
|
||||||
|
|
||||||
|
[Header("Settings")]
|
||||||
|
[Tooltip("Play sound on hit")]
|
||||||
|
public AudioClip[] hitSounds;
|
||||||
|
|
||||||
|
[Tooltip("Spawn particle effect on hit")]
|
||||||
|
public GameObject hitEffectPrefab;
|
||||||
|
|
||||||
|
[Tooltip("Destroy hit effect after this time")]
|
||||||
|
public float hitEffectLifetime = 2f;
|
||||||
|
|
||||||
|
[Header("Trajectory Settings")]
|
||||||
|
[Tooltip("Time to reach target in seconds")]
|
||||||
|
public float flightDuration = 1.5f;
|
||||||
|
|
||||||
|
[Tooltip("Arc height (higher = more curved trajectory)")]
|
||||||
|
public float arcHeight = 2f;
|
||||||
|
|
||||||
|
[Tooltip("Rotation speed while flying")]
|
||||||
|
public float rotationSpeed = 180f;
|
||||||
|
|
||||||
|
[Header("Collision Settings")]
|
||||||
|
[Tooltip("Bounce force multiplier when hitting collider")]
|
||||||
|
public float bounceForce = 5f;
|
||||||
|
|
||||||
|
[Tooltip("Additional upward force on bounce")]
|
||||||
|
public float bounceUpForce = 2f;
|
||||||
|
|
||||||
|
[Tooltip("Time to stay after reaching target before deactivating")]
|
||||||
|
public float postArrivalLifetime = 3f;
|
||||||
|
|
||||||
|
// Trajectory state
|
||||||
|
private Vector3 startPosition;
|
||||||
|
private Vector3 targetPosition;
|
||||||
|
private Transform targetTransform;
|
||||||
|
private float flightTime;
|
||||||
|
private float currentTime;
|
||||||
|
private bool isFlying = false;
|
||||||
|
private bool hasArrived = false;
|
||||||
|
private bool hasCollided = false;
|
||||||
|
private Vector3 rotationAxis;
|
||||||
|
private Vector3 arrivalVelocity;
|
||||||
|
|
||||||
|
// Components
|
||||||
|
private Rigidbody rb;
|
||||||
|
private AudioSource audioSource;
|
||||||
|
private Collider[] colliders;
|
||||||
|
|
||||||
|
// Lifetime
|
||||||
|
private float lifetime;
|
||||||
|
private float spawnTime;
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
rb = GetComponent<Rigidbody>();
|
||||||
|
audioSource = GetComponent<AudioSource>();
|
||||||
|
colliders = GetComponents<Collider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize the throwable object with target position
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(Transform target, float lifetime)
|
||||||
|
{
|
||||||
|
Initialize(target, lifetime, Vector3.zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize with offset for target position
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize(Transform target, float lifetime, Vector3 targetOffset)
|
||||||
|
{
|
||||||
|
this.lifetime = lifetime;
|
||||||
|
this.spawnTime = Time.time;
|
||||||
|
this.hasArrived = false;
|
||||||
|
this.hasCollided = false;
|
||||||
|
this.isFlying = true;
|
||||||
|
this.currentTime = 0f;
|
||||||
|
this.flightTime = flightDuration;
|
||||||
|
this.startPosition = transform.position;
|
||||||
|
this.targetTransform = target;
|
||||||
|
|
||||||
|
// Calculate target position with offset
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
this.targetPosition = target.position + targetOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.targetPosition = transform.position + Vector3.forward * 5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random rotation axis for spinning effect
|
||||||
|
rotationAxis = Random.insideUnitSphere.normalized;
|
||||||
|
|
||||||
|
// Disable physics during flight
|
||||||
|
if (rb != null)
|
||||||
|
{
|
||||||
|
rb.isKinematic = true;
|
||||||
|
rb.useGravity = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable colliders during flight
|
||||||
|
SetCollidersEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
// Check lifetime
|
||||||
|
if (lifetime > 0 && Time.time - spawnTime > lifetime)
|
||||||
|
{
|
||||||
|
Deactivate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFlying) return;
|
||||||
|
|
||||||
|
// Store previous position for velocity calculation
|
||||||
|
Vector3 prevPos = transform.position;
|
||||||
|
|
||||||
|
currentTime += Time.deltaTime;
|
||||||
|
float t = Mathf.Clamp01(currentTime / flightTime);
|
||||||
|
|
||||||
|
// Calculate parabolic position
|
||||||
|
Vector3 currentPos = CalculateParabolicPosition(t);
|
||||||
|
transform.position = currentPos;
|
||||||
|
|
||||||
|
// Calculate velocity for when we switch to physics
|
||||||
|
if (Time.deltaTime > 0)
|
||||||
|
{
|
||||||
|
arrivalVelocity = (currentPos - prevPos) / Time.deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate while flying
|
||||||
|
transform.Rotate(rotationAxis, rotationSpeed * Time.deltaTime, Space.World);
|
||||||
|
|
||||||
|
// Check if reached target
|
||||||
|
if (t >= 1f)
|
||||||
|
{
|
||||||
|
OnReachedTarget();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate position along parabolic arc
|
||||||
|
/// </summary>
|
||||||
|
private Vector3 CalculateParabolicPosition(float t)
|
||||||
|
{
|
||||||
|
// Linear interpolation for base position
|
||||||
|
Vector3 linearPos = Vector3.Lerp(startPosition, targetPosition, t);
|
||||||
|
|
||||||
|
// Parabolic arc (peaks at t=0.5)
|
||||||
|
float parabola = 4f * arcHeight * t * (1f - t);
|
||||||
|
|
||||||
|
// Add arc height to Y position
|
||||||
|
linearPos.y += parabola;
|
||||||
|
|
||||||
|
return linearPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when object reaches the target position
|
||||||
|
/// </summary>
|
||||||
|
private void OnReachedTarget()
|
||||||
|
{
|
||||||
|
isFlying = false;
|
||||||
|
hasArrived = true;
|
||||||
|
|
||||||
|
// Re-enable colliders for collision detection
|
||||||
|
SetCollidersEnabled(true);
|
||||||
|
|
||||||
|
// Enable physics - object will either hit a collider and bounce or fall
|
||||||
|
if (rb != null)
|
||||||
|
{
|
||||||
|
rb.isKinematic = false;
|
||||||
|
rb.useGravity = true;
|
||||||
|
|
||||||
|
// Continue with the arrival velocity (maintains momentum)
|
||||||
|
rb.linearVelocity = arrivalVelocity;
|
||||||
|
rb.angularVelocity = rotationAxis * rotationSpeed * Mathf.Deg2Rad;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule deactivation (object falls if no collision)
|
||||||
|
Invoke(nameof(Deactivate), postArrivalLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnCollisionEnter(Collision collision)
|
||||||
|
{
|
||||||
|
if (!hasArrived || hasCollided) return;
|
||||||
|
|
||||||
|
// Check if collision is with the target
|
||||||
|
bool isTargetHit = false;
|
||||||
|
if (targetTransform != null)
|
||||||
|
{
|
||||||
|
// Check if collided object is the target or a child of target
|
||||||
|
Transform hitTransform = collision.collider.transform;
|
||||||
|
isTargetHit = hitTransform == targetTransform || hitTransform.IsChildOf(targetTransform);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCollided = true;
|
||||||
|
|
||||||
|
// Only notify launcher and play effects if hit the target
|
||||||
|
if (isTargetHit)
|
||||||
|
{
|
||||||
|
if (launcher != null)
|
||||||
|
{
|
||||||
|
launcher.OnObjectHitTarget(gameObject, collision.collider, collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play hit sound
|
||||||
|
PlayHitSound();
|
||||||
|
|
||||||
|
// Spawn hit effect
|
||||||
|
SpawnHitEffect(collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply bounce force regardless of what was hit
|
||||||
|
if (rb != null && collision.contacts.Length > 0)
|
||||||
|
{
|
||||||
|
Vector3 normal = collision.contacts[0].normal;
|
||||||
|
Vector3 reflectedVel = Vector3.Reflect(rb.linearVelocity, normal);
|
||||||
|
|
||||||
|
// Apply bounce with some force
|
||||||
|
rb.linearVelocity = reflectedVel.normalized * bounceForce + Vector3.up * bounceUpForce;
|
||||||
|
rb.angularVelocity = Random.insideUnitSphere * 10f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetCollidersEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (colliders == null) return;
|
||||||
|
foreach (var col in colliders)
|
||||||
|
{
|
||||||
|
if (col != null) col.enabled = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayHitSound()
|
||||||
|
{
|
||||||
|
if (hitSounds == null || hitSounds.Length == 0) return;
|
||||||
|
|
||||||
|
AudioClip clip = hitSounds[Random.Range(0, hitSounds.Length)];
|
||||||
|
if (clip == null) return;
|
||||||
|
|
||||||
|
if (audioSource != null)
|
||||||
|
{
|
||||||
|
audioSource.PlayOneShot(clip);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AudioSource.PlayClipAtPoint(clip, transform.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnHitEffect(Collision collision = null)
|
||||||
|
{
|
||||||
|
if (hitEffectPrefab == null) return;
|
||||||
|
|
||||||
|
Vector3 pos = transform.position;
|
||||||
|
Quaternion rot = Quaternion.identity;
|
||||||
|
|
||||||
|
if (collision != null && collision.contacts.Length > 0)
|
||||||
|
{
|
||||||
|
pos = collision.contacts[0].point;
|
||||||
|
rot = Quaternion.LookRotation(collision.contacts[0].normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject effect = Instantiate(hitEffectPrefab, pos, rot);
|
||||||
|
if (hitEffectLifetime > 0)
|
||||||
|
{
|
||||||
|
Destroy(effect, hitEffectLifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Deactivate()
|
||||||
|
{
|
||||||
|
CancelInvoke();
|
||||||
|
isFlying = false;
|
||||||
|
hasArrived = false;
|
||||||
|
hasCollided = false;
|
||||||
|
|
||||||
|
if (launcher != null && launcher.usePooling)
|
||||||
|
{
|
||||||
|
// Return to pool
|
||||||
|
gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Destroy
|
||||||
|
Destroy(gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDisable()
|
||||||
|
{
|
||||||
|
CancelInvoke();
|
||||||
|
isFlying = false;
|
||||||
|
hasArrived = false;
|
||||||
|
hasCollided = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 53f705dd7db28b4489129cbca2dfe7b7
|
||||||
@ -0,0 +1,340 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
namespace Streamingle
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Launches throwable objects at a target (avatar's head).
|
||||||
|
/// Connect to WefLab donation events to throw objects on donations.
|
||||||
|
/// Uses parabolic interpolation for smooth arc trajectories.
|
||||||
|
/// </summary>
|
||||||
|
public class ThrowableObjectLauncher : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Target")]
|
||||||
|
[Tooltip("The target transform to throw objects at (usually avatar's head)")]
|
||||||
|
public Transform target;
|
||||||
|
|
||||||
|
[Tooltip("Fixed offset from target position")]
|
||||||
|
public Vector3 targetOffset = Vector3.zero;
|
||||||
|
|
||||||
|
[Tooltip("Random offset range for hit position")]
|
||||||
|
public Vector3 targetRandomOffset = new Vector3(0.1f, 0.1f, 0.1f);
|
||||||
|
|
||||||
|
[Header("Target Collider")]
|
||||||
|
[Tooltip("Automatically create a collider on target for bounce detection")]
|
||||||
|
public bool autoCreateTargetCollider = true;
|
||||||
|
|
||||||
|
[Tooltip("Radius of the auto-created sphere collider")]
|
||||||
|
public float targetColliderRadius = 0.15f;
|
||||||
|
|
||||||
|
[Header("Spawn Settings")]
|
||||||
|
[Tooltip("Prefabs to throw (randomly selected)")]
|
||||||
|
public GameObject[] throwablePrefabs;
|
||||||
|
|
||||||
|
[Tooltip("Spawn position offset from this transform")]
|
||||||
|
public Vector3 spawnOffset = new Vector3(0, 1f, 0);
|
||||||
|
|
||||||
|
[Tooltip("Randomize spawn position within this range")]
|
||||||
|
public Vector3 spawnRandomRange = new Vector3(5f, 1f, 5f);
|
||||||
|
|
||||||
|
[Header("Throw Settings")]
|
||||||
|
[Tooltip("Delay between throws when throwing multiple")]
|
||||||
|
public float throwInterval = 0.15f;
|
||||||
|
|
||||||
|
[Header("Object Lifetime")]
|
||||||
|
[Tooltip("Destroy thrown objects after this time (0 = never)")]
|
||||||
|
public float objectLifetime = 5f;
|
||||||
|
|
||||||
|
[Tooltip("Use object pooling instead of instantiate/destroy")]
|
||||||
|
public bool usePooling = true;
|
||||||
|
|
||||||
|
[Tooltip("Pool size per prefab")]
|
||||||
|
public int poolSizePerPrefab = 10;
|
||||||
|
|
||||||
|
[Header("Events")]
|
||||||
|
public UnityEvent<GameObject> onObjectThrown;
|
||||||
|
public UnityEvent<GameObject, Collision> onObjectHit;
|
||||||
|
|
||||||
|
// Object pool
|
||||||
|
private GameObject[][] objectPool;
|
||||||
|
private int[] poolIndices;
|
||||||
|
|
||||||
|
// Auto-created collider
|
||||||
|
private SphereCollider createdTargetCollider;
|
||||||
|
|
||||||
|
void Start()
|
||||||
|
{
|
||||||
|
if (usePooling)
|
||||||
|
{
|
||||||
|
InitializePool();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoCreateTargetCollider && target != null)
|
||||||
|
{
|
||||||
|
SetupTargetCollider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupTargetCollider()
|
||||||
|
{
|
||||||
|
// Check if target already has a collider
|
||||||
|
var existingCollider = target.GetComponent<Collider>();
|
||||||
|
if (existingCollider != null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.Log($"[ThrowableObjectLauncher] Target already has collider: {existingCollider.GetType().Name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create sphere collider on target with offset applied
|
||||||
|
createdTargetCollider = target.gameObject.AddComponent<SphereCollider>();
|
||||||
|
createdTargetCollider.radius = targetColliderRadius;
|
||||||
|
// Apply targetOffset to collider center (convert world offset to local space)
|
||||||
|
createdTargetCollider.center = target.InverseTransformDirection(targetOffset);
|
||||||
|
|
||||||
|
// Ensure target has rigidbody (kinematic) for collision detection
|
||||||
|
var rb = target.GetComponent<Rigidbody>();
|
||||||
|
if (rb == null)
|
||||||
|
{
|
||||||
|
rb = target.gameObject.AddComponent<Rigidbody>();
|
||||||
|
rb.isKinematic = true;
|
||||||
|
rb.useGravity = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnityEngine.Debug.Log($"[ThrowableObjectLauncher] Created SphereCollider on target: {target.name}, radius: {targetColliderRadius}, center: {createdTargetCollider.center}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnDestroy()
|
||||||
|
{
|
||||||
|
// Clean up created collider
|
||||||
|
if (createdTargetCollider != null)
|
||||||
|
{
|
||||||
|
Destroy(createdTargetCollider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializePool()
|
||||||
|
{
|
||||||
|
if (throwablePrefabs == null || throwablePrefabs.Length == 0) return;
|
||||||
|
|
||||||
|
objectPool = new GameObject[throwablePrefabs.Length][];
|
||||||
|
poolIndices = new int[throwablePrefabs.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < throwablePrefabs.Length; i++)
|
||||||
|
{
|
||||||
|
if (throwablePrefabs[i] == null) continue;
|
||||||
|
|
||||||
|
objectPool[i] = new GameObject[poolSizePerPrefab];
|
||||||
|
for (int j = 0; j < poolSizePerPrefab; j++)
|
||||||
|
{
|
||||||
|
var obj = Instantiate(throwablePrefabs[i], transform);
|
||||||
|
obj.SetActive(false);
|
||||||
|
|
||||||
|
// Add throwable component if not present
|
||||||
|
var throwable = obj.GetComponent<ThrowableObject>();
|
||||||
|
if (throwable == null)
|
||||||
|
{
|
||||||
|
throwable = obj.AddComponent<ThrowableObject>();
|
||||||
|
}
|
||||||
|
throwable.launcher = this;
|
||||||
|
|
||||||
|
objectPool[i][j] = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throw a random object at the target
|
||||||
|
/// </summary>
|
||||||
|
public void ThrowObject()
|
||||||
|
{
|
||||||
|
ThrowObject(Random.Range(0, throwablePrefabs.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throw a specific object at the target
|
||||||
|
/// </summary>
|
||||||
|
public void ThrowObject(int prefabIndex)
|
||||||
|
{
|
||||||
|
if (throwablePrefabs == null || throwablePrefabs.Length == 0)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("[ThrowableObjectLauncher] No throwable prefabs assigned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogWarning("[ThrowableObjectLauncher] No target assigned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefabIndex = Mathf.Clamp(prefabIndex, 0, throwablePrefabs.Length - 1);
|
||||||
|
if (throwablePrefabs[prefabIndex] == null) return;
|
||||||
|
|
||||||
|
// Get or create object
|
||||||
|
GameObject obj = GetObject(prefabIndex);
|
||||||
|
if (obj == null) return;
|
||||||
|
|
||||||
|
// Calculate spawn position
|
||||||
|
Vector3 spawnPos = transform.position + transform.TransformDirection(spawnOffset);
|
||||||
|
spawnPos += new Vector3(
|
||||||
|
Random.Range(-spawnRandomRange.x, spawnRandomRange.x),
|
||||||
|
Random.Range(-spawnRandomRange.y, spawnRandomRange.y),
|
||||||
|
Random.Range(-spawnRandomRange.z, spawnRandomRange.z)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate random offset for target position
|
||||||
|
Vector3 randomOffset = new Vector3(
|
||||||
|
Random.Range(-targetRandomOffset.x, targetRandomOffset.x),
|
||||||
|
Random.Range(-targetRandomOffset.y, targetRandomOffset.y),
|
||||||
|
Random.Range(-targetRandomOffset.z, targetRandomOffset.z)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine fixed offset + random offset
|
||||||
|
Vector3 combinedOffset = targetOffset + randomOffset;
|
||||||
|
|
||||||
|
// Setup object position
|
||||||
|
obj.transform.position = spawnPos;
|
||||||
|
obj.transform.rotation = Random.rotation;
|
||||||
|
obj.SetActive(true);
|
||||||
|
|
||||||
|
// Setup throwable component with combined offset
|
||||||
|
var throwable = obj.GetComponent<ThrowableObject>();
|
||||||
|
if (throwable != null)
|
||||||
|
{
|
||||||
|
throwable.Initialize(target, objectLifetime, combinedOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
onObjectThrown?.Invoke(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throw multiple objects
|
||||||
|
/// </summary>
|
||||||
|
public void ThrowMultiple(int count)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
// Delay each throw slightly
|
||||||
|
float delay = i * throwInterval;
|
||||||
|
StartCoroutine(ThrowDelayed(delay));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.Collections.IEnumerator ThrowDelayed(float delay)
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(delay);
|
||||||
|
ThrowObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameObject GetObject(int prefabIndex)
|
||||||
|
{
|
||||||
|
GameObject obj;
|
||||||
|
|
||||||
|
if (usePooling && objectPool != null && objectPool[prefabIndex] != null)
|
||||||
|
{
|
||||||
|
// Find inactive object in pool
|
||||||
|
for (int i = 0; i < poolSizePerPrefab; i++)
|
||||||
|
{
|
||||||
|
int idx = (poolIndices[prefabIndex] + i) % poolSizePerPrefab;
|
||||||
|
if (!objectPool[prefabIndex][idx].activeInHierarchy)
|
||||||
|
{
|
||||||
|
poolIndices[prefabIndex] = (idx + 1) % poolSizePerPrefab;
|
||||||
|
obj = objectPool[prefabIndex][idx];
|
||||||
|
// Ensure launcher reference is set
|
||||||
|
var throwable = obj.GetComponent<ThrowableObject>();
|
||||||
|
if (throwable != null) throwable.launcher = this;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All in use, reuse oldest
|
||||||
|
poolIndices[prefabIndex] = (poolIndices[prefabIndex] + 1) % poolSizePerPrefab;
|
||||||
|
obj = objectPool[prefabIndex][poolIndices[prefabIndex]];
|
||||||
|
obj.SetActive(false);
|
||||||
|
// Ensure launcher reference is set
|
||||||
|
var throwableOldest = obj.GetComponent<ThrowableObject>();
|
||||||
|
if (throwableOldest != null) throwableOldest.launcher = this;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Instantiate new
|
||||||
|
obj = Instantiate(throwablePrefabs[prefabIndex]);
|
||||||
|
var throwable = obj.GetComponent<ThrowableObject>();
|
||||||
|
if (throwable == null)
|
||||||
|
{
|
||||||
|
throwable = obj.AddComponent<ThrowableObject>();
|
||||||
|
}
|
||||||
|
throwable.launcher = this;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by ThrowableObject when it reaches the target
|
||||||
|
/// </summary>
|
||||||
|
public void OnObjectHitTarget(GameObject obj, Collider hitCollider, Collision collision)
|
||||||
|
{
|
||||||
|
onObjectHit?.Invoke(obj, collision);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Context Menu Test Methods
|
||||||
|
|
||||||
|
[ContextMenu("Test: Throw 1")]
|
||||||
|
private void TestThrowOne()
|
||||||
|
{
|
||||||
|
ThrowObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Test: Throw 5")]
|
||||||
|
private void TestThrowFive()
|
||||||
|
{
|
||||||
|
ThrowMultiple(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
void OnDrawGizmosSelected()
|
||||||
|
{
|
||||||
|
// Draw spawn area
|
||||||
|
Gizmos.color = Color.green;
|
||||||
|
Vector3 spawnPos = transform.position + transform.TransformDirection(spawnOffset);
|
||||||
|
Gizmos.DrawWireCube(spawnPos, spawnRandomRange * 2);
|
||||||
|
|
||||||
|
// Draw target
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
Gizmos.color = Color.red;
|
||||||
|
Vector3 targetPos = target.position + targetOffset;
|
||||||
|
Gizmos.DrawWireSphere(targetPos, 0.2f);
|
||||||
|
Gizmos.DrawWireCube(targetPos, targetRandomOffset * 2);
|
||||||
|
|
||||||
|
// Draw target collider radius (at offset position)
|
||||||
|
if (autoCreateTargetCollider)
|
||||||
|
{
|
||||||
|
Gizmos.color = Color.cyan;
|
||||||
|
Gizmos.DrawWireSphere(target.position + targetOffset, targetColliderRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw trajectory preview (parabolic arc)
|
||||||
|
Gizmos.color = Color.yellow;
|
||||||
|
DrawParabolicArc(spawnPos, targetPos, 2f, 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawParabolicArc(Vector3 start, Vector3 end, float arcHeight, int segments)
|
||||||
|
{
|
||||||
|
Vector3 prevPos = start;
|
||||||
|
for (int i = 1; i <= segments; i++)
|
||||||
|
{
|
||||||
|
float t = i / (float)segments;
|
||||||
|
Vector3 linearPos = Vector3.Lerp(start, end, t);
|
||||||
|
float parabola = 4f * arcHeight * t * (1f - t);
|
||||||
|
linearPos.y += parabola;
|
||||||
|
|
||||||
|
Gizmos.DrawLine(prevPos, linearPos);
|
||||||
|
prevPos = linearPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 85064059a4495fd48824a7a421bee446
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Streamingle.Background
|
namespace Streamingle.Background
|
||||||
@ -37,48 +36,4 @@ namespace Streamingle.Background
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 배경 씬 데이터를 저장하는 ScriptableObject
|
|
||||||
/// </summary>
|
|
||||||
[CreateAssetMenu(fileName = "BackgroundSceneDatabase", menuName = "Streamingle/Background Scene Database")]
|
|
||||||
public class BackgroundSceneDatabase : ScriptableObject
|
|
||||||
{
|
|
||||||
public List<BackgroundSceneInfo> scenes = new List<BackgroundSceneInfo>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 카테고리별로 씬 목록 반환
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, List<BackgroundSceneInfo>> GetScenesByCategory()
|
|
||||||
{
|
|
||||||
var result = new Dictionary<string, List<BackgroundSceneInfo>>();
|
|
||||||
|
|
||||||
foreach (var scene in scenes)
|
|
||||||
{
|
|
||||||
string category = scene.Category;
|
|
||||||
if (!result.ContainsKey(category))
|
|
||||||
{
|
|
||||||
result[category] = new List<BackgroundSceneInfo>();
|
|
||||||
}
|
|
||||||
result[category].Add(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 씬 이름으로 검색
|
|
||||||
/// </summary>
|
|
||||||
public BackgroundSceneInfo FindByName(string sceneName)
|
|
||||||
{
|
|
||||||
return scenes.Find(s => s.sceneName == sceneName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 씬 경로로 검색
|
|
||||||
/// </summary>
|
|
||||||
public BackgroundSceneInfo FindByPath(string scenePath)
|
|
||||||
{
|
|
||||||
return scenes.Find(s => s.scenePath == scenePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,50 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Streamingle.Background
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 배경 씬 데이터를 저장하는 ScriptableObject
|
||||||
|
/// </summary>
|
||||||
|
[CreateAssetMenu(fileName = "BackgroundSceneDatabase", menuName = "Streamingle/Background Scene Database")]
|
||||||
|
public class BackgroundSceneDatabase : ScriptableObject
|
||||||
|
{
|
||||||
|
public List<BackgroundSceneInfo> scenes = new List<BackgroundSceneInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 카테고리별로 씬 목록 반환
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, List<BackgroundSceneInfo>> GetScenesByCategory()
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, List<BackgroundSceneInfo>>();
|
||||||
|
|
||||||
|
foreach (var scene in scenes)
|
||||||
|
{
|
||||||
|
string category = scene.Category;
|
||||||
|
if (!result.ContainsKey(category))
|
||||||
|
{
|
||||||
|
result[category] = new List<BackgroundSceneInfo>();
|
||||||
|
}
|
||||||
|
result[category].Add(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 씬 이름으로 검색
|
||||||
|
/// </summary>
|
||||||
|
public BackgroundSceneInfo FindByName(string sceneName)
|
||||||
|
{
|
||||||
|
return scenes.Find(s => s.sceneName == sceneName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 씬 경로로 검색
|
||||||
|
/// </summary>
|
||||||
|
public BackgroundSceneInfo FindByPath(string scenePath)
|
||||||
|
{
|
||||||
|
return scenes.Find(s => s.scenePath == scenePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ee084dfc7f17cb7498423a57ca4bd971
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Rendering;
|
||||||
using UnityEngine.Rendering.Universal;
|
using UnityEngine.Rendering.Universal;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -164,6 +165,11 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
[System.NonSerialized] public Vector3 savedFocusPoint;
|
[System.NonSerialized] public Vector3 savedFocusPoint;
|
||||||
[System.NonSerialized] public bool hasOrbitState = false;
|
[System.NonSerialized] public bool hasOrbitState = false;
|
||||||
|
|
||||||
|
// 프리셋별 FOV 저장 (Alt+Q 복원용)
|
||||||
|
[System.NonSerialized] public float initialFOV;
|
||||||
|
[System.NonSerialized] public float savedFOV;
|
||||||
|
[System.NonSerialized] public bool hasInitialFOV = false;
|
||||||
|
|
||||||
public CameraPreset(CinemachineCamera camera)
|
public CameraPreset(CinemachineCamera camera)
|
||||||
{
|
{
|
||||||
virtualCamera = camera;
|
virtualCamera = camera;
|
||||||
@ -181,6 +187,14 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
initialPosition = virtualCamera.transform.position;
|
initialPosition = virtualCamera.transform.position;
|
||||||
initialRotation = virtualCamera.transform.rotation;
|
initialRotation = virtualCamera.transform.rotation;
|
||||||
hasInitialState = true;
|
hasInitialState = true;
|
||||||
|
|
||||||
|
// FOV 초기값 저장
|
||||||
|
if (virtualCamera.Lens.FieldOfView > 0)
|
||||||
|
{
|
||||||
|
initialFOV = virtualCamera.Lens.FieldOfView;
|
||||||
|
savedFOV = initialFOV;
|
||||||
|
hasInitialFOV = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 초기 상태 복원 (Alt+Q)
|
// 초기 상태 복원 (Alt+Q)
|
||||||
@ -189,25 +203,36 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
if (!hasInitialState || virtualCamera == null) return;
|
if (!hasInitialState || virtualCamera == null) return;
|
||||||
virtualCamera.transform.position = initialPosition;
|
virtualCamera.transform.position = initialPosition;
|
||||||
virtualCamera.transform.rotation = initialRotation;
|
virtualCamera.transform.rotation = initialRotation;
|
||||||
|
|
||||||
|
// FOV 초기값 복원
|
||||||
|
if (hasInitialFOV)
|
||||||
|
{
|
||||||
|
var lens = virtualCamera.Lens;
|
||||||
|
lens.FieldOfView = initialFOV;
|
||||||
|
virtualCamera.Lens = lens;
|
||||||
|
savedFOV = initialFOV;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// orbit 상태 저장 (카메라 전환 시)
|
// orbit 상태 저장 (카메라 전환 시)
|
||||||
public void SaveOrbitState(float hAngle, float vAngle, float dist, Vector3 focus)
|
public void SaveOrbitState(float hAngle, float vAngle, float dist, Vector3 focus, float fov)
|
||||||
{
|
{
|
||||||
savedHorizontalAngle = hAngle;
|
savedHorizontalAngle = hAngle;
|
||||||
savedVerticalAngle = vAngle;
|
savedVerticalAngle = vAngle;
|
||||||
savedDistance = dist;
|
savedDistance = dist;
|
||||||
savedFocusPoint = focus;
|
savedFocusPoint = focus;
|
||||||
|
savedFOV = fov;
|
||||||
hasOrbitState = true;
|
hasOrbitState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// orbit 상태 복원 (카메라 전환 시)
|
// orbit 상태 복원 (카메라 전환 시)
|
||||||
public bool TryRestoreOrbitState(out float hAngle, out float vAngle, out float dist, out Vector3 focus)
|
public bool TryRestoreOrbitState(out float hAngle, out float vAngle, out float dist, out Vector3 focus, out float fov)
|
||||||
{
|
{
|
||||||
hAngle = savedHorizontalAngle;
|
hAngle = savedHorizontalAngle;
|
||||||
vAngle = savedVerticalAngle;
|
vAngle = savedVerticalAngle;
|
||||||
dist = savedDistance;
|
dist = savedDistance;
|
||||||
focus = savedFocusPoint;
|
focus = savedFocusPoint;
|
||||||
|
fov = savedFOV;
|
||||||
return hasOrbitState;
|
return hasOrbitState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,6 +260,11 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
[SerializeField] private float minZoomDistance = 0.5f;
|
[SerializeField] private float minZoomDistance = 0.5f;
|
||||||
[SerializeField] private float maxZoomDistance = 50f;
|
[SerializeField] private float maxZoomDistance = 50f;
|
||||||
|
|
||||||
|
[Header("FOV Settings")]
|
||||||
|
[SerializeField, Range(0.1f, 5f)] private float fovSensitivity = 1f;
|
||||||
|
[SerializeField] private float minFOV = 1f;
|
||||||
|
[SerializeField] private float maxFOV = 90f;
|
||||||
|
|
||||||
[Header("Rotation Target")]
|
[Header("Rotation Target")]
|
||||||
[Tooltip("체크하면 아바타 머리를 자동으로 찾아 회전 중심점으로 사용합니다.")]
|
[Tooltip("체크하면 아바타 머리를 자동으로 찾아 회전 중심점으로 사용합니다.")]
|
||||||
[SerializeField] private bool useAvatarHeadAsTarget = true;
|
[SerializeField] private bool useAvatarHeadAsTarget = true;
|
||||||
@ -246,9 +276,12 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
[SerializeField] private bool useBlendTransition = false;
|
[SerializeField] private bool useBlendTransition = false;
|
||||||
[Tooltip("블렌드 전환 시간 (초)")]
|
[Tooltip("블렌드 전환 시간 (초)")]
|
||||||
[SerializeField, Range(0.1f, 2f)] private float blendTime = 0.5f;
|
[SerializeField, Range(0.1f, 2f)] private float blendTime = 0.5f;
|
||||||
|
[Tooltip("실시간 블렌딩 (두 카메라 동시 렌더링). 비활성화 시 스냅샷 블렌딩 사용")]
|
||||||
|
[SerializeField] private bool useRealtimeBlend = true;
|
||||||
|
|
||||||
// 블렌드용 렌더 텍스처와 카메라
|
// 블렌드용 렌더 텍스처와 카메라
|
||||||
private RenderTexture blendRenderTexture;
|
private RenderTexture blendRenderTexture;
|
||||||
|
private RenderTexture prevCameraRenderTexture; // 실시간 블렌딩용 이전 카메라 렌더 텍스처
|
||||||
private Camera blendCamera;
|
private Camera blendCamera;
|
||||||
|
|
||||||
private CinemachineCamera currentCamera;
|
private CinemachineCamera currentCamera;
|
||||||
@ -433,7 +466,6 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
avatarHeadTransform = animator.GetBoneTransform(HumanBodyBones.Head);
|
avatarHeadTransform = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||||
if (avatarHeadTransform != null)
|
if (avatarHeadTransform != null)
|
||||||
{
|
{
|
||||||
Debug.Log($"[CameraManager] 아바타 머리를 회전 중심점으로 설정: {avatarHeadTransform.name}");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -543,7 +575,7 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
|
|
||||||
private void HandleInput()
|
private void HandleInput()
|
||||||
{
|
{
|
||||||
// 입력 우선순위 처리: Orbit > AltRightZoom > Zoom > Rotation > Panning
|
// 입력 우선순위 처리: Orbit > CtrlRightZoom > Zoom > FOV > Rotation > Panning
|
||||||
if (inputHandler.IsOrbitActive())
|
if (inputHandler.IsOrbitActive())
|
||||||
{
|
{
|
||||||
HandleOrbiting();
|
HandleOrbiting();
|
||||||
@ -556,6 +588,10 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
{
|
{
|
||||||
HandleDragZoom();
|
HandleDragZoom();
|
||||||
}
|
}
|
||||||
|
else if (inputHandler.IsFOVActive())
|
||||||
|
{
|
||||||
|
HandleFOV();
|
||||||
|
}
|
||||||
else if (inputHandler.IsRightMouseHeld())
|
else if (inputHandler.IsRightMouseHeld())
|
||||||
{
|
{
|
||||||
HandleRotation();
|
HandleRotation();
|
||||||
@ -651,6 +687,24 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
targetDistance = Mathf.Clamp(targetDistance, minZoomDistance, maxZoomDistance);
|
targetDistance = Mathf.Clamp(targetDistance, minZoomDistance, maxZoomDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shift + 좌클릭/우클릭 드래그: FOV 조절 (위로 밀면 FOV 감소=줌인, 아래로 밀면 FOV 증가=줌아웃)
|
||||||
|
/// </summary>
|
||||||
|
private void HandleFOV()
|
||||||
|
{
|
||||||
|
if (currentCamera == null) return;
|
||||||
|
|
||||||
|
Vector2 delta = inputHandler.GetLookDelta();
|
||||||
|
if (delta.sqrMagnitude < float.Epsilon) return;
|
||||||
|
|
||||||
|
// 위로 밀면 FOV 감소 (줌인 효과), 아래로 밀면 FOV 증가 (줌아웃 효과)
|
||||||
|
float fovDelta = -delta.y * fovSensitivity;
|
||||||
|
|
||||||
|
var lens = currentCamera.Lens;
|
||||||
|
lens.FieldOfView = Mathf.Clamp(lens.FieldOfView + fovDelta, minFOV, maxFOV);
|
||||||
|
currentCamera.Lens = lens;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 스무딩을 적용하여 카메라 위치 업데이트
|
/// 스무딩을 적용하여 카메라 위치 업데이트
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -742,8 +796,6 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetImmediate(int index)
|
public void SetImmediate(int index)
|
||||||
{
|
{
|
||||||
Debug.Log($"[CameraManager] 카메라 {index}번으로 전환 시작 (총 {cameraPresets?.Count ?? 0}개)");
|
|
||||||
|
|
||||||
if (!ValidateCameraIndex(index)) return;
|
if (!ValidateCameraIndex(index)) return;
|
||||||
|
|
||||||
var newPreset = cameraPresets[index];
|
var newPreset = cameraPresets[index];
|
||||||
@ -760,7 +812,8 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
// 이전 프리셋의 orbit 상태 저장
|
// 이전 프리셋의 orbit 상태 저장
|
||||||
if (oldPreset != null && oldPreset.allowMouseControl)
|
if (oldPreset != null && oldPreset.allowMouseControl)
|
||||||
{
|
{
|
||||||
oldPreset.SaveOrbitState(targetHorizontalAngle, targetVerticalAngle, targetDistance, targetFocusPoint);
|
float currentFOV = oldPreset.virtualCamera != null ? oldPreset.virtualCamera.Lens.FieldOfView : 60f;
|
||||||
|
oldPreset.SaveOrbitState(targetHorizontalAngle, targetVerticalAngle, targetDistance, targetFocusPoint, currentFOV);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPreset = newPreset;
|
currentPreset = newPreset;
|
||||||
@ -770,7 +823,7 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
if (newPreset.hasOrbitState && newPreset.allowMouseControl)
|
if (newPreset.hasOrbitState && newPreset.allowMouseControl)
|
||||||
{
|
{
|
||||||
// 저장된 orbit 상태 복원
|
// 저장된 orbit 상태 복원
|
||||||
if (newPreset.TryRestoreOrbitState(out float hAngle, out float vAngle, out float dist, out Vector3 focus))
|
if (newPreset.TryRestoreOrbitState(out float hAngle, out float vAngle, out float dist, out Vector3 focus, out float fov))
|
||||||
{
|
{
|
||||||
targetHorizontalAngle = hAngle;
|
targetHorizontalAngle = hAngle;
|
||||||
targetVerticalAngle = vAngle;
|
targetVerticalAngle = vAngle;
|
||||||
@ -781,6 +834,14 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
verticalAngle = vAngle;
|
verticalAngle = vAngle;
|
||||||
currentDistance = dist;
|
currentDistance = dist;
|
||||||
focusPoint = focus;
|
focusPoint = focus;
|
||||||
|
|
||||||
|
// FOV 복원
|
||||||
|
if (newPreset.virtualCamera != null && fov > 0)
|
||||||
|
{
|
||||||
|
var lens = newPreset.virtualCamera.Lens;
|
||||||
|
lens.FieldOfView = fov;
|
||||||
|
newPreset.virtualCamera.Lens = lens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -797,7 +858,6 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
streamDeckManager.NotifyCameraChanged();
|
streamDeckManager.NotifyCameraChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log($"[CameraManager] 카메라 전환 완료: {newCameraName}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateCameraIndex(int index)
|
private bool ValidateCameraIndex(int index)
|
||||||
@ -832,9 +892,10 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 블렌드용 카메라 생성
|
// 블렌드용 카메라 생성 - 독립적인 오브젝트로 (부모 없음)
|
||||||
GameObject blendCamObj = new GameObject("BlendCamera");
|
// Cinemachine Brain의 영향을 피하기 위해 부모를 설정하지 않음
|
||||||
blendCamObj.transform.SetParent(mainCamera.transform.parent);
|
GameObject blendCamObj = new GameObject("BlendCamera_Independent");
|
||||||
|
blendCamObj.transform.SetParent(null); // 부모 없이 루트에 배치
|
||||||
blendCamera = blendCamObj.AddComponent<Camera>();
|
blendCamera = blendCamObj.AddComponent<Camera>();
|
||||||
|
|
||||||
// 메인 카메라 설정 복사
|
// 메인 카메라 설정 복사
|
||||||
@ -860,7 +921,6 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
blendCameraData.volumeTrigger = mainCameraData.volumeTrigger;
|
blendCameraData.volumeTrigger = mainCameraData.volumeTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log("[CameraManager] 블렌드 카메라 생성 완료 (URP 설정 포함)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -886,61 +946,73 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
Destroy(blendRenderTexture);
|
Destroy(blendRenderTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 새 렌더 텍스처 생성
|
// 새 렌더 텍스처 생성 - 색상 전용 (depth 없음), 리니어 색공간에서 올바른 블렌딩
|
||||||
blendRenderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
|
var descriptor = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0); // depth = 0
|
||||||
|
descriptor.sRGB = true; // sRGB 텍스처로 설정하여 리니어 파이프라인과 일치
|
||||||
|
blendRenderTexture = new RenderTexture(descriptor);
|
||||||
blendRenderTexture.name = "CameraBlendRT";
|
blendRenderTexture.name = "CameraBlendRT";
|
||||||
blendRenderTexture.Create();
|
blendRenderTexture.Create();
|
||||||
|
|
||||||
Debug.Log($"[CameraManager] 블렌드 렌더 텍스처 생성: {width}x{height}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 크로스 디졸브 블렌드 전환 코루틴
|
/// 크로스 디졸브 블렌드 전환 코루틴
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator BlendTransitionCoroutine(int targetIndex, float duration)
|
private IEnumerator BlendTransitionCoroutine(int targetIndex, float duration)
|
||||||
|
{
|
||||||
|
if (useRealtimeBlend)
|
||||||
|
{
|
||||||
|
yield return RealtimeBlendTransitionCoroutine(targetIndex, duration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return SnapshotBlendTransitionCoroutine(targetIndex, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 스냅샷 블렌딩 (이전 화면 정지)
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator SnapshotBlendTransitionCoroutine(int targetIndex, float duration)
|
||||||
{
|
{
|
||||||
isBlending = true;
|
isBlending = true;
|
||||||
|
|
||||||
// 블렌드 카메라/텍스처 준비
|
// 블렌드 텍스처 준비
|
||||||
EnsureBlendCamera();
|
|
||||||
EnsureBlendRenderTexture();
|
EnsureBlendRenderTexture();
|
||||||
|
|
||||||
if (blendCamera == null || blendRenderTexture == null)
|
if (blendRenderTexture == null)
|
||||||
{
|
{
|
||||||
Debug.LogError("[CameraManager] 블렌드 카메라 또는 텍스처 생성 실패");
|
|
||||||
SetImmediate(targetIndex);
|
SetImmediate(targetIndex);
|
||||||
isBlending = false;
|
isBlending = false;
|
||||||
blendCoroutine = null;
|
blendCoroutine = null;
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 메인 카메라의 현재 위치/회전 복사 (Cinemachine이 제어하는 실제 카메라)
|
// 렌더 패스에서 현재 프레임(포스트 프로세싱 적용 후)을 캡처하도록 요청
|
||||||
Camera mainCamera = Camera.main;
|
CameraBlendController.RequestCapture(blendRenderTexture);
|
||||||
if (mainCamera != null)
|
|
||||||
|
// 캡처가 완료될 때까지 대기 (다음 프레임 렌더링 후)
|
||||||
|
yield return new WaitForEndOfFrame();
|
||||||
|
yield return null; // 렌더 패스가 실행될 때까지 한 프레임 더 대기
|
||||||
|
|
||||||
|
// 캡처 완료 확인
|
||||||
|
if (!CameraBlendController.CaptureReady)
|
||||||
{
|
{
|
||||||
blendCamera.transform.position = mainCamera.transform.position;
|
CameraBlendController.EndBlend();
|
||||||
blendCamera.transform.rotation = mainCamera.transform.rotation;
|
SetImmediate(targetIndex);
|
||||||
blendCamera.fieldOfView = mainCamera.fieldOfView;
|
isBlending = false;
|
||||||
|
blendCoroutine = null;
|
||||||
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 블렌드 카메라로 현재 화면을 렌더 텍스처에 렌더링
|
// 블렌딩 시작 - BlendAmount = 1 (이전 카메라만 보임)
|
||||||
blendCamera.targetTexture = blendRenderTexture;
|
CameraBlendController.StartBlendAfterCapture();
|
||||||
blendCamera.enabled = true;
|
CameraBlendController.BlendAmount = 1f;
|
||||||
blendCamera.Render();
|
|
||||||
blendCamera.enabled = false;
|
|
||||||
|
|
||||||
Debug.Log($"[CameraManager] 블렌드 시작 - Duration: {duration}s");
|
// 카메라 전환 (이제 BlendAmount=1이므로 캡처된 이전 화면만 보임)
|
||||||
|
|
||||||
// 블렌딩 시작 - BlendAmount = 0 (이전 카메라 A만 보임)
|
|
||||||
CameraBlendController.StartBlend(blendRenderTexture);
|
|
||||||
|
|
||||||
// 한 프레임 대기 - 블렌딩이 적용된 상태로 렌더링되도록
|
|
||||||
yield return null;
|
|
||||||
|
|
||||||
// 카메라 전환 (이제 BlendAmount=0이므로 A만 보임)
|
|
||||||
SetImmediate(targetIndex);
|
SetImmediate(targetIndex);
|
||||||
|
|
||||||
// 블렌드 진행: 0 → 1 (A에서 B로)
|
// 블렌드 진행: 1 → 0 (이전 카메라가 서서히 사라지고 새 카메라가 드러남)
|
||||||
float elapsed = 0f;
|
float elapsed = 0f;
|
||||||
while (elapsed < duration)
|
while (elapsed < duration)
|
||||||
{
|
{
|
||||||
@ -948,21 +1020,145 @@ public class CameraManager : MonoBehaviour, IController
|
|||||||
float t = Mathf.Clamp01(elapsed / duration);
|
float t = Mathf.Clamp01(elapsed / duration);
|
||||||
// 부드러운 이징 적용 (SmoothStep)
|
// 부드러운 이징 적용 (SmoothStep)
|
||||||
t = t * t * (3f - 2f * t);
|
t = t * t * (3f - 2f * t);
|
||||||
CameraBlendController.BlendAmount = t;
|
// 1에서 0으로: 이전 카메라가 서서히 사라짐
|
||||||
|
CameraBlendController.BlendAmount = 1f - t;
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraBlendController.BlendAmount = 1f;
|
CameraBlendController.BlendAmount = 0f;
|
||||||
|
|
||||||
// 블렌딩 종료
|
// 블렌딩 종료
|
||||||
CameraBlendController.EndBlend();
|
CameraBlendController.EndBlend();
|
||||||
|
|
||||||
Debug.Log("[CameraManager] 블렌드 완료");
|
isBlending = false;
|
||||||
|
blendCoroutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 실시간 블렌딩 (두 카메라 동시 렌더링)
|
||||||
|
/// 이전 카메라 위치를 저장해두고 그 위치에서 렌더링, 서서히 사라지면서 새 카메라가 드러남
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator RealtimeBlendTransitionCoroutine(int targetIndex, float duration)
|
||||||
|
{
|
||||||
|
isBlending = true;
|
||||||
|
|
||||||
|
// 이전 카메라 프리셋 저장
|
||||||
|
var prevPreset = currentPreset;
|
||||||
|
if (prevPreset == null || prevPreset.virtualCamera == null)
|
||||||
|
{
|
||||||
|
SetImmediate(targetIndex);
|
||||||
|
isBlending = false;
|
||||||
|
blendCoroutine = null;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetImmediate 호출 전에 이전 카메라의 위치/회전/FOV를 저장
|
||||||
|
Vector3 prevCameraPosition = prevPreset.virtualCamera.transform.position;
|
||||||
|
Quaternion prevCameraRotation = prevPreset.virtualCamera.transform.rotation;
|
||||||
|
float prevCameraFOV = prevPreset.virtualCamera.Lens.FieldOfView;
|
||||||
|
|
||||||
|
// 블렌드 텍스처 준비
|
||||||
|
EnsureBlendRenderTexture();
|
||||||
|
EnsurePrevCameraRenderTexture();
|
||||||
|
|
||||||
|
if (blendRenderTexture == null || prevCameraRenderTexture == null)
|
||||||
|
{
|
||||||
|
SetImmediate(targetIndex);
|
||||||
|
isBlending = false;
|
||||||
|
blendCoroutine = null;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 블렌드 카메라 준비
|
||||||
|
EnsureBlendCamera();
|
||||||
|
if (blendCamera == null)
|
||||||
|
{
|
||||||
|
SetImmediate(targetIndex);
|
||||||
|
isBlending = false;
|
||||||
|
blendCoroutine = null;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 블렌드 카메라를 이전 카메라 위치로 설정하고 첫 프레임 렌더링
|
||||||
|
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||||
|
blendCamera.fieldOfView = prevCameraFOV;
|
||||||
|
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||||
|
|
||||||
|
// Camera.Render()로 렌더링 (URP에서도 포스트 프로세싱 적용됨)
|
||||||
|
blendCamera.Render();
|
||||||
|
Graphics.Blit(prevCameraRenderTexture, blendRenderTexture);
|
||||||
|
|
||||||
|
// 실시간 블렌딩 시작 (BlendAmount = 1에서 시작, 이전 카메라가 100% 보임)
|
||||||
|
CameraBlendController.StartRealtimeBlend(blendRenderTexture);
|
||||||
|
CameraBlendController.BlendAmount = 1f;
|
||||||
|
|
||||||
|
// 새 카메라로 전환 (메인 카메라는 이제 새 위치에서 렌더링됨)
|
||||||
|
SetImmediate(targetIndex);
|
||||||
|
|
||||||
|
// 블렌드 진행: 1 → 0 (이전 카메라 화면이 서서히 사라지고 새 카메라가 드러남)
|
||||||
|
float elapsed = 0f;
|
||||||
|
while (elapsed < duration)
|
||||||
|
{
|
||||||
|
// 이전 카메라 시점에서 먼저 렌더링 (메인 카메라 렌더링 전에)
|
||||||
|
blendCamera.transform.SetPositionAndRotation(prevCameraPosition, prevCameraRotation);
|
||||||
|
blendCamera.fieldOfView = prevCameraFOV;
|
||||||
|
blendCamera.targetTexture = prevCameraRenderTexture;
|
||||||
|
|
||||||
|
// Camera.Render()로 렌더링 (URP에서도 포스트 프로세싱 적용됨)
|
||||||
|
blendCamera.Render();
|
||||||
|
Graphics.Blit(prevCameraRenderTexture, blendRenderTexture);
|
||||||
|
|
||||||
|
// 다음 프레임까지 대기
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
elapsed += Time.deltaTime;
|
||||||
|
float t = Mathf.Clamp01(elapsed / duration);
|
||||||
|
// 부드러운 이징 적용 (SmoothStep)
|
||||||
|
t = t * t * (3f - 2f * t);
|
||||||
|
// 1에서 0으로: 이전 카메라가 서서히 사라짐
|
||||||
|
CameraBlendController.BlendAmount = 1f - t;
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraBlendController.BlendAmount = 0f;
|
||||||
|
|
||||||
|
// 블렌딩 종료
|
||||||
|
CameraBlendController.EndBlend();
|
||||||
|
|
||||||
isBlending = false;
|
isBlending = false;
|
||||||
blendCoroutine = null;
|
blendCoroutine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 실시간 블렌딩용 이전 카메라 렌더 텍스처 생성/갱신
|
||||||
|
/// </summary>
|
||||||
|
private void EnsurePrevCameraRenderTexture()
|
||||||
|
{
|
||||||
|
int width = Screen.width;
|
||||||
|
int height = Screen.height;
|
||||||
|
|
||||||
|
// 이미 적절한 크기의 텍스처가 있으면 재사용
|
||||||
|
if (prevCameraRenderTexture != null &&
|
||||||
|
prevCameraRenderTexture.width == width &&
|
||||||
|
prevCameraRenderTexture.height == height)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 텍스처 해제
|
||||||
|
if (prevCameraRenderTexture != null)
|
||||||
|
{
|
||||||
|
prevCameraRenderTexture.Release();
|
||||||
|
Destroy(prevCameraRenderTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 새 렌더 텍스처 생성 - HDR 포맷 + depth 포함 (포스트 프로세싱 지원)
|
||||||
|
var descriptor = new RenderTextureDescriptor(width, height, RenderTextureFormat.DefaultHDR, 24);
|
||||||
|
descriptor.sRGB = false; // HDR은 리니어 포맷
|
||||||
|
prevCameraRenderTexture = new RenderTexture(descriptor);
|
||||||
|
prevCameraRenderTexture.name = "PrevCameraRT";
|
||||||
|
prevCameraRenderTexture.Create();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateCameraPriorities(CinemachineCamera newCamera)
|
private void UpdateCameraPriorities(CinemachineCamera newCamera)
|
||||||
{
|
{
|
||||||
if (newCamera == null)
|
if (newCamera == null)
|
||||||
|
|||||||
@ -17,6 +17,7 @@ public class CameraManagerEditor : Editor
|
|||||||
private bool showControlSettings = true;
|
private bool showControlSettings = true;
|
||||||
private bool showSmoothingSettings = true;
|
private bool showSmoothingSettings = true;
|
||||||
private bool showZoomSettings = true;
|
private bool showZoomSettings = true;
|
||||||
|
private bool showFOVSettings = true;
|
||||||
private bool showRotationTarget = true;
|
private bool showRotationTarget = true;
|
||||||
private bool showBlendSettings = true;
|
private bool showBlendSettings = true;
|
||||||
|
|
||||||
@ -29,10 +30,14 @@ public class CameraManagerEditor : Editor
|
|||||||
private SerializedProperty rotationSmoothingProp;
|
private SerializedProperty rotationSmoothingProp;
|
||||||
private SerializedProperty minZoomDistanceProp;
|
private SerializedProperty minZoomDistanceProp;
|
||||||
private SerializedProperty maxZoomDistanceProp;
|
private SerializedProperty maxZoomDistanceProp;
|
||||||
|
private SerializedProperty fovSensitivityProp;
|
||||||
|
private SerializedProperty minFOVProp;
|
||||||
|
private SerializedProperty maxFOVProp;
|
||||||
private SerializedProperty useAvatarHeadAsTargetProp;
|
private SerializedProperty useAvatarHeadAsTargetProp;
|
||||||
private SerializedProperty manualRotationTargetProp;
|
private SerializedProperty manualRotationTargetProp;
|
||||||
private SerializedProperty useBlendTransitionProp;
|
private SerializedProperty useBlendTransitionProp;
|
||||||
private SerializedProperty blendTimeProp;
|
private SerializedProperty blendTimeProp;
|
||||||
|
private SerializedProperty useRealtimeBlendProp;
|
||||||
|
|
||||||
// 스타일
|
// 스타일
|
||||||
private GUIStyle headerStyle;
|
private GUIStyle headerStyle;
|
||||||
@ -52,10 +57,14 @@ public class CameraManagerEditor : Editor
|
|||||||
rotationSmoothingProp = serializedObject.FindProperty("rotationSmoothing");
|
rotationSmoothingProp = serializedObject.FindProperty("rotationSmoothing");
|
||||||
minZoomDistanceProp = serializedObject.FindProperty("minZoomDistance");
|
minZoomDistanceProp = serializedObject.FindProperty("minZoomDistance");
|
||||||
maxZoomDistanceProp = serializedObject.FindProperty("maxZoomDistance");
|
maxZoomDistanceProp = serializedObject.FindProperty("maxZoomDistance");
|
||||||
|
fovSensitivityProp = serializedObject.FindProperty("fovSensitivity");
|
||||||
|
minFOVProp = serializedObject.FindProperty("minFOV");
|
||||||
|
maxFOVProp = serializedObject.FindProperty("maxFOV");
|
||||||
useAvatarHeadAsTargetProp = serializedObject.FindProperty("useAvatarHeadAsTarget");
|
useAvatarHeadAsTargetProp = serializedObject.FindProperty("useAvatarHeadAsTarget");
|
||||||
manualRotationTargetProp = serializedObject.FindProperty("manualRotationTarget");
|
manualRotationTargetProp = serializedObject.FindProperty("manualRotationTarget");
|
||||||
useBlendTransitionProp = serializedObject.FindProperty("useBlendTransition");
|
useBlendTransitionProp = serializedObject.FindProperty("useBlendTransition");
|
||||||
blendTimeProp = serializedObject.FindProperty("blendTime");
|
blendTimeProp = serializedObject.FindProperty("blendTime");
|
||||||
|
useRealtimeBlendProp = serializedObject.FindProperty("useRealtimeBlend");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
@ -126,6 +135,9 @@ public class CameraManagerEditor : Editor
|
|||||||
// 줌 제한 섹션
|
// 줌 제한 섹션
|
||||||
DrawZoomLimitsSection();
|
DrawZoomLimitsSection();
|
||||||
|
|
||||||
|
// FOV 설정 섹션
|
||||||
|
DrawFOVSettingsSection();
|
||||||
|
|
||||||
// 회전 타겟 섹션
|
// 회전 타겟 섹션
|
||||||
DrawRotationTargetSection();
|
DrawRotationTargetSection();
|
||||||
|
|
||||||
@ -211,6 +223,35 @@ public class CameraManagerEditor : Editor
|
|||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawFOVSettingsSection()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(sectionBoxStyle);
|
||||||
|
|
||||||
|
showFOVSettings = EditorGUILayout.Foldout(showFOVSettings, "FOV Settings", true, EditorStyles.foldoutHeader);
|
||||||
|
|
||||||
|
if (showFOVSettings)
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space(5);
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(fovSensitivityProp, new GUIContent("FOV 감도", "Shift + 드래그 시 FOV 변화 감도 (0.01 ~ 5)"));
|
||||||
|
EditorGUILayout.PropertyField(minFOVProp, new GUIContent("최소 FOV", "카메라 최소 FOV (줌인 한계)"));
|
||||||
|
EditorGUILayout.PropertyField(maxFOVProp, new GUIContent("최대 FOV", "카메라 최대 FOV (줌아웃 한계)"));
|
||||||
|
|
||||||
|
// 경고 표시
|
||||||
|
if (minFOVProp.floatValue >= maxFOVProp.floatValue)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("최소 FOV는 최대 FOV보다 작아야 합니다.", MessageType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("Shift + 좌클릭/우클릭 드래그로 FOV를 조절합니다.\n위로 밀면 줌인(FOV 감소), 아래로 밀면 줌아웃(FOV 증가)", MessageType.Info);
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawRotationTargetSection()
|
private void DrawRotationTargetSection()
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginVertical(sectionBoxStyle);
|
EditorGUILayout.BeginVertical(sectionBoxStyle);
|
||||||
@ -254,11 +295,20 @@ public class CameraManagerEditor : Editor
|
|||||||
|
|
||||||
EditorGUI.BeginDisabledGroup(!useBlendTransitionProp.boolValue);
|
EditorGUI.BeginDisabledGroup(!useBlendTransitionProp.boolValue);
|
||||||
EditorGUILayout.PropertyField(blendTimeProp, new GUIContent("블렌드 시간", "크로스 디졸브 소요 시간 (초)"));
|
EditorGUILayout.PropertyField(blendTimeProp, new GUIContent("블렌드 시간", "크로스 디졸브 소요 시간 (초)"));
|
||||||
|
EditorGUILayout.PropertyField(useRealtimeBlendProp, new GUIContent("실시간 블렌딩", "두 카메라를 동시에 렌더링하며 블렌딩 (성능 비용 증가)"));
|
||||||
EditorGUI.EndDisabledGroup();
|
EditorGUI.EndDisabledGroup();
|
||||||
|
|
||||||
if (useBlendTransitionProp.boolValue)
|
if (useBlendTransitionProp.boolValue)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("URP Renderer에 CameraBlendRendererFeature가 추가되어 있어야 합니다.", MessageType.Info);
|
if (useRealtimeBlendProp.boolValue)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("실시간 모드: 블렌딩 중 두 카메라 모두 실시간으로 렌더링됩니다.\n성능 비용이 증가하지만 이전 카메라도 움직입니다.", MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("스냅샷 모드: 이전 화면을 캡처한 후 블렌딩합니다.\n성능 효율적이지만 이전 화면이 정지합니다.", MessageType.Info);
|
||||||
|
}
|
||||||
|
EditorGUILayout.HelpBox("URP Renderer에 CameraBlendRendererFeature가 추가되어 있어야 합니다.", MessageType.Warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ public class InputHandler : MonoBehaviour
|
|||||||
private bool isOrbitActive;
|
private bool isOrbitActive;
|
||||||
private bool isZoomActive;
|
private bool isZoomActive;
|
||||||
private bool isCtrlRightZoomActive;
|
private bool isCtrlRightZoomActive;
|
||||||
|
private bool isFOVActive;
|
||||||
|
|
||||||
// 현재 활성화된 입력 모드 (충돌 방지)
|
// 현재 활성화된 입력 모드 (충돌 방지)
|
||||||
private InputMode currentMode = InputMode.None;
|
private InputMode currentMode = InputMode.None;
|
||||||
@ -23,6 +24,7 @@ public class InputHandler : MonoBehaviour
|
|||||||
Orbit, // Alt + 우클릭 또는 Alt + 좌클릭
|
Orbit, // Alt + 우클릭 또는 Alt + 좌클릭
|
||||||
Zoom, // Ctrl + 좌클릭
|
Zoom, // Ctrl + 좌클릭
|
||||||
CtrlRightZoom, // Ctrl + 우클릭
|
CtrlRightZoom, // Ctrl + 우클릭
|
||||||
|
FOV, // Shift + 좌클릭 또는 Shift + 우클릭
|
||||||
Rotation, // 우클릭
|
Rotation, // 우클릭
|
||||||
Pan // 휠클릭
|
Pan // 휠클릭
|
||||||
}
|
}
|
||||||
@ -57,6 +59,7 @@ public class InputHandler : MonoBehaviour
|
|||||||
isOrbitActive = false;
|
isOrbitActive = false;
|
||||||
isZoomActive = false;
|
isZoomActive = false;
|
||||||
isCtrlRightZoomActive = false;
|
isCtrlRightZoomActive = false;
|
||||||
|
isFOVActive = false;
|
||||||
isRightMouseHeld = false;
|
isRightMouseHeld = false;
|
||||||
isMiddleMouseHeld = false;
|
isMiddleMouseHeld = false;
|
||||||
lastScrollValue = 0f;
|
lastScrollValue = 0f;
|
||||||
@ -85,6 +88,7 @@ public class InputHandler : MonoBehaviour
|
|||||||
bool middleMouse = Input.GetMouseButton(2);
|
bool middleMouse = Input.GetMouseButton(2);
|
||||||
bool altKey = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
|
bool altKey = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
|
||||||
bool ctrlKey = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
bool ctrlKey = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
||||||
|
bool shiftKey = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
|
||||||
|
|
||||||
// 모든 마우스 버튼이 해제되면 모드 리셋
|
// 모든 마우스 버튼이 해제되면 모드 리셋
|
||||||
if (!leftMouse && !rightMouse && !middleMouse)
|
if (!leftMouse && !rightMouse && !middleMouse)
|
||||||
@ -107,6 +111,10 @@ public class InputHandler : MonoBehaviour
|
|||||||
{
|
{
|
||||||
currentMode = InputMode.Zoom;
|
currentMode = InputMode.Zoom;
|
||||||
}
|
}
|
||||||
|
else if (shiftKey && (leftMouse || rightMouse))
|
||||||
|
{
|
||||||
|
currentMode = InputMode.FOV;
|
||||||
|
}
|
||||||
else if (rightMouse)
|
else if (rightMouse)
|
||||||
{
|
{
|
||||||
currentMode = InputMode.Rotation;
|
currentMode = InputMode.Rotation;
|
||||||
@ -121,6 +129,7 @@ public class InputHandler : MonoBehaviour
|
|||||||
isOrbitActive = (currentMode == InputMode.Orbit) && (rightMouse || leftMouse) && altKey;
|
isOrbitActive = (currentMode == InputMode.Orbit) && (rightMouse || leftMouse) && altKey;
|
||||||
isZoomActive = (currentMode == InputMode.Zoom) && leftMouse && ctrlKey;
|
isZoomActive = (currentMode == InputMode.Zoom) && leftMouse && ctrlKey;
|
||||||
isCtrlRightZoomActive = (currentMode == InputMode.CtrlRightZoom) && rightMouse && ctrlKey;
|
isCtrlRightZoomActive = (currentMode == InputMode.CtrlRightZoom) && rightMouse && ctrlKey;
|
||||||
|
isFOVActive = (currentMode == InputMode.FOV) && (leftMouse || rightMouse) && shiftKey;
|
||||||
isRightMouseHeld = (currentMode == InputMode.Rotation) && rightMouse;
|
isRightMouseHeld = (currentMode == InputMode.Rotation) && rightMouse;
|
||||||
isMiddleMouseHeld = (currentMode == InputMode.Pan) && middleMouse;
|
isMiddleMouseHeld = (currentMode == InputMode.Pan) && middleMouse;
|
||||||
|
|
||||||
@ -137,6 +146,10 @@ public class InputHandler : MonoBehaviour
|
|||||||
{
|
{
|
||||||
currentMode = InputMode.None;
|
currentMode = InputMode.None;
|
||||||
}
|
}
|
||||||
|
else if (currentMode == InputMode.FOV && ((!leftMouse && !rightMouse) || !shiftKey))
|
||||||
|
{
|
||||||
|
currentMode = InputMode.None;
|
||||||
|
}
|
||||||
else if (currentMode == InputMode.Rotation && !rightMouse)
|
else if (currentMode == InputMode.Rotation && !rightMouse)
|
||||||
{
|
{
|
||||||
currentMode = InputMode.None;
|
currentMode = InputMode.None;
|
||||||
@ -156,6 +169,7 @@ public class InputHandler : MonoBehaviour
|
|||||||
public bool IsOrbitActive() => isOrbitActive;
|
public bool IsOrbitActive() => isOrbitActive;
|
||||||
public bool IsZoomActive() => isZoomActive;
|
public bool IsZoomActive() => isZoomActive;
|
||||||
public bool IsCtrlRightZoomActive() => isCtrlRightZoomActive;
|
public bool IsCtrlRightZoomActive() => isCtrlRightZoomActive;
|
||||||
|
public bool IsFOVActive() => isFOVActive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 현재 어떤 입력 모드도 활성화되지 않았는지 확인합니다.
|
/// 현재 어떤 입력 모드도 활성화되지 않았는지 확인합니다.
|
||||||
|
|||||||
@ -726,9 +726,14 @@ namespace Streamingle.Prop.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (ext == ".prefab")
|
if (ext == ".prefab")
|
||||||
|
{
|
||||||
|
// Prefab 폴더 안에 있는 프리팹만 수집 (Particle 등 다른 폴더 제외)
|
||||||
|
string parentFolder = Path.GetFileName(Path.GetDirectoryName(file));
|
||||||
|
if (parentFolder.Equals("Prefab", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
propInfo.prefabPaths.Add(assetPath);
|
propInfo.prefabPaths.Add(assetPath);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (ext == ".glb" || ext == ".fbx" || ext == ".obj")
|
else if (ext == ".glb" || ext == ".fbx" || ext == ".obj")
|
||||||
{
|
{
|
||||||
propInfo.modelPaths.Add(assetPath);
|
propInfo.modelPaths.Add(assetPath);
|
||||||
|
|||||||
@ -33,7 +33,7 @@ Material:
|
|||||||
m_Offset: {x: 0, y: 0}
|
m_Offset: {x: 0, y: 0}
|
||||||
m_Ints: []
|
m_Ints: []
|
||||||
m_Floats:
|
m_Floats:
|
||||||
- _BlendAmount: 1
|
- _BlendAmount: 0.000075280666
|
||||||
m_Colors: []
|
m_Colors: []
|
||||||
m_BuildTextureStacks: []
|
m_BuildTextureStacks: []
|
||||||
m_AllowLocking: 1
|
m_AllowLocking: 1
|
||||||
|
|||||||
@ -37,14 +37,15 @@ Shader "Streamingle/CameraBlend"
|
|||||||
{
|
{
|
||||||
float2 uv = input.texcoord;
|
float2 uv = input.texcoord;
|
||||||
|
|
||||||
// 현재 카메라 (Blitter에서 전달됨)
|
// 현재 카메라 (Blitter에서 전달됨) - 동일한 샘플러 사용
|
||||||
float4 currentColor = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv);
|
float4 currentColor = SAMPLE_TEXTURE2D(_BlitTexture, sampler_LinearClamp, uv);
|
||||||
|
|
||||||
// 이전 카메라 (캡처된 텍스처)
|
// 이전 카메라 (캡처된 텍스처) - 동일한 Linear 샘플러 사용
|
||||||
float4 prevColor = SAMPLE_TEXTURE2D(_PrevTex, sampler_PrevTex, uv);
|
float4 prevColor = SAMPLE_TEXTURE2D(_PrevTex, sampler_LinearClamp, uv);
|
||||||
|
|
||||||
// 두 카메라를 블렌딩 (BlendAmount: 0 = 이전, 1 = 현재)
|
// 리니어 공간에서 블렌딩 (BlendAmount: 1 = 이전, 0 = 현재)
|
||||||
float4 finalColor = lerp(prevColor, currentColor, _BlendAmount);
|
// 이전 카메라(B)가 위에 덮이고 서서히 사라짐
|
||||||
|
float4 finalColor = lerp(currentColor, prevColor, _BlendAmount);
|
||||||
|
|
||||||
return finalColor;
|
return finalColor;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,8 @@ public class CameraBlendRendererFeature : ScriptableRendererFeature
|
|||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
public class Settings
|
public class Settings
|
||||||
{
|
{
|
||||||
// BeforeRenderingPostProcessing으로 변경 - 포스트 프로세싱 전에 블렌딩
|
// AfterRenderingPostProcessing - 모든 포스트 프로세싱 적용 후 블렌딩
|
||||||
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
|
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
||||||
public Material blendMaterial;
|
public Material blendMaterial;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,8 +35,9 @@ public class CameraBlendRendererFeature : ScriptableRendererFeature
|
|||||||
if (renderingData.cameraData.cameraType != CameraType.Game)
|
if (renderingData.cameraData.cameraType != CameraType.Game)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 블렌딩이 활성화된 경우에만 패스 추가
|
// 캡처 요청이 있거나 블렌딩 중일 때 패스 추가
|
||||||
if (CameraBlendController.IsBlending && CameraBlendController.BlendTexture != null)
|
if (CameraBlendController.CaptureRequested ||
|
||||||
|
(CameraBlendController.IsBlending && CameraBlendController.BlendTexture != null))
|
||||||
{
|
{
|
||||||
renderer.EnqueuePass(blendPass);
|
renderer.EnqueuePass(blendPass);
|
||||||
}
|
}
|
||||||
@ -63,6 +64,12 @@ public class CameraBlendRenderPass : ScriptableRenderPass
|
|||||||
public TextureHandle destinationTexture;
|
public TextureHandle destinationTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CapturePassData
|
||||||
|
{
|
||||||
|
public TextureHandle sourceTexture;
|
||||||
|
public RenderTexture targetTexture;
|
||||||
|
}
|
||||||
|
|
||||||
public CameraBlendRenderPass(CameraBlendRendererFeature.Settings settings)
|
public CameraBlendRenderPass(CameraBlendRendererFeature.Settings settings)
|
||||||
{
|
{
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
@ -75,16 +82,12 @@ public class CameraBlendRenderPass : ScriptableRenderPass
|
|||||||
if (settings.blendMaterial == null)
|
if (settings.blendMaterial == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!CameraBlendController.IsBlending || CameraBlendController.BlendTexture == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var resourceData = frameData.Get<UniversalResourceData>();
|
var resourceData = frameData.Get<UniversalResourceData>();
|
||||||
var cameraData = frameData.Get<UniversalCameraData>();
|
var cameraData = frameData.Get<UniversalCameraData>();
|
||||||
|
|
||||||
// 백버퍼인 경우 스킵 (BeforeRenderingPostProcessing에서는 일반적으로 false)
|
// 백버퍼인 경우 스킵
|
||||||
if (resourceData.isActiveTargetBackBuffer)
|
if (resourceData.isActiveTargetBackBuffer)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[CameraBlend] isActiveTargetBackBuffer - skipping");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,10 +96,36 @@ public class CameraBlendRenderPass : ScriptableRenderPass
|
|||||||
// source가 유효한지 확인
|
// source가 유효한지 확인
|
||||||
if (!source.IsValid())
|
if (!source.IsValid())
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[CameraBlend] source texture is not valid");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 캡처 요청 처리 - 현재 프레임(포스트 프로세싱 적용 후)을 캡처
|
||||||
|
if (CameraBlendController.CaptureRequested && CameraBlendController.BlendTexture != null)
|
||||||
|
{
|
||||||
|
// UnsafePass를 사용하여 RenderTexture에 직접 복사
|
||||||
|
using (var builder = renderGraph.AddUnsafePass<CapturePassData>("Camera Blend Capture", out var captureData, profilingSampler))
|
||||||
|
{
|
||||||
|
captureData.sourceTexture = source;
|
||||||
|
captureData.targetTexture = CameraBlendController.BlendTexture;
|
||||||
|
|
||||||
|
builder.UseTexture(source, AccessFlags.Read);
|
||||||
|
builder.AllowPassCulling(false);
|
||||||
|
|
||||||
|
builder.SetRenderFunc((CapturePassData data, UnsafeGraphContext context) =>
|
||||||
|
{
|
||||||
|
// NativeCommandBuffer를 통해 Blit 수행
|
||||||
|
var nativeCmd = CommandBufferHelpers.GetNativeCommandBuffer(context.cmd);
|
||||||
|
nativeCmd.Blit(data.sourceTexture, data.targetTexture);
|
||||||
|
CameraBlendController.CaptureReady = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return; // 캡처만 하고 블렌딩은 다음 프레임부터
|
||||||
|
}
|
||||||
|
|
||||||
|
// 블렌딩이 활성화되지 않았으면 스킵
|
||||||
|
if (!CameraBlendController.IsBlending || CameraBlendController.BlendTexture == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// cameraColorDesc 사용 - 카메라 색상 텍스처 설명자
|
// cameraColorDesc 사용 - 카메라 색상 텍스처 설명자
|
||||||
var cameraTargetDesc = renderGraph.GetTextureDesc(resourceData.cameraColor);
|
var cameraTargetDesc = renderGraph.GetTextureDesc(resourceData.cameraColor);
|
||||||
cameraTargetDesc.name = "_CameraBlendDestination";
|
cameraTargetDesc.name = "_CameraBlendDestination";
|
||||||
@ -158,6 +187,9 @@ public static class CameraBlendController
|
|||||||
private static bool isBlending = false;
|
private static bool isBlending = false;
|
||||||
private static float blendAmount = 1f;
|
private static float blendAmount = 1f;
|
||||||
private static RenderTexture blendTexture;
|
private static RenderTexture blendTexture;
|
||||||
|
private static bool captureRequested = false;
|
||||||
|
private static bool captureReady = false;
|
||||||
|
private static bool isRealtimeMode = false;
|
||||||
|
|
||||||
public static bool IsBlending
|
public static bool IsBlending
|
||||||
{
|
{
|
||||||
@ -177,6 +209,63 @@ public static class CameraBlendController
|
|||||||
set => blendTexture = value;
|
set => blendTexture = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool CaptureRequested
|
||||||
|
{
|
||||||
|
get => captureRequested;
|
||||||
|
set => captureRequested = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CaptureReady
|
||||||
|
{
|
||||||
|
get => captureReady;
|
||||||
|
set => captureReady = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsRealtimeMode
|
||||||
|
{
|
||||||
|
get => isRealtimeMode;
|
||||||
|
set => isRealtimeMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 다음 프레임에서 현재 화면을 캡처하도록 요청합니다. (스냅샷 모드)
|
||||||
|
/// </summary>
|
||||||
|
public static void RequestCapture(RenderTexture targetTexture)
|
||||||
|
{
|
||||||
|
blendTexture = targetTexture;
|
||||||
|
captureRequested = true;
|
||||||
|
captureReady = false;
|
||||||
|
isRealtimeMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 캡처가 완료된 후 블렌딩을 시작합니다. (스냅샷 모드)
|
||||||
|
/// BlendAmount = 1에서 시작 (이전 카메라 100% 보임)
|
||||||
|
/// </summary>
|
||||||
|
public static void StartBlendAfterCapture()
|
||||||
|
{
|
||||||
|
if (captureReady && blendTexture != null)
|
||||||
|
{
|
||||||
|
blendAmount = 1f;
|
||||||
|
isBlending = true;
|
||||||
|
captureRequested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 실시간 블렌딩을 시작합니다. (매 프레임 이전 카메라 렌더링)
|
||||||
|
/// BlendAmount = 1에서 시작 (이전 카메라 100% 보임)
|
||||||
|
/// </summary>
|
||||||
|
public static void StartRealtimeBlend(RenderTexture texture)
|
||||||
|
{
|
||||||
|
blendTexture = texture;
|
||||||
|
blendAmount = 1f;
|
||||||
|
isBlending = true;
|
||||||
|
isRealtimeMode = true;
|
||||||
|
captureRequested = false;
|
||||||
|
captureReady = false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void StartBlend(RenderTexture texture)
|
public static void StartBlend(RenderTexture texture)
|
||||||
{
|
{
|
||||||
blendTexture = texture;
|
blendTexture = texture;
|
||||||
@ -189,6 +278,9 @@ public static class CameraBlendController
|
|||||||
isBlending = false;
|
isBlending = false;
|
||||||
blendAmount = 1f;
|
blendAmount = 1f;
|
||||||
blendTexture = null;
|
blendTexture = null;
|
||||||
|
captureRequested = false;
|
||||||
|
captureReady = false;
|
||||||
|
isRealtimeMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Reset()
|
public static void Reset()
|
||||||
|
|||||||
@ -47,14 +47,29 @@ namespace WefLab
|
|||||||
[Tooltip("Maximum donation amount (inclusive, -1 for unlimited)")]
|
[Tooltip("Maximum donation amount (inclusive, -1 for unlimited)")]
|
||||||
public int maxAmount = -1;
|
public int maxAmount = -1;
|
||||||
|
|
||||||
[Header("Event Settings")]
|
[Header("Repeat Settings")]
|
||||||
[Tooltip("Number of times to repeat the event (minimum 1)")]
|
[Tooltip("Use amount-based repeat count (divide amount by amountPerRepeat)")]
|
||||||
|
public bool useAmountBasedRepeat = false;
|
||||||
|
|
||||||
|
[Tooltip("Amount of KRW per repeat (e.g., 1000 = 1 repeat per 1000 KRW)")]
|
||||||
[Min(1)]
|
[Min(1)]
|
||||||
public int repeatCount = 1;
|
public int amountPerRepeat = 1000;
|
||||||
|
|
||||||
|
[Tooltip("Minimum repeat count")]
|
||||||
|
[Min(1)]
|
||||||
|
public int minRepeatCount = 1;
|
||||||
|
|
||||||
|
[Tooltip("Maximum repeat count (0 = unlimited)")]
|
||||||
|
[Min(0)]
|
||||||
|
public int maxRepeatCount = 20;
|
||||||
|
|
||||||
|
[Tooltip("Fixed repeat count (used when useAmountBasedRepeat is false)")]
|
||||||
|
[Min(1)]
|
||||||
|
public int fixedRepeatCount = 1;
|
||||||
|
|
||||||
[Tooltip("Delay between each repeat in seconds (0 for immediate)")]
|
[Tooltip("Delay between each repeat in seconds (0 for immediate)")]
|
||||||
[Min(0)]
|
[Min(0)]
|
||||||
public float repeatDelay = 0f;
|
public float repeatDelay = 0.15f;
|
||||||
|
|
||||||
[Header("Event")]
|
[Header("Event")]
|
||||||
[Tooltip("Unity event to trigger when donation amount is in range")]
|
[Tooltip("Unity event to trigger when donation amount is in range")]
|
||||||
@ -73,6 +88,29 @@ namespace WefLab
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculate repeat count based on donation amount
|
||||||
|
/// </summary>
|
||||||
|
public int GetRepeatCount(int amount)
|
||||||
|
{
|
||||||
|
if (!useAmountBasedRepeat)
|
||||||
|
{
|
||||||
|
return fixedRepeatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate repeat count based on amount
|
||||||
|
int count = amount / amountPerRepeat;
|
||||||
|
|
||||||
|
// Apply min/max limits
|
||||||
|
count = Mathf.Max(count, minRepeatCount);
|
||||||
|
if (maxRepeatCount > 0)
|
||||||
|
{
|
||||||
|
count = Mathf.Min(count, maxRepeatCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -89,6 +127,27 @@ namespace WefLab
|
|||||||
[Tooltip("Donation event triggers based on amount ranges")]
|
[Tooltip("Donation event triggers based on amount ranges")]
|
||||||
public DonationEventTrigger[] donationTriggers = Array.Empty<DonationEventTrigger>();
|
public DonationEventTrigger[] donationTriggers = Array.Empty<DonationEventTrigger>();
|
||||||
|
|
||||||
|
[Header("Queue Settings")]
|
||||||
|
[Tooltip("Enable sequential processing of donations")]
|
||||||
|
public bool enableQueue = true;
|
||||||
|
|
||||||
|
[Tooltip("Delay between each donation alert (seconds)")]
|
||||||
|
[Min(0.1f)]
|
||||||
|
public float alertDelay = 3f;
|
||||||
|
|
||||||
|
[Tooltip("Maximum queue size (0 = unlimited)")]
|
||||||
|
[Min(0)]
|
||||||
|
public int maxQueueSize = 50;
|
||||||
|
|
||||||
|
// Queue state (visible in Inspector for debugging)
|
||||||
|
[Header("Queue Status (Read Only)")]
|
||||||
|
[SerializeField] private int queueCount = 0;
|
||||||
|
[SerializeField] private bool isProcessingQueue = false;
|
||||||
|
|
||||||
|
// Donation queue
|
||||||
|
private Queue<DonationData> donationQueue = new Queue<DonationData>();
|
||||||
|
private Coroutine queueProcessorCoroutine = null;
|
||||||
|
|
||||||
// Hidden connection info
|
// Hidden connection info
|
||||||
[HideInInspector] public bool isConnected = false;
|
[HideInInspector] public bool isConnected = false;
|
||||||
[HideInInspector] public string currentSid = "";
|
[HideInInspector] public string currentSid = "";
|
||||||
@ -532,7 +591,7 @@ namespace WefLab
|
|||||||
timestamp = timestamp
|
timestamp = timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
TriggerDonationEvents(donation);
|
EnqueueDonation(donation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -580,8 +639,64 @@ namespace WefLab
|
|||||||
|
|
||||||
Debug.Log($"[WefLab] Donation: {donorName} - {rawAmount} → {amount} KRW ({platform})");
|
Debug.Log($"[WefLab] Donation: {donorName} - {rawAmount} → {amount} KRW ({platform})");
|
||||||
|
|
||||||
|
// Enqueue donation for sequential processing
|
||||||
|
EnqueueDonation(donation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enqueue donation for sequential processing
|
||||||
|
/// </summary>
|
||||||
|
private void EnqueueDonation(DonationData donation)
|
||||||
|
{
|
||||||
|
if (enableQueue)
|
||||||
|
{
|
||||||
|
// Check queue size limit
|
||||||
|
if (maxQueueSize > 0 && donationQueue.Count >= maxQueueSize)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[WefLab] Queue full ({maxQueueSize}), dropping oldest donation");
|
||||||
|
donationQueue.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
donationQueue.Enqueue(donation);
|
||||||
|
queueCount = donationQueue.Count;
|
||||||
|
Debug.Log($"[WefLab] Donation queued. Queue size: {queueCount}");
|
||||||
|
|
||||||
|
// Start queue processor if not running
|
||||||
|
if (!isProcessingQueue)
|
||||||
|
{
|
||||||
|
queueProcessorCoroutine = StartCoroutine(ProcessDonationQueue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Direct trigger without queue (original behavior)
|
||||||
|
TriggerDonationEvents(donation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process donation queue sequentially with fixed delay
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator ProcessDonationQueue()
|
||||||
|
{
|
||||||
|
isProcessingQueue = true;
|
||||||
|
|
||||||
|
while (donationQueue.Count > 0)
|
||||||
|
{
|
||||||
|
var donation = donationQueue.Dequeue();
|
||||||
|
queueCount = donationQueue.Count;
|
||||||
|
|
||||||
// Trigger events
|
// Trigger events
|
||||||
TriggerDonationEvents(donation);
|
TriggerDonationEvents(donation);
|
||||||
|
|
||||||
|
Debug.Log($"[WefLab] Alert triggered. Waiting {alertDelay:F1}s. Remaining in queue: {donationQueue.Count}");
|
||||||
|
|
||||||
|
// Wait for fixed delay
|
||||||
|
yield return new WaitForSeconds(alertDelay);
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessingQueue = false;
|
||||||
|
queueProcessorCoroutine = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -606,10 +721,11 @@ namespace WefLab
|
|||||||
// Check if donation amount is in range
|
// Check if donation amount is in range
|
||||||
if (trigger.IsInRange(donation.amount))
|
if (trigger.IsInRange(donation.amount))
|
||||||
{
|
{
|
||||||
Debug.Log($"[WefLab] Triggering: {trigger.triggerName} (Amount: {donation.amount}, Range: {trigger.minAmount}-{(trigger.maxAmount >= 0 ? trigger.maxAmount.ToString() : "unlimited")}, Repeat: {trigger.repeatCount}x)");
|
int repeatCount = trigger.GetRepeatCount(donation.amount);
|
||||||
|
Debug.Log($"[WefLab] Triggering: {trigger.triggerName} (Amount: {donation.amount}, Range: {trigger.minAmount}-{(trigger.maxAmount >= 0 ? trigger.maxAmount.ToString() : "unlimited")}, Repeat: {repeatCount}x)");
|
||||||
|
|
||||||
// Start coroutine to handle repeated invocation
|
// Start coroutine to handle repeated invocation
|
||||||
StartCoroutine(InvokeRepeatedEvent(trigger, donation));
|
StartCoroutine(InvokeRepeatedEvent(trigger, donation, repeatCount));
|
||||||
triggeredAny = true;
|
triggeredAny = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -623,15 +739,15 @@ namespace WefLab
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Coroutine to invoke event multiple times with delay
|
/// Coroutine to invoke event multiple times with delay
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private IEnumerator InvokeRepeatedEvent(DonationEventTrigger trigger, DonationData donation)
|
private IEnumerator InvokeRepeatedEvent(DonationEventTrigger trigger, DonationData donation, int repeatCount)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < trigger.repeatCount; i++)
|
for (int i = 0; i < repeatCount; i++)
|
||||||
{
|
{
|
||||||
// Invoke the event
|
// Invoke the event
|
||||||
trigger.onDonation?.Invoke(donation);
|
trigger.onDonation?.Invoke(donation);
|
||||||
|
|
||||||
// Wait for delay before next repetition (skip on last iteration)
|
// Wait for delay before next repetition (skip on last iteration)
|
||||||
if (i < trigger.repeatCount - 1 && trigger.repeatDelay > 0)
|
if (i < repeatCount - 1 && trigger.repeatDelay > 0)
|
||||||
{
|
{
|
||||||
yield return new WaitForSeconds(trigger.repeatDelay);
|
yield return new WaitForSeconds(trigger.repeatDelay);
|
||||||
}
|
}
|
||||||
@ -724,6 +840,81 @@ namespace WefLab
|
|||||||
return isConnected && ws != null && ws.IsAlive;
|
return isConnected && ws != null && ws.IsAlive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get current queue count
|
||||||
|
/// </summary>
|
||||||
|
public int GetQueueCount()
|
||||||
|
{
|
||||||
|
return donationQueue.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if queue is being processed
|
||||||
|
/// </summary>
|
||||||
|
public bool IsQueueProcessing()
|
||||||
|
{
|
||||||
|
return isProcessingQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clear all pending donations in queue
|
||||||
|
/// </summary>
|
||||||
|
public void ClearQueue()
|
||||||
|
{
|
||||||
|
donationQueue.Clear();
|
||||||
|
queueCount = 0;
|
||||||
|
Debug.Log("[WefLab] Queue cleared");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Skip current alert and move to next in queue
|
||||||
|
/// </summary>
|
||||||
|
public void SkipCurrentAlert()
|
||||||
|
{
|
||||||
|
if (queueProcessorCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(queueProcessorCoroutine);
|
||||||
|
queueProcessorCoroutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (donationQueue.Count > 0)
|
||||||
|
{
|
||||||
|
Debug.Log("[WefLab] Skipping to next alert");
|
||||||
|
queueProcessorCoroutine = StartCoroutine(ProcessDonationQueue());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isProcessingQueue = false;
|
||||||
|
Debug.Log("[WefLab] No more alerts in queue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pause queue processing
|
||||||
|
/// </summary>
|
||||||
|
public void PauseQueue()
|
||||||
|
{
|
||||||
|
if (queueProcessorCoroutine != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(queueProcessorCoroutine);
|
||||||
|
queueProcessorCoroutine = null;
|
||||||
|
isProcessingQueue = false;
|
||||||
|
Debug.Log("[WefLab] Queue paused");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resume queue processing
|
||||||
|
/// </summary>
|
||||||
|
public void ResumeQueue()
|
||||||
|
{
|
||||||
|
if (!isProcessingQueue && donationQueue.Count > 0)
|
||||||
|
{
|
||||||
|
queueProcessorCoroutine = StartCoroutine(ProcessDonationQueue());
|
||||||
|
Debug.Log("[WefLab] Queue resumed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user