Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class MultiKeyringScene: SKScene {
var carabinerBackImageURL: String? // 카라비너 뒷면 이미지 (hamburger 타입)
var carabinerFrontImageURL: String? // 카라비너 앞면 이미지 (hamburger 타입)

// MARK: - 영상 생성용 최적화 플래그
var disableShadows: Bool = false // 그림자 비활성화 (영상 생성 시 성능 최적화)

// MARK: - 카라비너 크기 및 위치 정보
var carabinerId: String = "" // 카라비너 ID (bundleKeyringScale용)
var carabinerX: CGFloat = 0 // 카라비너 중심 X 좌표
Expand Down Expand Up @@ -243,6 +246,8 @@ class MultiKeyringScene: SKScene {
/// - offsetY: Y축 오프셋 (기본값 -8)
/// - blurRadius: Gaussian Blur 강도 (기본값 5.0)
private func addShadowToNode(_ node: SKSpriteNode, offsetX: CGFloat = 8, offsetY: CGFloat = -8, blurRadius: CGFloat = 5.0) {
// 영상 생성 시 그림자 비활성화 (성능 최적화)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

영상 만드는데 그림자가 좀 무거웠나보네요 조금 아쉽긴 하지만 편의성을 위해서 조금 덜고 가는게 맞을 것 같네요.

guard !disableShadows else { return }
// 원본 노드를 복제해서 그림자로 사용
guard let shadowNode = node.copy() as? SKSpriteNode else { return }

Expand Down
68 changes: 46 additions & 22 deletions Keychy/Keychy/Core/Video/Bundle/BundleVideoGenerator+Particle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import Lottie
extension BundleVideoGenerator {

/// 파티클 재생 정보
/// 현재 재생 중인 파티클을 렌더링하기 위해 필요한 모든 정보를 담고 있음
/// 시작 시점에 모든 프레임을 프리렌더링하여 재생 중에는 캐시된 텍스처만 사용
struct ParticlePlaybackInfo {
let displaySprite: SKSpriteNode // 화면에 표시되는 스프라이트
let startedAtFrame: Int // 파티클 시작 프레임
let particleId: String // 파티클 ID
let lottieRenderer: LottieAnimationView // Lottie → 이미지 변환 렌더러
let animationData: LottieAnimation // Lottie 메타데이터 (총 프레임 수 등)
let displaySprite: SKSpriteNode // 화면에 표시되는 스프라이트
let startedAtFrame: Int // 파티클 시작 프레임
let particleId: String // 파티클 ID
let preRenderedTextures: [SKTexture] // 프리렌더링된 텍스처 배열
}

/// 파티클 업데이트
Expand All @@ -43,60 +42,85 @@ extension BundleVideoGenerator {
particleIndicesToRemove.forEach { playingParticles.removeValue(forKey: $0) }
}

/// 파티클 시작
/// 파티클 시작 (모든 프레임을 프리렌더링)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

뭐든 미리 준비해두는게 빠르네요ㅋㅋㅋㅋ 굿! 확실히 많이 매끄러워졌어요.

private func startParticle(for keyringIndex: Int, particleId: String, at frameIndex: Int, scene: MultiKeyringScene) {
guard let animation = findParticleAnimation(particleId: particleId) else {
return
}

// Lottie 뷰 설정
let config = LottieConfiguration(renderingEngine: .mainThread)
let lottieView = LottieAnimationView(animation: animation, configuration: config)
lottieView.frame = CGRect(origin: .zero, size: CGSize(width: scene.size.width, height: scene.size.height))
lottieView.contentMode = .scaleAspectFit
lottieView.backgroundBehavior = .pauseAndRestore

// 모든 프레임을 프리렌더링
let textures = preRenderAllFrames(lottieView: lottieView, animation: animation)

// 스프라이트 생성
let sprite = SKSpriteNode()
sprite.size = CGSize(width: scene.size.width, height: scene.size.height)
sprite.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2)
sprite.zPosition = 100
sprite.alpha = 1.0

// 첫 프레임 텍스처 설정
if let firstTexture = textures.first {
sprite.texture = firstTexture
}

scene.addChild(sprite)

playingParticles[keyringIndex] = ParticlePlaybackInfo(
displaySprite: sprite,
startedAtFrame: frameIndex,
particleId: particleId,
lottieRenderer: lottieView,
animationData: animation
preRenderedTextures: textures
)
}

/// 파티클 렌더링
/// Lottie 애니메이션의 모든 프레임을 SKTexture로 프리렌더링
private func preRenderAllFrames(lottieView: LottieAnimationView, animation: LottieAnimation) -> [SKTexture] {
let totalFrames = Int(animation.endFrame - animation.startFrame)
var textures: [SKTexture] = []
textures.reserveCapacity(totalFrames)

// UIGraphicsImageRenderer를 한 번만 생성 (재사용)
let imageRenderer = UIGraphicsImageRenderer(bounds: lottieView.bounds)

for frameOffset in 0..<totalFrames {
let targetFrame = animation.startFrame + CGFloat(frameOffset)
lottieView.currentFrame = AnimationFrameTime(targetFrame)
lottieView.setNeedsDisplay()
lottieView.layer.displayIfNeeded()

let image = imageRenderer.image { context in
lottieView.layer.render(in: context.cgContext)
}
textures.append(SKTexture(image: image))
}

return textures
}

/// 파티클 렌더링 (프리렌더링된 텍스처 사용)
private func updateActiveParticle(for keyringIndex: Int, at frameIndex: Int, scene: MultiKeyringScene) -> Bool {
guard let particleInfo = playingParticles[keyringIndex] else {
return false
}

let sprite = particleInfo.displaySprite
let offset = frameIndex - particleInfo.startedAtFrame
let targetFrame = particleInfo.animationData.startFrame + CGFloat(offset)

if targetFrame >= particleInfo.animationData.endFrame {
// 애니메이션 종료 체크
if offset >= particleInfo.preRenderedTextures.count {
sprite.removeFromParent()
return true
}

particleInfo.lottieRenderer.currentFrame = AnimationFrameTime(targetFrame)
particleInfo.lottieRenderer.setNeedsDisplay()
particleInfo.lottieRenderer.layer.displayIfNeeded()

let imageRenderer = UIGraphicsImageRenderer(bounds: particleInfo.lottieRenderer.bounds)
let image = imageRenderer.image { context in
particleInfo.lottieRenderer.layer.render(in: context.cgContext)
}

sprite.texture = SKTexture(image: image)
// 프리렌더링된 텍스처 사용 (매우 빠름)
sprite.texture = particleInfo.preRenderedTextures[offset]

return false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ extension BundleVideoGenerator {
scene.currentCarabinerType = carabinerType
scene.scaleMode = .aspectFill
scene.size = CGSize(width: sceneWidth, height: sceneHeight)
scene.disableShadows = true // 영상 생성 시 그림자 비활성화 (성능 최적화)

if let bgImage = backgroundImage {
let backgroundNode = SKSpriteNode(texture: SKTexture(image: bgImage))
Expand Down