diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index 746afec..af20c37 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -8,6 +8,7 @@ import Foundation import Moya import Combine +import UIKit import AgoraRtcKit import AgoraRtmKit @@ -23,6 +24,8 @@ struct BigHeartParticle: Identifiable { var rotation: Double var life: Double // 남은 수명 (초) var size: CGFloat // 파편 기본 크기 (pt) + var isRain: Bool // 낙하 파편 여부 + var gravityScale: CGFloat // 중력 계수(파티클별 낙하 속도 분산) } final class LiveRoomViewModel: NSObject, ObservableObject { @@ -232,6 +235,12 @@ final class LiveRoomViewModel: NSObject, ObservableObject { @Published var remoteWavePhase: CGFloat = 0 @Published var bigHeartParticles: [BigHeartParticle] = [] + // 최근 폭발 파편의 개수/크기 기록(낙하 효과에 사용) + private var lastExplosionCount: Int = 0 + private var lastExplosionSizes: [CGFloat] = [] + // 폭발 파편이 모두 사라진 직후 비(낙하)를 스폰해야 하는지 여부 + private var shouldSpawnRainAfterExplosionEnds: Bool = false + var signatureImageUrls = [String]() var signatureList = [LiveRoomDonationResponse]() var isShowSignatureImage = false @@ -2149,9 +2158,9 @@ final class LiveRoomViewModel: NSObject, ObservableObject { var vy = CGFloat(sin(angle)) * speed // 조금 더 위로 포물선을 그리도록 초기 위쪽 임펄스 추가 vy -= CGFloat.random(in: 120...220) - // 크기: 30~80pt + // 크기: 20~65pt let size = CGFloat.random(in: 20...65) - let scale: CGFloat = 1.0 // 요구사항: 최종 크기 20~65 유지 (시간 경과에 따라 약간 축소) + let scale: CGFloat = 1.0 // 최종 크기 유지(시간 경과에 따라 약간 축소) let life: Double = Double.random(in: 1.0...1.5) let particle = BigHeartParticle( id: UUID(), @@ -2163,45 +2172,119 @@ final class LiveRoomViewModel: NSObject, ObservableObject { scale: scale, rotation: Double.random(in: 0...360), life: life, - size: size + size: size, + isRain: false, + gravityScale: 1.0 ) particles.append(particle) } + // 기록: 낙하 효과에 동일 개수/크기를 사용하기 위해 저장 + self.lastExplosionCount = count + self.lastExplosionSizes = particles.map { $0.size } DispatchQueue.main.async { self.bigHeartParticles = particles } + // 폭발 파편이 모두 사라진 직후 낙하 하트를 생성하도록 플래그 설정 + self.shouldSpawnRainAfterExplosionEnds = true + } + + // MARK: - BIG HEART - Rain Particles (after explosion) + private func spawnHeartRainFromLastExplosion() -> [BigHeartParticle] { + let count = max(0, self.lastExplosionCount) + guard count > 0 else { return [] } + let bounds = UIScreen.main.bounds + let startYBase = -bounds.height * 0.5 - 40 // 화면 위쪽 바깥에서 시작(기본) + var rains: [BigHeartParticle] = [] + rains.reserveCapacity(count) + for i in 0..= floorY { + continue + } if p.life > 0 && p.opacity > 0.01 { next.append(p) } } + // 폭발 파편 존재 여부(현재 프레임) + let hasExplosionAfter = next.contains(where: { !$0.isRain }) + // 요구사항: 폭발 파편이 모두 사라진 직후 비를 시작 + if hadExplosionBefore && !hasExplosionAfter && self.shouldSpawnRainAfterExplosionEnds { + let rains = self.spawnHeartRainFromLastExplosion() + if !rains.isEmpty { + next.append(contentsOf: rains) + } + self.shouldSpawnRainAfterExplosionEnds = false + } self.bigHeartParticles = next if next.isEmpty { self.bigHeartParticleTimer?.cancel()