在小程序中用DOM的方式实现矿工游戏

在家已经憋了一个多月了,在此期间,新产品的需求也还在继续着。
然后上周接到了一个新的小程序需求,就是还原一个挖矿的游戏,但内部还兼顾了积分商城和抽奖等页面
综合考虑来看的话,是不适合用小游戏来做的,所以只能想办法用网页来实现挖矿的展现。

布局

先敲定布局,待抓取物品使用固定的坐标,在一个固定容器里进行摆放。抓手自身固定一个初始高度,然后绳子和抓钩分离,绳子以绝对定位保持顶部和底部固定,这样,当外部抓手的高度调整时,就可以做到伸出的一个效果。

初始状态

在页面的onShow事件里先记录一下抓手的初始状态(以垂直向下为初始),其中moved属性,是控制抓手左右摆动的开关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Page({
...
...
onShow(){
self.createSelectorQuery().select("#thehands").boundingClientRect((res)=>{
self.setData({
oheight: res.height,
owidth: res.width,
otop: res.top,
oleft: res.left,
oright: res.right,
moved: true
});
}).exec();
}
...
...
});

之后抓手就会依据css动画中写好的进行左右摆动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@keyframes thehandanimate{
0%{
transform: rotate(0);
}
25%{
transform: rotate(-45deg);
}
75%{
transform: rotate(45deg);
}
100%{
transform: rotate(0);
}
}
.topcontent .rotateblock{
position: absolute;
...
...
transform-origin: 50% 0;
animation: thehandanimate 4s infinite linear;
}
.topcontent .rotateblock.paused{
animation-play-state: paused;
}

注意设定transform-origin的值,使变形的基准点在绳子根部。由于在抓手伸长的时候,要使用小程序的动画功能,所以左右摆动的动画要设置在外层的view里,这样动画就不会冲突了。

按下抓取后的状态判断

当按下抓取按钮的时候,停止摆动view的动画,然后根据当前抓手的位置及大小数据,计算当前摆动的角度,以竖直向下为基准。然后接下来需要判断这次能否抓取到物品。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Page({
...
...
dopickup(){
let
self = this;
if(self.data.picking){
return;
}
self.setData({
picking: true
});
self.createSelectorQuery().select("#thehands").boundingClientRect((res)=>{
let
tleft = res.left,
tright = res.right,
owidth = self.data.owidth,
oleft = self.data.oleft,
oright = self.data.oright,
angle = -1;
if(oleft > tleft){
// console.log("向左转");
angle = 180 * Math.acos((owidth / 2 - Math.abs(tright - oright)) / (owidth / 2)) / Math.PI;
}else if(oleft < tleft){
// console.log("向右转");
angle = -180 * Math.acos((owidth / 2 - Math.abs(tleft - oleft)) / (owidth / 2)) / Math.PI;
}else{
// console.log("竖直向下");
angle = 0;
}
// console.log("角度",angle);
self.setData({
waitcheck: true,
moved: false,
theAngle: angle
});
//判断是否能抓取到物体
self.checkPickUp();
}).exec();
},
...
...
});

碰撞检测

由于使用css动画实现抓手伸缩,所以在碰撞检测上,我采用的是先检测,再执行动画的方法。先使用小程序自带的方法createSelectorQuery提取所有待抓取物品的位置信息,然后计算物品基于抓手的可抓取角度。之后判断当前抓手的角度是否在物品的可抓取角度之内,把所有在抓取范围内的物品,到抓手的距离进行比对,找出最近的一个即可。

在计算物品的可抓取角度时,我将判断基准由一开始的最边缘,改成了抓手的中线,一是可以降低计算难度,二是也更贴近现实,只擦了一点边就抓到总感觉怪怪的。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
Page({
...
...
checkPickUp(){
let
self = this,
owidth = self.data.owidth,
oleft = self.data.oleft,
angle = self.data.theAngle,
tmps = [],
lineitem = [],
catchItem = null;
oleft += owidth / 2;
self.createSelectorQuery().selectAll(".theitem").fields({
id: true,
size: true,
computedStyle: ["top", "left", "margin-left"]
},(rects)=>{
tmps = rects;
checkOut();
}).exec();

function checkOut(){
for(let i in tmps){
//计算每个物品的角度,找出路线上的所有物品
let
j = tmps[i],
checkAngle = getItemTouchAngle(j);
if(angle <= checkAngle.left && angle >= checkAngle.right){
lineitem.push(j);
}
}
//遍历所有路径上的物品,找出最近的一个
if(lineitem.length > 0){
for(let j in lineitem){
let item = lineitem[j];
item.movelen = Math.sqrt(Math.pow(item.top, 2) + Math.pow(item.left - oleft, 2)) + 10;
if(!catchItem || catchItem.movelen > item.movelen){
catchItem = item;
}
}
self.setData({
catchItem: catchItem
});
setTimeout(()=>{
//抓取物品
self.handsDown();
}, 200);
}else{
//没有抓到物品
self.createSelectorQuery().select("#pickupblock").boundingClientRect((res)=>{
let
len = 0,
tangle = Math.abs(angle);
tangle = tangle * Math.PI / 180;
if(tangle > 30){
len = res.width / 2 / Math.sin(tangle);
}else{
len = res.height / Math.cos(tangle);
}
self.setData({
catchItem: {
id: -1,
movelen: len
}
});
setTimeout(()=>{
self.handsDown();
}, 200);
}).exec();
}
}
function getItemTouchAngle(item){
item.top = Util.pxToInt(item.top);
item.left = Util.pxToInt(item.left);
item.marginLeft = Util.pxToInt(item["margin-left"]);
item.top += item.marginLeft;
item.left += item.marginLeft;
let
left = 0,
right = 0,
thetop = item.top + item.height / 2,
theleft = item.left + item.width / 2;
//碰撞检测修改为,中线碰撞边缘,而非边缘碰撞边缘
if(thetop == oleft){
//在中线
left = 180 * Math.atan((item.width / 2) / item.top) / Math.PI;
right = -180 * Math.atan((item.width / 2) / item.top) / Math.PI;
}else if(theleft < oleft){
//在左侧
left = 180 * Math.atan((oleft - item.left) / item.top) / Math.PI;
if(item.left + item.width > oleft){
//偏右
right = -180 * Math.atan((item.left + item.width - oleft) / item.top) / Math.PI;
}else if(item.left + item.width < oleft){
//偏左
right = 180 * Math.atan((oleft - item.left - item.width) / (item.top + item.height)) / Math.PI;
}else{
//竖直向下 right = 0
}
}else{
//在右侧
right = -180 * Math.atan((item.left + item.width - oleft) / item.top) / Math.PI;
if(item.left < oleft){
//偏左
left = 180 * Math.atan((oleft - item.left) / item.top) / Math.PI;
}else if(item.left > oleft){
//偏右
left = -180 * Math.atan((item.left - oleft) / (item.top + item.height)) / Math.PI;
}else{
//竖直向下 left = 0
}
}
return {left: left, right: right};
}
},
...
...
});

完成抓取

最后就是完成抓手的抓取动画,有了运动长度,直接让抓手伸长,在完成伸长动画之后,将被抓取物品隐藏,并在抓手这端,显示一个同样的物品,最后将抓手收回原始的长度即可。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Page({
...
...
handsDown(){
let
self = this,
catchItem = self.data.catchItem,
animateObj = self.animateObj;
self.setData({
waitcheck: false
});
animateObj.height(catchItem.movelen).step({
duration: self.data.animateDuration
});
let animatedata = animateObj.export();
self.setData({
animatedata: animatedata
});
setTimeout(self.handsUp, self.data.animateDuration);
},
handsUp(){
let
self = this,
catchItem = self.data.catchItem,
theitem = null,
animateObj = self.animateObj;
animateObj.height(self.data.oheight).step();
let animatedata = animateObj.export();
self.setData({
animatedata: animatedata
});
if(catchItem.id != -1){
let
itemid = catchItem.id.lastIndexOf("_");
itemid = catchItem.id.substr(itemid + 1);
theitem = self.data.pickup_items[itemid];
self.setData({
showCatchItem: true,
[`pickup_items[${itemid}].active`]: false,
[`catchItem.icon`]: theitem.theitem.item_icon
});
}
setTimeout(()=>{
if(catchItem.id == -1){
//没抓
}else{
//抓到
}
self.setData({
moved: true,
catchItem: null,
picking: false,
theAngle: null,
animatedata: null,
showCatchItem: false
});
}, self.data.animateDuration);
},
...
...
});