很快微信开发者平台

 找回密码
 注册
查看: 7645|回复: 218

[Demo源码] 微信小游戏《弹一弹》js版,物理引擎+js+canvas

  [复制链接]

3

主题

4

帖子

407

积分

新人求带

积分
407
 楼主| 发表于 2018-5-7 15:40:44 | 显示全部楼层 |阅读模式
微信小游戏《弹一弹》js版,物理引擎+js+canvas

demo.gif

在线Demo:javascript+canvas实现弹一弹

依赖库:easycanvas渲染

代码加上css和注释一共400行
  1. <head>
  2.     <script src="./build/easycanvas.standalone.prod.js"></script>
  3.     <script src="./build/plugin.physics.standalone.prod.js"></script>
  4. </head>

  5. <style>
  6.     body {
  7.         margin: 0;
  8.         text-align: center;
  9.         background: black;
  10.     }
  11.     canvas {
  12.         border: 1px solid grey;
  13.         height: 100%;
  14.         max-width: 100%;
  15.         background-image: url(http://a3.topitme.com/2/d4/ff/1144306867e94ffd42o.jpg);
  16.         background-size: auto 100%;
  17.     }
  18. </style>
  19. <body>
  20.     <canvas id="el"></canvas>
  21. </body>

  22. <script>
  23.     // 在html直接写代码,不编译、不构建,不然应该用const的
  24.     var width = 400, height = 600, ballSize = 20;
  25.     // 记录鼠标轨迹
  26.     var mouse = {x: 300, y: 50};
  27.     var mouseRecord = function ($e) {
  28.         mouse.x = $e.canvasX;
  29.         mouse.y = Math.max(30, $e.canvasY);
  30.     };
  31.     // 游戏状态
  32.     var canShoot = true;
  33.     var score = 0, ballLeft = 0, ballCount = 5;
  34.     var blockArray = [];
  35.     // 图片
  36.     var BALL = Easycanvas.imgLoader('./ball.png');
  37.     var BLOCK = Easycanvas.imgLoader('./block.jpg');
  38.     var TRIANGLE = Easycanvas.imgLoader('./triangle.png');
  39.     // 用于碰撞检测
  40.     var BALL_TYPE = 1, BLOCK_TYPE = 2, BORDER_TYPE = 3, BOTTOM_TYPE = 4, BONUS_TYPE = 5;
  41.     // 初始化easycanvas实例
  42.     var $Painter = new Easycanvas.painter();
  43.     $Painter.register(el, {
  44.         width: width,
  45.         height: height,
  46.         events: {
  47.             mousemove: mouseRecord,
  48.             touchmove: mouseRecord,
  49.             mouseup: shoot,
  50.             touchend: shoot,
  51.         }
  52.     });
  53.     $Painter.start();
  54.     $Painter.add({
  55.         content: {
  56.             text: function () {
  57.                 return '得分:' + score;
  58.             }
  59.         },
  60.         style: {
  61.             tx: 5, ty: 5,
  62.             textAlign: 'left', textVerticalAlign: 'top',
  63.             color: 'black'
  64.         }
  65.     });
  66.     $Painter.add({
  67.         content: {
  68.             text: function () {
  69.                 return '小球个数:' + ballCount;
  70.             }
  71.         },
  72.         style: {
  73.             tx: 395, ty: 5,
  74.             textAlign: 'right', textVerticalAlign: 'top',
  75.             color: 'black'
  76.         }
  77.     });
  78.     // 初始化easycanvas物理引擎
  79.     var $space = new Easycanvas.class.sprite({
  80.         physics: {
  81.             gravity: 2,
  82.             accuracy: 2,
  83.         },
  84.     });
  85.     $Painter.add($space);
  86.     // 设置easycanvas碰撞处理规则
  87.     var space = $space.launch();
  88.     // 球和球不碰撞(感觉看着很不真实啊,但是游戏就是这样的)
  89.     space.addCollisionHandler(BALL_TYPE, BALL_TYPE, function (cp) {
  90.         return false;
  91.     });
  92.     // 球和方块碰撞
  93.     space.addCollisionHandler(BALL_TYPE, BLOCK_TYPE, null,  function (cp) {
  94.         if (Math.abs(cp.body_a.vy) < 1) {
  95.             // 防止小球停到方块上
  96.             cp.body_a.setVel({
  97.                 x: Math.random() * 10 - 5,
  98.                 y: -300,
  99.             });
  100.         }
  101.         return true;
  102.     }, null,
  103.     function (cp) {
  104.         var block = cp.b.$sprite.name === 'block' ? cp.b.$sprite : cp.a.$sprite;
  105.         var ball = cp.a.$sprite.name === 'block' ? cp.b.$sprite : cp.a.$sprite;
  106.         // 撞过一次就重置作用力(这样只剩下重力了,就开始往下掉)
  107.         ball.$physics.body.resetForces();
  108.         // 这里直接通过父对象从子对象拿数据,这种数据的依赖方式不好,但是这么简单个应用,无所谓了
  109.         // 更好的是例如通过easycanvas的自定义事件广播下去
  110.         block.children[0].content.text--;
  111.         score++;
  112.         if (!block.children[0].content.text) {
  113.             // 把方块撞成0了,先隐藏
  114.             block.style.visible = false;
  115.             // 异步删除这个方块(为啥异步?因为要在碰撞处理之后再删,不然就透过去了)
  116.             $Painter.nextTick(function () {
  117.                 block.physicsOff();
  118.                 block.remove();
  119.                 blockArray.splice(blockArray.indexOf(block), 1);
  120.             });
  121.         }
  122.         return false;
  123.     });
  124.     // 球和侧边碰撞
  125.     space.addCollisionHandler(BALL_TYPE, BORDER_TYPE, null, null, null, function (cp) {
  126.         var ball = cp.a.$sprite.name === 'ball' ? cp.a.$sprite : cp.b.$sprite;
  127.         // 撞过一次就重置作用力(这样只剩下重力了,就开始往下掉)
  128.         ball.$physics.body.resetForces();
  129.         return false;
  130.     });
  131.     // 球和吃的碰撞
  132.     space.addCollisionHandler(BALL_TYPE, BONUS_TYPE, null, null, null, function (cp) {
  133.         var bonus = cp.a.$sprite.name === 'bonus' ? cp.a.$sprite : cp.b.$sprite;
  134.         var ball = cp.a.$sprite.name === 'ball' ? cp.a.$sprite : cp.b.$sprite;
  135.         // 撞过一次就重置作用力(这样只剩下重力了,就开始往下掉)
  136.         ball.$physics.body.resetForces();
  137.         // 一个球被连续撞,只加一次
  138.         if (bonus.used) return false;
  139.         bonus.used = 1;
  140.         // 异步删除
  141.         $Painter.nextTick(function () {
  142.             bonus.physicsOff();
  143.             bonus.remove();
  144.             blockArray.splice(blockArray.indexOf(bonus), 1);
  145.             ballCount++;
  146.         });
  147.         return false;
  148.     });
  149.     // 球和底部碰撞,准备收回
  150.     space.addCollisionHandler(BALL_TYPE, BOTTOM_TYPE, function (cp) {
  151.         var ball = cp.a.$sprite.name === 'ball' ? cp.a.$sprite : cp.b.$sprite;
  152.         if (ball.toRemove) {
  153.             return true;
  154.         }
  155.         ball.toRemove = true;
  156.         ball.style.opacity = Easycanvas.transition.linear(1, 0, 500);
  157.         setTimeout(function () {
  158.             ball.physicsOff();
  159.             ball.remove();
  160.             ballLeft--;
  161.             if (ballLeft === 0) {
  162.                 canShoot = true;
  163.                 blockArray.forEach(function (block) {
  164.                     block.physicsOff();
  165.                     block.physicsOn();
  166.                     block.style.ty -= 50;
  167.                     if (block.style.ty < 50) {
  168.                         canShoot = false;
  169.                     }
  170.                 });
  171.                 if (!canShoot) {
  172.                     alert('You lose');
  173.                 } else {
  174.                     startAim();
  175.                     addBlock(5 + score / 10, true);
  176.                     Math.random() < 0.5 && addBlock(5 + score / 9, true);
  177.                     Math.random() < 0.3 && addBlock(5 + score / 8, true);
  178.                     Math.random() < 0.2 && addBlock(5 + score / 7, true);
  179.                     Math.random() < 0.3 && addBonus();
  180.                     Math.random() < 0.3 && addBonus();
  181.                 }
  182.             }
  183.         }, 500);
  184.         return true;
  185.     });
  186.     // 显示瞄准轨迹
  187.     var startAim = function () {
  188.         for (var i = 0; i < 7; i ++) {
  189.             $Painter.add({
  190.                 content: {
  191.                     img: BALL,
  192.                 },
  193.                 data: {
  194.                     gap: i / 6,
  195.                 },
  196.                 style: {
  197.                     tx: function () {
  198.                         return 200 + (mouse.x - 200) * this.data.gap;
  199.                     },
  200.                     ty: function () {
  201.                         return 20 + (mouse.y - 20) * this.data.gap;
  202.                     },
  203.                     tw: 20, th: 20,
  204.                     opacity: 0.4,
  205.                 },
  206.                 hooks: {
  207.                     shoot: function () {
  208.                         this.remove();
  209.                     }
  210.                 }
  211.             });
  212.         }
  213.     };
  214.     startAim();
  215.     function shoot () {
  216.         if (!canShoot) return;
  217.         $Painter.broadcast('shoot');
  218.         canShoot = false;
  219.         var currentMouse = JSON.parse(JSON.stringify(mouse));
  220.         for (var i = 0; i < ballCount; i++) {
  221.             setTimeout(function () {
  222.                 addBall(currentMouse);
  223.             }, i * 100);
  224.         }
  225.     };
  226.     function addBall (mouse) {
  227.         ballLeft++;
  228.         var $ball = new Easycanvas.class.sprite({
  229.             name: 'ball',
  230.             content: {
  231.                 img: BALL,
  232.             },
  233.             physics: {
  234.                 shape: [
  235.                     // 形状是一个以(ballSize / 2, ballSize / 2)为圆心的,半径也是ballSize / 2的圆
  236.                     // 改成位运算符吧,看着能高大上一点(其实在这里卵用没有)
  237.                     [ballSize >> 1, ballSize >> 1, ballSize >> 1]
  238.                 ],
  239.                 mass: 1, // 质量
  240.                 friction: 0.1, // 摩擦(摩擦太大了会损失能量)
  241.                 elasticity: 0.8, // 弹性
  242.                 collisionType: BALL_TYPE,
  243.             },
  244.             style: {
  245.                 tw: ballSize, th: ballSize,
  246.                 sx: 0, sy: 0,
  247.                 tx: 200,
  248.                 ty: 20,
  249.                 zIndex: 1,
  250.             },
  251.         });
  252.         $space.add($ball);
  253.         $ball.physicsOn();
  254.         $Painter.nextTick(function () {
  255.             $Painter.nextTick(function () {
  256.                 // 抵消重力
  257.                 $ball.$physics.body.applyForce({x: 0, y: 1000}, {x: 0, y: 0});
  258.                 // 初速度
  259.                 var speed = {
  260.                     x: (mouse.x - 200) / (20 - mouse.y),
  261.                     y: 1
  262.                 };
  263.                 // 修正速度,确保从各个角度射出小球的速度差不多
  264.                 var muti = Math.sqrt(Math.pow(speed.x, 2) + Math.pow(speed.y, 2)) / 700;
  265.                 $ball.$physics.body.setVel({
  266.                     x: -speed.x / muti,
  267.                     y: -speed.y / muti,
  268.                 });
  269.             });
  270.         });
  271.     }
  272.     // 防止方块重叠,记录上一次方块的X坐标
  273.     var lastBlockPositionX = 50;
  274.     function addBlock (max, boolAddToBottom) {
  275.         var deg = Math.floor(Math.random() * 360);
  276.         var sprite = $space.add(new Easycanvas.class.sprite({
  277.             name: 'block',
  278.             content: {
  279.                 img: BLOCK,
  280.             },
  281.             physics: {
  282.                 shape: [
  283.                     [[0, 0], [0, 30]],
  284.                     [[0, 30], [30, 30]],
  285.                     [[30, 30], [30, 0]],
  286.                     [[30, 0], [0, 0]]
  287.                 ],
  288.                 mass: 1,
  289.                 friction: 0.1,
  290.                 elasticity: 0.9,
  291.                 collisionType: BLOCK_TYPE,
  292.                 static: true,
  293.             },
  294.             style: {
  295.                 tw: 30, th: 30,
  296.                 tx: lastBlockPositionX + Math.floor(Math.random() * 20 - 10),
  297.                 ty: boolAddToBottom ? 500 : height - 100 - Math.floor(Math.random() * 100),
  298.                 locate: 'lt',
  299.                 zIndex: Math.random(),
  300.                 rotate: deg,
  301.             },
  302.             children: [{
  303.                 content: {
  304.                     text: Math.floor(Math.random() * max) + 1,
  305.                 },
  306.                 style: {
  307.                     color: 'yellow',
  308.                     textAlign: 'center',
  309.                     textVerticalAlign: 'middle',
  310.                     textFont: '28px Arial',
  311.                     tx: 15, ty: 10
  312.                 }
  313.             }]
  314.         }));
  315.         sprite.physicsOn();
  316.         blockArray.push(sprite);
  317.         lastBlockPositionX += 50;
  318.         if (lastBlockPositionX > 350) {
  319.             lastBlockPositionX = 50;
  320.         }
  321.     }
  322.     function addBonus () {
  323.         var sprite = $space.add(new Easycanvas.class.sprite({
  324.             name: 'bonus',
  325.             bonus: true,
  326.             content: {
  327.                 img: BALL,
  328.             },
  329.             physics: {
  330.                 shape: [
  331.                     [[0, 0], [0, 30]],
  332.                     [[0, 30], [30, 30]],
  333.                     [[30, 30], [30, 0]],
  334.                     [[30, 0], [0, 0]]
  335.                 ],
  336.                 mass: 1,
  337.                 friction: 0.1,
  338.                 elasticity: 0.5,
  339.                 collisionType: BONUS_TYPE,
  340.                 static: true,
  341.             },
  342.             style: {
  343.                 tw: 30, th: 30,
  344.                 tx: lastBlockPositionX + Math.floor(Math.random() * 20 - 10),
  345.                 ty: 500,
  346.                 locate: 'lt',
  347.                 zIndex: 2,
  348.                 fv: Easycanvas.transition.pendulum(0, 0.2, 200).loop(),
  349.                 fhv: Easycanvas.transition.pendulum(0.2, 0, 200).loop(),
  350.             },
  351.         }));
  352.         sprite.physicsOn();
  353.         blockArray.push(sprite);
  354.         lastBlockPositionX += 50;
  355.         if (lastBlockPositionX > 350) {
  356.             lastBlockPositionX = 50;
  357.         }
  358.     }
  359.     // 上半部分的边,摩擦小、弹性大
  360.     var borderSprite = $space.add(new Easycanvas.class.sprite({
  361.         physics: {
  362.             shape: [
  363.                 [[0, 0], [width, 0]],
  364.                 [[0, 0], [0, height * 0.9]],
  365.                 [[width, 0], [width, height * 0.9]],
  366.             ],
  367.             friction: 0.1,
  368.             elasticity: 0.7,
  369.             collisionType: BORDER_TYPE,
  370.             static: true
  371.         },
  372.         style: {
  373.             tx: 0, ty: 0, tw: width, th: height,
  374.             locate: 'lt',
  375.         },
  376.     }));
  377.     borderSprite.physicsOn();
  378.     // 下半部分的边,摩擦大、弹性小
  379.     var bottomSprite = $space.add(new Easycanvas.class.sprite({
  380.         physics: {
  381.             shape: [
  382.                 [[0, height], [width, height]],
  383.                 [[0, height * 0.9], [0, height]],
  384.                 [[width, height * 0.9], [width, height]],
  385.             ],
  386.             friction: 5,
  387.             elasticity: 0,
  388.             collisionType: BOTTOM_TYPE,
  389.             static: true
  390.         },
  391.         style: {
  392.             tx: 0, ty: 0, tw: width, th: height,
  393.             locate: 'lt',
  394.         },
  395.     }));
  396.     bottomSprite.physicsOn();
  397.     // 第一关7个方块
  398.     for (var i = 0; i < 7; i++) {
  399.         addBlock(5);
  400.     }
  401.     // 阻止微信浏览器的默认下拉
  402.     document.body.addEventListener('touchmove' , function (e) {
  403.         e.preventDefault();
  404.     });
  405. </script>
复制代码


游客,如果您要查看本帖隐藏内容请回复
回复

使用道具 举报

0

主题

64

帖子

1703

积分

专家路上

积分
1703
发表于 2018-5-7 15:47:17 | 显示全部楼层
回复

使用道具 举报

0

主题

64

帖子

1703

积分

专家路上

积分
1703
发表于 2018-5-7 15:48:45 | 显示全部楼层
前来学习~~~~前来学习~~~~前来学习~~~~
回复

使用道具 举报

0

主题

64

帖子

1703

积分

专家路上

积分
1703
发表于 2018-5-7 15:48:49 | 显示全部楼层
前来学习~~~~前来学习~~~~
回复

使用道具 举报

0

主题

64

帖子

1703

积分

专家路上

积分
1703
发表于 2018-5-7 15:48:53 | 显示全部楼层
前来学习~~~~前来学习~~~~
回复

使用道具 举报

0

主题

8

帖子

60

积分

等待验证会员

积分
60
发表于 2018-5-7 16:16:59 | 显示全部楼层
感谢楼主分享
回复

使用道具 举报

13

主题

2459

帖子

6640

积分

S1

积分
6640
发表于 2018-5-7 17:21:58 | 显示全部楼层

感谢楼主分享
回复

使用道具 举报

13

主题

2459

帖子

6640

积分

S1

积分
6640
发表于 2018-5-7 17:22:01 | 显示全部楼层

感谢楼主分享
回复

使用道具 举报

13

主题

2459

帖子

6640

积分

S1

积分
6640
发表于 2018-5-7 17:22:04 | 显示全部楼层

感谢楼主分享
回复

使用道具 举报

13

主题

2459

帖子

6640

积分

S1

积分
6640
发表于 2018-5-7 17:22:08 | 显示全部楼层

感谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|手机版|小黑屋|很快微信开发者平台 ( 津ICP备16002644号-1 )

GMT+8, 2021-5-10 13:47 , Processed in 0.043765 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表