菜单

H5 游戏开发:推金币

2018年11月15日 - Html/Html5

对象回收

及时也是玩玩支付中常用之优化手段,通过回收从边界没有的目标,让对象好复用,防止以累创建对象而发生大量的内存消耗。

采取「自动识图」的建议

尽管笔者于该地测试的上可管所有的「底图」识别出,但是并无克管其他开发者上传的图是否被坏好之辨别出来。笔者建议,可以管「自动识图」做也一个独门的家伙使用。

作者写了一个「自动识图」的单独工具页面:https://leeenx.github.io/OneStroke/src/plugin.html
得当此页面生成对应之关卡配置。

H5 游戏支付:推金币

2017/11/10 · HTML5 · 1
评论 ·
游戏

原文出处: 坑坑洼洼实验室   

近年来与开发的一样舒缓「京东11.11推金币赢现金」(已下线)小游戏一经颁布上线就当情人围引起大量传出。看到大家娱乐得不亦乐乎,同时为抓住众多网友狂讨论,有的说老振奋,有的大呼被套路被耍猴(无奈脸),这还与自身的预料相去甚远。在连锁事务数据呈上上涨过程遭到,曾已被微信「有关机构」盯上连要求做出调整,真是给宠若惊。接下来就跟大家大快朵颐下支付这款游戏之心路历程。

底图绘制

「一笔画」是多关卡的玩耍模式,笔者决定拿卡(连通图)的定制以一个部署接口的样式对外暴露。对外暴露关卡接口需要有平等学描述连通图形状的标准,而于作者面前有些许独选择:

举个连通图 —— 五角星为条例来说一下就片只选择。

图片 1

接触记法如下:

JavaScript

levels: [ // 当前关卡 { name: “五角星”, coords: [ {x: Ax, y: Ay}, {x:
Bx, y: By}, {x: Cx, y: Cy}, {x: Dx, y: Dy}, {x: Ex, y: Ey}, {x: Ax, y:
Ay} ] } … ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
levels: [
// 当前关卡
{
name: "五角星",
coords: [
{x: Ax, y: Ay},
{x: Bx, y: By},
{x: Cx, y: Cy},
{x: Dx, y: Dy},
{x: Ex, y: Ey},
{x: Ax, y: Ay}
]
}
]

线记法如下:

JavaScript

levels: [ // 当前关卡 { name: “五角星”, lines: [ {x1: Ax, y1: Ay, x2:
Bx, y2: By}, {x1: Bx, y1: By, x2: Cx, y2: Cy}, {x1: Cx, y1: Cy, x2: Dx,
y2: Dy}, {x1: Dx, y1: Dy, x2: Ex, y2: Ey}, {x1: Ex, y1: Ey, x2: Ax, y2:
Ay} ] } ]

1
2
3
4
5
6
7
8
9
10
11
12
13
levels: [
// 当前关卡
{
name: "五角星",
lines: [
{x1: Ax, y1: Ay, x2: Bx, y2: By},
{x1: Bx, y1: By, x2: Cx, y2: Cy},
{x1: Cx, y1: Cy, x2: Dx, y2: Dy},
{x1: Dx, y1: Dy, x2: Ex, y2: Ey},
{x1: Ex, y1: Ey, x2: Ax, y2: Ay}
]
}
]

「点记法」记录关卡通关的一个答案,即端点使准一定之一一存放到数组
coords着,它是有序性的笔录。「线记法」通过个别触及描述连通图的线条,它是无序的笔录。「点记法」最酷的优势是表现还简明,但她要记录一个过关答案,笔者只是关卡的苦力不是卡创造者,所以笔者最终选择了「线记法」。:)

推板

JavaScript

var bounds = this.pusher.getBounds(); this.pusher.body =
Matter.Bodies.trapezoid( this.pusher.x, this.pusher.y, bounds.width,
bounds.height }); Matter.World.add(this.world,
\[this.pusher.body\]);

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f3a3238851771206130-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238851771206130-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238851771206130-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238851771206130-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238851771206130-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238851771206130-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238851771206130-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238851771206130-8">
8
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f3a3238851771206130-1" class="crayon-line">
var bounds = this.pusher.getBounds();
</div>
<div id="crayon-5b8f3a3238851771206130-2" class="crayon-line crayon-striped-line">
this.pusher.body = Matter.Bodies.trapezoid(
</div>
<div id="crayon-5b8f3a3238851771206130-3" class="crayon-line">
  this.pusher.x,
</div>
<div id="crayon-5b8f3a3238851771206130-4" class="crayon-line crayon-striped-line">
  this.pusher.y,
</div>
<div id="crayon-5b8f3a3238851771206130-5" class="crayon-line">
  bounds.width,
</div>
<div id="crayon-5b8f3a3238851771206130-6" class="crayon-line crayon-striped-line">
  bounds.height
</div>
<div id="crayon-5b8f3a3238851771206130-7" class="crayon-line">
});
</div>
<div id="crayon-5b8f3a3238851771206130-8" class="crayon-line crayon-striped-line">
Matter.World.add(this.world, [this.pusher.body]);
</div>
</div></td>
</tr>
</tbody>
</table>

JavaScript

var direction, velocity, ratio, deltaY, minY = 550, maxY = 720,
minScale = .74; Matter.Events.on(this.engine, 'beforeUpdate',
function (event) { // 长度控制(点击伸长技能时) if
(this.isPusherLengthen) { velocity = 90; this.pusherMaxY = maxY; }
else { velocity = 85; this.pusherMaxY = 620; } // 方向控制 if
(this.pusher.y &gt;= this.pusherMaxY) { direction = -1; //
移动到最大长度时结束伸长技能 this.isPusherLengthen = false; } else
if (this.pusher.y &lt;= this.pusherMinY) { direction = 1; } //
速度控制 this.pusher.y += direction \* velocity; //
缩放控制,在最大长度变化时保持同样的缩放量,防止突然放大/缩小 ratio
= (1 - minScale) \* ((this.pusher.y - minY) / (maxY - minY))
this.pusher.scaleX = this.pusher.scaleY = minScale + ratio; //
同步控制,刚体跟推板位置同步 Body.setPosition(this.pusher.body, { x:
this.pusher.x, y: this.pusher.y }); })

<table>
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
</colgroup>
<tbody>
<tr class="odd">
<td><div class="crayon-nums-content" style="font-size: 13px !important; line-height: 15px !important;">
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-1">
1
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-2">
2
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-3">
3
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-4">
4
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-5">
5
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-6">
6
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-7">
7
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-8">
8
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-9">
9
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-10">
10
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-11">
11
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-12">
12
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-13">
13
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-14">
14
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-15">
15
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-16">
16
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-17">
17
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-18">
18
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-19">
19
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-20">
20
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-21">
21
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-22">
22
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-23">
23
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-24">
24
</div>
<div class="crayon-num" data-line="crayon-5b8f3a3238855483243812-25">
25
</div>
<div class="crayon-num crayon-striped-num" data-line="crayon-5b8f3a3238855483243812-26">
26
</div>
</div></td>
<td><div class="crayon-pre" style="font-size: 13px !important; line-height: 15px !important; -moz-tab-size:4; -o-tab-size:4; -webkit-tab-size:4; tab-size:4;">
<div id="crayon-5b8f3a3238855483243812-1" class="crayon-line">
var direction, velocity, ratio, deltaY, minY = 550, maxY = 720, minScale = .74;
</div>
<div id="crayon-5b8f3a3238855483243812-2" class="crayon-line crayon-striped-line">
Matter.Events.on(this.engine, 'beforeUpdate', function (event) {
</div>
<div id="crayon-5b8f3a3238855483243812-3" class="crayon-line">
  // 长度控制(点击伸长技能时)
</div>
<div id="crayon-5b8f3a3238855483243812-4" class="crayon-line crayon-striped-line">
  if (this.isPusherLengthen) {
</div>
<div id="crayon-5b8f3a3238855483243812-5" class="crayon-line">
    velocity = 90;
</div>
<div id="crayon-5b8f3a3238855483243812-6" class="crayon-line crayon-striped-line">
    this.pusherMaxY = maxY;
</div>
<div id="crayon-5b8f3a3238855483243812-7" class="crayon-line">
  } else {
</div>
<div id="crayon-5b8f3a3238855483243812-8" class="crayon-line crayon-striped-line">
    velocity = 85;
</div>
<div id="crayon-5b8f3a3238855483243812-9" class="crayon-line">
    this.pusherMaxY = 620;
</div>
<div id="crayon-5b8f3a3238855483243812-10" class="crayon-line crayon-striped-line">
  }
</div>
<div id="crayon-5b8f3a3238855483243812-11" class="crayon-line">
  // 方向控制
</div>
<div id="crayon-5b8f3a3238855483243812-12" class="crayon-line crayon-striped-line">
  if (this.pusher.y &gt;= this.pusherMaxY) {
</div>
<div id="crayon-5b8f3a3238855483243812-13" class="crayon-line">
    direction = -1;
</div>
<div id="crayon-5b8f3a3238855483243812-14" class="crayon-line crayon-striped-line">
    // 移动到最大长度时结束伸长技能
</div>
<div id="crayon-5b8f3a3238855483243812-15" class="crayon-line">
    this.isPusherLengthen = false;
</div>
<div id="crayon-5b8f3a3238855483243812-16" class="crayon-line crayon-striped-line">
  } else if (this.pusher.y &lt;= this.pusherMinY) {
</div>
<div id="crayon-5b8f3a3238855483243812-17" class="crayon-line">
    direction = 1;
</div>
<div id="crayon-5b8f3a3238855483243812-18" class="crayon-line crayon-striped-line">
  }
</div>
<div id="crayon-5b8f3a3238855483243812-19" class="crayon-line">
  // 速度控制
</div>
<div id="crayon-5b8f3a3238855483243812-20" class="crayon-line crayon-striped-line">
  this.pusher.y += direction * velocity;
</div>
<div id="crayon-5b8f3a3238855483243812-21" class="crayon-line">
  // 缩放控制,在最大长度变化时保持同样的缩放量,防止突然放大/缩小
</div>
<div id="crayon-5b8f3a3238855483243812-22" class="crayon-line crayon-striped-line">
  ratio = (1 - minScale) * ((this.pusher.y - minY) / (maxY - minY))
</div>
<div id="crayon-5b8f3a3238855483243812-23" class="crayon-line">
  this.pusher.scaleX = this.pusher.scaleY = minScale + ratio;
</div>
<div id="crayon-5b8f3a3238855483243812-24" class="crayon-line crayon-striped-line">
  // 同步控制,刚体跟推板位置同步
</div>
<div id="crayon-5b8f3a3238855483243812-25" class="crayon-line">
  Body.setPosition(this.pusher.body, { x: this.pusher.x, y: this.pusher.y });
</div>
<div id="crayon-5b8f3a3238855483243812-26" class="crayon-line crayon-striped-line">
})
</div>
</div></td>
</tr>
</tbody>
</table>

图片 2

为此用做遮挡处理,这里用 CreateJS 的 mask
遮罩属性可以生好的举行「溢起」裁剪:

JavaScript

var shape = new createjs.Shape();
shape.graphics.beginFill(‘#ffffff’).drawRect(0, 612, 750, 220);
this.pusher.mask = shape

1
2
3
var shape = new createjs.Shape();
shape.graphics.beginFill(‘#ffffff’).drawRect(0, 612, 750, 220);
this.pusher.mask = shape

末了效果如下:

图片 3

自动识图

作者于录入关卡配置时,发现一个7条边以上的接图很轻录错或录重线段。笔者在考虑是否开发一个自动识别图形的插件,毕竟「一画画」的图样是发生平整之几哪图形。

图片 4

点的关卡「底图」,一眼便可以识有三独颜色:

并且就三栽颜色以「底图」的面积大小顺序是:白底 > 线段颜色 >
端点颜色。底图的「采集色值表算法」很简单,如下伪代码:

JavaScript

let imageData = ctx.getImageData(); let data = imageData.data; // 色值表
let clrs = new Map(); for(let i = 0, len = data.length; i < len; i +=
4) { let [r, g, b, a] = [data[i], data[i + 1], data[i + 2],
data[i + 3]]; let key = `rgba(${r}, ${g}, ${b}, ${a})`; let value =
clrs.get(key) || {r, g, b, a, count: 0}; clrs.has(key) ? ++value.count :
clrs.set(rgba, {r, g, b, a, count}); }

1
2
3
4
5
6
7
8
9
10
let imageData = ctx.getImageData();
let data = imageData.data;
// 色值表
let clrs = new Map();
for(let i = 0, len = data.length; i < len; i += 4) {
let [r, g, b, a] = [data[i], data[i + 1], data[i + 2], data[i + 3]];
let key = `rgba(${r}, ${g}, ${b}, ${a})`;
let value = clrs.get(key) || {r, g, b, a, count: 0};
clrs.has(key) ? ++value.count : clrs.set(rgba, {r, g, b, a, count});
}

对于连通图来说,只要拿端点识别出来,连通图的大概为就是出去了。

调节方法

由于用了物理引擎,当以创建刚体时索要与 CreateJS
图形保持一致,这里可以使用 Matter.js 自带的 Render
为大体现象独立创建一个晶莹剔透的渲染层,然后盖在 CreateJS
场景之上,这里贴起大概代码:

JavaScript

Matter.Render.create({ element:
document.getElementById(‘debugger-canvas’), engine: this.engine,
options: { width: 750, height: 1206, showVelocity: true, wireframes:
false // 设置也非线框,刚体才可以渲染出颜色 } });

1
2
3
4
5
6
7
8
9
10
Matter.Render.create({
  element: document.getElementById(‘debugger-canvas’),
  engine: this.engine,
  options: {
    width: 750,
    height: 1206,
    showVelocity: true,
    wireframes: false // 设置为非线框,刚体才可以渲染出颜色
  }
});

安刚体的 render 属性为半透明色块,方便观察与调试,这里因为推板为条例:

JavaScript

this.pusher.body = Matter.Bodies.trapezoid( … // 略 { isStatic: true,
render: { opacity: .5, fillStyle: ‘red’ } });

1
2
3
4
5
6
7
8
9
this.pusher.body = Matter.Bodies.trapezoid(
… // 略
{
  isStatic: true,
  render: {
    opacity: .5,
    fillStyle: ‘red’
  }
});

成效如下,调试起来要十分便利的:

图片 5

娱乐之落实

「一笔画」的兑现无复杂,笔者把落实过程分成两步:

  1. 底图绘制
  2. 相绘制

「底图绘制」把连过渡图为「点线」的样式显得在画布上,是游玩最容易实现的一对;「交互绘制」是用户绘制解题路径的长河,这个历程会重点是处理点与点动态成线的逻辑。

奖品

出于奖品需要根据工作情况开展控制,所以将它们和金币进行了离别不举行打处理(内心是不容的),所以有了「螃蟹步」现象,这里虽未做了多介绍了。

特性优化

出于「自动识图」需要针对图像的底比如说素点进行扫描,那么性能确实是独待关注之题目。笔者设计之「自动识图算法」,在识别图像的历程被得对图像的像素做片浅扫描:「采集色值表」
与 「采集端点」。在扫描次数达其实非常不便下降了,但是对一张 750 * 1334
的底图来说,「自动识图算法」需要遍历两不好长度为
750 * 1334 * 4 = 4,002,000
的频繁组,压力还是会见有些。笔者是自从压缩为围观数组的尺码来提升性的。

给围观数组的尺寸怎么削减?
作者直接通过压缩画布的尺寸来上缩小为围观数组尺寸的。伪代码如下:

JavaScript

// 要缩减的倍数 let resolution = 4; let [width, height] = [img.width
/ resolution >> 0, img.height / resolution >> 0];
ctx.drawImage(img, 0, 0, width, height); let imageData =
ctx.getImageData(), data = imageData;

1
2
3
4
5
// 要压缩的倍数
let resolution = 4;
let [width, height] = [img.width / resolution >> 0, img.height / resolution >> 0];
ctx.drawImage(img, 0, 0, width, height);
let imageData = ctx.getImageData(), data = imageData;

把源图片缩小4倍增后,得到的图像素数组只有本的
4^2 = 16倍。这当性质达到是杀死的晋升。

金币

遵循常规思路,应该当点击屏幕时即当出币口创建金币刚体,让该以地心引力作用下自然落和回弹。但是于调试过程遭到发觉,金币掉落后和台面上别样金币产生冲击会促成乱飞现象,甚至会见卡壳到障碍物里面去(原因暂未知),后面改成为用
TweenJS 的 Ease.bounceOut
来实现金币掉落动画,让金币掉得到变得重可控,同时尽量接近自然落效果。这样金币从创立及没有过程尽管让拆分成了三独号:

点击屏幕从左右走的出币口创建金币,然后坠落到台面。需要小心的是,由于创建金币时是透过
appendChild 方式加入到舞台的,这样金币会非常有规律的以 z
轴方向上叠加,看起很稀奇,所以要自由设置金币的
z-index,让金币叠加更自然,伪代码如下:

JavaScript

var index = Utils.getRandomInt(1, Game.coinContainer.getNumChildren());
Game.coinContainer.setChildIndex(this.coin, index);

1
2
var index = Utils.getRandomInt(1, Game.coinContainer.getNumChildren());
Game.coinContainer.setChildIndex(this.coin, index);

是因为金币已经不欲重力场,所以待装物理世界的重力为
0,这样金币不会见因自身重量(需要装重量来控制打时走的进度)做自由落体运动,安安静静的平躺在台面上,等待和推板、其他金币和障碍物之间时有发生撞击:

JavaScript

this.engine = Matter.Engine.create(); this.engine.world.gravity.y = 0;

1
2
this.engine = Matter.Engine.create();
this.engine.world.gravity.y = 0;

鉴于打要逻辑都汇集者等级,所以拍卖起来会稍为复杂些。真实情况下一旦金币掉落并附着在推板上后,会尾随推板的伸缩而于带动,最终于推板缩进到最好差时于悄悄的墙壁阻挡而挤下推板,此过程看起大概但落实起来会要命耗时,最后因时间达紧的此处为开了简化处理,就是管推板是伸长还是缩进,都被推板上的金币向前「滑行」尽快离推板。若是金币离开推板则马上为该缔造同的刚体,为后续之拍做准备,这样就算水到渠成了金币的撞击处理。

JavaScript

Matter.Events.on(this.engine, ‘beforeUpdate’, function (event) { //
处理金币与推板碰撞 for (var i = 0; i < this.coins.length; i++) { var
coin = this.coins[i]; // 金币在推板上 if (coin.sprite.y <
this.pusher.y) { // 无论推板伸长/缩进金币都于前头挪 if (deltaY > 0)
{ coin.sprite.y += deltaY; } else { coin.sprite.y -= deltaY; } //
金币缩放 if (coin.sprite.scaleX < 1) { coin.sprite.scaleX += 0.001;
coin.sprite.scaleY += 0.001; } } else { // 更新刚体坐标 if (coin.body) {
Matter.Body.set(coin.body, { position: { x: coin.sprite.x, y:
coin.sprite.y } }) } else { // 金币离开推板则开创对应刚体 coin.body =
Matter.Bodies.circle(coin.sprite.x, coin.sprite.y);
Matter.World.add(this.world, [coin.body]); } } } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Matter.Events.on(this.engine, ‘beforeUpdate’, function (event) {
  // 处理金币与推板碰撞
  for (var i = 0; i < this.coins.length; i++) {
    var coin = this.coins[i];
    // 金币在推板上
    if (coin.sprite.y < this.pusher.y) {
      // 无论推板伸长/缩进金币都往前移动
      if (deltaY > 0) {
        coin.sprite.y += deltaY;
      } else {
        coin.sprite.y -= deltaY;
      }
      // 金币缩放
      if (coin.sprite.scaleX < 1) {
        coin.sprite.scaleX += 0.001;
        coin.sprite.scaleY += 0.001;
      }
    } else {
      // 更新刚体坐标
      if (coin.body) {
        Matter.Body.set(coin.body, { position: { x: coin.sprite.x, y: coin.sprite.y } })
      } else {
        // 金币离开推板则创建对应刚体
        coin.body = Matter.Bodies.circle(coin.sprite.x, coin.sprite.y);
        Matter.World.add(this.world, [coin.body]);
      }
    }
  }
})

趁金币不断的下、碰撞与倒,最终金币会从台面的下沿掉得到并没有,此阶段的处理同第一路,这里虽不更了。

H5游戏开发:一笔画

2017/11/07 · HTML5 ·
游戏

原稿出处: 坑坑洼洼实验室   

图片 6

技术实现

以是 2D 版本,所以无需要建造各种模型和贴图,整个娱乐场景通过 canvas
绘制,覆盖在背景图上,然后再开生机型适配问题,游戏主场景即处理得几近了,其他跟
3D
思路差不多,核心因素包含障碍物、推板、金币、奖品和技艺,接下便分别介绍其的兑现思路。

线条识别

作者分点儿个步骤完成「线段识别」:

  1. 加的蝇头只端点连接成线,并采集连线上N个「样本点」;
  2. 遍历样本点像素,如果如素色值不等于线段色值则象征马上有限只端点之间未存线段

什么样搜集「样式点」是单问题,太密集会影响性;太松精准度不克担保。

当笔者面前有零星个挑选:N 是常量;N 是变量。
假设 N === 5。局部提取「样式点」如下:

图片 7

及图,会识别出三长长的线:AB, BC 和 AC。而事实上,AC不可知成线,它只是是以
AB 和 BC 视觉上同台一丝之结果。当然将 N 值向上提高得缓解是题材,不过 N
作为常量的话,这个常量的取量需要靠经验来判定,果然放弃。

为了避免 AB 与 BC 同处一直线时 AC 被识别成线,其实生简单 ——
简单独「样本点」的区间小于或等端点直径
假设 N = S / (2 * R),S 代表两碰之离开,R
表示端点半径。局部提取「样式点」如下:

图片 8

而达到图,成功地缠绕了了 AC。「线段识别算法」的伪代码实现如下:

JavaScript

for(let i = 0, len = vertexes.length; i < len – 1; ++i) { let {x: x1,
y: y1} = vertexes[i]; for(let j = i + 1; j < len; ++j) { let {x:
x2, y: y2} = vertexes[j]; let S = Math.sqrt(Math.pow(x1 – x2, 2) +
Math.pow(y1 – y2, 2)); let N = S / (R * 2); let stepX = (x1 – x2) / N,
stepY = (y1 – y2) / n; while(–N) { // 样本点不是线段色
if(!isBelongLine(x1 + N * stepX, y1 + N * stepY)) break; } //
样本点都过关 —- 表示两接触成线,保存 if(0 === N) lines.push({x1, y1, x2,
y2}) } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for(let i = 0, len = vertexes.length; i < len – 1; ++i) {
let {x: x1, y: y1} = vertexes[i];
for(let j = i + 1; j < len; ++j) {
let {x: x2, y: y2} = vertexes[j];
let S = Math.sqrt(Math.pow(x1 – x2, 2) + Math.pow(y1 – y2, 2));
let N = S / (R * 2);
let stepX = (x1 – x2) / N, stepY = (y1 – y2) / n;
while(–N) {
// 样本点不是线段色
if(!isBelongLine(x1 + N * stepX, y1 + N * stepY)) break;
}
// 样本点都合格 —- 表示两点成线,保存
if(0 === N) lines.push({x1, y1, x2, y2})
}
}

初期预研

每当感受过 AppStore 上某些放缓推金币游戏 App
后,发现游戏中心模型或特别简单的,不过 H5
版的实现在网上挺少见。由于组织一直于做 2D 类互动小游戏,在 3D
方向暂时没实际的项目输出,然后做此次游戏的性状,一开始想挑战用 3D
来促成,并因为这个项目为突破口,跟设计师进行深合作,抹平开发过程的各种障碍。

图片 9

出于时日紧,需要以紧缺日外敲定方案可行性,否则路推迟人头不保险。在飞速尝试了
Three.js + Ammo.js 方案后,发现不尽人意,最终为每方面原因放弃了 3D
方案,主要是匪可控因素太多:时间达到、设计与技术经历及、移动端 WebGL
性能表现上,主要还是事情达到需要针对游乐有绝对的控制,加上是第一涂鸦接手复杂的小游戏,担心项目无法正常上丝,有点保守,此方案遂卒。

设读者出趣味之口舌可以品尝下 3D 实现,在建模方面,首推
Three.js
,入手非常简单,文档和案例为酷详尽。当然入门的说话肯定推就首
Three.js入门指南,另外同事分享的即时首
Three.js
现学现卖
也得看看,这里接受上粗的 推金币 3D 版
Demo

端点识别

辩驳及,通过收集的「色值表」可以直接拿端点的坐标识别出来。笔者设计之「端点识别算法」分以下2步:

  1. 比如像从扫描底图直到撞「端点颜色」的像素,进入次步
  2. 于底图上消除端点并记下她的坐标,返回继续第一步

伪代码如下:

JavaScript

for(let i = 0, len = data.length; i < len; i += 4) { let [r, g, b,
a] = [data[i], data[i + 1], data[i + 2], data[i + 3]]; //
当前如素颜色属于端点 if(isBelongVertex(r, g, b, a)) { // 在 data
被清空端点 vertex = clearVertex(i); // 记录端点信息
vertexes.push(vertext); } }

1
2
3
4
5
6
7
8
9
10
for(let i = 0, len = data.length; i < len; i += 4) {
let [r, g, b, a] = [data[i], data[i + 1], data[i + 2], data[i + 3]];
// 当前像素颜色属于端点
if(isBelongVertex(r, g, b, a)) {
// 在 data 中清空端点
vertex = clearVertex(i);
// 记录端点信息
vertexes.push(vertext);
}
}

But…
上面的算法就能够跑无损图。笔者在使了同一摆设手机截屏做测试的时刻发现,收集至之「色值表」长度也
5000+ !这直接促成端点和线的色值无法直接得到。

通过分析,可以窥见「色值表」里大部分色值都是看似的,也便是以原先的「采集色值表算法」的根基及补偿加一个类颜色过滤即好查找来端点和线的主色。伪代码实现如下:

JavaScript

let lineColor = vertexColor = {count: 0}; for(let clr of clrs) { //
与底色相近,跳了 if(isBelongBackground(clr)) continue; //
线段是数量第二大多的颜色,端点是第三大抵的水彩 if(clr.count >
lineColor.count) { [vertexColor, lineColor] = [lineColor, clr] } }

1
2
3
4
5
6
7
8
9
let lineColor = vertexColor = {count: 0};
for(let clr of clrs) {
// 与底色相近,跳过
if(isBelongBackground(clr)) continue;
// 线段是数量第二多的颜色,端点是第三多的颜色
if(clr.count > lineColor.count) {
[vertexColor, lineColor] = [lineColor, clr]
}
}

获取到端点的主色后,再走同一次「端点识别算法」后居识别出 203
个端点!这是干吗吗?

图片 10

及图是拓宽5倍增后的底图局部,蓝色端点的四周及中间充斥在大量噪点(杂色块)。事实上在「端点识别」过程被,由于噪点的存,把本的端点被解释变成十几个或数十单稍端点了,以下是飞过「端点识别算法」后的底图:

图片 11

由此上图,可以直观地查获一个定论:识别出的小端点只以目标(大)端点上汇集分布,并且大端点范围外的小端点叠加交错。

设若将叠加交错的小端点归并化作一个多边点,那么这大端点将十分类目标端点。小端点的联结伪代码如下:

JavaScript

for(let i = 0, len = vertexes.length; i < len – 1; ++i) { let vertexA
= vertexes[i]; if(vertextA === undefined) continue; // 注意这里 j = 0
而非是 j = i +1 for(let j = 0; j < len; ++j) { let vertexB =
vertexes[j]; if(vertextB === undefined) continue; //
点A与点B有增大,点B合并及点A并去点B if(isCross(vertexA, vertexB)) {
vertexA = merge(vertexA, vertexB); delete vertexA; } } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for(let i = 0, len = vertexes.length; i < len – 1; ++i) {
let vertexA = vertexes[i];
if(vertextA === undefined) continue;
// 注意这里 j = 0 而不是 j = i +1
for(let j = 0; j < len; ++j) {
let vertexB = vertexes[j];
if(vertextB === undefined) continue;
// 点A与点B有叠加,点B合并到点A并删除点B
if(isCross(vertexA, vertexB)) {
vertexA = merge(vertexA, vertexB);
delete vertexA;
}
}
}

加了小端点归并算法后,「端点识别」的准确度就上去了。经笔者本地测试就可以
100% 识别有误的接图了。

事件销毁

鉴于金币和奖生命周期内使用了 Tween,当他们由屏幕及消灭后记忆移除掉:

JavaScript

createjs.Tween.removeTweens(this.coin);

1
createjs.Tween.removeTweens(this.coin);

至此,推金币各个关键环节都生提到了,最后附上一摆实际游戏效果:
图片 12

H5游戏开发:一笔画画

by leeenx on 2017-11-02

同等笔画是图论[科普](https://zh.wikipedia.org/wiki/%E5%9B%BE%E8%AE%BA)倍受一个著名的题材,它起源于柯尼斯堡七桥问题[科普](https://zh.wikipedia.org/wiki/%E6%9F%AF%E5%B0%BC%E6%96%AF%E5%A0%A1%E4%B8%83%E6%A1%A5%E9%97%AE%E9%A2%98)。数学家欧拉在他1736年发表的舆论《柯尼斯堡之七桥》中不仅化解了七桥题材,也提出了同样笔画画定理,顺带解决了一样笔画问题。用图论的术语来说,对于一个加的连通图[科普](https://zh.wikipedia.org/wiki/%E8%BF%9E%E9%80%9A%E5%9B%BE)在一样漫长刚含有有线段并且没有还的门径,这长达路线就是是「一笔画」。

寻并过渡图立即条路的经过尽管是「一画画」的游艺经过,如下:

图片 13

安卓卡顿

一律开始是吃推板一个稳定的进度进行伸缩处理,发现在 iOS
上呈现流畅,但是当有的安卓机上也亮差强人意。由于一些安卓机型 FPS
比较小,导致推板在单位时间内各移比较小,表现出来就是显示卡顿不流利。后面让推板位移根据刷新时不同进行递增/减,保证不同帧频机型下都能保持一致的运动,代码大致如下:

JavaScript

var delta = 0, prevTime = 0; Matter.Events.on(this.engine,
‘beforeUpdate’, function (event) { delta = event.timestamp – prevTime;
prevTime = event.timestamp; // … 略 this.pusher.y += direction *
velocity * (delta / 1000) })

1
2
3
4
5
6
7
var delta = 0, prevTime = 0;
Matter.Events.on(this.engine, ‘beforeUpdate’, function (event) {
  delta = event.timestamp – prevTime;
  prevTime = event.timestamp;
  // … 略
  this.pusher.y += direction * velocity * (delta / 1000)
})

结语

下面是本文介绍的「一画画」的线及
DEMO 的次维码:

图片 14

娱的源码托管在:https://github.com/leeenx/OneStroke
个中玩实现之重头戏代码在:https://github.com/leeenx/OneStroke/blob/master/src/script/onestroke.es6
自动识图的代码在:https://github.com/leeenx/OneStroke/blob/master/src/script/oneStrokePlugin.es6

谢耐心看完本文章的读者。本文特表示作者的个人观点,如发生不妥之远在请求不吝赐教。

谢你的阅读,本文由 坑坑洼洼实验室
版权所有。如要转载,请注明出处:凹凸实验室(https://aotu.io/notes/2017/11/02/onestroke/)

1 赞 1 收藏
评论

图片 15

属性/体验优化

交互绘制

当画布上制图路径,从视觉及即「选择还是连续连通图端点」的过程,这个过程得缓解2单问题:

采访连通图端点的坐标,再监听手指滑了之坐标可以解「手指下是否出硌」。以下伪代码是采端点坐标:

JavaScript

// 端点坐标信息 let coords = []; lines.forEach(({x1, y1, x2, y2})
=> { // (x1, y1) 在 coords 数组不设有 if(!isExist(x1, y1))
coords.push([x1, y1]); // (x2, y2) 在 coords 数组不存在
if(!isExist(x2, y2)) coords.push([x2, y2]); });

1
2
3
4
5
6
7
8
// 端点坐标信息
let coords = [];
lines.forEach(({x1, y1, x2, y2}) => {
// (x1, y1) 在 coords 数组不存在
if(!isExist(x1, y1)) coords.push([x1, y1]);
// (x2, y2) 在 coords 数组不存在
if(!isExist(x2, y2)) coords.push([x2, y2]);
});

以下伪代码是监听手指滑动:

JavaScript

easel.addEventListener(“touchmove”, e => { let x0 =
e.targetTouches[0].pageX, y0 = e.targetTouches[0].pageY; // 端点半径
—— 取连通图端点半径的2加倍,提升活动端体验 let r = radius * 2;
for(let [x, y] of coords){ if(Math.sqrt(Math.pow(x – x0, 2) +
Math.pow(y – y0), 2) <= r){ // 手指下有端点,判断能否连线
if(canConnect(x, y)) { // todo } break; } } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
easel.addEventListener("touchmove", e => {
let x0 = e.targetTouches[0].pageX, y0 = e.targetTouches[0].pageY;
// 端点半径 —— 取连通图端点半径的2倍,提升移动端体验
let r = radius * 2;
for(let [x, y] of coords){
if(Math.sqrt(Math.pow(x – x0, 2) + Math.pow(y – y0), 2) <= r){
// 手指下有端点,判断能否连线
if(canConnect(x, y)) {
// todo
}
break;
}
}
})

每当未绘制任何线段或端点之前,手指滑了之任意端点都见面给当「一笔画」的起始点;在绘制了线(或来选中点)后,手指滑了之端点能否与选中点错并成线段用基于现有条件进行判定。

图片 16

达图,点A与点B可连日来成线,而点A与点C不克连。笔者把「可以和指定端点连接成线段的端点称作中连接点」。连通图端点的管用连接点从并通图的线条中取:

JavaScript

coords.forEach(coord => { // 有效连接点(坐标)挂载在端点坐标下
coord.validCoords = []; lines.forEach(({x1, y1, x2, y2}) => { //
坐标是眼前线的起点 if(coord.x === x1 && coord.y === y1) {
coord.validCoords.push([x2, y2]); } // 坐标是时下线的巅峰 else
if(coord.x === x2 && coord.y === y2) { coord.validCoords.push([x1,
y1]); } }) })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
coords.forEach(coord => {
// 有效连接点(坐标)挂载在端点坐标下
coord.validCoords = [];
lines.forEach(({x1, y1, x2, y2}) => {
// 坐标是当前线段的起点
if(coord.x === x1 && coord.y === y1) {
coord.validCoords.push([x2, y2]);
}
// 坐标是当前线段的终点
else if(coord.x === x2 && coord.y === y2) {
coord.validCoords.push([x1, y1]);
}
})
})

But…有效连接点只能判断两单点是否也底图的线条,这才是一个静态的参考,在实质上的「交互绘制」中,会遇上以下情形:

图片 17
一经达到图,AB已串并成线,当前选中点B的中连接点是 A 与 C。AB
已经连续成线,如果 BA 也差并成线,那么线段就是再次了,所以这时候 BA
不克成线,只有 AC 才能够成线。

针对选中点而言,它的得力连接点有一定量种:

其间「未成线的有效连接点」才会与「交互绘制」,并且她是动态的。

图片 18

回头本节内容开始提的个别只问题「手指下是否生端点」 与
「选中点及待选中点之间是否成线」,其实只是统一为一个题目:手指下是否在「未成线的得力连接点」。只须将监听手指滑动遍历的数组由连通图所有的端点坐标
coords 替换为当前选中点的「未成线的灵光连接点」即可。

至此「一笔画」的要害成效已经实现。可以抢体验一下:

图片 19

https://leeenx.github.io/OneStroke/src/onestroke.html

支配目标数量

趁着游戏的频频台面上积累的金币数量会连长,金币之间的碰撞计算量也会见陡增,必然会造成手机卡顿以及发热。这时就需要控制金币的重叠度,而金币之间重叠的区域大小是出于金币刚体的尺寸大小决定的,通过适当的调整刚体半径为金币分布得比全匀,这样可以使得控制金币数量,提升游戏性。

结语

感各位耐心读毕,希望能具有得,有考虑不足之地方迎留言指出。

连带资源

Three.js 官网

Three.js入门指南

Three.js
现学现卖

Matter.js 官网

Matter.js 2D
物理引擎试玩报告

游戏
createjs
h5
canvas
game
推金币
matter.js

Web开发

谢谢您的读书,本文由 坑坑洼洼实验室
版权所有。如一旦转载,请注明出处:凹凸实验室(https://aotu.io/notes/2017/11/06/coindozer/)

上次创新:2017-11-08 19:29:54

2 赞 收藏 1
评论

图片 20

技能设计

形容好游戏主逻辑下,技能就属于锦上添花的业务了,不过让游玩又富有可玩性,想想金币哗啦啦往生掉的发还是好硬的。

抖动:这里取了只刚,是让舞台容器上加了 CSS3
实现之振荡效果,然后于震荡时间外给抱有的金币的 y
坐标累加固定值产生完全慢慢前换效果,由于什么卓下支持系统震动
API,所以加了个彩蛋让游戏体验更实在。

CSS3 抖动实现重点是参考了
csshake
这个样式,非常幽默的等同组抖动动画集合。

JS 抖动 API

JavaScript

// 安卓震动 if (isAndroid) { window.navigator.vibrate =
navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate ||
navigator.msVibrate; window.navigator.vibrate([100, 30, 100, 30, 100,
200, 200, 30, 200, 30, 200, 200, 100, 30, 100, 30, 100]);
window.navigator.vibrate(0); // 停止抖动 }

1
2
3
4
5
6
// 安卓震动
if (isAndroid) {
  window.navigator.vibrate = navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate;
  window.navigator.vibrate([100, 30, 100, 30, 100, 200, 200, 30, 200, 30, 200, 200, 100, 30, 100, 30, 100]);
  window.navigator.vibrate(0); // 停止抖动
}

伸长:伸长处理吧殊简单,通过变更推板移动的绝要命 y
坐标值让金币产生重复甚的倒距离,不过细节上出几碰得专注的地方,在推板最酷
y 坐标值改变以后需要保障移动速度不移,不然就是会见产生「瞬移」(不平易)问题。

障碍物

透过审稿确定金币以及奖品的移动区域,然后将运动区域之外的区域都作为障碍物,用来限制金币的活动范围,防止金币碰撞时盖边界。这里可以用
Matter.js 的 Bodies.fromVertices
方法,通过传播边界各套的顶峰坐标一次性绘制有形象不规则的障碍物。 不过
Matter.js 在渲染不规则状时在问题,需要引入
poly-decomp 做配合处理。

图片 21

JavaScript

World.add(this.world, [ Bodies.fromVertices(282, 332,[ // 顶点坐标 {
x: 0, y: 0 }, { x: 0, y: 890 }, { x: 140, y: 815 }, { x: 208, y: 614 },
{ x: 548, y: 614 }, { x: 612, y: 815 }, { x: 750, y: 890 }, { x: 750, y:
0 } ]) ]);

1
2
3
4
5
6
7
8
9
10
11
12
13
World.add(this.world, [
  Bodies.fromVertices(282, 332,[
    // 顶点坐标
    { x: 0, y: 0 },
    { x: 0, y: 890 },
    { x: 140, y: 815 },
    { x: 208, y: 614 },
    { x: 548, y: 614 },
    { x: 612, y: 815 },
    { x: 750, y: 890 },
    { x: 750, y: 0 }
  ])
]);

技能选型

舍了 3D 方案,在 2D 技术选型上便不行从容了,最终确定用
CreateJS + Matter.js 组合作吗渲染引擎和物理引擎,理由如下:

背景介绍

一年一度的双十一狂欢购物节即将拉开序幕,H5
互动类小游戏作为京东微信手Q营销特色玩法,在当年预热期底第一波造势中,势必要玩点新花样,主要担负着张罗传播及发券的目的。推金币为传统街机推币机为原型,结合手机强大的力量以及生态衍生出可玩性很高的玩法。

相关文章

标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图