用原生js+html写一个像素鸟游戏

前言: 用html+js+css写一个面向对象板的像素鸟游戏

看一下效果把:

在这里插入图片描述

游戏需引用的图片:(右键保存图片)

bird.png
bird.png
land.png
land.png
pipeDown.png
pipeDown.png
pipeUp.png
pipeUp.png
sky.png
sky.png

看一下全部的代码把:

HTML代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>像素鸟</title>
    <link rel="stylesheet" href="index.css">
</head>

<body>
    <!-- 这是游戏的容器 -->
    <div id="game">
            <!-- 天空容器 -->
            <div class="sky"></div>
            <!-- 大地容器 -->
            <div class="land"></div>
            <!-- 小鸟容器 -->
            <div class="bird"></div>
            <!-- 柱子的容器,后期自动生成的,这里只是为了调节样式 -->
            <!-- <div class="pipeup"></div>
        <div class="pipedown"></div> -->
        </div>
    <p class="msg">回车键开始/暂停,空格键跳跃</p>
    <script src="./index.js"></script>
</body>

</html>
CSS代码
#game {
    width: 800px;
    height: 600px;
    border: 2px solid;
    margin: 0 auto;
    position: relative;
    overflow: hidden;
}

#game .sky {
    width: 1600px;
    height: 600px;
    background: url('./img/sky.png');
    position: absolute;
    left: 0;
    top: 0;
}

#game .land {
    width: 1600px;
    height: 112px;
    background: url('./img/land.png');
    position: absolute;
    left: 0;
    bottom: 0;
}

#game .bird {
    width: 33px;
    height: 26px;
    background: url('./img/bird.png');
    position: absolute;
    left: 200px;
    top: 150px;
    z-index: 1;
    background-position: -8px -10px;
}

#game .pipeup {
    background: url("img/pipeDown.png") no-repeat left bottom;
    width: 52px;
    height: 200px;
    position: absolute;
    top: 0;
    right: 0;
}

#game .pipedown {
    background: url("img/pipeUp.png") no-repeat;
    width: 52px;
    height: 200px;
    position: absolute;
    bottom: 112px;
    right: 0;
}

.msg {
    text-align: center;
}
JS代码

let skyBg, landBg, bird, game, pipes;

// 考虑到计时器的使用频率非常的高,所以专门封装一个函数
// 该函数返回一个对象,对象包含两个属性
// start:创建一个计时器   stop:停止计时器
// duration:计时器每隔多少毫秒执行
// callback:每隔duration毫秒执行callback函数
// thisObj:为了得到正确的this指向
let getTimer = function (duration, thisObj, callback) {
    var timer = null;
    return {
        start: function () {
            if (!timer) {
                timer = setInterval(function () {
                    callback.bind(thisObj)();
                }, duration);
            }
        },
        stop: function () {
            if (timer) {
                clearInterval(timer);
                timer = null;
            }
        }
    };
}

// 系统对象,统一管理其他对象的开始和结束
game = {
    paused : true,  // 当前游戏是否暂停
    isGmaeOver : false, // 当前游戏是否结束
    dom : document.querySelector('#game'),
    start : function(){
        skyBg.timer.start();
        landBg.timer.start();
        bird.wingTimer.start();
        bird.dropTimer.start();
        pipes.produceTimer.start();
        pipes.moveTimer.start();
    },
    stop : function(){
        skyBg.timer.stop();
        landBg.timer.stop();
        bird.wingTimer.stop();
        bird.dropTimer.stop();
        pipes.produceTimer.stop();
        pipes.moveTimer.stop();
    },
    // 该方法用于判断游戏是否结束
    gameOver : function(){
        // 游戏有两种情况都会导致游戏结束:1.  小鸟落地  2. 小鸟碰撞柱子
        // 1. 小鸟落地
        if(bird.top === 462){
            console.log('游戏结束');

            this.isGmaeOver = true;
            this.stop();
        }
        // 2. 小鸟是否碰撞到柱子(需要检测碰撞)
        // 小鸟中心点
        let bx = bird.left + (bird.width/2);
        let by = bird.top + (bird.height/2);
        // 柱子中心点
        for(let i=0;i<pipes.all.length;i++){
            let p = pipes.all[i]; // 当前的柱子
            // 获取当前柱子的中心点
            let px = p.left + (p.width/2);
            let py = p.top + (p.height/2);
            // 判断是否碰撞
            if(Math.abs(bx - px) < (p.width + bird.width) / 2 && 
                Math.abs(by - py) < (p.height + bird.height) / 2){
                console.log('游戏结束');
                this.isGmaeOver = true;
                this.stop();
            }

           
            
        }
    }
}

// 天空对象
skyBg = {
    left: 0,
    dom: document.querySelector('#game .sky'),
    // 该方法用于重新更新天空的 left 值
    show: function () {
        this.dom.style.left = this.left + 'px'
    }
}
skyBg.timer = getTimer(30, skyBg, function () {
    this.left -= 1;
    if (this.left === -800) {
        this.left = 0;
    }
    this.show();
})


// 大地对象
landBg = {
    left: 0,
    dom: document.querySelector('#game .land'),
    show() {
        this.dom.style.left = this.left + 'px'
    }
}
landBg.timer = getTimer(30, landBg, function () {
    this.left -= 2
    if (this.left === -800) {
        this.left = 0;
    }
    this.show();
})


// 小鸟对象
bird = {
    width : 33,
    height : 26,
    top : 150,
    left : 200,
    dom : document.querySelector('#game .bird'),
    wingIndex : 0, // 该属性用于记录当前小鸟的背景图片
    speed : 0, // 小鸟往下面掉的速度
    a : 0.005, // 加速度
    // 显示小鸟的方法:统一在 show 方法中显示小鸟的最终状态
    show : function(){
        // 根据图片的索引,来设置当前小鸟背景图的位置
        if(this.wingIndex === 0){
            this.dom.style.backgroundPosition = '-8px -10px';
        } else if(this.wingIndex === 1){
            this.dom.style.backgroundPosition = '-60px -10px';
        } else {
            this.dom.style.backgroundPosition = '-113px -10px';
        }
        // 设置小鸟的 top 值
        this.dom.style.top = this.top + 'px';
    },
    // 设置小鸟的 top 值
    setTop(newTop){
        if(newTop < 0){
            newTop = 0;
        }
        if(newTop > 462){
            newTop = 462;
        }
        this.top = newTop;
    },
    jump(){
        this.speed = -0.5;
    }
}
// 让小鸟不停的扇动翅膀的计时器
bird.wingTimer = getTimer(100,bird,function(){
    // 这里面主要要做的事儿:修改 wingIndex,然后调用 show
    this.wingIndex = (this.wingIndex + 1) % 3;
    this.show();
});
bird.dropTimer = getTimer(16,bird,function(){
    // 主要要做的事儿:改变高度,然后调用 setTop 方法以及 show 方法
    // 小鸟做的是匀加速运动
    // s = vt + 1/2 * a * t * t
    // 如何获取匀加速的末速度:v = v0 + at
    let s = this.speed * 16 + 0.5 * this.a * 16 * 16;
    this.speed = this.speed + this.a * 16;
    this.setTop(this.top + s);
    this.show();
});

// 上下的管道
pipes = {
    width: 52,
    getRandom : function(min,max){
        return Math.floor(Math.random() * (max - min) + min);
    },
    all : [],  // 用于存放所有的柱子
    // 创建柱子的方法
    createPipe() {
        let minHeight = 60, // 柱子最小的高度
            gap = 150; // 中间的间隙
            maxHeight = 488 - gap - minHeight;
        // 接下来确定一组柱子的高度
        let h1 = this.getRandom(minHeight,maxHeight),
            h2 = 488 - gap - h1;
        // 接下来根据这两个高度来创建柱子
        // 上面的柱子
        let div1 = document.createElement("div");
        div1.className = "pipeup";
        div1.style.height = h1 + "px";
        div1.style.left = "800px";
        game.dom.appendChild(div1);
        this.all.push({
            dom : div1,
            height : h1,
            width : this.width,
            top : 0,
            left : 800
        });
        // 下面的柱子
        let div2 = document.createElement("div");
        div2.className = "pipedown";
        div2.style.height = h2 + "px";
        div2.style.left = "800px";
        game.dom.appendChild(div2);
        this.all.push({
            dom : div2,
            height : h2,
            width : this.width,
            top : h1 + gap,
            left : 800
        });
    },
}
// 创建柱子
pipes.produceTimer = getTimer(2500,pipes,function(){
    this.createPipe();
})

// 移动柱子
pipes.moveTimer = getTimer(30,pipes,function(){
    // 因为要移动所有的柱子   && 对游戏进行积分
    for(let i=0;i<this.all.length;i++){
        let p = this.all[i]; // 得到当前的柱子
        p.left -= 2;
        if(p.left < -p.width){
            p.dom.remove();
            this.all.splice(i,1);
            i--;
        } else {
            p.dom.style.left = p.left + 'px';
        }

        //判断柱子是否过了小鸟,若过了则说明小鸟过了一根柱子
        if(p.left<=(bird.left-pipes.width)){
            console.log("+1");
            
        }

    }
   





    game.gameOver(); // 每次柱子移动后,都需要判断游戏是否结束
});



document.documentElement.onkeydown = function(e){
    if(e.key === ' '){
        bird.jump();
    }
    if(e.key === 'Enter'){
        // 按下回车键后,有三种状态(游戏运行中,游戏暂停中,游戏已结束)
        if(game.isGmaeOver){
            location.reload();
        }
        if(game.paused){
            game.start();
            game.paused = !game.paused
        } else {
            game.stop();
            game.paused = !game.paused
        }

    }
}

注:鸟的下落速度可能比较快,(可通过修改下图位置的参数更改)有些其它功能还没实现,可自行添加
在这里插入图片描述或直接下载源码CSDN下载:

下载后可以在webstrom中打开运行。 1. underscore中random产生随机数、without从数组中移除数据 2. 在游戏开发的时候,应该始终用面向对象的思想来做。有一个游戏管控大类(Game)。 3. 用一个json文件,保存我们需要的图片数据,方便在内存中创建每一张图片对象。 4. canvas的宽度和高度不能固定,根据电脑动态设置。 5. 使用ES6语法创建类。 6. FPS (每秒传输帧数(Frames Per Second)) 刷新率FNO 记录当前走过总帧数 7. 在每一次canvas绘制之前,都要进行清屏操作。ctx.clearRect(0, 0, canvas.width, canvas.height); 8. 资源加载。通过AJAX加载资源。静态资源类StaticResourceUtil.js用来加载本地图片。 9. 图片绘制,背景类BackGround.js,用来绘制背景。 通过图片的移动实现游戏的移动。 图片的移动即不断改变图片的X坐标,不断绘制。 图片的无缝移动:绘制图片的两倍,当全部图片宽度走完的时候,将图片X重新改为0. 背景图的速度设置,事物离得越近速度越快,越远速度越慢。 10. 取整、字符串转数字:parseInt(txt); 11. 管道绘制,管道类Pipe.js,用来绘制管道。 管道的高度、位置要随机生成。 但要有最小高度与最大高度。 需要一个管道数组来保存所有的管道。 当管道移出画布,需要将管道从管道数组中移除。 12. 用gameEnd来标志游戏是否结束。 13. 鸟的绘制,鸟类bird.js,用来绘制鸟。 根据帧率不断煽动翅膀。 14. 模拟重力加速度,利用dY,不断更新鸟的y值。导致鸟的下落越来越快。 15. 鸟的旋转。利用save和restore保存和恢复画布的状态。 利用translate旋转画布的x、y轴,旋转鸟恢复。 16. 鼠标点击事件,在鸟的上升过程中,固定值模拟阻力。 17. 边界检测。上边界ycanvas.height - 地板高度 - 鸟高度 18. 碰撞检测。利用管道进行检测。首先判断鸟有没有进到管道竖着的区域,再判断鸟是在空白天空区域还是管道区域,从而进行检测。 19. 小鸟死亡,在死亡的地方绘制鲜血。鲜血撒完游戏结束。 20. 分数类。根据几位判断分数显示基准线。判断小鸟是否飞到管道竖着的部分,如果飞过,则分数加一。 21. 求一个数字有几位。num.toString().length 22. 游戏初始化。设置背景及图片,点击开始后div隐藏。
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

acqui~Zhang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值