前言

半年前用js和canvas仿了热血传奇网游(地址),基本功能写完之后,剩下的都是堆数据、堆时间才能完成的任务了,没什么新鲜感,因此进度极慢。这次看到微信《弹一弹》比较火,因为涉及到物理引擎(为了真实),于是动手试了一下。一共用了10个小时,不仅完成了这个demo,<删除线>并且打上了弹一弹好友排行榜的第一页</删除线>。

资料汇总

  • 在线demo:点击即玩
  • 代码:400行带注释
  • canvas渲染库:支持物理引擎及chrome调试工具,这里

准备工作

微信这个小游戏的游戏规则很简单,看图就能看明白,这里不再赘述。涉及到的几个开发难度:

1.物理引擎

当然不用也可以,无非就是改改图片的位置,可以自己模拟掉落和碰撞效果。不过由于我追(wu)求(li)体(hen)验(cha),因此开始寻找第三方的物理引擎。

最后我使用的是chipmunk的js版(这个库是底层计算库,因此star不多,但是比较有名气的hilo和cocos2d的物理引擎用到了这个库)。主要原因之一是,这个库的功能只是进行了物理运算,并且支持重力、弹性、摩擦、浮力等功能。当然体积也比较小。毕竟我们只是写一个小demo,引入一个游戏框架的话很可能徒增成本。

不过我使用的时候遇到了几个罕见的bug,应该是作者的疏漏,在issue中也有人反馈。看作者更新频率很低,我拿来用的时候有一些修改。如果其它人使用的时候遇到js报错,可以试试这里

2.UI渲染

我选择的是canvas,因为涉及到频繁的样式更新,每帧都去改写style的话太占性能。而且用canvas写的话,以后还可以迭代一些碰撞产生的画效。

之前封装了一个easycanvas库,可以将树形数据结构“翻译”成“canvas画布中的一个个对象”。这次又顺手补充了一个支持chipmunk的插件,这样整个“弹一弹”的开发就完全只需要管理数据即可,渲染工作很少。

开始开发

html及背景

由于项目较小,我把html、css、js堆在了一个文件(最后写完之后,发现一共连同注释才400行)。

首先创建一个空html,为了看起来高大上,我搜了一张天空主题的背景图。

<style>body {margin: 0;text-align: center;background: black;}canvas {border: 1px solid grey;height: 100%;max-width: 100%;background-image: url(http://a3.topitme.com/2/d4/ff/1144306867e94ffd42o.jpg);background-size: auto 100%;}
</style>
<body><canvas id="el"></canvas>
</body>

可能用到的变量

接下来,准备一些我们需要用到的数据。例如游戏的宽高、小球的大小、当前游戏状态(是否可以射击)、每次可以射出的小球数、玩家的分数,blabla。

由于是直接在html里写码,为了兼容老浏览器,只能var来var去。

// 在html直接写代码,不编译、不构建,不然应该用const的
var width = 400, height = 600, ballSize = 20;// 游戏状态
var canShoot = true;
var score = 0, ballLeft = 0, ballCount = 5;
var blockArray = [];// 图片
var BALL = Easycanvas.imgLoader('./ball.png');
var BLOCK = Easycanvas.imgLoader('./block.jpg');
var TRIANGLE = Easycanvas.imgLoader('./triangle.png');// 给每个东西起一个type,后面会用来做碰撞检测
var BALL_TYPE = 1, BLOCK_TYPE = 2, BORDER_TYPE = 3, BOTTOM_TYPE = 4, BONUS_TYPE = 5;

顶部文本

接下来先将分数和小球个数写到canvas中。首先创建一个easycanvas实例,宽400,高600。然后add2个对象。一个以左上角(5,5)为顶点,向右下方写分数。一个以右上角(395, 5)为顶点,向左下角写当前小球个数。

// 初始化easycanvas实例
var $Painter = new Easycanvas.painter();
$Painter.register(el, {width: width,height: height,
});
$Painter.start();$Painter.add({content: {text: function () {return '得分:' + score;}},style: {tx: 5, ty: 5,textAlign: 'left', textVerticalAlign: 'top',color: 'black'}
});
$Painter.add({content: {text: function () {return '小球个数:' + ballCount;}},style: {tx: 395, ty: 5,textAlign: 'right', textVerticalAlign: 'top',color: 'black'}
});

添加方块

接下来,设置整个场景的重力,并且添加一些方块进去。每个方块对象含有一个child,用来展示数字(还可以撞几下)。为了避免方块重叠,我们让方块的x坐标在50、100、150、……、300、350循环。同时,为了避免看起来“太整齐”,每次添加一个小的随机数,让这些方块们错落有致。(“错落”指参差不齐,“致”指情趣。形容事物的布局虽然参差不齐,但却极有情趣,使人看了有好感。——某度)

每个方块的大小是30x30,因此shapes包括4条边,例如(0,0)到(30,0)是一条边。这些方块是失重的(不会掉下去),因此static设置为true。为了更加错落有致,我们给他一个随机的角度rotate。

每个方块含有一个child,写着一个数字。不需要给数字设置rotate,否则6和9可能就分不清了。

// 初始化easycanvas物理引擎,添加一个有物理树形的空容器
var $space = new Easycanvas.class.sprite({physics: {gravity: 2, // 重力默认为1,但是游戏进程有点慢,看着不够爽accuracy: 2,},
});
$Painter.add($space);var space = $space.launch();// 防止方块重叠,记录上一次方块的X坐标
var lastBlockPositionX = 50;
function addBlock (max, boolAddToBottom) {var deg = Math.floor(Math.random() * 360);var sprite = $space.add(new Easycanvas.class.sprite({name: 'block',content: {img: BLOCK,},physics: {shape: [[[0, 0], [0, 30]],[[0, 30], [30, 30]],[[30, 30], [30, 0]],[[30, 0], [0, 0]]],mass: 1,friction: 0.1,elasticity: 0.9,collisionType: BLOCK_TYPE,static: true,},style: {tw: 30, th: 30,tx: lastBlockPositionX + Math.floor(Math.random() * 20 - 10),ty: boolAddToBottom ? 500 : height - 100 - Math.floor(Math.random() * 100),locate: 'lt',rotate: deg,},children: [{content: {text: Math.floor(Math.random() * max) + 1,},style: {color: 'yellow',textAlign: 'center',textVerticalAlign: 'middle',textFont: '28px Arial',tx: 15, ty: 10}}]}));sprite.physicsOn();blockArray.push(sprite);lastBlockPositionX += 50;if (lastBlockPositionX > 350) {lastBlockPositionX = 50;}
}

接下来,我们做瞄准部分。大致功能是,有一排小圆点,会随着鼠标运动,并且有弹簧的感觉。

首先要记录鼠标的轨迹,我们给easycanvas实例$Painter加上事件监听。在“弹一弹”游戏中,小球不能向上发射。因此记录鼠标的Y坐标值的时候,我们让他至少为30。

// 记录鼠标轨迹
var mouse = {x: 300, y: 50};
var mouseRecord = function ($e) {mouse.x = $e.canvasX;mouse.y = Math.max(30, $e.canvasY);
};$Painter.register(el, {width: width,height: height,events: {mousemove: mouseRecord,touchmove: mouseRecord,mouseup: shoot,touchend: shoot,}
});

小球瞄准

接下来,我们添加7个小球,让他们排列在一条线上,从游戏正上方的(300, 20)点到鼠标位置均匀铺开。具体逻辑就是,我们将鼠标位置和(300, 20)的坐标差进行6等分,第一个球的坐标向鼠标位置偏移0/6、第二个球偏移1/6……,最后一个球偏移6/6(正好落在了鼠标位置)。这几个球我们给他们一个透明度,并且不启用物理规则(因为这个阶段小球不能掉下来)。我们在每个小球上设置一个shoot钩子,当玩家射出真实的小球时,删除这个瞄准用的小球。

// 显示瞄准轨迹
var startAim = function () {for (var i = 0; i < 7; i ++) {$Painter.add({content: {img: BALL,},data: {gap: i / 6,},style: {tx: function () {return 200 + (mouse.x - 200) * this.data.gap;},ty: function () {return 20 + (mouse.y - 20) * this.data.gap;},tw: 20, th: 20,opacity: 0.4,},hooks: {shoot: function () {this.remove();}}});}
};
startAim();

发射小球

接下来,我们添加真实的小球(受到物理规则影响的小球)。

当射击时,我们广播shoot事件,移除刚才瞄准用的小球。

之后,我们间隔100毫秒,连续调用addBall方法来创建小球。addBall方法中,我们为每个小球设置物理规则。包括形状、弹性、摩擦等。

这里有一个坑,就是一旦开始射击,不管鼠标怎么移动,射击的方向都不能变化。因此我们要先记录下当前的mouse值,这里用的是JSON.parse(JSON.stringify(mouse))来copy一个简单对象。

这里又有一个坑:“弹一弹”游戏中,刚射击出去的小球是不受重力影响的(不然瞄准还有什么意义)。因此,我们在每个小球上增加一个和重力相反的作用力,抵消重力。(在其它部分的代码中,有着“当小球发生一次碰撞后,取消这个作用力”的实现,这里为了清晰没有一起贴出来)。

同时,我们给小球加上初速度。

这里又又又又又有一个坑(好烦啊):不管怎么射击,小球初始获得的速度是相同的。哪怕小球的瞄准位置距离射出位置很近,速度也不能慢。这里需要修正一下初始速度,这里用到了著名的Pythagoras theorem定理:直角三角形的两条直角边的平方和等于斜边的平方。

function shoot () {if (!canShoot) return;$Painter.broadcast('shoot');canShoot = false;var currentMouse = JSON.parse(JSON.stringify(mouse));for (var i = 0; i < ballCount; i++) {setTimeout(function () {addBall(currentMouse);}, i * 100);}
};function addBall (mouse) {ballLeft++;var $ball = new Easycanvas.class.sprite({name: 'ball',content: {img: BALL,},physics: {shape: [// 形状是一个以(ballSize / 2, ballSize / 2)为圆心的,半径也是ballSize / 2的圆// 改成位运算符吧,看着能高大上一点(其实在这里卵用没有)[ballSize >> 1, ballSize >> 1, ballSize >> 1]],mass: 1, // 质量friction: 0.1, // 摩擦(摩擦太大了会损失能量)elasticity: 0.8, // 弹性collisionType: BALL_TYPE,},style: {tw: ballSize, th: ballSize,sx: 0, sy: 0,tx: 200,ty: 20,zIndex: 1,},});$space.add($ball);$ball.physicsOn();// 抵消重力$ball.$physics.body.applyForce({x: 0, y: 1000}, {x: 0, y: 0});// 初速度var speed = {x: (mouse.x - 200) / (20 - mouse.y),y: 1};// 修正速度,确保从各个角度射出小球的速度差不多// 这里用到的著名的高等数学知识:勾股定理var muti = Math.sqrt(Math.pow(speed.x, 2) + Math.pow(speed.y, 2)) / 700;$ball.$physics.body.setVel({x: -speed.x / muti,y: -speed.y / muti,});
}

其它

轮廓已经有了,后面的部分不再是难点。不过做到最后,坑还是比较多的:

例如小球可能会停在方块上(就是这么巧),这是需要人为给予小球一个速度(“弹一弹”游戏里也是这样做的)。

例如小球撞到方块上,可能会触发2次碰撞,因为影响不大,我先搁置了。这个是因为时间精度没有太细,小球在上一帧没有发生碰撞,因为速度较快,下一帧同时撞到了2个边界。

js+canvas仿微信《弹一弹》小游戏相关推荐

  1. 【Cocos Creator游戏开发教程】仿微信趣味画赛车小游戏(三)代码实现

    [Cocos Creator游戏开发教程]仿微信趣味画赛车小游戏(一)前言,界面UI [Cocos Creator游戏开发教程]仿微信趣味画赛车小游戏(二)物理刚体关节 项目地址已放到 github ...

  2. 【Cocos Creator游戏开发教程】仿微信趣味画赛车小游戏(一)前言,界面UI

    前言 这个是我去年3月份在简书上发布的,不玩简书了,就迁到CSDN吧-- 最近遇到一款游戏,感觉玩起来还行,于是顺带就用来熟悉一下Cocos Creator(太久没用). 项目地址已放到 github ...

  3. 【Cocos Creator游戏开发教程】仿微信趣味画赛车小游戏(二)物理刚体关节

    [Cocos Creator游戏开发教程]仿微信趣味画赛车小游戏(一)前言,界面UI 项目地址已放到 github 上,需要的小伙伴可自行下载. 这节我们讲一下车子的物理刚体关节. 我在项目中添加了一 ...

  4. js canvas仿微信《弹一弹》小游戏

    前言 半年前用js和canvas仿了热血传奇网游(地址),基本功能写完之后,剩下的都是堆数据.堆时间才能完成的任务了,没什么新鲜感,因此进度极慢.这次看到微信<弹一弹>比较火,因为涉及到物 ...

  5. 仿微信软键盘弹出与隐藏

    仿微信软键盘弹出与隐藏,效果图如下: 实现输入框弹出,软键盘弹出,获取焦点,否则失去焦点. 首先在 AndroidManifest 文件的对应 Activity 中加入下面代码: android:wi ...

  6. 已被多次定制!!“模拟微信答题的H5小游戏

    今天推荐一款"模拟微信"答题的H5小游戏,这个也是涛舅舅这边客户定制的最多的一款游戏,曾经为现代汽车.万达.和<三妹>电视剧都作过定制! 以下是<三妹>定制 ...

  7. 轩辕剑天之痕java论坛_仿《天之痕》小游戏

    仿<天之痕>小游戏 我是一个<天之痕>的游戏迷,刚刚学了点JAVA,仿照其中一个支线小游戏做了一个小程序,相信玩过<天之痕>的兄弟们都记得,但是我这程序出了点小问题 ...

  8. JS与HTML、CSS实现2048小游戏(一)

    JS与HTML.CSS实现2048小游戏(一) 引言 知识储备 编译器推荐 游戏框架 构建游戏的基础页面 后续文章 引言 这是大一刚结束的时候做的东西,之前也写了文章做记录,最近想发一下博客,就重新整 ...

  9. 工作篇 之 高仿微信双击消息弹出可自由复制

    LZ-Says:书山有路勤为径,学海无涯苦作舟. 前言 最近呐,难已琢磨. 很喜欢,却又很忧愁. 喜欢的是,找到了自己认可的.喜欢的工作: 忧愁的是,压力山大. I Love-! 举个栗子 Enmmm ...

最新文章

  1. pythoning ——3、数据类型(字符串)
  2. win7 64 下安装ubuntu14.04
  3. 深度学习caffe的代码怎么读?
  4. 2020-10-27(汇编收获)
  5. ultraedit正则表达式
  6. Waiting 180 more seconds for 1 worker threads to finish
  7. [状压dp]洛谷 P2157 学校食堂
  8. 全新拟态个人主页/引导页源码
  9. 轻松弄懂var、let、const之间的区别
  10. [wp7游戏]wp7~~超级血腥类游戏~~集合贴~~
  11. 【kafka】kafka record is corrupt(记录损坏)
  12. python软件下载-python下载_python免费下载[编程工具]-下载之家
  13. idea 2020 社区版传递参数
  14. mysql增删改处理
  15. android自动计步_Android计步模块(类似微信运动)
  16. 「Pytorch」CNN实现手写汉字识别(数据集制作,网络搭建,训练验证测试全部代码)
  17. 《大秦帝国之裂变》感悟与经典语录
  18. 牛客寒假基础集训营 | Day1 G-eli和字符串
  19. JavaScript个人学习心得
  20. 小李飞刀系列之Oracle EBS期间平均成本(PAC)--生产成本计算(一)基础

热门文章

  1. WPF系列教程(二十五):绑定到非元素对象Source属性、RelativeSource属性、DataContext属性
  2. Android 屏幕旋转相关解析
  3. Retrofit封装-json解析
  4. 微信公众号开发java流程_微信公众号开发教程java 编程语言的特点及选择
  5. 当上项目经理才知道!2021年Java开发者常见面试题
  6. 时光剪影、拂袖只为淡然
  7. nginx 卸载和重装
  8. SSL 链接安全协议的enum
  9. 知识表示学习(五):RotatE
  10. GridView中显示数据库里的图片