DAY13-JavaScript实作网页小游戏

截图

前言:

昨天介绍完了一些简单的应用,今天阿森想用JavaScript来写一个贪吃蛇的网页游戏,相信透过这个过程大家可以更清楚JavaScript的使用逻辑。

那我们就开始吧!

HTML:

<!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的部分就完成罗。

CSS:

再来我们帮这个页面做一点美化:

body {
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

canvas{
    box-shadow: black 10px 10px 50px;
}

这时我们的页面会变成这样:

截图

可以看到东西都置中了,而且游戏画面也多了阴影。

JavaScript:

接下来就是重头戏了,首先我们主要的游戏架构是这样:

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:

snakePosition:

function snakePosition() {
    headX = headX + xV;
    headY = headY + yV;
}

位置和速度的加成为新的位置。

isOver:

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,游戏结束。

playAgain:

function playAgain(event) {
    if(event.keyCode == 32){
        location.reload();
    }
}

如果按下空白键则重新载入这个页面,让游戏再跑一次。

clearScreen:

function clearScreen() {
    ctx.fillStyle= 'black';
    ctx.fillRect(0, 0, 400, 400);
}

把背景设为黑色。

checkColli:

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,每到达偶数分速度就加一。

isWin:

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。

drawApple:

function drawApple() {
    ctx.fillStyle = "red";
    ctx.fillRect(appleX * tileCount, appleY * tileCount, tileSize, tileSize);
}

设定频果为红色,还有设定size。

drawSnake:

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);
}

负责画出蛇的身体,并把他接在身上!

drawScore:

function drawScore() {
    ctx.fillStyle = "white";
    ctx.font = "10px Poppins";
    ctx.fillText("Score: " + score, canvas.width-50, 10);
}

设定字颜色、字体和位置等。

setSpeed:

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的实际操作上,跟阿森一起继续变强吧!


<<:  [Tableau Public] day 25:台湾姓氏分布分析-3

>>:  Day 10 进阶型别 Part - 3

用科学化除错方法替你的 zk 程序除错之一

Steve McConnel 所着的CODE COMPLETE:软件开发实务指南 中, 第 23 章...

# Day5--Funny Function!一招函式打天下?

函式是每个程序语言都会有的一个语法,非常的实用,只要是编写功能,一定与函式脱离不了关系,而函式的内容...

Day 23: Recurrent Neural Network — 循环精神网路初探(下)

Recurrent Neural Network 循环精神网路 前面讲述了许多关於RNN循环精神网路...

[Day 2] 资料产品第一层 - 原始资料

就像稻米之於米苔目,小麦之於面疙瘩,原始资料就是任何资料产品最基础的存在。 在研究所修读统计的时候,...

15. Error x Exception x Bug Trackers

初学阶段对错误处理没什麽感觉(尽管几乎所有程序语言书都有这一章节),写的都是不用维护的小专案、没有真...