使用Canvas實現一個在線發牌遊戲 [純前端、附源碼]

寫在開頭

  • 一位作者開源了這個遊戲,純前端實現,原生Canvas

  • 希望大家給他點個star,源碼地址:https://github.com/leeseean/sic-bo

  • 這個項目克隆很慢,因爲比較大,如果你想知道怎麼克隆快,可以看今天公衆號第二條推文 《如何把github的clone速度提升到1MB/S》

爲什麼要推薦這個項目

  • 在我看來,這個作者是有一定技術實力的,對canvas理解和使用,以及瀏覽器渲染機制,都是比較瞭解的,還有原生dom操作能力都可以

  • 裏面大量使用了canvas的路徑繪製、填充,以及requestAnimationFrame

技術棧

  • 使用原生javascript + html +css

  • 主要繪製是canvas實現

項目初始狀態

怎麼玩這個遊戲

  • 點擊想押注的地方

  • 籌碼會有一個動畫飛向你押注的區域

  • 押注完成後,定時開始搖骰子,開獎

大概實現

  • 初始調用 init函數,生成canvas畫布,掛載onclick事件

   init() {
        let _this = this;
        _this.loadImage();
        _this.scale = screen.width < 1500 ? screen.width / 1920 : 1;
        let scale = _this.scale;
        //拿到畫布
        let canvas_fly = document.getElementById('canvas_fly'); //渲染飛出去的籌碼
        canvas_fly.width = document.body.clientWidth;
        canvas_fly.height = 912 * scale || 800;
        _this.ctxFly = canvas_fly.getContext('2d');
        let canvas_stop = document.getElementById('canvas_stop'); //渲染未確認投注放桌面上的籌碼
        canvas_stop.width = document.body.clientWidth;
        canvas_stop.height = 912 * scale || 800;
        _this.ctxStop = canvas_stop.getContext('2d');
        let canvas_betted = document.getElementById('canvas_betted'); //渲染確認投注的籌碼
        canvas_betted.width = document.body.clientWidth;
        canvas_betted.height = 912 * scale || 800;
        _this.ctxBetted = canvas_betted.getContext('2d');

        //拿到chipsImgObj對象
        let img = new Image();
        img.onload = function () {
            _this.chipsImgObj = this; //拿到chipsImgObj對象
        }
        img.src = './images/chips.png';

        //確定所用籌碼
        $('.chips>.chip').off('click').on('click', function (e) {
            $(this).addClass('on').siblings('.chip').removeClass('on');
            _this.priceNum = +$(this).attr('priceNum');
        });
        $('.chips>.chip10').trigger('click'); //默認籌碼10

        const pieceIntervalOver = { //連續點擊翻倍生不生效的flag
            'betFor': false,
            'betted': false,
            'pieceCount': 0,
        };
        const cancelOk = {
            ok: false,
            count: 0,
        }; //取消完畢,默認false
        const resetOk = {
            ok: false,
            count: 0,// 防止重複點擊
        }
        //點擊桌面選號
        $('[rel="selectCode"]').off('click').on('click', function (e) {
            if (cancelOk.ok || cancelOk.count === 0) {
                cancelOk.count = 0;
            } else {
                return; //沒取消完畢不準過去
            }

            _this.flyState = 'flyTo';
            let code = $(this).attr('value');
            let method = $(this).attr('method');
            let startPos = {
                x: $(`.chips .chip${_this.priceNum}`).offset().left,
                y: $(`.chips .chip${_this.priceNum}`).offset().top,
            };
            let endPos = {
                x: $(this).offset().left + $(this)[0].offsetWidth * scale / 2 - $('.chips>.chip').width() * scale / 2,
                y: $(this).offset().top + $(this)[0].offsetHeight * scale / 2 - $('.chips>.chip').height() * scale / 2,
            };
            _this.eachBetCount[code] = _this.eachBetCount[code] || 0;
            _this.eachBetCount[code] += _this.priceNum;
            _this.betOrderRecords[code] = { //記錄order
                method: method,
                code: code,
                price: _this.priceNum,
                amount: _this.eachBetCount[code],
                piece: _this.eachBetCount[code],
            };
            let clickedElemOption = { //被點擊元素的相關數據
                priceNum: _this.priceNum,
                code: code,
                position: {
                    x: $(this).offset().left,
                    y: $(this).offset().top,
                },
                width: $(this).outerWidth(),
                height: $(this).outerHeight(),
            };
            _this.betForRecords.push({ //記錄投注
                elemOption: copyJSON(clickedElemOption),
                priceNum: _this.priceNum,
                startPos,
                endPos,
            });

            _this.chipFly(_this.ctxFly, _this.ctxStop, _this.chipsImgObj, _this.priceNum, startPos, endPos, clickedElemOption, 10);
            $('.betMoneyAmount').text(_this.calculateBetMoney());
        });
        //取消投注
        $('.cancelButton').off('click').on('click', function (e) {
            if (cancelOk.ok || cancelOk.count === 0) {
                cancelOk.ok = false;
            } else {
                return; //沒取消完畢不準過去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {

            } else {
                return; //上次翻倍全部渲染結束才能進行下一次點擊操作,沒結束操作無效                
            }
            cancelOk.count += 1;

            pieceIntervalOver.pieceCount = 0;
            if (_this.betForRecords.length === 0) {
                return;
            }
            _this.flyState = 'flyBack';
            //timechunk 分時函數
            let i = 0;
            let interval = setInterval(() => {
                if (i === _this.betForRecords.length) {
                    cancelOk.ok = true;
                    cancelOk.count = 0; //回到0
                    _this.betForRecords.length = 0;
                    _this.betOrderRecords = {};
                    _this.eachBetCount = {};
                    _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
                    $('.betMoneyAmount').text(_this.calculateBetMoney());
                    return clearInterval(interval);
                }
                let record = _this.betForRecords[i];
                _this.chipFly(_this.ctxFly, _this.ctxStop, _this.chipsImgObj, record.priceNum, record.endPos, record.startPos, record.elemOption, 10);
                i++;
            }, 10);
        });
        //重置投注
        $('.resetButton').off('click').on('click', function (e) {
            if (resetOk.ok || resetOk.count === 0) {
                resetOk.ok = false;
            } else {
                return; //沒取消完畢不準過去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {

            } else {
                return; //上次翻倍全部渲染結束才能進行下一次點擊操作,沒結束操作無效                
            }
            resetOk.count += 1;

            pieceIntervalOver.pieceCount = 0;
            if (_this.betForRecords.length === 0 && _this.bettedRecords.length === 0) {
                return;
            }
            _this.flyState = 'flyBack';
            //timechunk 分時函數
            let i = 0;
            let interval = setInterval(() => {
                if (i === _this.betForRecords.length) {
                    _this.betForRecords.length = 0;
                    _this.betOrderRecords = {};
                    _this.eachBetCount = {};
                    _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
                    $('.betMoneyAmount').text(_this.calculateBetMoney());
                    clearInterval(interval);
                    let j = 0;
                    const _interval = setInterval(() => {
                        if (j === _this.bettedRecords.length) {
                            resetOk.ok = true;
                            resetOk.count = 0; //回到0
                            _this.bettedRecords.length = 0;
                            _this.ctxBetted.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
                            $('.betMoneyAmount').text(_this.calculateBetMoney());
                            return clearInterval(_interval);
                        }
                        console.log(j)
                        const bettedRecord = _this.bettedRecords[j];
                        _this.chipFly(_this.ctxFly, _this.ctxBetted, _this.chipsImgObj, bettedRecord.priceNum, bettedRecord.endPos, bettedRecord.startPos, bettedRecord.elemOption, 10);
                        j++;
                    }, 10);
                    return;
                }
                const betForRecord = _this.betForRecords[i];
                _this.chipFly(_this.ctxFly, _this.ctxStop, _this.chipsImgObj, betForRecord.priceNum, betForRecord.endPos, betForRecord.startPos, betForRecord.elemOption, 10);
                i++;
            }, 10);

        });
        //確認投注
        $('.betButton').off('click').on('click', function (e) {
            if (_this.betForRecords.length === 0) {
                alert('請先下注!');
                return;
            }
            if (cancelOk.ok || cancelOk.count === 0) {
            } else {
                return; //沒取消完畢不準過去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {

            } else {
                return; //上次翻倍全部渲染結束才能進行下一次點擊操作,沒結束操作無效                
            }
            _this.flyState = 'betted';
            _this.bettedRecords = _this.bettedRecords.concat(_this.betForRecords);
            _this.betForRecords.forEach((record) => {
                _this.chipFly(_this.ctxFly, _this.ctxBetted, _this.chipsImgObj, record.priceNum, record.endPos, record.endPos, record.elemOption, 30);
            });
            _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
            _this.betForRecords.length = 0;
            _this.betOrderRecords = {};
            _this.eachBetCount = {};
        });
        //翻倍投注 
        $('.pieceButtoon').off('click').on('click', function (e) {
            let bettedLen = _this.bettedRecords.length;
            let betForLen = _this.betForRecords.length;
            if (bettedLen === 0 && betForLen === 0) {
                return;
            }
            if (cancelOk.ok || cancelOk.count === 0) {
            } else {
                return; //沒取消完畢不準過去
            }
            if ((pieceIntervalOver.betFor && pieceIntervalOver.betted) || pieceIntervalOver.pieceCount === 0) {
                pieceIntervalOver.betFor = false;
                pieceIntervalOver.betted = false;
                pieceIntervalOver.pieceCount += 1;
            } else {
                return; //上次翻倍全部渲染結束才能進行下一次點擊操作,沒結束操作無效                
            }

            //timechunk分時函數,防止短時間多次觸發卡死瀏覽器
            let i = 0;
            let interval_bet = setInterval(() => {
                if (i === bettedLen) {
                    pieceIntervalOver.betted = true;
                    return clearInterval(interval_bet);
                }
                let code = _this.bettedRecords[i]['elemOption']['code'];
                _this.priceNum = _this.bettedRecords[i]['priceNum'];
                $(`[rel="selectCode"][value="${code}"]`).trigger('click'); //自動桌面選號點擊
                i++;
            }, 10);
            let j = 0;
            let interval_betted = setInterval(() => {
                if (j === betForLen) {
                    pieceIntervalOver.betFor = true;
                    return clearInterval(interval_betted);
                }
                let code = _this.betForRecords[j]['elemOption']['code'];
                _this.priceNum = _this.betForRecords[j]['priceNum'];
                $(`[rel="selectCode"][value="${code}"]`).trigger('click'); //自動桌面選號點擊
                j++;
            }, 10);
        });
    },
  • init函數初始化中將canvas畫筆掛載到this中

_this.ctxStop = canvas_stop.getContext('2d');
_this.ctxBetted = canvas_betted.getContext('2d');
...
  • 確認投注後,清空未確認投注放桌面上的籌碼畫布

 $('.betButton').off('click').on('click'、、、
 _this.ctxStop.clearRect(0, 0, document.body.clientWidth, document.body.clientHeight);
  • 如果沒有下注,就提示:

  if (_this.betForRecords.length === 0) {
                alert('請先下注!');
                return;
            }

最後,希望大家多看看這個源碼,瞭解canvas使用

  • 記得給作者點個star哦,這是一個入門canvas的一個很好的開源項目,點擊左下角的閱讀原文就可以進入到源碼地址啦:https://github.com/leeseean/sic-bo

  • 如果深入點的話,可以再學習一下canvas的像素控制,或者pixijs的使用

  • 在線體驗這個遊戲的地址是:

    •  
    https://leeseean.github.io/sic-bo/

轉自https://mp.weixin.qq.com/s/Z08SXHWPCckny2TLMifPlA 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章