iOS 直播特殊礼物特效实现方案(Swift实现,超详细!)

发布于:2025-05-26 ⋅ 阅读:(29) ⋅ 点赞:(0)

特殊礼物特效是提升直播互动体验的关键功能,下面我将详细介绍如何在iOS应用中实现各种高级礼物特效。

  1. 基础特效类型

1.1 全屏动画特效

class FullScreenAnimationView: UIView {
    static func show(with gift: GiftModel, in view: UIView) {
        let effectView = FullScreenAnimationView(gift: gift)
        effectView.frame = view.bounds
        view.addSubview(effectView)
        effectView.startAnimation()
    }
    
    private let gift: GiftModel 
    private var imageView: UIImageView!
    private var animationFrames: [UIImage] = []
    
    init(gift: GiftModel) {
        self.gift = gift
        super.init(frame: .zero)
        setupUI()
        loadAnimationFrames()
    }
    
    private func setupUI() {
        backgroundColor = UIColor.black.withAlphaComponent(0.7)
        
        imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit 
        imageView.frame = bounds
        addSubview(imageView)
    }
    
    private func loadAnimationFrames() {
        // 从本地或网络加载动画帧
        for i in 1...30 { // 假设有30帧动画 
            if let image = UIImage(named: "\(gift.id)_frame_\(i)") {
                animationFrames.append(image)
            }
        }
    }
    
    func startAnimation() {
        imageView.animationImages = animationFrames
        imageView.animationDuration = 3.0 
        imageView.animationRepeatCount = 1 
        imageView.startAnimating()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.removeFromSuperview()
        }
    }
}

1.2 粒子特效

class ParticleEffectView: UIView {
    static func show(with gift: GiftModel, in view: UIView) {
        let effectView = ParticleEffectView(gift: gift)
        effectView.frame = view.bounds 
        view.addSubview(effectView)
        effectView.startEmitter()
    }
    
    private let gift: GiftModel
    private var emitterLayer: CAEmitterLayer!
    
    init(gift: GiftModel) {
        self.gift = gift 
        super.init(frame: .zero)
        setupEmitter()
    }
    
    private func setupEmitter() {
        emitterLayer = CAEmitterLayer()
        emitterLayer.emitterPosition = CGPoint(x: bounds.midX, y: bounds.midY)
        emitterLayer.emitterSize = CGSize(width: bounds.width, height: 1)
        emitterLayer.emitterShape = .sphere 
        emitterLayer.renderMode = .additive 
        emitterLayer.beginTime = CACurrentMediaTime()
        
        let cell = CAEmitterCell()
        cell.contents = UIImage(named: "particle_\(gift.id)")?.cgImage
        cell.birthRate = 50 
        cell.lifetime = 5 
        cell.lifetimeRange = 1 
        cell.velocity = 100
        cell.velocityRange = 50 
        cell.emissionRange = .pi * 2 
        cell.spin = 1 
        cell.spinRange = 2
        cell.scale = 0.5
        cell.scaleRange = 0.3
        cell.scaleSpeed = -0.05 
        cell.alphaSpeed = -0.2 
        
        emitterLayer.emitterCells = [cell]
        layer.addSublayer(emitterLayer)
    }
    
    func startEmitter() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.emitterLayer.birthRate = 0
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                self.removeFromSuperview()
            }
        }
    }
}
  1. 高级特效实现

2.1 3D模型特效 (使用SceneKit)

import SceneKit 
 
class Model3DEffectView: UIView {
    static func show(with gift: GiftModel, in view: UIView) {
        let effectView = Model3DEffectView(gift: gift)
        effectView.frame = view.bounds 
        view.addSubview(effectView)
        effectView.startAnimation()
    }
    
    private let sceneView = SCNView()
    private let gift: GiftModel
    
    init(gift: GiftModel) {
        self.gift = gift
        super.init(frame: .zero)
        setupScene()
    }
    
    private func setupScene() {
        sceneView.frame = bounds
        sceneView.backgroundColor = .clear
        addSubview(sceneView)
        
        let scene = SCNScene()
        sceneView.scene = scene
        
        // 加载3D模型
        if let modelNode = loadModelNode() {
            scene.rootNode.addChildNode(modelNode)
            
            // 添加相机
            let cameraNode = SCNNode()
            cameraNode.camera = SCNCamera()
            cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
            scene.rootNode.addChildNode(cameraNode)
            
            // 添加光源 
            let lightNode = SCNNode()
            lightNode.light = SCNLight()
            lightNode.light?.type = .omni
            lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
            scene.rootNode.addChildNode(lightNode)
        }
    }
    
    private func loadModelNode() -> SCNNode? {
        guard let sceneURL = Bundle.main.url(forResource: gift.id, withExtension: "scn"),
              let sceneSource = SCNSceneSource(url: sceneURL, options: nil) else {
            return nil
        }
        
        return sceneSource.entryWithIdentifier("MDL_OBJ_ROOT", withClass: SCNNode.self)
    }
    
    func startAnimation() {
        // 旋转动画 
        let rotateAction = SCNAction.rotateBy(x: 0, y: .pi * 2, z: 0, duration: 5)
        sceneView.scene?.rootNode.childNodes.first?.runAction(rotateAction)
        
        // 缩放动画
        let scaleUp = SCNAction.scale(to: 1.5, duration: 1)
        let scaleDown = SCNAction.scale(to: 0.5, duration: 1)
        let scaleSequence = SCNAction.sequence([scaleUp, scaleDown])
        let repeatScale = SCNAction.repeat(scaleSequence, count: 2)
        sceneView.scene?.rootNode.childNodes.first?.runAction(repeatScale)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            self.removeFromSuperview()
        }
    }
}

2.2 视频特效 (使用AVPlayer)

class VideoEffectView: UIView {
    static func show(with gift: GiftModel, in view: UIView) {
        let effectView = VideoEffectView(gift: gift)
        effectView.frame = view.bounds 
        view.addSubview(effectView)
        effectView.playVideo()
    }
    
    private let playerLayer = AVPlayerLayer()
    private let gift: GiftModel 
    
    init(gift: GiftModel) {
        self.gift = gift 
        super.init(frame: .zero)
        setupPlayer()
    }
    
    private func setupPlayer() {
        guard let videoURL = URL(string: gift.effectVideoURL) else { return }
        
        let player = AVPlayer(url: videoURL)
        playerLayer.player = player 
        playerLayer.frame = bounds 
        playerLayer.videoGravity = .resizeAspectFill
        layer.addSublayer(playerLayer)
        
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(playerDidFinishPlaying),
            name: .AVPlayerItemDidPlayToEndTime,
            object: player.currentItem
        )
    }
    
    func playVideo() {
        playerLayer.player?.play()
    }
    
    @objc private func playerDidFinishPlaying() {
        removeFromSuperview()
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}
  1. 复合特效组合

3.1 组合多个特效

class CompositeEffectCoordinator {
    static func playEffect(for gift: GiftModel, in view: UIView) {
        // 根据礼物类型播放不同组合特效
        switch gift.effectLevel {
        case .basic:
            FullScreenAnimationView.show(with: gift, in: view)
            
        case .advanced:
            FullScreenAnimationView.show(with: gift, in: view)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                ParticleEffectView.show(with: gift, in: view)
            }
            
        case .premium:
            FullScreenAnimationView.show(with: gift, in: view)
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                Model3DEffectView.show(with: gift, in: view)
            }
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                VideoEffectView.show(with: gift, in: view)
            }
        }
    }
}

3.2 特效序列管理器

class EffectSequenceManager {
    private var effectQueue: [() -> Void] = []
    private var isPlaying = false 
    
    func addEffect(_ effect: @escaping () -> Void) {
        effectQueue.append(effect)
        playNextIfNeeded()
    }
    
    private func playNextIfNeeded() {
        guard !isPlaying, !effectQueue.isEmpty else { return }
        
        isPlaying = true
        let effect = effectQueue.removeFirst()
        
        effect()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            self.isPlaying = false 
            self.playNextIfNeeded()
        }
    }
}
 
// 使用示例 
let effectManager = EffectSequenceManager()
effectManager.addEffect {
    FullScreenAnimationView.show(with: gift1, in: view)
}
effectManager.addEffect {
    ParticleEffectView.show(with: gift2, in: view)
}
  1. 高级交互特效

4.1 用户交互式特效

class InteractiveEffectView: UIView {
    private var touchPoints: [CGPoint] = []
    private var displayLink: CADisplayLink?
    private var particleLayers: [CALayer] = []
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        guard let touch = touches.first else { return }
        
        let point = touch.location(in: self)
        createParticleEmitter(at: point)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        guard let touch = touches.first else { return }
        
        let point = touch.location(in: self)
        touchPoints.append(point)
        
        if touchPoints.count > 5 {
            touchPoints.removeFirst()
        }
        
        if touchPoints.count % 2 == 0 {
            createParticleEmitter(at: point)
        }
    }
    
    private func createParticleEmitter(at position: CGPoint) {
        let emitterLayer = CAEmitterLayer()
        emitterLayer.emitterPosition = position 
        emitterLayer.emitterSize = CGSize(width: 20, height: 20)
        emitterLayer.emitterShape = .circle
        emitterLayer.renderMode = .additive
        
        let cell = CAEmitterCell()
        cell.contents = UIImage(named: "sparkle")?.cgImage 
        cell.birthRate = 20 
        cell.lifetime = 2 
        cell.velocity = 50 
        cell.velocityRange = 30 
        cell.emissionRange = .pi * 2 
        cell.scale = 0.2 
        cell.scaleRange = 0.1
        cell.alphaSpeed = -0.5
        
        emitterLayer.emitterCells = [cell]
        layer.addSublayer(emitterLayer)
        particleLayers.append(emitterLayer)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            emitterLayer.birthRate = 0
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                emitterLayer.removeFromSuperlayer()
                if let index = self.particleLayers.firstIndex(of: emitterLayer) {
                    self.particleLayers.remove(at: index)
                }
            }
        }
    }
}

4.2 手势控制特效

class GestureControlledEffectView: UIView {
    private var panRecognizer: UIPanGestureRecognizer!
    private var pinchRecognizer: UIPinchGestureRecognizer!
    private var rotationRecognizer: UIRotationGestureRecognizer!
    private var effectNode: SKSpriteNode!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupScene()
        setupGestures()
    }
    
    private func setupScene() {
        let skView = SKView(frame: bounds)
        skView.backgroundColor = .clear
        addSubview(skView)
        
        let scene = SKScene(size: bounds.size)
        scene.backgroundColor = .clear
        skView.presentScene(scene)
        
        effectNode = SKSpriteNode(imageNamed: "magic_effect")
        effectNode.position = CGPoint(x: scene.size.width/2, y: scene.size.height/2)
        effectNode.setScale(0.5)
        scene.addChild(effectNode)
    }
    
    private func setupGestures() {
        panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
        addGestureRecognizer(panRecognizer)
        
        pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
        addGestureRecognizer(pinchRecognizer)
        
        rotationRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:)))
        addGestureRecognizer(rotationRecognizer)
        
        // 允许多个手势同时识别
        panRecognizer.delegate = self 
        pinchRecognizer.delegate = self
        rotationRecognizer.delegate = self 
    }
    
    @objc private func handlePan(_ recognizer: UIPanGestureRecognizer) {
        let translation = recognizer.translation(in: self)
        effectNode.position.x += translation.x 
        effectNode.position.y -= translation.y
        recognizer.setTranslation(.zero, in: self)
    }
    
    @objc private func handlePinch(_ recognizer: UIPinchGestureRecognizer) {
        effectNode.setScale(effectNode.xScale * recognizer.scale)
        recognizer.scale = 1.0 
    }
    
    @objc private func handleRotation(_ recognizer: UIRotationGestureRecognizer) {
        effectNode.zRotation += recognizer.rotation 
        recognizer.rotation = 0.0
    }
}
 
extension GestureControlledEffectView: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, 
                          shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}
  1. 性能优化方案

5.1 对象池模式

class EffectPool {
    static let shared = EffectPool()
    
    private var fullScreenEffects: [FullScreenAnimationView] = []
    private var particleEffects: [ParticleEffectView] = []
    
    func dequeueFullScreenEffect(for gift: GiftModel) -> FullScreenAnimationView {
        if let effect = fullScreenEffects.first(where: { $0.isHidden }) {
            effect.gift = gift
            effect.isHidden = false 
            return effect 
        } else {
            let effect = FullScreenAnimationView(gift: gift)
            fullScreenEffects.append(effect)
            return effect
        }
    }
    
    func dequeueParticleEffect(for gift: GiftModel) -> ParticleEffectView {
        if let effect = particleEffects.first(where: { $0.isHidden }) {
            effect.gift = gift
            effect.isHidden = false 
            return effect 
        } else {
            let effect = ParticleEffectView(gift: gift)
            particleEffects.append(effect)
            return effect
        }
    }
    
    func recycle(effect: UIView) {
        effect.isHidden = true 
        effect.layer.removeAllAnimations()
    }
}

5.2 特效资源预加载

class EffectPreloader {
    static func preloadEffects(for gifts: [GiftModel]) {
        DispatchQueue.global(qos: .userInitiated).async {
            for gift in gifts {
                switch gift.effectType {
                case .animation:
                    _ = UIImage(contentsOfFile: Bundle.main.path(forResource: "\(gift.id)_frame_1", ofType: "png") ?? "")
                    
                case .particle:
                    _ = UIImage(contentsOfFile: Bundle.main.path(forResource: "particle_\(gift.id)", ofType: "png") ?? "")
                    
                case .video:
                    let _ = AVAsset(url: URL(string: gift.effectVideoURL)!)
                    
                case .model3D:
                    _ = SCNScene(named: "\(gift.id).scn")
                }
            }
        }
    }
}
  1. 特效配置文件
{
  "effects": [
    {
      "id": "rocket",
      "name": "超级火箭",
      "type": "composite",
      "components": [
        {
          "type": "animation",
          "duration": 2.0,
          "frames": 30,
          "framePrefix": "rocket_anim_"
        },
        {
          "type": "particle",
          "particleImage": "rocket_particle",
          "birthRate": 200,
          "lifetime": 3.0,
          "velocity": 150
        },
        {
          "type": "sound",
          "file": "rocket_launch.mp3",
          "volume": 1.0 
        }
      ],
      "trigger": "immediate",
      "zOrder": 1000 
    },
    {
      "id": "fireworks",
      "name": "豪华烟花",
      "type": "sequence",
      "components": [
        {
          "type": "animation",
          "duration": 1.5,
          "frames": 45,
          "framePrefix": "fireworks_launch_"
        },
        {
          "type": "particle",
          "particleImage": "sparkle",
          "birthRate": 500,
          "lifetime": 2.5,
          "velocity": 80,
          "delay": 1.5 
        }
      ],
      "trigger": "delayed",
      "zOrder": 900
    }
  ]
}

实现建议

  1. 分层架构设计:

    • 表现层:处理特效的视觉展示
    • 控制层:管理特效的播放顺序和组合
    • 数据层:加载和解析特效配置
  2. 性能监控:

    func monitorPerformance() {
        DispatchQueue.main.async {
            let fps = CADisplayLink(target: self, selector: #selector(checkFPS))
            fps.add(to: .current, forMode: .common)
        }
    }
    
    @objc private func checkFPS(displayLink: CADisplayLink) {
        if displayLink.timestamp - lastTimestamp >= 1 {
            let fps = Double(frameCount) / (displayLink.timestamp - lastTimestamp)
            print("Current FPS: \(fps)")
            
            if fps < 50 {
                // 降低特效质量或数量
                EffectQualityManager.shared.reduceQuality()
            }
            
            lastTimestamp = displayLink.timestamp 
            frameCount = 0
        }
        frameCount += 1 
    }
    
  3. 动态调整:

    • 根据设备性能自动调整特效质量
    • 在低电量模式下减少粒子数量
    • 后台时暂停复杂特效

通过以上方案,可以实现从简单到复杂的各种特殊礼物特效,并根据实际需求灵活扩展。记得在实现过程中充分考虑性能优化和内存管理,确保特效的流畅运行。


网站公告

今日签到

点亮在社区的每一天
去签到