From 31319e429208d40cc97f458ae37933ecd90220e4 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 17 Nov 2025 17:50:38 +0900 Subject: [PATCH] =?UTF-8?q?feat(live-room):=20=EC=99=95=ED=95=98=ED=8A=B8?= =?UTF-8?q?=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Live/Room/LiveRoomViewModel.swift | 114 ++++++++++++------ .../Sources/Live/Room/V2/LiveRoomViewV2.swift | 3 +- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift index fa59594..4819a63 100644 --- a/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift +++ b/SodaLive/Sources/Live/Room/LiveRoomViewModel.swift @@ -9,6 +9,7 @@ import Foundation import Moya import Combine import UIKit +import SwiftUI import AgoraRtcKit import AgoraRtmKit @@ -238,6 +239,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject { @Published var isShowRemoteBigHeart: Bool = false @Published var remoteWaterProgress: CGFloat = 0 @Published var remoteWavePhase: CGFloat = 0 + // 수신자 하트 스케일(0~1): 1.0~1.5초 랜덤으로 0→1 스케일 업 + @Published var remoteHeartScale: CGFloat = 1.0 @Published var bigHeartParticles: [BigHeartParticle] = [] // 최근 폭발 파편의 개수/크기 기록(낙하 효과에 사용) @@ -245,6 +248,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject { private var lastExplosionSizes: [CGFloat] = [] // 폭발 파편이 모두 사라진 직후 비(낙하)를 스폰해야 하는지 여부 private var shouldSpawnRainAfterExplosionEnds: Bool = false + // 진행 중인 연쇄 폭발(버스트) 남은 횟수(중앙 1회 + 랜덤 6회 = 7) + private var pendingExplosionBursts: Int = 0 var signatureImageUrls = [String]() var signatureList = [LiveRoomDonationResponse]() @@ -2102,29 +2107,34 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } private func addBigHeartAnimation() { - // 로컬 발신 직후 1회는 원격 물 채움 연출을 생략하고 바로 폭발만 실행 + // 로컬 발신 직후 1회는 원격 물 채움 연출을 생략하고 바로 연쇄 폭발만 실행 if suppressNextRemoteWaterFill { suppressNextRemoteWaterFill = false - spawnHeartExplosion() + scheduleExplosionBursts() startParticlesTimer() return } - // 요구사항 변경: 물 채우기(1초) 연출 제거. - // 가득 찬 하트를 잠깐(0.15초) 보여준 뒤 폭발 이펙트를 실행. + // 요구사항: 수신자 하트는 0→현재 크기까지 0.5초 동안 스케일 업 후 연쇄 폭발 DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.remoteWaterTimer?.cancel() self.remoteWaterTimer = nil self.remoteWavePhase = 0 self.remoteWaterProgress = 1.0 + self.remoteHeartScale = 0.0 self.isShowRemoteBigHeart = true - DEBUG_LOG("BIG_HEART: show filled heart, then explode after 0.30s") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.30) { [weak self] in + let growDuration = Double.random(in: 1.0...1.5) + withAnimation(.easeOut(duration: growDuration)) { + self.remoteHeartScale = 1.0 + } + DEBUG_LOG("BIG_HEART: scale-up \(growDuration)s then sequential 7 bursts") + DispatchQueue.main.asyncAfter(deadline: .now() + growDuration) { [weak self] in guard let self = self else { return } self.isShowRemoteBigHeart = false self.remoteWaterProgress = 0 self.remoteWavePhase = 0 - self.spawnHeartExplosion() + self.remoteHeartScale = 1.0 + self.scheduleExplosionBursts() self.startParticlesTimer() } } @@ -2165,11 +2175,11 @@ final class LiveRoomViewModel: NSObject, ObservableObject { } // MARK: - BIG HEART - Explosion Particles - private func spawnHeartExplosion() { - // 중심(0,0)에서 폭발하는 작은 하트 파편 생성 - // 색상은 View에서 #ff959a로 렌더링 + private func spawnHeartExplosion(origin: CGPoint = .zero) { + // 주어진 origin에서 폭발하는 작은 하트 파편 생성 + let explosionDuration: Double = 0.7 // 1회 폭발 수명 var particles: [BigHeartParticle] = [] - let count = Int.random(in: 20...35) // 요구사항: 파편 개수 20~35개 + let count = Int.random(in: 20...35) // 파편 개수 20~35개 for i in 0.. [BigHeartParticle] { - let count = max(0, self.lastExplosionCount) - guard count > 0 else { return [] } + // 요구사항: 폭발 후 비 내리는 하트는 80~100개 랜덤 + let count = Int.random(in: 80...100) let bounds = UIScreen.main.bounds let startYBase = -bounds.height * 0.5 - 40 // 화면 위쪽 바깥에서 시작(기본) var rains: [BigHeartParticle] = [] rains.reserveCapacity(count) - for i in 0..