昨天介绍完了一些简单的应用,今天阿森想用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的实际操作上,跟阿森一起继续变强吧!
<<: [Tableau Public] day 25:台湾姓氏分布分析-3
Steve McConnel 所着的CODE COMPLETE:软件开发实务指南 中, 第 23 章...
函式是每个程序语言都会有的一个语法,非常的实用,只要是编写功能,一定与函式脱离不了关系,而函式的内容...
Recurrent Neural Network 循环精神网路 前面讲述了许多关於RNN循环精神网路...
就像稻米之於米苔目,小麦之於面疙瘩,原始资料就是任何资料产品最基础的存在。 在研究所修读统计的时候,...
初学阶段对错误处理没什麽感觉(尽管几乎所有程序语言书都有这一章节),写的都是不用维护的小专案、没有真...