推盒子小遊戲是一款多年前很流行的小遊戲(即使現在也有很多人玩),遊戲目的很簡單,就是人推箱子,把所有的箱子推到目的地,就遊戲成功:看似跟簡單的邏輯,其實還是有一定難度的,我也是依靠了別人的幫助才完成的,現在開始介紹如何用js,html5編寫該遊戲(方便起見我們把人用一個粉圓形代替):
一.能力要求
#JavaScript,HTML畫布,物件導向的基本思想,合理的編程邏輯。
二.寫順序
#1.pushBox.html檔
#2.pojo.js檔案(用來存放所有的物件)
#3.paint.js檔案(用來寫畫畫的語句)
4.game.js檔案(用來寫運行邏輯部分)
5.allLevels.js檔案(用來存放關卡)
*註:這是我的書寫習慣,按內容和功能將個各類別分開,如有更好地寫法歡迎評論
三.開始寫
1.建立基本的pushBox .html檔:
內容很簡單,裡面只要
<body> <canvas id='can1' width=1536 height=733></canvas> <audio id="walk" autoplay></audio> <audio id="push" autoplay></audio> <audio id="win" autoplay></audio> </body>
<script>有引入其他四個.js文件,還有一個呼叫run()方法:</script>
<script type="text/javascript" src="pojo.js"></script> <script type="text/javascript" src="game.js"></script> <script type="text/javascript" src="paint.js"></script> <script type="text/javascript" src="allLevels.js"></script> <script> window.onload = function(){ run(); } </script>
2.寫pojo類別:
首先我們需要知道總共有這些類別:
人,箱子,目標點,磚塊和圍牆,很簡單,所有的類別都有color(顏色),size(邊長/半徑),x(橫座標),y(縱座標)這些屬性。然後我們要記得,人和磚塊可能和目標點重合,所以在箱子和人的類別裡面要加上isOnTarget(是否和目標點重合)屬性,這樣就完成了:
//人类 function Person(x, y){ this.color = 'pink'; this.size = 20; this.x = x; this.y = y; //判断这个人是否在目标点上 this.isOnTarget = false; } //箱子类 function Box(x, y){ this.color = 'yellow'; this.size = 40; this.x = x; this.y = y; //判断某个箱子是否在目标点上 this.isOnTarget = false; } //目标点类 function Target(x, y){ this.size = 12; this.x = x; this.y = y; this.color = 'lime'; } //地砖类 function Brick(x, y){ this.x = x; this.y = y; this.size = 40; } //围墙类 function Wall(x, y){ this.x = x; this.y = y; this.size = 40; }
3.寫paint.js類別
我們需要對剛剛在pojo.js類別中寫的所有類別寫出畫的方法:
需要注意一點很重要:我們如果使用確定的x,y座標,比如說要畫箱子:
ctx.fillRect(x, y, size, size);
如果如下面的方式調用的話,我們在allLevels裡面這樣畫這個箱子:
ctx.fillRect(500, 500, 40, 40);
我們就無法確定這個箱子的旁邊是什麼,不好判斷,於是沒有辦法寫邏輯層。
所以,我們這樣構思:我們用一個二維數組來構造這個關卡,每一個物塊(箱子,人,目標點,磚塊或牆)都放到這個數組當中,arr1[] [],像這樣:
var arr1 = [ ['','wall','wall','wall','wall','wall','',''], ['','wall','brick','person','wall','wall','wall',''], ['','wall','brick','box','brick','brick','wall',''], ['wall','wall','wall','brick','wall','brick','wall','wall'], ['wall','target','wall','brick','wall','brick','brick','wall'], ['wall','target','box','brick','brick','wall','brick','wall'], ['wall','target','brick','brick','brick','box','brick','wall'], ['wall','wall','wall','wall','wall','wall','wall','wall'] ];
比如說arr1[0][1]就是牆了,這樣一來就能寫邏輯層了。而且還有一個優點,就是我們之後創建新關卡的時候很方便,只需要按照座標順序在數組裡寫出來。
所以,我們在paint.js裡面這樣寫,每個方法裡面都把x和y進行一些運算,使它能正確地在網頁中畫出:
//首先还是清屏 function clearScreen(ctx){ ctx.clearRect(0,0,1536,750); } //画人 function paintPerson(ctx, x, y, size,color){ ctx.beginPath(); ctx.fillStyle = color; //我们在内部就写好该往哪里画,传过来的x,y坐标值就可以直接在里面计算了 ctx.arc(530+x*size*2+20, 180+y*size*2+20, size, 0, 2*Math.PI); ctx.fill(); } //画箱子 function paintBox(ctx, x, y, size,color){ ctx.beginPath(); ctx.fillStyle = color; ctx.fillRect(530+x*size, 180+y*size, size, size); ctx.strokeStyle = 'black'; ctx.lineWidth = 2; ctx.moveTo(530+x*size, 180+y*size); ctx.lineTo(530+x*size+size, 180+y*size+size); ctx.stroke(); ctx.beginPath(); ctx.moveTo(530+x*size+size, 180+y*size); ctx.lineTo(530+x*size, 180+y*size+size); ctx.stroke(); ctx.strokeRect(530+x*size, 180+y*size, size, size); } //画目标点 function paintTarget(ctx, x, y, size,color){ ctx.beginPath(); ctx.fillStyle = color; ctx.arc(530+x*size*4+20, 180+y*size*4+20, size, 0, 2*Math.PI); ctx.fill(); } //画地砖,其中用了for循环简化了代码量 function paintBrick(ctx, x, y, size){ ctx.beginPath(); ctx.fillStyle = 'blue'; ctx.fillRect(530+x*size, 180+y*size, size, size); ctx.strokeStyle = 'lightblue'; for(var i = 0; i <= 3; i++){ ctx.beginPath(); ctx.moveTo(530+x*size, 180+y*size+0.25*(i+1)*size); ctx.lineTo(530+x*size+size, 180+y*size+0.25*(i+1)*size); ctx.stroke(); } for(var i = 0; i < 4; i++){ ctx.beginPath(); if(i%2 == 0){ ctx.moveTo(530+x*size+0.5*size, 180+y*size+0.25*i*size); ctx.lineTo(530+x*size+0.5*size, 180+y*size+0.25*(i+1)*size); ctx.stroke(); }else{ ctx.moveTo(530+x*size+0.25*size, 180+y*size+0.25*i*size); ctx.lineTo(530+x*size+0.25*size, 180+y*size+0.25*(i+1)*size); ctx.stroke(); ctx.beginPath(); ctx.moveTo(530+x*size+0.75*size, 180+y*size+0.25*i*size); ctx.lineTo(530+x*size+0.75*size, 180+y*size+0.25*(i+1)*size); ctx.stroke(); } } } //画围墙 function paintWall(ctx, x, y, size){ ctx.beginPath(); ctx.fillStyle = 'gray'; ctx.fillRect(530+x*size, 180+y*size, size, size); ctx.strokeStyle = 'white'; ctx.beginPath(); ctx.moveTo(530+x*size+0.5*size, 180+y*size); ctx.lineTo(530+x*size+0.5*size, 180+y*size+size); ctx.stroke(); ctx.beginPath(); ctx.moveTo(530+x*size, 180+y*size+0.5*size); ctx.lineTo(530+x*size+size, 180+y*size+0.5*size); ctx.stroke(); }
然後,我們在調用畫的方法時候這樣調用,按照每個物塊在數組中的位置進行畫畫:
unction getMap(ctx, person){ clearScreen(ctx); for(var i = 0; i<arr1.length; i++){ for(var j = 0; j<arr1[0].length; j++){ //画初始图 if(arr1[i][j] == 'wall'){ //这样我们就把坐标作为参数,引入到那些paint方法里,经过计算,在网页中正确显示了 paintWall(ctx,j,i,40); }else if(arr1[i][j] == 'brick'){ paintBrick(ctx, j, i,40); }else if(arr1[i][j] == 'target'){ paintBrick(ctx, j, i,40); paintTarget(ctx, j, i,10,'lime'); }else if(arr1[i][j] == 'box'){ paintBrick(ctx, j, i,40); var index = getBoxIndex(boxes,i,j); if(boxes[index].isOnTarget){ paintBox(ctx, j, i,40,'red'); }else{ paintBox(ctx, j, i,40,'yellow'); } }else if(arr1[i][j] == 'person'){ paintBrick(ctx, j, i,40); paintPerson(ctx, j, i,20,'pink'); } } } }
在game.js裡面的run()調用getMap()方法後,效果就是這樣:
4.game.js類別:
首先我們還是需要建立人和箱子的物件:
var person = new Person(0,0);
var boxLevel1Count = 3; var boxes = new Array(boxLevel1Count); for(var i = 0;i<boxLevel1Count;i++){ boxes[i] = new Box(0,0); }
然後我們要知道人在二維數組中的位置(座標),以及三個箱子在其中的座標需要以下兩個方法:
//找到人的坐标 function findPerson(){ for (var i = 0; i < arr1.length; i++) { var tmp = arr1[i]; for (var j = 0; j < tmp.length; j++) { if (arr1[i][j] == 'person') { //使用json传变量 return {personX:i,personY:j}; } } } } //找箱子的坐标,并把他们放到数组里 function findBox(){ var count = 0; for (var i = 0; i < arr1.length; i++) { var tmp = arr1[i]; for (var j = 0; j < tmp.length; j++) { if (arr1[i][j] == 'box') { boxes[count].x = i; boxes[count].y = j; count++; } } } return boxes; }
找到人的座標之後,我們要把person物件的x,y和二維數組裡的i,j關聯起來,就是:
//接收person的坐标 var position = findPerson(); //i是person的横坐标 var i = position.personX; //j是person的纵坐标 var j = position.personY; //使对象的属性和人在二维数组的坐标关联 person.x = i; person.y = j;
<br/>
然後我們就可以開始寫邏輯了,比如說用戶按方向鍵左,要判斷左邊是什麼,如果是箱子的話,還要判斷箱子的左邊是什麼:
正確的邏輯如下所示(我們就拿人往左移動為例,然後上下右都是一樣的):
具體的語法很簡單:比如說,按照第一個為例,左邊是磚塊,而人踩的不是目標點:
if(arr1[i][j-1] == 'brick'){ arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; Audio1.src = '走路emm.wav'; }
如果我們下一步,人踩到了目標點,我們就要把person.isOnTarget 設為true,當人移開時候,我們把這個屬性設為false
当人左边是箱子的时候,比较麻烦,首先必须明白有一点:我们到底推的是哪个箱子?之前已经有了一个存放所有箱子的数组了,所以现在需要一个方法,可以让我们知道我们推的是哪个箱子:
function getBoxIndex(boxes, i,j){ var index = 0; for(var k = 0;k<boxes.length;k++){ if(boxes[k].x == i && boxes[k].y == j){ //找到了这个箱子的下标 index = k; } } return index; }
在实际调用中,里面的参数(i,j)就写下一步要走的那个位置,比如说向左走,就是
var index = getBoxIndex(boxes, i,j-1);
这个index就是我们要找的第i个箱子了,接下来就很好办了,我们按照刚才的逻辑一步一步写,一堆的if、else,只需注意两点,当人踩到目标点时,把person.isOnTarget = true,移开之后false;箱子踩到目标点时boxes[index].isOnTarget = true,移开之后false,然后再整理一下,简化代码量,就是:
//玩家操作 document.onkeydown = function(ev){ var oCan = document.getElementById('can1'); var ctx = oCan.getContext('2d'); var oEvent = ev || event; var Audio1 = document.getElementById('walk'); var Audio2 = document.getElementById('push'); //接收person的坐标 var position = findPerson(); //i是person的横坐标 var i = position.personX; //j是person的纵坐标 var j = position.personY; //使对象的属性和人在二维数组的坐标关联 person.x = i; person.y = j; if(oEvent.keyCode == 37){ if(person.isOnTarget){ if(arr1[i][j-1] == 'brick'){ arr1[i][j-1] = 'person'; arr1[i][j] = 'target'; person.isOnTarget = false; }else if(arr1[i][j-1] == 'box' && arr1[i][j-2] != 'wall' && arr1[i][j-2] != 'box'){ var index = getBoxIndex(boxes, i,j-1); if(!boxes[index].isOnTarget){ if(arr1[i][j-2] == 'brick'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'target'; person.isOnTarget = false; }else if(arr1[i][j-2] == 'target'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'target'; person.isOnTarget = false; boxes[index].isOnTarget = true; } }else if(boxes[index].isOnTarget){ if(arr1[i][j-2] == 'brick'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'target'; boxes[index].isOnTarget = false; }else if(arr1[i][j-2] == 'target'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'target'; } } boxes[index].y--; }else if(arr1[i][j-1] == 'target'){ arr1[i][j-1] = 'person'; arr1[i][j] = 'target'; } }else if(!person.isOnTarget){ if(arr1[i][j-1] == 'brick'){ arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; }else if(arr1[i][j-1] == 'box' && arr1[i][j-2] != 'wall' && arr1[i][j-2] != 'box'){ var index = getBoxIndex(boxes, i,j-1); //箱子踩的不是目标点 if(!boxes[index].isOnTarget){ //箱子左边是地面 if(arr1[i][j-2] == 'brick'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; }//箱子左边是目标点 else if(arr1[i][j-2] == 'target'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; boxes[index].isOnTarget = true; } }else if(boxes[index].isOnTarget){ if(arr1[i][j-2] == 'brick'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; boxes[index].isOnTarget = false; person.isOnTarget = true; }else if(arr1[i][j-2] == 'target'){ arr1[i][j-2] = 'box'; arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; person.isOnTarget = true; } } boxes[index].y--; }else if(arr1[i][j-1] == 'target'){ arr1[i][j-1] = 'person'; arr1[i][j] = 'brick'; person.isOnTarget = true; } } }
这样,向左走的所有逻辑就完成了,然后是上,右,下,接着else if 就行,照猫画虎,把里面的i和j一更换就完成了。
所有逻辑写完,验证无误后,我们要判断通关条件,这个很简单,当所有的箱子都isOnTarget时候,成功,当然是在每次按完方向键之后都要判断:
function judgeWin(boxes){ var count = 0; for(var p = 0;p<boxes.length;p++){ if(boxes[p].isOnTarget) count++; }if(count == boxes.length){ var Audio3 = document.getElementById('win'); Audio3.src = '鼓掌.mp3'; alert('You Win! 一共走了'+countStep+'步'); } }
通关的画面如下:
最后,我们加入音效,主要划分成以下几类:人走到砖块的,人推箱子的,人碰到墙的(和人推箱子碰到墙的),成功后的掌声。
总结:
1.我开始以为这个很简单,和我之前做的那个flappy bird 差不多,没想到这里面的逻辑其实很复杂,我的flappy bird链接如下:点击打开链接。所以,一定要在刚开始的时候要构思好大局,别越写越麻烦,容易产生放弃心里。
2.利用二维数组存放地图,然后在paint()方法里面写画的位置,大小等,调用的时候paint()里面就填坐标,这样有两个好处:(1)可以知道每个物块的上下左右都是什么,易于判断;(2)新建关卡的时候易于创建,只需要按照坐标位置,把wall,brick,person,target,box放进去即可
3.在创建人的对象后,我们需要在地图数组中把人的坐标找出来,然后将对象的x,y属性和坐标关联;在创建箱子的对象数组后,我们需要getIndex()方法,找到人到底推的是哪个箱子,才能使这个箱子的isOnTarget改成true或者false,这两点很关键。
以上是JS和H5編寫推箱子遊戲的詳細內容。更多資訊請關注PHP中文網其他相關文章!

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

Dreamweaver Mac版
視覺化網頁開發工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。