本系列文章对应游戏代码已开源 Sinuous gamejavascript
上一节介绍了canvas的基础用法,了解了游戏开发所要用到的API。这篇文章开始,我将介绍怎么运用这些API来完成各类各样的游戏效果。这个过程更重要的是参透一些游戏开发的思路和想法,而不是仅仅知道怎么写代码来完成这个游戏。java
先用一张图来了解一下整个游戏的构成。git
Map表示整个背景地图,做用很简单,就是渲染黑色背景。
Player 表示玩家粒子,它尾巴中带有生命点,咱们用Life类来表示。
Enemy为红色的敌人粒子,由于技能粒子和Enemy粒子具备不少共性,因此Skill粒子继承自Enemy粒子。
粒子之间撞击后有爆炸效果,用Paticle来表示爆炸粒子。github
简单来讲,游戏就是一帧一帧图像的叠加播放,并经过捕获用户反馈来实现游戏中的人机交互。web
图像的逐帧播放能够类比为放映电影,经过在荧幕上连续投放图像来产生动做的效果。canvas
首先要建立这样一个荧幕, 并设置银幕的大小。segmentfault
//index.js const canvas = document.getElementById('world'); canvas.width = window.innerWidth > 1000 ? 1000 : window.innerWidth; canvas.height = window.innerHeight;
在游戏中,荧幕对应一个地图,咱们将这个地图抽象为一个类,并提供基本的渲染方法。浏览器
//Map.js /** * 地图类 */ class Map { init(options) { this.canvas = options.canvas; this.ctx = this.canvas.getContext('2d'); this.width = options.width; this.height = options.height; } clear() { this.ctx.clearRect(0, 0, this.width, this.height); } render() { this.clear(); this.ctx.fillStyle = "black"; this.ctx.fillRect(0, 0, this.width, this.height); } } export default new Map();
在入口处初始化地图函数
map.init({ canvas, width: window.innerWidth > 1000 ? 1000 : window.innerWidth, height: window.innerHeight });
荧幕准备好后,怎么放映图像,对应于游戏中的放映机是什么呢?动画
想一想在js中用于定时执行的方法有哪些,setInterval, setTimeout, requestAnimationFrame?
setInterval这个方法在游戏中是不能用的。因为js是单线程,setInterval开启的定时循环间隔会受到CPU使用状况的影响,同时电脑对setInterval的最短间隔也有不一样的要求。因为游戏对帧率的要求比较高,因此在游戏中应该避免使用setInterval来执行定时任务。因为没法把握每帧执行的具体时间,setTimeout也有会遇到相似的问题。
懂的人已经懂了,现代的H5游戏开发都是经过requestAnimationFrame来执行循环播放的。它的优点就是能根据浏览器的实时渲染帧率来执行函数,使的动画播放比较流畅。而不会由于函数的执行时间跟定时器时间不一样致使的播放卡顿现象。
通常requestAnimationFrame每帧的绘制时间是1000/60 ms。也就是每秒能绘制60帧。好就好在时间不须要咱们本身设置,而是浏览器的内在机制。在不一样的浏览器中方法名会有所不一样,咱们经过下面的方法来定义一个requestAnimationFrame函数
const raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); //每帧1000/60ms };
有个这个方法,咱们若有神助。只须要在一个动画方法中使用raf调用自身方法。就能实现循环调用的功能,而且如丝般顺滑。使用以下:
(function animate() { map.render(); raf(animate); })();
这样就会不断调用map的render方法,实现逐帧播放。只不过map的render方法只是把画布涂黑,因此看起来并无什么变化。
咱们的游戏中有玩家粒子,敌人粒子,还有技能粒子,撞击爆破等效果。咱们的游戏就是不断地往animate这个方法中添加内容,在每一帧中渲染多个不一样东西,看起来就是整个游戏画面了。咱们能够想象一下将来啊animate方法是这样的。
(function animate() { map.render(); player.render(); enemy.render(); skill.render(); effect.render(); raf(animate); })();
咱们须要扩展player, enemy...等等的render方法。让它们表现出不一样的效果。
这样渲染出来的画面仍是死的,怎样让每一帧渲染出来的图像有所不一样,实现动画的效果呢?
在每一个物体类中,都有一个update方法,该方法用于改变物体的位移形状等,因此每一帧渲染出来的画面都会不同。
//经过update方法来控制物体位移或形态变化 update() { this.x += 1; this.y += 1; } render() { cxt.fillRect(this.x, this.y, 10, 10); }
在animate中,咱们须要在每次render后调用update方法
(function animate() { map.render(); player.render(); player.update(); raf(animate); })();
这样,借助于游戏的发条,player就动起来了!咱们前面所过,游戏就是逐帧播放和人机交互。那怎样来处理玩家反馈呢?
在PC和手机中的所谓玩家反馈一般是鼠标的点击滑动以及手势等动做。经过监听鼠标或手势事件来改变物体的属性,达到控制物体变化的目的。例如让player跟随鼠标移动。
window.addEventListener('mousemove', (e) => { self.x = e.clientX; self.y = e.clientY; });
达到的效果跟update方法本质上是一致的。
至此整个游戏基本原理已经讲得差很少了,下一节要讲的是如何建立各类粒子,还有player那条会动的尾巴。敬请期待《从零开始开发一款H5小游戏(三) 攻守阵营,赋予粒子新的生命》