这两年,随着移动互联网的发展,h5小游戏也大火了一把。这里我就来讲讲实现一个躲水果的小游戏。如截图所示
再附上demo二维码~
目前游戏的套路,基本上是先载入游戏,然后触发游戏开始,游戏结束时记录游戏分数,诱导你转发盆友圈炫耀,或者再来一次。
这里我们可以定义一个game_control 对象来控制这一系列流程,同时我们还需要一个人物对象,3个水果对象。
我们可以定义一个基础原型对象ship作为类,可以记录位置,大小,x,y轴的移动速度,相应的图片,然后人物对象和水果对象就是它的几个实例啦。然后通过canvas的drawImage方法将它们画出在画布中。
function ship(options){
if (options) {
var width=options.width,
height=options.height;
this.x=options.x;
this.y=options.y;
this.width=width;
this.height=height;
this.first_x=options.x;
this.first_y=options.y;
this.speedx=options.speedx;
this.speedy=options.speedy;
this.csspeedx=options.speedx;
this.csspeedy=options.speedy;
this.xDirection=options.xDirection||1;//x轴移动方向
this.yDirection=options.yDirection||1;//y轴移动方向
var canvasOffscreen = document.createElement('canvas');
canvasOffscreen.width =width;
canvasOffscreen.height =height;
canvasOffscreen.getContext('2d').drawImage(options.image, options.sourcex, options.sourcey, options.sourcewidth, options.sourceheight, 0, 0, width, height);
this.canvasOffscreen=canvasOffscreen;
this.init();
}
}
ship.prototype={
init:function(){
},
reset:function(){
},
draw:function(ctx){
//let canvasOffscreen=this.canvasOffscreen;
ctx.drawImage(this.canvasOffscreen, this.x, this.y);
},
move:function(modifier){
this.x+=this.xDirection*this.speedx * modifier;
this.y+=this.yDirection*this.speedy * modifier;
if(this.x>winwidth-this.width){
this.x=winwidth-this.width;
this.xDirection=-1;
}else if(this.x<0){
this.x=0;
this.xDirection=1
}
if(this.y>winheight-this.height){
this.y=winheight-this.height;
this.yDirection=-1;
}else if(this.y<0){
this.y=0;
this.yDirection=1;
}
}
}
这个对象有个移动方法需要注意一下,我们需要判断物体到边界时,物体的移动方向需要转变(也就是将yDirection或者xDirection反向)。
在这里我们可以将3个水果图片,和人物图片做成雪碧图。然后先将他们用canvas画出来,然后游戏进行中的每一帧将这些canvas再画出来,这样效率比每次都从雪碧图截取要高。
var canvasOffscreen = document.createElement('canvas');
canvasOffscreen.width = dw;
canvasOffscreen.height = dh;
canvasOffscreen.getContext('2d').drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh);
// 在绘制每一帧的时候,绘制这个图形
context.drawImage(canvasOffscreen, x, y);
然后通过game_control中的draw方法画出
draw:function(obj,ctx){
var ctx=this.ctx,
monsters=this.monsters;
var now=Date.now(),
def=now-this.then;
this.clear();
for(var i=0,len=monsters.length;i<len;i++){
if (this.then) {
monsters[i].move(def/1000);
}
monsters[i].draw(ctx);
}
if (this.then) {
this.check();
this.then = now;
}
this.objs.draw(ctx);
},
game_control 对象定义几个方法,init,start,draw,end,reset等方法。
init方法主要是初始化游戏的,主要处理图片加载,图片加载完后,几个对象的初始化。
init:function(){
var canvas=document.getElementById('game-canvas'),
self=this,
ctx=canvas.getContext('2d');
self.ctx=ctx;
canvas.width=winwidth;
canvas.height=winheight;
let img=new Image();
img.οnlοad=function(){
var zjb=new hero({
image:img,
x:gettruesize(250),
y:gettruesize(56),
width:gettruesize(50),
height:gettruesize(50),
sourcewidth:104,
sourceheight:104,
sourcex:0,
sourcey:0
});
for(var i=0;i<3;i++){
var x=60,y=110;
if(i==1){x=38,y=330;}
if(i==2){x=218,y=338;}
var monster=new ship({
image:img,
x:gettruesize(x),
y:gettruesize(y),
width:gettruesize(50),
height:gettruesize(50),
sourcewidth:104,
sourceheight:104,
speedx:gettruesize(getrandom(60,100)),
speedy:gettruesize(getrandom(60,100)),
sourcex:104*(i+1),
sourcey:0
});
self.monsters.push(monster);
}
self.objs=zjb;
self.draw();
self.bindmove(canvas,zjb);
}
img.src="all.png";
}
然后调用给bindmove方法给canvas注册手指触摸事件,如果手机触摸在人物对象这个位置,那么代表游戏开始,调用start方法。
bindmove:function(canvas,hero){
let self=this;
canvas.addEventListener('touchstart', function(e) {
var event = e||window.event,
csx = event.touches[0].clientX,
csy = event.touches[0].clientY,
nanshengcsx = hero.x,
nanshengcsy = hero.y;
if (csx <= hero.x + hero.width && csx >= hero.x && csy <= hero.y + hero.height && csy >= hero.y) {
if (!self.startstate) {
self.start();
timer = setInterval(function(){
self.draw();
}, 1);
}
document.addEventListener('touchmove', move,false);
function move(e){
e.preventDefault();
var event = e||window.event,
nowx = event.touches[0].clientX,
nowy = event.touches[0].clientY;
hero.x = nanshengcsx + nowx - csx;
hero.y = nanshengcsy + nowy - csy;
if(hero.x<0){
hero.x=0;
}else if(hero.x+hero.width>winwidth){
hero.x=winwidth-hero.width;
}
if(hero.y<0){
hero.y=0;
}else if(hero.y+hero.height>winheight){
hero.y=winheight-hero.height;
}
}
function moveend(e){
document.removeEventListener('touchend',moveend);
document.removeEventListener('touchmove',move);
}
document.addEventListener('touchend', moveend ,false);
}
},false);
}
start方法主要是实现每一帧的循环方法。
var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 20);
};
不断的去调用raf(fun)就可以啦,这里我直接用了setInterval方法
start:function(){
var self=this;
this.startstate=true;
this.then=Date.now();
this.starttime=this.then;
document.getElementById('tips').style.display="none";
timer = setInterval(function(){
self.draw();//在画布中画出
}, 1);
}
在循环方法中,通过手指的移动来计算 人物的x,y 轴的位置,通过时间 和 水果x,y 轴的速度计算出相应的位置,然后再进行一一比较,看是否发生触碰,同时,随着时间的推移,物体移动的速度慢慢新增,这里我们定了每5秒增加速度,这里我们将模型当作矩形(复杂模型我也不造怎么算~~),当发生触碰时,则判定游戏结束,将坚持了的时间输出。
check:function(){
var last=this.then-this.starttime;
var monsters=this.monsters;
var nansheng=this.objs;
if (this.monsterSum != Math.floor(last / 5000)){//如果时间经过5秒就增加速度
this.monsterSum ++;
for(var i=0;i<monsters.length;i++){
monsters[i].speedx+=60;
monsters[i].speedy+=60;
}
}
for(var i=0;i<monsters.length;i++){
var monster1=monsters[i];
if ((monster1.x - nansheng.width) <= nansheng.x && nansheng.x <= (monster1.x + monster1.width) && (monster1.y - nansheng.height) <= nansheng.y && nansheng.y <= (monster1.y + monster1.height)) {
this.end();
}
}
}
至于持续性的与后台的数据交互可以通过websocket来实现(不过我没有试过~~,当时做项目的时候只是结束的时候去保存了一下),游戏结束时提示 分享页面或者在玩一次即可。