首先准备好各类空文件 index.js css html 和图片
图片是下面这些,如果没有的可在这里下载
2 开发开始
好了,基础准备工作完毕,开发开始,
首先,先把天空,大地,小鸟的盒子准备好,并为其添加样式
需要注意的是,天空和大地,都是宽度200%,这是因为我们使用绝对定位,让图片往左移来模拟移动,所以不能设置100%
效果如下
3.给小鸟设置飞行状态
给小鸟添加飞行状态的css,并给小鸟设置初始飞行状态和初始位置
4. 添加水管
之后就是添加水管了,注意下面的水管应该加上地面的高度也就是112px,这样才能正常的显示,不然就穿出地表了
以上,使用css 和html静态页面布局就完成了,接下来是使用js来控制行为了
创建js对象
因为标题说的是使用面向对象的方式来写这个游戏,
为了不做标题单,所以我先开始创建对象
,根据上面写的那些可以很轻易地得出
这个游戏拥有的对象为
鸟
天空
大地
水管
然后再仔细观察,该如何来判断小鸟是不是装上了水管呢?,小游戏,自然是把他们看做是一个个的矩形(这也是为什么我一直留着边框)
只需要js判断矩形重合了,那就是撞到了
所以在这里可以抽象出他们的共同父类,矩形,
所以这里新建一个class.js用来做对象
然后就需要想一想,这个类需要有什么成员
- 小鸟,水管,大地,天空,都是要移动的,所以这个类要有移动这个成员
- 宽度,高度,这是基本的
- 小鸟水管在地图的坐标
- 既然他们都能移动,那就有速度,速度,这是2d的那就得分为,横向速度,纵向速度
于是就有了下面这个基类
class Rectangle {
constructor(width, height, left, top, xSpeed, ySpeed, dom) {
this.width = width;
this.height = height;
this.left = left;
this.top = top;
this.xSpeed = xSpeed;
this.ySpeed = ySpeed;
this.dom = dom;
}
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";
}
move(duration) {
const xDis = this.xSpeed * duration;
const yDis = this.ySpeed * duration;
const newList = this.left + xDis
const newTop = this.top + yDis;
this.render()
}
}
父类到这就定义完了,然后就是子类继承这个父类,
因为这些共有的类该有的属性大多都有了,比如移动啊,渲染啊,计算坐标之类的.所以子类我们可以写的非常简单,如下
天空&大地类定义
// 天空对象,继承自矩形
class Sky extends Rectangle {
constructor() {
const dom = document.querySelector(".sky");
const styles = getComputedStyle(dom);
super(parseFloat(styles.width), parseFloat(styles.height), 0, 0, -50, 0, dom);
}
// 天空每次运动到自身的一半,为避免超出(露馅)重新设置其left值
onMoving() {
if (this.left <= - this.width / 2) {
this.left = 0;
}
}
}
// 大地的对象,和天空类似
class Land extends Rectangle {
constructor(speed) {
const styles = getComputedStyle(landDom);
super(parseFloat(styles.width), parseFloat(styles.height), 0, parseFloat(styles.top), speed, 0, landDom);
}
onMoving() {
if (this.left <= - this.width / 2) {
this.left = 0;
}
}
}
小鸟对象
class Bird extends Rectangle {
constructor() {
const dom = document.querySelector(".bird");
const styles = getComputedStyle(dom);
super(parseFloat(styles.width), parseFloat(styles.height), parseFloat(styles.left), parseFloat(styles.top), 0, 0, dom);
this.maxY = gameHeight - this.height - landHeight;
this.timer = null;
this.curSwingStatus = 1;
this.g = 1500; // 每毫秒下降15像素
this.render();
}
// 飞翔的方法,用于设置鸟飞翔的动作
startFly() {
if (this.timer) return;
this.timer = setInterval(() => {
this.curSwingStatus = (this.curSwingStatus + 1) == 4 ? 1 : (this.curSwingStatus + 1);
this.render();
}, 100);
}
// 停止飞翔
stopFly() {
clearInterval(this.timer);
this.timer = null;
}
// 每一次渲染都要设置一下鸟的翅膀状态
render() {
this.dom.className = `bird swing${this.curSwingStatus}`;
super.render();
}
// 鸟不能超出游戏区域
onMoving() {
if (this.top < 0) {
this.top = 0;
}
else if (this.top > this.maxY) {
this.top = this.maxY;
}
}
// 鸟跳跃的方法
jump() {
this.ySpeed = -450;
}
// 鸟向下移动的方法
move(duration) {
super.move(duration);
this.ySpeed += this.g * duration;
}
}
水管类
// 管子对象
class Pipe extends Rectangle {
constructor(height, top, speed, dom) {
super(52, height, gameWidth, top, speed, 0, dom);
}
onMoving() {
if (this.left < -this.width) {
this.dom.remove();
}
}
}
function getRandomHeight(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
// 管子对的对象
class PipePair {
constructor(speed) {
this.spaceHeight = 150;
this.minHeight = 80;
this.maxHeight = landTop - this.minHeight - this.spaceHeight;
const upPipeHeight = getRandomHeight(this.minHeight, this.maxHeight);
const upPipeDom = document.createElement("div");
upPipeDom.className = "pipe up";
this.upPipe = new Pipe(upPipeHeight, 0, speed, upPipeDom);
const downPipeHeight = landTop - upPipeHeight - this.spaceHeight;
const downPipeDom = document.createElement("div");
downPipeDom.className = "pipe down";
const downPipeDomTop = landTop - downPipeHeight;
this.downPipe = new Pipe(downPipeHeight, downPipeDomTop, speed, downPipeDom);
gameDom.appendChild(upPipeDom);
gameDom.appendChild(downPipeDom);
}
// 管子对是否不可见(用于管子对工厂定期移除不可见的管子对)
get hidden() {
return this.upPipe.left <= -this.upPipe.width;
}
// 管子对移动的时候,需要上下两根管子一起东
move(duration) {
this.upPipe.move(duration);
this.downPipe.move(duration);
}
}
// 管子工厂
class PipePairFactory {
constructor(speed) {
this.speed = speed;
this.pipePairs = [];
this.newTimer = null;
this.moveTimer = null;
this.tick = 1500;
}
// 开始生产管子的方法
startNew() {
if (this.newTimer) return;
this.newTimer = setInterval(() => {
this.pipePairs.push(new PipePair(this.speed));
for (let o = 0; o < this.pipePairs.length; o++) {
const pipePair = this.pipePairs[o];
if (pipePair.hidden) {
this.pipePairs.splice(o, 1);
o--;
}
}
}, this.tick);
this.startRun();
}
// 管子对开始运动的方法
startRun() {
if (this.moveTimer) return;
this.moveTimer = setInterval(() => {
this.pipePairs.forEach(pipePair => {
pipePair.move(16 / 1000);
});
}, 16);
}
// 停止生产管子对的方法
stopNew() {
clearInterval(this.newTimer);
clearInterval(this.moveTimer);
this.newTimer = null;
this.moveTimer = null;
}
}
class Game {
constructor() {
const speed = -100;
this.sky = new Sky();
this.land = new Land(speed);
this.bird = new Bird();
this.pipePireFactory = new PipePairFactory(speed);
this.tick = 16;
this.speed = this.tick / 1000;
this.timer = null;
this.gameOver = false;
this.bindEvent();
}
isGameOver() {
if (this.bird.top == this.bird.maxY) {
return true;
}
// 获取在小鸟区域的柱子对
const pipePair = this.pipePireFactory.pipePairs.find(pipePair => {
const pipePairLeft = pipePair.upPipe.left;
const pipePairRight = pipePair.upPipe.left + pipePair.upPipe.width;
return pipePairLeft <= this.bird.left && this.bird.left <= pipePairRight;
});
return pipePair && (this.isConflict(this.bird, pipePair.upPipe) || this.isConflict(this.bird, pipePair.downPipe));
}
isConflict(rec1, rec2) {
// 碰撞检测:两矩形中心点距离小于两矩形长度的一半
const centerX1 = rec1.left + rec1.width / 2;
const centerY1 = rec1.top + rec1.height / 2;
const centerX2 = rec2.left + rec2.width / 2;
const centerY2 = rec2.top + rec2.height / 2;
const disX = Math.abs(centerX1 - centerX2);
const disY = Math.abs(centerY1 - centerY2);
return disX < (rec1.width + rec2.width) / 2 &&
disY < (rec1.height + rec2.height) / 2;
}
start() {
if (this.timer) return;
if (this.gameOver) {
location.reload();
return;
}
this.bird.startFly();
this.pipePireFactory.startNew();
this.timer = setInterval(() => {
this.sky.move(this.speed);
this.land.move(this.speed);
this.bird.move(this.speed);
if (this.isGameOver()) {
this.stop();
this.gameOver = true;
}
}, this.tick);
}
stop() {
clearInterval(this.timer);
this.timer = null;
this.bird.stopFly();
this.pipePireFactory.stopNew();
}
bindEvent() {
window.addEventListener("keyup", e => {
if (e.key == "Enter") {
if (this.timer) {
this.stop();
}
else {
this.start();
}
}
else if (e.key == " ") {
this.bird.jump();
}
});
}
}
var game = new Game();
game.start();
可以运行了