html
<!DOCTYPE html>
<html lang="cmn-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.gameBox {
width: 800px;
height: 500px;
border: 1px solid black;
margin: 50px auto 10px;
position: relative;
overflow: hidden;
}
.info {
text-align: center;
}
.background {
width: 200%;
height: 100%;
background: url("imgs/sky.png");
position: absolute;
top: 0;
left: 0;
}
.bird {
height: 25px;
position: absolute;
background-image: url("imgs/bird.png");
width: 35px;
}
.bird.swing1 {
background-position: -8px -10px;
}
.bird.swing2 {
background-position: -60px -10px;
}
.bird.swing3 {
background-position: -113px -10px;
}
.pipeUp,
.pipeDown {
width: 52px;
height: 100px;
position: absolute;
}
.pipeUp {
background-image: url("imgs/pipeUp.png");
bottom: 110px;
}
.pipeDown {
background-image: url("imgs/pipeDown.png");
background-position: bottom;
}
.ground {
width: 200%;
height: 110px;
top: 390px;
position: absolute;
background-image: url("imgs/land.png");
}
</style>
</head>
<body>
<div class="gameBox">
<div class="background"></div>
<div class="bird swing1"></div>
<div class="ground"></div>
</div>
<p class="info">按Enter开始或暂停游戏,按Space跳跃</p>
</body>
</html>
<script src="./js/Common.js"></script>
<script src="./js/Sky.js"></script>
<script src="./js/Ground.js"></script>
<script src="./js/Bird.js"></script>
<script src="./js/Pipe.js"></script>
<script src="./js/Game.js"></script>common. js
js
class Common {
//公共父类,速度单位:像素每秒
constructor(width, height, left, top, speedX, speedY, dom) {
this.width = width;
this.height = height;
this.left = left;
this.top = top;
this.speedX = speedX;
this.speedY = speedY;
this.dom = dom;
this.timer = null;
}
move(duration) {
//按照speed的速度,移动duration的时间,duration为秒
const disX = this.speedX * duration;
const disY = this.speedY * duration;
this.left += disX;
this.top += disY;
}
render() {
this.dom.style.width = this.width + "px";
this.dom.style.height = this.height + "px";
this.dom.style.left = this.left + "px";
this.dom.style.top = this.top + "px";
}
}sky. js
js
const skyDom = document.querySelector(".background");
class Sky extends Common {
//天空类
constructor(speedX, skyDom) {
super(1600, 500, 0, 0, speedX, 0, skyDom);
}
move(duration) {
//按照speed的速度,移动duration的时间,duration为秒
super.move(duration);
if (this.left <= -800) {
this.left = 0;
}
this.render();
}
startMove() {
if (this.timer) return;
this.timer = setInterval(() => {
this.move(16 / 1000);
}, 16);
}
stopMove() {
clearInterval(this.timer);
this.timer = null;
}
}ground. js
js
const groundDom = document.querySelector(".ground");
class Ground extends Common {
constructor(speedX, dom) {
super(1600, 500, 0, 390, speedX, 0, dom);
}
move(duration) {
//按照speed的速度,移动duration的时间,duration为秒
super.move(duration);
if (this.left <= -800) {
this.left = 0;
}
this.render();
}
startMove() {
if (this.timer) return;
this.timer = setInterval(() => {
this.move(16 / 1000);
}, 16);
}
stopMove() {
clearInterval(this.timer);
this.timer = null;
}
}bird. js
js
const birdDom = document.querySelector(".bird");
const birdStyle = window.getComputedStyle(birdDom);
const birdWidth = parseFloat(birdStyle.width);
const birdHeight = parseFloat(birdStyle.height);
class Bird extends Common {
constructor(speedY, birdDom) {
super(birdWidth, birdHeight, 100, 100, 0, speedY, birdDom);
this.g = 500;
this.swingTimer = null;
this.swingFlag = 1;
}
move(duration) {
//按照speed的速度,移动duration的时间,duration为秒
super.move(duration);
this.speedY += this.g * duration;
if (this.top < 0) {
this.top = 0;
}
if (this.top > 500 - 110 - birdHeight) {
this.top = 500 - 110 - birdHeight;
}
this.render();
}
startSwing() {
if (this.swingTimer) {
return;
}
this.swingTimer = setInterval(() => {
this.swingFlag++;
if (this.swingFlag == 4) this.swingFlag = 1;
this.render();
}, 300);
}
stopSwing() {
clearInterval(this.swingTimer);
this.swingTimer = null;
}
render() {
super.render();
this.dom.className = "bird swing" + this.swingFlag;
}
startMove() {
if (this.timer) return;
this.timer = setInterval(() => {
this.move(16 / 1000);
}, 16);
}
stopMove() {
clearInterval(this.timer);
this.timer = null;
}
jump() {
this.speedY = -200;
}
}
// var bird = new Bird(200, birdDom);
// bird.startMove();
// bird.startSwing();pipe. js
js
const gameBox = document.querySelector(".gameBox");
class Pipe extends Common {
constructor(height, isUp, speedX) {
super(52, height, 800, 0, speedX, 0, null);
if (isUp) this.top = 500 - 110 - height;
const newPipe = document.createElement("div");
if (isUp) newPipe.classList.add("pipeUp");
else newPipe.classList.add("pipeDown");
this.dom = newPipe;
this.dom.style.left = this.left + "px";
gameBox.append(this.dom);
}
move(duration) {
super.move(duration);
if (this.left <= -52) {
this.dom.remove();
}
this.render();
}
startMove() {
if (this.timer) return;
this.timer = setInterval(() => {
this.move(16 / 1000);
}, 16);
}
stopMove() {
clearInterval(this.timer);
this.timer = null;
}
}
// const pipe = new Pipe(200, false, -50);
class PipeController {
constructor(speedX) {
this.pipes = [];
this.timer = null;
this.speedX = speedX;
}
producePipe() {
if (this.timer) return;
this.pipes.forEach(function (item) {
item.startMove();
});
this.timer = setInterval(() => {
this.pipes.forEach(function (item, i, arr) {
if (item.left < -52) arr.splice(i, 1);
});
const gap = 100;
const minHeight = 80;
const maxHeight = 390 - gap - minHeight;
const height =
Math.floor(Math.random() * (maxHeight - minHeight)) + minHeight;
const newPipeUp = new Pipe(height, true, this.speedX);
const newPipeDown = new Pipe(
390 - gap - height,
false,
this.speedX
);
this.pipes.push(newPipeDown, newPipeUp);
newPipeUp.startMove();
newPipeDown.startMove();
}, 2500);
}
stopProducePipe() {
clearInterval(this.timer);
this.timer = null;
this.pipes.forEach((item) => {
item.stopMove();
});
}
}
// var test = new PipeController();
// test.producePipe();game. js
js
/**
* 游戏类,负责游戏的整体逻辑和管理。
* @param {number} pipeSpeed 管道移动的速度。
* @param {number} groundSpeed 地面移动的速度。
* @param {number} birdSpeed 鸟飞行的速度。
* @param {number} skySpeed 天空移动的速度。
*/
class Game {
constructor(pipeSpeed, groundSpeed, birdSpeed, skySpeed) {
// 初始化游戏速度和各个游戏元素
this.pipeSpeed = pipeSpeed;
this.groundSpeed = groundSpeed;
this.birdSpeed = birdSpeed;
this.skySpeed = skySpeed;
this.bird = new Bird(this.birdSpeed, birdDom);
this.sky = new Sky(this.skySpeed, skyDom);
this.ground = new Ground(this.groundSpeed, groundDom);
this.pipe = new PipeController(this.pipeSpeed);
this.end = false;
this.pause = false;
}
/**
* 开始游戏移动,启动所有游戏元素的移动。
*/
startMove() {
this.pause = false;
this.bird.startMove();
this.sky.startMove();
this.ground.startMove();
this.pipe.producePipe();
// 每16ms检查游戏是否结束
setInterval(() => {
if (this.isEnd()) {
this.stopMove();
this.end = true;
}
}, 16);
}
/**
* 停止游戏移动,暂停所有游戏元素的移动。
*/
stopMove() {
this.pause = true;
this.bird.stopMove();
this.sky.stopMove();
this.ground.stopMove();
this.pipe.stopProducePipe();
}
/**
* 检测两个DOM元素是否相撞。
* @param {HTMLElement} dom1 第一个DOM元素。
* @param {HTMLElement} dom2 第二个DOM元素。
* @returns {boolean} 如果相撞返回true,否则返回false。
*/
crash(dom1, dom2) {
const dom1Style = window.getComputedStyle(dom1);
const dom2Style = window.getComputedStyle(dom2);
const dom1CenterX =
parseFloat(dom1Style.width) / 2 + parseFloat(dom1Style.left);
const dom1CenterY =
parseFloat(dom1Style.height) / 2 + parseFloat(dom1Style.top);
const dom2CenterX =
parseFloat(dom2Style.width) / 2 + parseFloat(dom2Style.left);
const dom2CenterY =
parseFloat(dom2Style.height) / 2 + parseFloat(dom2Style.top);
if (
Math.abs(dom1CenterX - dom2CenterX) <
Math.abs(
(parseFloat(dom1Style.width) +
parseFloat(dom2Style.width)) /
2
) &&
Math.abs(dom1CenterY - dom2CenterY) <
Math.abs(
(parseFloat(dom1Style.height) +
parseFloat(dom2Style.height)) /
2
)
) {
return true;
} else {
return false;
}
}
/**
* 检测游戏是否结束。
* @returns {boolean} 如果游戏结束返回true,否则返回false。
*/
isEnd() {
// 检查鸟是否撞上地面或者顶部,或者撞上管道
if (this.bird.top <= 0 || this.bird.top >= 390 - this.bird.height) {
return true;
}
for (let i = 0; i < this.pipe.pipes.length; i++) {
const item = this.pipe.pipes[i];
if (this.crash(item.dom, this.bird.dom)) {
return true;
}
}
return false;
}
/**
* 监听键盘事件,控制游戏的开始、暂停和重载。
*/
onEvent() {
window.addEventListener("keydown", (e) => {
// 空格键让鸟跳跃
if (e.key === " ") this.bird.jump();
// 回车键用于游戏重载或暂停/继续游戏
if (e.key === "Enter") {
if (this.end) {
window.location.reload();
} else {
if (this.pause) {
this.startMove();
} else {
this.stopMove();
}
}
}
});
}
}
// 创建游戏实例并启动
var test = new Game(-80, -80, 200, -50);
test.onEvent();
test.startMove();