摘要:在彈一彈游戲中,小球不能向上發(fā)射。這里又有一個(gè)坑彈一彈游戲中,剛射擊出去的小球是不受重力影響的不然瞄準(zhǔn)還有什么意義。
前言
半年前用js和canvas仿了熱血傳奇網(wǎng)游(地址),基本功能寫完之后,剩下的都是堆數(shù)據(jù)、堆時(shí)間才能完成的任務(wù)了,沒什么新鮮感,因此進(jìn)度極慢。這次看到微信《彈一彈》比較火,因?yàn)樯婕暗轿锢硪妫榱苏鎸?shí)),于是動(dòng)手試了一下。一共用了10個(gè)小時(shí),不僅完成了這個(gè)demo,<刪除線>并且打上了彈一彈好友排行榜的第一頁刪除線>。
資料匯總在線demo:點(diǎn)擊即玩
代碼:400行帶注釋
canvas渲染庫:支持物理引擎及chrome調(diào)試工具,這里
準(zhǔn)備工作微信這個(gè)小游戲的游戲規(guī)則很簡(jiǎn)單,看圖就能看明白,這里不再贅述。涉及到的幾個(gè)開發(fā)難度:
1.物理引擎當(dāng)然不用也可以,無非就是改改圖片的位置,可以自己模擬掉落和碰撞效果。不過由于我追(wu)求(li)體(hen)驗(yàn)(cha),因此開始尋找第三方的物理引擎。
最后我使用的是chipmunk的js版(這個(gè)庫是底層計(jì)算庫,因此star不多,但是比較有名氣的hilo和cocos2d的物理引擎用到了這個(gè)庫)。主要原因之一是,這個(gè)庫的功能只是進(jìn)行了物理運(yùn)算,并且支持重力、彈性、摩擦、浮力等功能。當(dāng)然體積也比較小。畢竟我們只是寫一個(gè)小demo,引入一個(gè)游戲框架的話很可能徒增成本。
不過我使用的時(shí)候遇到了幾個(gè)罕見的bug,應(yīng)該是作者的疏漏,在issue中也有人反饋。看作者更新頻率很低,我拿來用的時(shí)候有一些修改。如果其它人使用的時(shí)候遇到j(luò)s報(bào)錯(cuò),可以試試這里
2.UI渲染我選擇的是canvas,因?yàn)樯婕暗筋l繁的樣式更新,每幀都去改寫style的話太占性能。而且用canvas寫的話,以后還可以迭代一些碰撞產(chǎn)生的畫效。
之前封裝了一個(gè)easycanvas庫,可以將樹形數(shù)據(jù)結(jié)構(gòu)“翻譯”成“canvas畫布中的一個(gè)個(gè)對(duì)象”。這次又順手補(bǔ)充了一個(gè)支持chipmunk的插件,這樣整個(gè)“彈一彈”的開發(fā)就完全只需要管理數(shù)據(jù)即可,渲染工作很少。
開始開發(fā) html及背景由于項(xiàng)目較小,我把html、css、js堆在了一個(gè)文件(最后寫完之后,發(fā)現(xiàn)一共連同注釋才400行)。
首先創(chuàng)建一個(gè)空html,為了看起來高大上,我搜了一張?zhí)炜罩黝}的背景圖。
可能用到的變量
接下來,準(zhǔn)備一些我們需要用到的數(shù)據(jù)。例如游戲的寬高、小球的大小、當(dāng)前游戲狀態(tài)(是否可以射擊)、每次可以射出的小球數(shù)、玩家的分?jǐn)?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"); // 給每個(gè)東西起一個(gè)type,后面會(huì)用來做碰撞檢測(cè) var BALL_TYPE = 1, BLOCK_TYPE = 2, BORDER_TYPE = 3, BOTTOM_TYPE = 4, BONUS_TYPE = 5;頂部文本
接下來先將分?jǐn)?shù)和小球個(gè)數(shù)寫到canvas中。首先創(chuàng)建一個(gè)easycanvas實(shí)例,寬400,高600。然后add2個(gè)對(duì)象。一個(gè)以左上角(5,5)為頂點(diǎn),向右下方寫分?jǐn)?shù)。一個(gè)以右上角(395, 5)為頂點(diǎn),向左下角寫當(dāng)前小球個(gè)數(shù)。
// 初始化easycanvas實(shí)例 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 "小球個(gè)數(shù):" + ballCount; } }, style: { tx: 395, ty: 5, textAlign: "right", textVerticalAlign: "top", color: "black" } });添加方塊
接下來,設(shè)置整個(gè)場(chǎng)景的重力,并且添加一些方塊進(jìn)去。每個(gè)方塊對(duì)象含有一個(gè)child,用來展示數(shù)字(還可以撞幾下)。為了避免方塊重疊,我們讓方塊的x坐標(biāo)在50、100、150、……、300、350循環(huán)。同時(shí),為了避免看起來“太整齊”,每次添加一個(gè)小的隨機(jī)數(shù),讓這些方塊們錯(cuò)落有致。(“錯(cuò)落”指參差不齊,“致”指情趣。形容事物的布局雖然參差不齊,但卻極有情趣,使人看了有好感?!扯龋?/p>
每個(gè)方塊的大小是30x30,因此shapes包括4條邊,例如(0,0)到(30,0)是一條邊。這些方塊是失重的(不會(huì)掉下去),因此static設(shè)置為true。為了更加錯(cuò)落有致,我們給他一個(gè)隨機(jī)的角度rotate。
每個(gè)方塊含有一個(gè)child,寫著一個(gè)數(shù)字。不需要給數(shù)字設(shè)置rotate,否則6和9可能就分不清了。
// 初始化easycanvas物理引擎,添加一個(gè)有物理樹形的空容器 var $space = new Easycanvas.class.sprite({ physics: { gravity: 2, // 重力默認(rèn)為1,但是游戲進(jìn)程有點(diǎn)慢,看著不夠爽 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; } }
接下來,我們做瞄準(zhǔn)部分。大致功能是,有一排小圓點(diǎn),會(huì)隨著鼠標(biāo)運(yùn)動(dòng),并且有彈簧的感覺。
首先要記錄鼠標(biāo)的軌跡,我們給easycanvas實(shí)例$Painter加上事件監(jiān)聽。在“彈一彈”游戲中,小球不能向上發(fā)射。因此記錄鼠標(biāo)的Y坐標(biāo)值的時(shí)候,我們讓他至少為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, } });小球瞄準(zhǔn)
接下來,我們添加7個(gè)小球,讓他們排列在一條線上,從游戲正上方的(300, 20)點(diǎn)到鼠標(biāo)位置均勻鋪開。具體邏輯就是,我們將鼠標(biāo)位置和(300, 20)的坐標(biāo)差進(jìn)行6等分,第一個(gè)球的坐標(biāo)向鼠標(biāo)位置偏移0/6、第二個(gè)球偏移1/6……,最后一個(gè)球偏移6/6(正好落在了鼠標(biāo)位置)。這幾個(gè)球我們給他們一個(gè)透明度,并且不啟用物理規(guī)則(因?yàn)檫@個(gè)階段小球不能掉下來)。我們?cè)诿總€(gè)小球上設(shè)置一個(gè)shoot鉤子,當(dāng)玩家射出真實(shí)的小球時(shí),刪除這個(gè)瞄準(zhǔn)用的小球。
// 顯示瞄準(zhǔn)軌跡 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ā)射小球
接下來,我們添加真實(shí)的小球(受到物理規(guī)則影響的小球)。
當(dāng)射擊時(shí),我們廣播shoot事件,移除剛才瞄準(zhǔn)用的小球。
之后,我們間隔100毫秒,連續(xù)調(diào)用addBall方法來創(chuàng)建小球。addBall方法中,我們?yōu)槊總€(gè)小球設(shè)置物理規(guī)則。包括形狀、彈性、摩擦等。
這里有一個(gè)坑,就是一旦開始射擊,不管鼠標(biāo)怎么移動(dòng),射擊的方向都不能變化。因此我們要先記錄下當(dāng)前的mouse值,這里用的是JSON.parse(JSON.stringify(mouse))來copy一個(gè)簡(jiǎn)單對(duì)象。
這里又有一個(gè)坑:“彈一彈”游戲中,剛射擊出去的小球是不受重力影響的(不然瞄準(zhǔn)還有什么意義)。因此,我們?cè)诿總€(gè)小球上增加一個(gè)和重力相反的作用力,抵消重力。(在其它部分的代碼中,有著“當(dāng)小球發(fā)生一次碰撞后,取消這個(gè)作用力”的實(shí)現(xiàn),這里為了清晰沒有一起貼出來)。
同時(shí),我們給小球加上初速度。
這里又又又又又有一個(gè)坑(好煩?。翰还茉趺瓷鋼?,小球初始獲得的速度是相同的。哪怕小球的瞄準(zhǔ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: [ // 形狀是一個(gè)以(ballSize / 2, ballSize / 2)為圓心的,半徑也是ballSize / 2的圓 // 改成位運(yùn)算符吧,看著能高大上一點(diǎn)(其實(shí)在這里卵用沒有) [ballSize >> 1, ballSize >> 1, ballSize >> 1] ], mass: 1, // 質(zhì)量 friction: 0.1, // 摩擦(摩擦太大了會(huì)損失能量) 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 }; // 修正速度,確保從各個(gè)角度射出小球的速度差不多 // 這里用到的著名的高等數(shù)學(xué)知識(shí):勾股定理 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)有了,后面的部分不再是難點(diǎn)。不過做到最后,坑還是比較多的:
例如小球可能會(huì)停在方塊上(就是這么巧),這是需要人為給予小球一個(gè)速度(“彈一彈”游戲里也是這樣做的)。
例如小球撞到方塊上,可能會(huì)觸發(fā)2次碰撞,因?yàn)橛绊懖淮?,我先擱置了。這個(gè)是因?yàn)闀r(shí)間精度沒有太細(xì),小球在上一幀沒有發(fā)生碰撞,因?yàn)樗俣容^快,下一幀同時(shí)撞到了2個(gè)邊界。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/94760.html
摘要:直到年,世界上第一部動(dòng)畫片滑稽臉的幽默相問世。上一次視神經(jīng)傳遞的圖像將會(huì)在大腦中存留,直到下一次神經(jīng)信號(hào)到達(dá)。移動(dòng)設(shè)備還是相當(dāng)慘烈,并沒有開始支持。市面上有很多動(dòng)畫庫,大家可以開箱即用。有一些是針對(duì)操作的,也有一些是針對(duì)對(duì)象。 背景 138.2億年前,世界上沒有時(shí)間和空間,或許世界都不存在,在一個(gè)似有似無的點(diǎn)上,匯集了所有的物質(zhì),它孕育著無限的能量與可能性。 宇宙大爆炸 巨大的內(nèi)力已無...
摘要:于是,我決定厚著臉皮來宣傳一下我的幾個(gè)開源項(xiàng)目,雖然大多數(shù)都是一些比較簡(jiǎn)單的游戲,但是這可以讓更多人看到我的項(xiàng)目,也可以讓我自己知道哪里地方做得不好,并且加以改進(jìn)。正文清技背單詞使用開發(fā)的背單詞應(yīng)用,開發(fā)時(shí)間為一個(gè)月,目前是版本。 前言 之前陸陸續(xù)續(xù)在 GitHub 上創(chuàng)建了幾個(gè)項(xiàng)目,奈何沒人關(guān)注(可能我的項(xiàng)目太垃圾了)。于是,我決定厚著臉皮來宣傳一下我的幾個(gè)開源項(xiàng)目,雖然大多數(shù)都是一...
閱讀 1807·2023-04-25 16:28
閱讀 751·2021-11-23 09:51
閱讀 1530·2019-08-30 15:54
閱讀 1220·2019-08-30 15:53
閱讀 2896·2019-08-30 15:53
閱讀 3479·2019-08-30 15:43
閱讀 3325·2019-08-30 11:18
閱讀 3388·2019-08-26 10:25