特殊礼物特效是提升直播互动体验的关键功能,下面我将详细介绍如何在iOS应用中实现各种高级礼物特效。
- 基础特效类型
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()
}
}
}
}
- 高级特效实现
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)
}
}
- 复合特效组合
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)
}
- 高级交互特效
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
}
}
- 性能优化方案
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")
}
}
}
}
}
- 特效配置文件
{
"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
}
]
}
实现建议
-
分层架构设计:
- 表现层:处理特效的视觉展示
- 控制层:管理特效的播放顺序和组合
- 数据层:加载和解析特效配置
-
性能监控:
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 }
-
动态调整:
- 根据设备性能自动调整特效质量
- 在低电量模式下减少粒子数量
- 后台时暂停复杂特效
通过以上方案,可以实现从简单到复杂的各种特殊礼物特效,并根据实际需求灵活扩展。记得在实现过程中充分考虑性能优化和内存管理,确保特效的流畅运行。