成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

js+canvas仿微信《彈一彈》小游戲

Invoker / 679人閱讀

摘要:在彈一彈游戲中,小球不能向上發(fā)射。這里又有一個坑彈一彈游戲中,剛射擊出去的小球是不受重力影響的不然瞄準還有什么意義。

前言

半年前用js和canvas仿了熱血傳奇網(wǎng)游(地址),基本功能寫完之后,剩下的都是堆數(shù)據(jù)、堆時間才能完成的任務(wù)了,沒什么新鮮感,因此進度極慢。這次看到微信《彈一彈》比較火,因為涉及到物理引擎(為了真實),于是動手試了一下。一共用了10個小時,不僅完成了這個demo,<刪除線>并且打上了彈一彈好友排行榜的第一頁

資料匯總

在線demo:點擊即玩

代碼:400行帶注釋

canvas渲染庫:支持物理引擎及chrome調(diào)試工具,這里

準備工作

微信這個小游戲的游戲規(guī)則很簡單,看圖就能看明白,這里不再贅述。涉及到的幾個開發(fā)難度:

1.物理引擎

當(dāng)然不用也可以,無非就是改改圖片的位置,可以自己模擬掉落和碰撞效果。不過由于我追(wu)求(li)體(hen)驗(cha),因此開始尋找第三方的物理引擎。

最后我使用的是chipmunk的js版(這個庫是底層計算庫,因此star不多,但是比較有名氣的hilo和cocos2d的物理引擎用到了這個庫)。主要原因之一是,這個庫的功能只是進行了物理運算,并且支持重力、彈性、摩擦、浮力等功能。當(dāng)然體積也比較小。畢竟我們只是寫一個小demo,引入一個游戲框架的話很可能徒增成本。

不過我使用的時候遇到了幾個罕見的bug,應(yīng)該是作者的疏漏,在issue中也有人反饋。看作者更新頻率很低,我拿來用的時候有一些修改。如果其它人使用的時候遇到j(luò)s報錯,可以試試這里

2.UI渲染

我選擇的是canvas,因為涉及到頻繁的樣式更新,每幀都去改寫style的話太占性能。而且用canvas寫的話,以后還可以迭代一些碰撞產(chǎn)生的畫效。

之前封裝了一個easycanvas庫,可以將樹形數(shù)據(jù)結(jié)構(gòu)“翻譯”成“canvas畫布中的一個個對象”。這次又順手補充了一個支持chipmunk的插件,這樣整個“彈一彈”的開發(fā)就完全只需要管理數(shù)據(jù)即可,渲染工作很少。

開始開發(fā) html及背景

由于項目較小,我把html、css、js堆在了一個文件(最后寫完之后,發(fā)現(xiàn)一共連同注釋才400行)。

首先創(chuàng)建一個空html,為了看起來高大上,我搜了一張?zhí)炜罩黝}的背景圖。



    
可能用到的變量

接下來,準備一些我們需要用到的數(shù)據(jù)。例如游戲的寬高、小球的大小、當(dāng)前游戲狀態(tài)(是否可以射擊)、每次可以射出的小球數(shù)、玩家的分數(shù),blabla。

由于是直接在html里寫碼,為了兼容老瀏覽器,只能var來var去。

// 在html直接寫代碼,不編譯、不構(gòu)建,不然應(yīng)該用const的
var width = 400, height = 600, ballSize = 20;

// 游戲狀態(tài)
var canShoot = true;
var score = 0, ballLeft = 0, ballCount = 5;
var blockArray = [];

// 圖片
var BALL = Easycanvas.imgLoader("./ball.png");
var BLOCK = Easycanvas.imgLoader("./block.jpg");
var TRIANGLE = Easycanvas.imgLoader("./triangle.png");

// 給每個東西起一個type,后面會用來做碰撞檢測
var BALL_TYPE = 1, BLOCK_TYPE = 2, BORDER_TYPE = 3, BOTTOM_TYPE = 4, BONUS_TYPE = 5;
頂部文本

接下來先將分數(shù)和小球個數(shù)寫到canvas中。首先創(chuàng)建一個easycanvas實例,寬400,高600。然后add2個對象。一個以左上角(5,5)為頂點,向右下方寫分數(shù)。一個以右上角(395, 5)為頂點,向左下角寫當(dāng)前小球個數(shù)。

// 初始化easycanvas實例
var $Painter = new Easycanvas.painter();
$Painter.register(el, {
    width: width,
    height: height,
});
$Painter.start();

$Painter.add({
    content: {
        text: function () {
            return "得分:" + score;
        }
    },
    style: {
        tx: 5, ty: 5,
        textAlign: "left", textVerticalAlign: "top",
        color: "black"
    }
});
$Painter.add({
    content: {
        text: function () {
            return "小球個數(shù):" + ballCount;
        }
    },
    style: {
        tx: 395, ty: 5,
        textAlign: "right", textVerticalAlign: "top",
        color: "black"
    }
});
添加方塊

接下來,設(shè)置整個場景的重力,并且添加一些方塊進去。每個方塊對象含有一個child,用來展示數(shù)字(還可以撞幾下)。為了避免方塊重疊,我們讓方塊的x坐標(biāo)在50、100、150、……、300、350循環(huán)。同時,為了避免看起來“太整齊”,每次添加一個小的隨機數(shù),讓這些方塊們錯落有致。(“錯落”指參差不齊,“致”指情趣。形容事物的布局雖然參差不齊,但卻極有情趣,使人看了有好感?!扯龋?/p>

每個方塊的大小是30x30,因此shapes包括4條邊,例如(0,0)到(30,0)是一條邊。這些方塊是失重的(不會掉下去),因此static設(shè)置為true。為了更加錯落有致,我們給他一個隨機的角度rotate。

每個方塊含有一個child,寫著一個數(shù)字。不需要給數(shù)字設(shè)置rotate,否則6和9可能就分不清了。

// 初始化easycanvas物理引擎,添加一個有物理樹形的空容器
var $space = new Easycanvas.class.sprite({
    physics: {
        gravity: 2, // 重力默認為1,但是游戲進程有點慢,看著不夠爽
        accuracy: 2,
    },
});
$Painter.add($space);

var space = $space.launch();

// 防止方塊重疊,記錄上一次方塊的X坐標(biāo)
var lastBlockPositionX = 50;
function addBlock (max, boolAddToBottom) {
    var deg = Math.floor(Math.random() * 360);
    var sprite = $space.add(new Easycanvas.class.sprite({
        name: "block",
        content: {
            img: BLOCK,
        },
        physics: {
            shape: [
                [[0, 0], [0, 30]],
                [[0, 30], [30, 30]],
                [[30, 30], [30, 0]],
                [[30, 0], [0, 0]]
            ],
            mass: 1,
            friction: 0.1,
            elasticity: 0.9,
            collisionType: BLOCK_TYPE,
            static: true,
        },
        style: {
            tw: 30, th: 30,
            tx: lastBlockPositionX + Math.floor(Math.random() * 20 - 10),
            ty: boolAddToBottom ? 500 : height - 100 - Math.floor(Math.random() * 100),
            locate: "lt",
            rotate: deg,
        },
        children: [{
            content: {
                text: Math.floor(Math.random() * max) + 1,
            },
            style: {
                color: "yellow",
                textAlign: "center",
                textVerticalAlign: "middle",
                textFont: "28px Arial",
                tx: 15, ty: 10
            }
        }]
    }));
    sprite.physicsOn();
    blockArray.push(sprite);

    lastBlockPositionX += 50;
    if (lastBlockPositionX > 350) {
        lastBlockPositionX = 50;
    }
}

接下來,我們做瞄準部分。大致功能是,有一排小圓點,會隨著鼠標(biāo)運動,并且有彈簧的感覺。

首先要記錄鼠標(biāo)的軌跡,我們給easycanvas實例$Painter加上事件監(jiān)聽。在“彈一彈”游戲中,小球不能向上發(fā)射。因此記錄鼠標(biāo)的Y坐標(biāo)值的時候,我們讓他至少為30。

// 記錄鼠標(biāo)軌跡
var mouse = {x: 300, y: 50};
var mouseRecord = function ($e) {
    mouse.x = $e.canvasX;
    mouse.y = Math.max(30, $e.canvasY);
};

$Painter.register(el, {
    width: width,
    height: height,
    events: {
        mousemove: mouseRecord,
        touchmove: mouseRecord,
        mouseup: shoot,
        touchend: shoot,
    }
});
小球瞄準

接下來,我們添加7個小球,讓他們排列在一條線上,從游戲正上方的(300, 20)點到鼠標(biāo)位置均勻鋪開。具體邏輯就是,我們將鼠標(biāo)位置和(300, 20)的坐標(biāo)差進行6等分,第一個球的坐標(biāo)向鼠標(biāo)位置偏移0/6、第二個球偏移1/6……,最后一個球偏移6/6(正好落在了鼠標(biāo)位置)。這幾個球我們給他們一個透明度,并且不啟用物理規(guī)則(因為這個階段小球不能掉下來)。我們在每個小球上設(shè)置一個shoot鉤子,當(dāng)玩家射出真實的小球時,刪除這個瞄準用的小球。

// 顯示瞄準軌跡
var startAim = function () {
    for (var i = 0; i < 7; i ++) {
        $Painter.add({
            content: {
                img: BALL,
            },
            data: {
                gap: i / 6,
            },
            style: {
                tx: function () {
                    return 200 + (mouse.x - 200) * this.data.gap;
                },
                ty: function () {
                    return 20 + (mouse.y - 20) * this.data.gap;
                },
                tw: 20, th: 20,
                opacity: 0.4,
            },
            hooks: {
                shoot: function () {
                    this.remove();
                }
            }
        });
    }
};
startAim();
發(fā)射小球

接下來,我們添加真實的小球(受到物理規(guī)則影響的小球)。

當(dāng)射擊時,我們廣播shoot事件,移除剛才瞄準用的小球。

之后,我們間隔100毫秒,連續(xù)調(diào)用addBall方法來創(chuàng)建小球。addBall方法中,我們?yōu)槊總€小球設(shè)置物理規(guī)則。包括形狀、彈性、摩擦等。

這里有一個坑,就是一旦開始射擊,不管鼠標(biāo)怎么移動,射擊的方向都不能變化。因此我們要先記錄下當(dāng)前的mouse值,這里用的是JSON.parse(JSON.stringify(mouse))來copy一個簡單對象。

這里又有一個坑:“彈一彈”游戲中,剛射擊出去的小球是不受重力影響的(不然瞄準還有什么意義)。因此,我們在每個小球上增加一個和重力相反的作用力,抵消重力。(在其它部分的代碼中,有著“當(dāng)小球發(fā)生一次碰撞后,取消這個作用力”的實現(xiàn),這里為了清晰沒有一起貼出來)。

同時,我們給小球加上初速度。

這里又又又又又有一個坑(好煩?。翰还茉趺瓷鋼簦∏虺跏极@得的速度是相同的。哪怕小球的瞄準位置距離射出位置很近,速度也不能慢。這里需要修正一下初始速度,這里用到了著名的Pythagoras theorem定理:直角三角形的兩條直角邊的平方和等于斜邊的平方。

function shoot () {
    if (!canShoot) return;

    $Painter.broadcast("shoot");
    canShoot = false;

    var currentMouse = JSON.parse(JSON.stringify(mouse));
    for (var i = 0; i < ballCount; i++) {
        setTimeout(function () {
            addBall(currentMouse);
        }, i * 100);
    }
};

function addBall (mouse) {
    ballLeft++;
    var $ball = new Easycanvas.class.sprite({
        name: "ball",
        content: {
            img: BALL,
        },
        physics: {
            shape: [
                // 形狀是一個以(ballSize / 2, ballSize / 2)為圓心的,半徑也是ballSize / 2的圓
                // 改成位運算符吧,看著能高大上一點(其實在這里卵用沒有)
                [ballSize >> 1, ballSize >> 1, ballSize >> 1]
            ],
            mass: 1, // 質(zhì)量
            friction: 0.1, // 摩擦(摩擦太大了會損失能量)
            elasticity: 0.8, // 彈性
            collisionType: BALL_TYPE,
        },
        style: {
            tw: ballSize, th: ballSize,
            sx: 0, sy: 0,
            tx: 200,
            ty: 20,
            zIndex: 1,
        },
    });
    $space.add($ball);

    $ball.physicsOn();

    // 抵消重力
    $ball.$physics.body.applyForce({x: 0, y: 1000}, {x: 0, y: 0});

    // 初速度
    var speed = {
        x: (mouse.x - 200) / (20 - mouse.y),
        y: 1
    };

    // 修正速度,確保從各個角度射出小球的速度差不多
    // 這里用到的著名的高等數(shù)學(xué)知識:勾股定理
    var muti = Math.sqrt(Math.pow(speed.x, 2) + Math.pow(speed.y, 2)) / 700;

    $ball.$physics.body.setVel({
        x: -speed.x / muti,
        y: -speed.y / muti,
    });
}
其它

輪廓已經(jīng)有了,后面的部分不再是難點。不過做到最后,坑還是比較多的:

例如小球可能會停在方塊上(就是這么巧),這是需要人為給予小球一個速度(“彈一彈”游戲里也是這樣做的)。

例如小球撞到方塊上,可能會觸發(fā)2次碰撞,因為影響不大,我先擱置了。這個是因為時間精度沒有太細,小球在上一幀沒有發(fā)生碰撞,因為速度較快,下一幀同時撞到了2個邊界。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/94760.html

相關(guān)文章

  • 大話 JavaScript 動畫

    摘要:直到年,世界上第一部動畫片滑稽臉的幽默相問世。上一次視神經(jīng)傳遞的圖像將會在大腦中存留,直到下一次神經(jīng)信號到達。移動設(shè)備還是相當(dāng)慘烈,并沒有開始支持。市面上有很多動畫庫,大家可以開箱即用。有一些是針對操作的,也有一些是針對對象。 背景 138.2億年前,世界上沒有時間和空間,或許世界都不存在,在一個似有似無的點上,匯集了所有的物質(zhì),它孕育著無限的能量與可能性。 宇宙大爆炸 巨大的內(nèi)力已無...

    syoya 評論0 收藏0
  • 我的一些開源項目(前端)

    摘要:于是,我決定厚著臉皮來宣傳一下我的幾個開源項目,雖然大多數(shù)都是一些比較簡單的游戲,但是這可以讓更多人看到我的項目,也可以讓我自己知道哪里地方做得不好,并且加以改進。正文清技背單詞使用開發(fā)的背單詞應(yīng)用,開發(fā)時間為一個月,目前是版本。 前言 之前陸陸續(xù)續(xù)在 GitHub 上創(chuàng)建了幾個項目,奈何沒人關(guān)注(可能我的項目太垃圾了)。于是,我決定厚著臉皮來宣傳一下我的幾個開源項目,雖然大多數(shù)都是一...

    Yuqi 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<