昨天介紹完了一些簡單的應用,今天阿森想用JavaScript來寫一個貪吃蛇的網頁遊戲,相信透過這個過程大家可以更清楚JavaScript的使用邏輯。
那我們就開始吧!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./style.css">
<title>Document</title>
</head>
<body>
<h1>貪吃蛇</h1>
<canvas id = "game" width="400" height="400"></canvas>
<h2>使用上下左右來控制!</h2>
<h2>先得25分就贏了!</h2>
<script src="app.js"></script>
</body>
</html>
HTML的部分我們就簡單的使用一個h1當標題,再用canvas這個tag當作遊戲畫面。
所以只要這樣HTML的部分就完成囉。
再來我們幫這個頁面做一點美化:
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
canvas{
box-shadow: black 10px 10px 50px;
}
這時我們的頁面會變成這樣:
可以看到東西都置中了,而且遊戲畫面也多了陰影。
接下來就是重頭戲了,首先我們主要的遊戲架構是這樣:
function startGame() {
snakePosition();
let lose = isOver();
if(lose){
document.body.addEventListener('keydown', playAgain);
return;
}
clearScreen();
checkColli();
let win = isWin();
if(win){
return;
}
drawApple();
drawSnake();
drawScore();
setSpeed();
setTimeout(startGame, 1000/speed);
}
依序是
snakePosition-負責管理與調整snake的位置
isOver-確認遊戲結束了沒
playAgain-確認是否再玩一次
clearScreen-初始化遊戲畫面
checkColli-確認蛇和蘋果的碰撞
isWin-確認勝利條件
drawApple-生產蘋果方塊
drawSnake-生產蛇方塊
drawScore-顯示分數
setSpeed-更改速度
setTimeout-重複跑上述內容
接下來是一開始的DOM操作:
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
先抓到id為game的canvas後,設定他的Context為ctx。
再來是一些遊戲常數設定:
class SnakePart{
constructor(x, y){
this.x = x;
this.y = y;
}
}
let speed = 8;
let tileCount = 20;
let tileSize = canvas.width / tileCount - 2;
let headX = 10;
let headY = 10;
const snakePart = [];
let tailLen = 0;
let appleX = 5;
let appleY = 5;
let xV = 0;
let yV = 0;
let score = 0;
這裡我們設定這張地圖為20 * 20大小的方格地圖,每個方格大小是canvas總寬度除以20再減2,這樣可以讓每個方格隔開一點空間,跑起來也比較好看。
再來我們設定一個class叫snakePart,如果你沒有學過class的概念,你只要想像他是一塊模板,透過宣告一個東西是這個class type我們可以建構出很多內容結構一樣的物件。
我們也透過head x 跟 y 設定蛇一開始的位置為(10, 10),蘋果一開始的位置為(5, 5),蛇長度一開始為0,x軸和y軸方向速度皆為0,分數也為0。
再來我們一個一個介紹function:
function snakePosition() {
headX = headX + xV;
headY = headY + yV;
}
位置和速度的加成為新的位置。
function isOver() {
let Over = false;
if(headX < 0 || headX == 20 || headY < 0 || headY == 20){
Over = true;
}
for(let i = 0; i < snakePart.length; i++){
if(headX == snakePart[i].x && headY == snakePart[i].y){
Over = true;
}
}
if(Over){
ctx.fillStyle = "white";
ctx.font = "50px Poppins";
ctx.fillText("Game Over!", canvas.width/6.5, canvas.height /2);
ctx.font = "40px Poppins";
ctx.fillText("再玩一次?", canvas.width/3.5, canvas.height /2 + 50 );
ctx.font = "25px Poppins";
ctx.fillText("按空白鍵", canvas.width/2.7, canvas.height /2 +100 );
}
return Over;
}
如果蛇頭跑到了地圖的邊界,或是撞上自己的身體,則回傳Over = true,遊戲結束。
function playAgain(event) {
if(event.keyCode == 32){
location.reload();
}
}
如果按下空白鍵則重新載入這個頁面,讓遊戲再跑一次。
function clearScreen() {
ctx.fillStyle= 'black';
ctx.fillRect(0, 0, 400, 400);
}
把背景設為黑色。
function checkColli() {
if(appleX === headX && appleY === headY){
appleX = Math.floor(Math.random() * tileCount);
appleY = Math.floor(Math.random() * tileCount);
tailLen ++;
score ++;
if(score > 5 && score % 2 == 0){
speed ++;
}
}
}
如果蛇和蘋果碰撞,則分數和長度皆加一,再如果分數大於5,每到達偶數分速度就加一。
function isWin() {
let win = false;
if(score == 25){
win = true;
}
if(win){
ctx.fillStyle = "white";
ctx.font = "50px Poppins";
ctx.fillText("你贏了!", canvas.width/3.3, canvas.height /2)
}
return win;
}
當得到25分時會出現"你贏了!"的字樣,同時回傳win = true。
function drawApple() {
ctx.fillStyle = "red";
ctx.fillRect(appleX * tileCount, appleY * tileCount, tileSize, tileSize);
}
設定頻果為紅色,還有設定size。
function drawSnake() {
ctx.fillStyle = "green";
for(let i = 0; i< snakePart.length; i++){
let part = snakePart[i];
ctx.fillRect(part.x * tileCount, part.y * tileCount, tileSize, tileSize);
}
snakePart.push( new SnakePart(headX, headY));
if(snakePart.length > tailLen){
snakePart.shift();
}
ctx.fillStyle = 'orange';
ctx.fillRect(headX * tileCount, headY *tileCount, tileSize, tileSize);
}
負責畫出蛇的身體,並把他接在身上!
function drawScore() {
ctx.fillStyle = "white";
ctx.font = "10px Poppins";
ctx.fillText("Score: " + score, canvas.width-50, 10);
}
設定字顏色、字體和位置等。
function setSpeed() {
if(score == 5){
speed = 10;
}
}
這裡我設定在得到五分的時候會進行一個提速。
再來我們要在最下面寫幾個負責偵測按鍵的Listener:
document.body.addEventListener('keydown', keyDown);
function keyDown(event) {
//go up
if(event.keyCode== 38){
if(yV == 1)
return;
yV = -1;
xV = 0;
}
//go down
if(event.keyCode == 40){
if(yV == -1)
return;
yV = 1;
xV = 0;
}
//go left
if(event.keyCode == 37){
if(xV == 1)
return;
yV = 0;
xV = -1;
}
//go right
if(event.keyCode == 39){
if(xV == -1)
return;
yV = 0;
xV = 1;
}
}
function playAgain(event) {
if(event.keyCode == 32){
location.reload();
}
}
這樣就可以透過上下左右來改變方向了!
最後記得在最下面加上:
startGame();
這樣我們的貪吃蛇遊戲就大功告成了!
想試玩或想做比對的可以到這裡:
https://cooksen.github.io/eating-snake.github.io/
今天我們透過JavaScript和DOM寫出了一個簡單的小遊戲,當然這個遊戲還有很多可以加強的地方,像是難度選擇、計分板、客製化顏色等等。可是從今天的進度來看,JavaScript只需要短短的100行就可以寫出這樣一個好玩的小遊戲,可見他真的是很強大的一個程式語言呢!
接下來阿森要介紹的是一個由JavaScript延伸出來的一個非常厲害的工具,沒錯就是React!講了這麼久終於要講到題目裡的內容了,希望接下來大家可以運用這些前面學到的概念,應用在React的實際操作上,跟阿森一起繼續變強吧!