这两年,随着移动互联网的发展,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.onload=function(){ var zjb=new hero({ image:img, x:gettruesize(250), y:gettruesize(56), gettruesize(50), height:gettruesize(50), source104, 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), gettruesize(50), height:gettruesize(50), source104, 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来实现(不过我没有试过~~,当时做项目的时候只是结束的时候去保存了一下),游戏结束时提示 分享页面或者在玩一次即可。