18

本系列文章对应游戏代码已开源 Sinuous game

到这里我们已经讲了游戏的整体设计和实现。一个游戏要完整,还需要给它制定一个评分机制,它是整个游戏的关键所在。就好比一部电影,特效再好看,如果剧情狗血,那也是一部烂片。

相信大家都玩过一些简单但很吸引人的小游戏。比如很久以前微信上的打飞机,围住神经猫,还有前段时间大火的slither.io。他们都简单易玩,但却能让人肾上腺素飙升,百玩不腻。

所以一款好玩的小游戏必须具备了这样的特点,简单易玩,却能给人制造紧张感,有时还能利用一些攀比心理。本游戏也基本具备了这样的特点。

计分实现

游戏以秒数作为计分,随着时间的增加,Enemy粒子的运动速度会越来越快,躲避难度也就越来越大。游戏中的计秒实现比较简单,就是用setTimeout来实现,这里不使用setInterval,原因在第一章已经大致讲过了,就是考虑到准确性的问题。

//index.js
function initTimer() {
    holdingTime = 0;
    holdingLevel = 0;
    clearTimeout(timer);
    let time = function() {
        timer = setTimeout(function() {
            holdingTime = +timeEle.innerText + 1;
            timeEle.innerText = holdingTime;
            //每隔10秒加速一次
            if (holdingTime % 10 === 0) {
                holdingLevel++;
                levelEle.innerText = holdingLevel;
                for (let i = 0; i < enemys.length; i++) {
                    //Enemy粒子速度增加
                    enemys[i].speedUp();
                }
            }
            clearTimeout(timer);
            time();
        }, 1000)
    };
    time();
}

每隔10s, Enemy粒子的速度增加一次,Enemy中封装了speedUp方法。

//Enemy.js
speedUp(speed) {
   this.speed += speed || 0.2;
}

在技能粒子中,有一个护盾粒子。吃了护盾后,撞击Enemy粒子能增加分数。实现起来也很简单,直接修改计分板上的分数就行了。

//Player.js
let score = document.getElementById('time').innerText;
document.getElementById('time').innerText = (+score + REDSCORE);

粒子的初始生命值有三条,每次撞击到Enemy粒子都会减少一条,而如果撞击到视界的边界则会直接狗带。这里我们需要增加一个游戏结束的画面。给出最后的分数。

roadmap.path

开始和结束画面都是通过DOM实现的,这部分比较简单,就不做具体介绍了。

其实在游戏的评分机制上还可以做很多改进,比如增加排行榜,或记录自己的最优成绩,并可分享到朋友圈等。这部分可以极大增加游戏的热度。 读者可以自己展开想象,对玩法进行扩展。

预加载

当我在微信打开游戏的时候,发现开始画面和结束画面的图片加载很慢。导致DOM结构出来了,图片却迟迟没看到,没法给玩家准确的提示。所以需要增加一个图片预加载的功能。当然这也是每一个网页游戏框架必备的功能。

这部分功能直接参考了阿里的一个游戏框架Hilo,并把它抽象到loader.js。读者可自行查阅实现细节。

抽象后在入口处预加载所需的图片:

//index.js
let loader = new Loader();
let source = [
    {src: 'assets/images/number.png'},
    {src: 'assets/images/over.png'},
    {src: 'assets/images/sprites.png'}
];
loader.load(source, function() {
    start(); //开始游戏
});

预加载的时候还需要有个提示画面来告知加载进度。

进度条的实现也独立成一个文件loading.js,并暴露一个外部API给游戏使用。

我们还需要将预加载插件和进度条结合起来,每个图片加载完成后,loader会触发一次load事件,用一个计数器统计加载的图片数,除以总数得到一个进度比例。然后将这个比例barRatio传给进度条。让其渲染出相应的进度。

//根据加载进度渲染进度条
let loaded = 0;
loader.on('load', e => {
    ++loaded;
    barRatio = loaded / source.length;
});

//进度条渲染
(function loading() {
    drawLoading(barRatio);
    if (!loadingFinish) {
        raf(loading);    
    }
})();

需要注意的一点是,进度条是通过canvas画布实现的。所以进度canvas的draw方法是在不停运行的。如果每张图片加载完的时候才改变进度条的位置,就会造成进度跳跃式地前进,无法连续顺滑加载的效果。
这个逻辑在loading中通过一个判断来解决。

//loading.js
let currentBarWidth = bar.total * ratio;
if (bar.width < currentBarWidth) {
    bar.width += 2;
}

保证进度条每次增加只能是2。而不是直接让bar.width = currentBarWidth;

结语

至此整个游戏的开发就介绍到这了,主要还是讲游戏的实现思路。 游戏中还是有挺多细节处理的,这些真的要亲自动手写一下才能了解。

本教程的初衷就是想让读者能对H5游戏开发有个宏观的了解,知道怎么入手。想起几周前自己要写这个游戏的时候还无从下手,如今也完成开发并写了几篇总结,算是有所沉淀。

其实H5游戏开发远比这个复杂,本游戏只是基于画笔实现,还没有涉及到图片的绘制,坐标轴转换等等。还有很多了要学习的东西啊。当然这只是自己一时的兴趣尝试,等什么时候心血来潮了,说不定再写一个系列呢。


fwon
2.5k 声望259 粉丝