小程序canvas開發水果老虎機

https://juejin.im/post/5e3c2d44f265da575e37a202css


在這個超長假期中,無聊。。。,因此動手作一個早就計劃要作的小玩意,水果老虎機,嗯,這是一個小程序而不是小遊戲...

html

使用結構仍是canvas?

使用模板結構(view)生成水果盤的好處一是用戶可自定義產出n x 4的定製化老虎機,二是容易經過算法樣式生成佈局,三是經過wx.selectQueryAll的方法可以很方便的抓到定位數據。但,問題是動畫性能過於孱弱,如圖構建一個7x4的水果盤,動畫性能估計會慘不忍睹,並且純粹模板結構不管使用animation動畫方法仍是css的keyframe的動畫方法獲得的動畫效果都很是差(測試過的結論),還有是已知的動畫方法可控性不好node

使用canvas來生成水果盤好處是動畫性能很好(canvas2d),可是定製性和擴展性比較差git

so綜上考慮,使用模板(view)佈局,使用canvas來實現動畫。既保證了組件的性能,同時定製型,擴展性也很好github

準備計時器方法

動畫的生成離不開計時器方法,settimeout/setinterval這兩兄弟真的不夠看啊,問題還多,作過web開發的必定都知道window.requestAnimationFrame,這貨在小程序的計時器方法中不存在,好在canvas2d中能夠使用Canvas.requestAnimationFrame(function callback)方法來實現web

準備運動算法

在水果老虎機中,激活狀態會沿着四方的水果盤作非線性運動(easeInOut比較好用),須要基礎的運動算法來計算實際的運動距離。在animation動畫方法中,咱們能夠使用ease-in/ease-out等緩動算法來實現動畫效果,但在這裏必需要藉助tween.js中的緩動算法來實現運動效果(由於須要控制運動節點)。算法

你會不會想到用css的keyframe動畫來作這個運動效果,通過個人測試,css的動畫和animation的動畫會在每一條邊上實現一次(ease)緩動運動(很奇怪的效果)canvas

推薦這篇文章小程序

使用其中一個,節省代碼量ide

/* * Tween.js * t: current time(當前時間); * b: beginning value(初始值); * c: change in value(變化量); * d: duration(持續時間)。 */
// Quart 四次方的緩動
const easeInOutQuart = function (t, b, c, d) {
  if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
  return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
複製代碼

tween算法是以時間爲基準(時間比率 = 距離比率)來計算單位時間的實際運動距離

佈局

以上面的圖爲例,咱們須要作一個 7 x 4的水果盤,實際有效的獎品格子數爲 7*4-4共24個有效格子

有效格子算法
js

// 0-6 第一行全部格子所有有效 
// 21-27 最後一行全部格子所有有效 
// 中間部分 i%7===0 和 i%7 === (7-1) 有效
// 算法源碼有點無聊,依據上述思路,便可遍歷28個格子並標識獎品格子valide=true
// 能夠擴展想想 6x6 5x5,思路是同樣的
複製代碼

wxml

<view class="fruits-container" >
    <view class="fruits-table" >
        <block wx:for="{{ary}}" wx:key="index" >
            <view wx:if="{{item.valide}}" class="valide">{{item.title}}</view>
            <view wx:else class="in-valide"></view>
        </block>
    </view>
    <canvas type="2d" .... />
</view>
複製代碼

樣式

只節選關鍵樣式,目的是讓canvas覆蓋在水果盤上,長寬一致

.fruits-container {
    position: relative;
    width: 400px;
    height: 400px;
    ...
}

.fruits-table {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    ...
}
複製代碼

抓取位置信息

canvas的繪製須要X軸, Y軸的精確信息,能夠使用wx.createSelectorQuery方式抓取類名爲‘valide’的view(獎品格子)的位置信息

let query = wx.createSelectorQuery().in(this)
query.selectAll(`.fruits-table .valide`).boundingClientRect(ret => {
    ....
    console.log(ret[0]) // top, left, right, bottom, width, height
    console.log(ret[1]) // top, left, right, bottom, width, height
    ...
    ...
    console.log(ret[23]) // top, left, right, bottom, width, height
})
複製代碼

獲得每個獎品格子的位置信息後,就能夠使用canvas的fillRect方法來繪製激活狀態了。

繪製一個激活狀態

let query = wx.createSelectorQuery().in(this)
query.selectAll(`.fruits-table .valide`).boundingClientRect(ret => {
    ....
    let {top, left, right, bottom, width, height} = ret[0]
    const canvasQuery = wx.createSelectorQuery()
    canvasQuery.select('#fruit-canvas')
    .fields({ node: true, size: true })
    .exec((res) => {
        const canvas = res[0].node
        const ctx = canvas.getContext('2d') 
        let x = top
        let y = left
        let dx = width
        let dy = height
        ctx.shadowOffsetX = 2
        ctx.shadowOffsetY = -2
        ctx.shadowColor = 'red'
        ctx.shadowBlur = 50
        ctx.lineWidth = 5
        ctx.strokeStyle = 'red'
        ctx.clearRect(0, 0, canvas.width, canvas.height)
        ctx.strokeRect(x, y, dx, dy)
    })
})
複製代碼

跑起來

已經繪製了一個激活狀態,接下來使它可以簡單動起來

// 抽象激活方法 
functon rect(point, canvas){
    let {x, y, dx, dy} = getPosition(point)
    ctx.shadowOffsetX = 2
    ctx.shadowOffsetY = -2
    ...
    ...
    ctx.clearRect(0, 0, canvas.width, canvas.height) // 擦除整個水果盤
    ctx.strokeRect(x, y, dx, dy) // 繪製激活區域
}

function run(){
    setTimeout(()=>{
        if (ret.length) {
            let point = ret.shift()
            rect(point, canvas)
            run()
        }
    }, 100)
}
複製代碼

執行run方法後能夠看到水果盤的激活狀態一步一步的往前走(100毫秒),拖拉機終於能夠啓動了

配上運動算法

通過上面的試驗咱們終於能夠看到基本的運動效果了,接下來配上運動算法和計時器方法

// Quart 四次方的緩動
const easeInOutQuart = function (t, b, c, d) {
  if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
  return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}

let start = 0  // 開始時間
let begin = 0  // 開始獎品位置
let end = 23  // 終點位置,這裏跑一圈
let during = 5000 // 運動總時間

// 1000/60 ≈ 17,
// 17毫秒即表示屏幕60幀刷新率每秒 ≈ requestAnimationFrame計數頻率(通常狀況) 
const steper = () => {
  // left爲位移距離
  // 老虎機的運動位移是節點位移,不是精確位移
  // 因此這裏用parseInt處理,只取整數部分
  // 數據變化爲 0,1,2,3,4,5...23
  // 間隔時間/距離由easeInOutQuart算法計算
  var left = easeInOutQuart(start, begin, end, during);
  let idx = parseInt(left)
  start = start + 17; 
  if (idx <= end) {
    let point = this.ret[idx] // 取節點位置信息
    this.rect(point) // 繪製
  }
  
  // 時間遞增
  if (start <= during) {
    this.ctx.requestAnimationFrame(steper); // 計時器
  } else {
    // 動畫結束,這裏能夠插入回調...
    // callback()...
  }
};

steper(); // 啓動
複製代碼

以上爲個人小程序水果老虎機的基本開發思路

demo戳這裏

相關文章
相關標籤/搜索