最近除了做业务,也在尝试学习h5和移动端,在这个过程中,学到了很多,利用h5和canvas做了一个爱心鱼的小游戏。点这里去玩一下

PS: 貌似有点闪屏,亲测多刷新两下就好了==。代码在本地跑都不会闪,放到博客里就闪了,我也不知道为什么。。。回头我再看看是什么问题。

另外,我把代码放到github上了,博友们也可以直接down代码,不需要任何服务,本地就能跑起来。大家要是觉得还行,就给个star吧!源码地址点这里

首先截个图来看看界面效果:

下买我就做游戏的步骤来分享总结一下用到的h5API和一些常见的数学函数。(推荐你先去玩一玩游戏,才能更好的明白这些逻辑哟~)

序言

首先,一个游戏最重要的就是动画,怎么让元素动起来呢?先来看一句话:

元素的位置移动,就形成了动画。

一帧一帧的来渲染这个元素,而且这个元素每一帧的位置都不一样,我们的眼睛看到的就是动画了。OK,先来介绍requestAnimationFrame这个函数。

我们都知道,隔一段时间重新渲染,可以用到setTimeoutsetInterval这两个函数,那这里为什么不用呢?

我来简单举个例子吧:

  • setInterval(myFun, 1); 意思是隔一毫秒执行一个myFun函数,但是这样就有一个问题了,比如我myFun函数里面绘制的东西比较耗时,而1ms之内还没有完全绘制出来,但是这段代码强制1ms之后又开始绘制下一帧了,所以就会出现丢帧的问题,而如果时间设置太长,就会出现视觉卡顿的问题。
  • requestAnimationFrame(myFun); 如果我们这样写,又是什么意思呢?意思是根据一定的时间间隔,会自动执行myFun函数来进行绘制。这个“一定的时间间隔”就是根据浏览器的性能或者网速快慢来决定了,总之,它会保证你绘制完这一帧,才会绘制下一帧,保证性能的同时,也保证动画的流畅。

动画解决了,那么用什么来绘制每一帧的页面呢?这时就要用到h5的神奇——canvas了,所以canvas画布的API非常重要。

html文件

  1. <div class="page">
  2. <div class="content" id = "main">
  3. <canvas id = "canvas1" width="800" height="600">
  4. </canvas>
  5. <canvas id = "canvas2" width="800" height="600">
  6. </canvas>
  7. </div>
  8. </div>
  • 定义两个画布,分别在画布上绘制相应的物体;
  • canvas2 上绘制,背景、海葵、果实;
  • canvas1 上绘制,大鱼、小鱼、显示文字、圆圈特效;

js文件

  1. function init(){
  2. can1 = document.getElementById('canvas1'); //画布
  3. ctx1 = can1.getContext('2d'); //画笔
  4. can2 = document.getElementById('canvas2');
  5. ctx2 = can2.getContext('2d'); //下面的canvas
  6. }
  7. function gameloop(){
  8. requestAnimFrame(gameLoop);
  9. //绘制物体...
  10. }
  11. var requestAnimFrame = (function() {
  12. return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
  13. function(callback, element) {
  14. return window.setTimeout(callback, 1000 / 60);
  15. };
  16. })();
  • init函数初始化一些变量,比如海葵对象,大鱼、小鱼对象等等。
  • gameloop函数用于绘制每一帧的页面。下面所介绍的所有绘制函数都是在这里执行。
  • requestAnimFrame函数是为了兼容所有浏览器。

下面我们就开始绘制游戏中出现的东西,顺便看看都用到了哪些有趣的API函数。go!go!go!

绘制背景和海葵

背景是一张图,而海葵是一个类,它有x坐标,y坐标,个数等等属性,有初始化init和draw方法。

  1. drawImage(image, x, y, width, height)
  2. ctx2.save();
  3. ctx2.globalAlpha = 0.7;
  4. ctx2.lineWidth = 20;
  5. ctx2.lineCap = 'round';
  6. ctx2.strokeStyle = '#3b154e';
  7. ctx2.beginPath();
  8. ctx2.moveTo(this.rootx[i], canHei); //起始点
  9. ctx2.lineTo(this.rootx[i], canHei - 220,); //结束点 ctx2.stroke();
  10. ctx2.restore();
  • ctx2.drawImage(image, x, y, width, height) //x,y代表坐标,width和height代表宽高
  • ctx2.save(); //定义作用空间
  • ctx2.globalAlpha = 0.7; //定义线的透明度
  • ctx2.lineWidth = 20; // 宽度
  • ctx2.lineCap = ‘round’; // 圆角
  • ctx2.strokeStyle = '#3b154e'; //定义绘制线条的颜色
  • ctx2.beginPath(); //开始路径
  • ctx2.moveTo(x,y); //线的起点,x,y代表坐标(坐标原点在左上角)
  • ctx2.lineTo(x,y); // 线条从起点连接到这个点
  • ctx2.stroke(); // 开始绘制线条
  • ctx2.restore(); //作用空间结束

海葵产生果实

果实也是一个类,他的属性有:坐标、类型(黄色和蓝色)、大小、状态(显示还是隐藏)、速度(向上漂浮的速度)等等属性;他的方法有:初始化init、出生born和绘制draw。

draw方法:

  1. for(var i =0;i< this.num; i++){
  2. if(this.alive[i]){
  3. //find an ane, grow, fly up...
  4. if(this.size[i] <= 16){ //长大状态
  5. this.grow[i] = false;
  6. this.size[i] += this.speed[i] * diffframetime * 0.8;
  7. }else{ //已经长大,向上漂浮
  8. this.grow[i] = true;
  9. this.y[i] -= this.speed[i] * 5 * diffframetime;
  10. }
  11. var pic = this.orange;
  12. if(this.type[i] == 'blue') pic = this.blue;
  13. ctx2.drawImage(pic, this.x[i] - this.size[i] * 0.5, this.y[i] - this.size[i] * 0.5, this.size[i], this.size[i]);
  14. if(this.y[i] < 8){
  15. this.alive[i] = false;
  16. }
  17. }
  18. }

born方法:随机找到一个海葵的坐标,在海葵的坐标上出生一个果实。

绘制大鱼和小鱼

大鱼和小鱼都是一个类,它的属性有:坐标、旋转角度、尾巴摆动时间间隔、眨眼睛时间间隔、身体图片数组....等等

先把大鱼绘制出来,用canvas的drawImage方法。

比较难的是大鱼的动画,大鱼会随着鼠标移动而移动的动画,这里定义了两个函数:

  1. function lerpAngle(a, b, t) { //计算每一帧旋转的角度
  2. var d = b - a;
  3. if (d > Math.PI) d = d - 2 * Math.PI;
  4. if (d < -Math.PI) d = d + 2 * Math.PI;
  5. return a + d * t;
  6. }
  7. function lerpDistance(aim, cur, ratio) { //aim:目标 cur:当前 ratio:百分比 计算每一帧趋近的距离
  8. var delta = cur - aim;
  9. return aim + delta * ratio
  10. }
  11. this.momTailTimer += diffframetime;
  12. if(this.momTailTimer > 50){
  13. this.momTailIndex = (this.momTailIndex + 1) % 8; //根据时间间隔改变尾巴图片
  14. this.momTailTimer %= 50;
  15. }
  • lerpDistance 是计算每一帧大鱼趋紧到鼠标的距离。
  • lerpAngle 用来计算大鱼每一帧向鼠标旋转的角度。 定义这两个函数,让大鱼动起来比较平滑。

获得了一个角度之后,怎么让大鱼旋转起来呢?这里又需要用到几个API了。

  • ctx1.save(); //建议每次绘制都使用save和restore,可以避免定义样式,发生冲突。
  • ctx1.translate(this.x, this.y); //把原点变成(this.x , this.y);
  • ctx1.rotate(this.angle); //根据原点顺时针旋转一个角度

绘制小鱼跟大鱼是一样的,不做详述。但是需要注意的是绘制小鱼的时候有个判断,当小鱼的颜色变白的时候,游戏结束。

  1. this.babyBodyTimer += diffframetime;
  2. if(this.babyBodyTimer > 550){ //身体图片变化的计数器 > 550ms
  3. this.babyBodyIndex += 1; //身体图片变淡
  4. this.babyBodyTimer %= 550;
  5. scoreOb.strength = ((20 - this.babyBodyIndex)/2).toFixed(0);
  6. if(this.babyBodyIndex > 19){ //如果身体变成白色,game over;
  7. this.babyBodyIndex = 19;
  8. scoreOb.gameOver = true;
  9. can1.style.cursor = "pointer";
  10. }
  11. }

大鱼吃果实

大鱼吃果实是根据距离来判断定的,如果大鱼和果实的距离小于30,则让果实消失,并且出现白色圆环,并且分值有一定的变化。

  1. jzk.momEatFruit = function(){ //判断果实和大鱼之间的距离,小于30说明被吃掉
  2. for(var i = 0;i < fruitOb.num; i++ ){
  3. if(fruitOb.alive[i] && fruitOb.grow[i]){
  4. var len = calLength2(fruitOb.x[i], fruitOb.y[i], momOb.x, momOb.y);
  5. if(len < 30){
  6. fruitOb.dead(i); //如果距离小于30,则被吃掉
  7. waveOb.born(i); //吃掉的时候,产生圆圈
  8. scoreOb.fruitNum ++; //吃到的果实数量+1
  9. momOb.momBodyIndex = momOb.momBodyIndex == 7 ? momOb.momBodyIndex : (momOb.momBodyIndex + 1); //大鱼的身体颜色红
  10. if(fruitOb.type[i] == 'blue'){
  11. scoreOb.doubleNum ++; //吃到蓝色果实,倍数+1
  12. }
  13. }
  14. }
  15. }
  16. }

其中有一个calLength2函数,使用来计算两个点之间的距离的。

  1. function calLength2(x1, y1, x2, y2) { //计算两个点之间的距离,,, 先求平方和,再开平方
  2. return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
  3. }

大鱼吃到果实的时候,会产生一个白色的圆圈,这个效果怎么实现呢?

首先,我们定义一个waveObject类,它的属性有:坐标、数量、半径、使用状态。它的方法有:初始化、绘制和出生。

我们来看一下绘制圆圈的方法:

  1. for(var i = 0;i< this.num; i++){
  2. if(this.status[i]){ //如果圆圈是使用状态,则绘制圆圈
  3. this.r[i] += diffframetime * 0.04;
  4. if(this.r[i] > 60){
  5. this.status[i] = false;
  6. return false;
  7. }
  8. var alpha = 1 - this.r[i] / 60;
  9. ctx1.strokeStyle = "rgba(255, 255, 255, "+ alpha +")";
  10. ctx1.beginPath();
  11. ctx1.arc(this.x[i], this.y[i], this.r[i], 0, 2 * Math.PI); //画圆,
  12. ctx1.stroke();
  13. }
  14. }

一帧一帧的画每一个圆,圆的半径逐渐增大,透明度逐渐减小,直到半径大于60的时候,把状态设为false,让其回归物体池中。

这里又用到了一个新的方法:ctx1.arc(x,y,r,deg); //画圆,x,y是中心圆点,r是半径,deg是角度,360度就是一个整圆。

再来看一下出生的方法:

  1. for(var i = 0; i< this.num; i++){
  2. if(!this.status[i]){
  3. this.status[i] = true; //把圆圈状态设为使用状态
  4. this.x[i] = fruitOb.x[index];
  5. this.y[i] = fruitOb.y[index];
  6. this.r[i] = 10;
  7. return false; //找到一个未使用的圆圈,就结束。
  8. }
  9. }

圆圈出生的坐标就是被吃果实的坐标。

大鱼喂小鱼

大鱼喂小鱼同上,不再详述,这里喂小鱼之后,大鱼身体变白,小鱼随果实数量相应增多,另外需要注意的是,此时产生圆圈的坐标是小鱼的坐标。

游戏分值计算

定义一个数据类,它的属性有:吃到的果实数量、倍数、总分、力量值、游戏状态(是否结束)等;方法有:初始化、绘制分数。

这里我们需要在画布上绘制文字,又用到了新的API:

  • ctx1.save();
  • ctx1.font = '40px verdana'; 定义文字的大小和字体;
  • ctx1.shadowBlur = 10; 定义文字的阴影宽度
  • ctx1.shadowColor = "white"; 定义文字阴影的颜色;
  • ctx1.fillStyle = "rgba(255, 255, 255, "+ this.alpha +")"; 定义文字的颜色(rgba,a代表透明度)
  • ctx1.fillText("GAME OVER", canWid * 0.5, canHei * 0.5 - 25); 绘制文字,第一个参数是字符串,支持表达式,后两个参数是坐标值。
  • ctx1.font = '25px verdana';
  • ctx1.fillText("CLICK TO RESTART", canWid * 0.5, canHei * 0.5 + 25);
  • ctx1.restore();

总结

好啦,整个游戏的制作过程就分享完了,做的过程中有遇到过很多问题,不过都一一解决了,加深了很多以前模糊的概念,也学到了很多新的知识,比如使用rgba()来一起控制颜色和透明度,以前还真没用到过。

这个游戏本身功能比较简单,但是动画还算比较酷炫。这也算是一个比较基本的动画基础框架了,而比较不容易理解的地方也有很多,比如求趋近的角度函数lerpAngle(a,b,c),还有Math.atan2()这个函数,等等。

欢迎大家提出bug或者改进建议~~~

ps: 本实例是从慕课网上学习到的。然后自己跟着老师从头做了一遍,并且优化了很多代码,新加了一些功能。我觉得只要自己能画出来,就是自己的作品。

html5+Canvas实现酷炫的小游戏的更多相关文章

  1. HTML5 Canvas核心技术:图形、动画与游戏开发 PDF扫描版​

    HTML5 Canvas核心技术:图形.动画与游戏开发 内容简介: <HTML5 Canvas核心技术:图形.动画与游戏开发>中,畅销书作家David Geary(基瑞)先生以实用的范例程 ...

  2. canvas写个简单的小游戏

    之前在HTML5 Canvas属性和方法汇总一文中,介绍过Canvas的各种属性以及方法的说明,并列举了自己写的一些Canvas demo,接下来开始写一个简单的小游戏吧,有多简单,这么说吧,代码不到 ...

  3. 用html5 canvas和JS写个数独游戏

    为啥要写这个游戏? 因为我儿子二年级数字下册最后一章讲到了数独.他想玩儿. 因为我也想玩有提示功能的数独. 因为我也正想决定要把HTML5和JS搞搞熟.熟悉一个编程平台,最好的办法,就是了解其原理与思 ...

  4. html5面向对象做一个贪吃蛇小游戏

    canvas加面向对象方式的贪吃蛇 2016-08-25 这个小游戏可以增加对面向对象的理解,可以加强js逻辑能力,总之认真自己敲一两遍收获还是不少啊!!适合刚学canvas的同学练习!! 废话不多说 ...

  5. canvas实现酷炫气泡效果

    canvas实现动画主要是靠设置定时器(setinterval())和定时清除画布里的元素实现,canvas动画上手很简单,今天可以自己动手来实现一个酷炫气泡效果. 气泡炸裂效果(类似水面波纹) 代码 ...

  6. 用canvas制作酷炫射击游戏--part3

    今天介绍下 游戏中的sprite模块,也就是构建玩家及怪物的模块.有了这个模块,就可以在咱们的游戏里加入人物了. 想必用过css的朋友都知道sprite,一种将需要加载的图片拼接在一张图里以减少请求的 ...

  7. 用canvas制作酷炫射击游戏--part2

    今天这一部分主要讲游戏的实现原理与游戏循环的代码实现. 先说原理,大家都看过动画吧.在我看来,游戏就是玩家能人为控制动画剧情发展方向的动画.所以,我们的游戏引擎其实说白了就是个动画引擎再加上鼠标事件. ...

  8. 用canvas制作酷炫射击游戏--part1

    好久没写博客了,因为过年后一直在学游戏制作方面的知识.学得差不多后又花了3个月时间做了个作品出来,现在正拿着这个作品找工作. 作品地址:https://betasu.github.io/Crimonl ...

  9. HTML5 canvas画布写炫彩动态的倒计时效果

    html代码如下,插入了2个js代码. <!DOCTYPE html> <html> <head> <title>canvas</title> ...

随机推荐

  1. 测试框架Mocha与断言expect

    测试框架Mocha与断言expect在浏览器和Node环境都可以使用除了Mocha以外,类似的测试框架还有Jasmine.Karma.Tape等,也很值得学习. 整个项目源代码: 为什么学习测试代码? ...

  2. python django基础(一)

    Django简介:Django是一个开放源代码的Web应用框架,由Python写成.采用了MVC的框架模式,即模型M,视图V和控制器C.不过在Django实际使用中,Django更关注的是模型(Mod ...

  3. invalidate()和postInvalidate() 的区别及使用

    Android提供了Invalidate方法实现界面刷新,但是Invalidate不能直接在线程中调用,因为他是违背了单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中 ...

  4. Attach Volume 操作(Part I) - 每天5分钟玩转 OpenStack(53)

    上一节我们创建了 volume,本节讨论如何将 volume attach 到 Instance,今天是第一部分. Volume 的最主要用途是作为虚拟硬盘提供给 instance 使用.Volume ...

  5. [Keras] mnist with cnn

    典型的卷积神经网络. Keras傻瓜式读取数据:自动下载,自动解压,自动加载. # X_train: array([[[[ 0., 0., 0., ..., 0., 0., 0.], [ 0., 0. ...

  6. IOS中block和代理

    从ios4开始引入block,就是代码块,结构类c语言 基本结构 返回值 (^block名称)(参数):int(^BlockName)(int):返回值为int型,参数是一个int值的叫BlockNa ...

  7. Oracle启动报错ORA-03113解决

    环境:RHEL6.4 + Oracle 11.2.0.4 步骤摘要:1.启动报错ORA-031132.查看alert日志查找原因3.根据实际情况采取合理的措施,这里我们先增加闪回区大小,把库启动起来4 ...

  8. SQL Server基础之《视图的概述和基本操作》

     数据库中的视图是一个虚拟表.同真实的表一样,视图包含一系列带有名称的列和行数据,行和列数据用来自由定义视图和查询所引用的表,并且在引用视图时动态产生.本篇将通过一些实例来介绍视图的概念,视图的作用, ...

  9. 锁升级(Lock Escalations)——它们经常发生么?

    前段时间,我写了一些SQL Server里锁升级的基础知识,还有它是如何影响执行计划的.今天,我想进一步谈下锁升级: 锁升级什么时候发生? 通常在SQL Server里如果在SQL语句里你请求的行数超 ...

  10. 代码片段添加智能提示,打造一款人见人爱的ORM框架

    SqlSugar ORM优点: 1.高性能,达到原生最高水准,比SqlHelper性能要高,比Dapper快30% 比EF快50% 2.支持多种数据库 ,sql版本更新最快,其它会定期更新,可以在多种 ...