LineageM – Slot Machine ( 角子机动画 )- 陳董 Don - iOS 開發

453 阅读5分钟

接著上一個主題的內容 Lineage M 卡包動畫 – Frame animation 這次要加入抽卡的元素。

當我們打開卡包的時候,會有多張卡片在畫面上輪轉,最後慢慢停到一張卡片。


Slot Machine  – 角子機動畫

除了前一個主題做的卡包動畫外,這次新增的部分有:

  • 打開卡包時,閃爍一下。
  • 多張卡片開始向做快速移動
  • 最後慢慢停到一張卡片上
  • 再次閃爍一下
  • 顯示卡片背景氣息動畫(圖片在快速移動的時候不顯示)

CardModel

卡片根據稀有度會有不一樣的背景色和氣息動畫。

enum CardLevel {
    case gray
    case green
    case blue
    case red
}
 
struct CardModel {
    var level:CardLevel
    var image:UIImage
}

通過 CardModel & CardLevel 建立一批不同稀有度的卡片。

var cards = [CardModel]()
for i in 1...36 {
    var cardLevel:CardLevel = .gray
    switch i % 4 {
        case 0: cardLevel = .red
        case 1: cardLevel = .blue
        case 2: cardLevel = .green
        case 3: cardLevel = .gray
        default: break;
    }
    
    let card = CardModel(level: cardLevel, image: UIImage(named: "monster-\(i)")!)
    cards.append(card)
}


SlotView

繼續沿用上一個主題的卡包動畫,但原本打開卡片就看到卡片內容的部分拿掉了,而是變成打開後先輪播所有卡片,最後停留在最後一張卡片上。

建立輪播圖

無視性能的最簡單實現方法,建立一個 monsterScrollView 根據卡片數量來設定 width 然後將所有的卡片都放上去。

根據 CardModel 提供的 level 來設定不一樣的背景圖,結合卡片圖放在 monsterScrollView 上面。

fileprivate func addMonsterView() {
    monsterScrollViewMask = UIView()
    monsterScrollViewMask!.frame = frame
    monsterScrollViewMask?.clipsToBounds = true
    addSubview(monsterScrollViewMask!)
    
    let monsterScrollViewFrame = CGRect(x: 0, y: 0, width: frame.width * CGFloat(cards.count), height: frame.height)
    monsterScrollView = UIView(frame: monsterScrollViewFrame)
    monsterScrollViewMask?.addSubview(monsterScrollView!)
    
    for i in 0..<cards.count {
        let monsterFrame = CGRect(x: CGFloat(i) * frame.width, y: 0, width: frame.width, height: frame.height)
        let monsterLevelView = UIImageView(frame: monsterFrame)
        
        switch cards[i].level {
            case .gray: monsterLevelView.image = UIImage(named:"img-card-bg-gray")
            case .green: monsterLevelView.image = UIImage(named:"img-card-bg-green")
            case .blue: monsterLevelView.image = UIImage(named:"img-card-bg-blue")
            case .red: monsterLevelView.image = UIImage(named:"img-card-bg-red")
        }
        monsterScrollView?.addSubview(monsterLevelView)
        
        let monsterImageView = UIImageView(frame: monsterFrame)
        monsterImageView.image = cards[i].image
        monsterScrollView!.contentMode = .scaleAspectFit
        monsterScrollView!.addSubview(monsterImageView)
    }
}

輪播圖動畫

將很長的 monsterScrollView 直接 addSubView 在 SlotView 上,當打開卡包的時候將整個 monsterScrollView 向左邊移動。

這時候看起來會下面的效果,但其實我們希望卡片只會顯示在白色的框內,這方面可以通過  mask 或者 clicpsToBounds 來實現。

需要注意的是,因爲我們所有的 View 都是放在 SlotView 上的,
如果單純直接對 SlotView 設定 clickpsToBounds = true 卡片外的效果就會被切掉,比如卡包背後超出卡片的氣息動畫。

fileprivate func startScrollingSlot() {
    if monsterScrollView == nil { return }
    
    if isScrolling {
        return
    } else {
        isScrolling = true
    }
    
    shineOnce()
    
    // show border when scrolling
    layer.borderColor = UIColor.white.cgColor
    layer.borderWidth = 1
    
    // reset position
    let originalFrame = monsterScrollView!.frame
    monsterScrollView!.frame = CGRect(x: 0, y: 0, width: originalFrame.width, height: originalFrame.height)
    
    let newFrame = CGRect(x: -self.frame.width * CGFloat(cards.count - 1),
                          y: 0,
                          width: self.frame.width,
                          height: self.frame.height)
    
    UIView.animate(withDuration: 3, delay: 0, options: .curveEaseOut, animations: {
        self.monsterScrollView!.frame = newFrame
        
    }, completion: { finished in
        self.isScrolling = false
        self.shineOnce()
        self.showCardAura()
        
        // hide border when scroll ended
        self.layer.borderWidth = 0.0
    })
    
}


閃現動畫

打開卡包的一瞬間以及卡片移動停止的時候,畫面都會閃現一下,這裡一樣是通過一張光亮的圖片,通過控制 Alpha 來實現。

fileprivate func addShineView() {
    let shineMask = UIView(frame: frame)
    shineMask.clipsToBounds = true
    addSubview(shineMask)
    
    shineView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.width * 1.3, height: frame.height * 1.3))
    shineView?.center = center
    shineView!.image = UIImage(named:"img-card-shime")!
    shineView?.contentMode = .scaleAspectFill
    shineView!.alpha = 0.0
    shineMask.addSubview(shineView!)
}


筆記

優化

這次因為只有一個長條圖在畫面上移動,所以即使是在最近效能不好的模擬器上移動都不會有卡頓的感覺,但實際上 SlotMachine 常常是要同時有多個輪播圖的效果,那麼上面的例子就不適合了。

更好的作法應該是類似 UITableView/UICollectionView 的方式,留個 preload 空間,剩下的圖都不留在畫面上,比如這次的例子,畫面上其實留5張圖片就夠用了。


Reference