添加文档
BIN
doc/Coin_fly_to_wallet/resourse/fly_gold.gif
Normal file
After Width: | Height: | Size: 366 KiB |
BIN
doc/Coin_fly_to_wallet/resourse/fly_gold_circle_0.png
Executable file
After Width: | Height: | Size: 172 KiB |
BIN
doc/Coin_fly_to_wallet/resourse/fly_gold_circle_1.png
Executable file
After Width: | Height: | Size: 94 KiB |
BIN
doc/Coin_fly_to_wallet/resourse/fly_gold_circle_2.png
Executable file
After Width: | Height: | Size: 36 KiB |
BIN
doc/Coin_fly_to_wallet/resourse/preview_ewm.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
68
doc/Coin_fly_to_wallet/金币落袋效果.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# 效果演示
|
||||||
|
金币从初始点散开然后逐个飞落到指定的位置,这是游戏里面很常用的一个动画,效果如下
|
||||||
|
![金币落袋](./resourse/fly_gold.gif)
|
||||||
|
|
||||||
|
# 实现思路
|
||||||
|
要实现这个效果,我们已知三个条件,分别是起点位置,终点位置,金币个数。
|
||||||
|
|
||||||
|
## 生成金币位置
|
||||||
|
金币散开的位置看起来很随机,但是经过我们的拆解,其实它的第一步是先生成一个标准的圆。假设我们现在是8个金币,我们知道起点的坐标,如何求这8个金币的散开位置呢,这其实是一个数学问题。
|
||||||
|
`8`个金币平分一个圆,每个金币夹角是`360度 / 8 = 45度`,假设圆的半径`r`是确定的,我们又知道圆心的坐标,结合三角函数我们就能够很轻易算出每个金币的位置。
|
||||||
|
![fly_gold_circle_0](./resourse/fly_gold_circle_0.png)
|
||||||
|
|
||||||
|
当我们拥有每一个金币的标准位置之后,再给它们每个的位置叠加一个随机偏移,这样子他们的位置看起来就是围绕着起点做随机分布
|
||||||
|
|
||||||
|
![fly_gold_circle_1](./resourse/fly_gold_circle_1.png)
|
||||||
|
|
||||||
|
以上代码如下
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* 以某点为圆心,生成圆周上等分点的坐标
|
||||||
|
*
|
||||||
|
* @param {number} r 半径
|
||||||
|
* @param {cc.Vec2} pos 圆心坐标
|
||||||
|
* @param {number} count 等分点数量
|
||||||
|
* @param {number} [randomScope=80] 等分点的随机波动范围
|
||||||
|
* @returns {cc.Vec2[]} 返回等分点坐标
|
||||||
|
*/
|
||||||
|
getCirclePoints(r: number, pos: cc.Vec2, count: number, randomScope: number = 60): cc.Vec2[] {
|
||||||
|
let points = [];
|
||||||
|
let radians = (Math.PI / 180) * Math.round(360 / count);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
let x = pos.x + r * Math.sin(radians * i);
|
||||||
|
let y = pos.y + r * Math.cos(radians * i);
|
||||||
|
points.unshift(cc.v3(x + Math.random() * randomScope, y + Math.random() * randomScope, 0));
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 金币落袋
|
||||||
|
拆分效果可以看到,金币落袋的过程中还有先后的顺序,我们在实现的时候先计算一下每个金币到袋子的距离,然后做个排序,让距离袋子近的金币先执行进袋的动画,远的后执行。
|
||||||
|
![fly_gold_circle_2](./resourse/fly_gold_circle_2.png)
|
||||||
|
代码的实现如下:
|
||||||
|
```js
|
||||||
|
goldNodeList.sort(node => node.dis);
|
||||||
|
|
||||||
|
goldNodeList.forEach((node, idx) => {
|
||||||
|
node.gold.runAction(cc.sequence(
|
||||||
|
cc.moveTo(0.3, node.startPoint),
|
||||||
|
cc.delayTime(idx * 0.03),
|
||||||
|
cc.moveTo(0.6, node.endPoint),
|
||||||
|
cc.callFunc(() => {
|
||||||
|
this.goldPool.put(node.gold);
|
||||||
|
})
|
||||||
|
))
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 效果预览
|
||||||
|
|
||||||
|
源码获取请点击**查看原文**,长按二维码查看效果👇
|
||||||
|
|
||||||
|
![ewm](./resourse/preview_ewm.png)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
doc/Infinite_bg_scroll/resourse/Infinite_bg_scroll.gif
Normal file
After Width: | Height: | Size: 375 KiB |
BIN
doc/Infinite_bg_scroll/resourse/detail.png
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
doc/Infinite_bg_scroll/resourse/overflow.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
doc/Infinite_bg_scroll/resourse/preview_ewm.png
Normal file
After Width: | Height: | Size: 17 KiB |
56
doc/Infinite_bg_scroll/背景无限滚动.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 效果演示
|
||||||
|
|
||||||
|
这是游戏里面很常用的一个功能模块,它就像你的生活,有着走不完的路程。它的实现也很简单,要么做一个很长的背景图,然后移动相机;要么就是实现一个跑马灯,像那些轮播图什么的,大家应该都有写过。
|
||||||
|
|
||||||
|
![Infinite_bg_scroll](./resourse/Infinite_bg_scroll.gif)
|
||||||
|
|
||||||
|
# 实现思路
|
||||||
|
|
||||||
|
## 背景无缝切换实现
|
||||||
|
|
||||||
|
这个demo的实现思路是跑马灯。背景是两张一样的背景图,第一张图在前,第二张图在后,两张图同时移动,当第一张图正好移动到出屏幕的时候,第二张图正好移进屏幕,这个时候复原两张图的初始位置。这个过程不停循环就有走不完的路程了。
|
||||||
|
|
||||||
|
![detail](./resourse/detail.png)
|
||||||
|
|
||||||
|
代码如下
|
||||||
|
```js
|
||||||
|
speed: number = 500;
|
||||||
|
update(dt) {
|
||||||
|
const temp = dt * this.speed;
|
||||||
|
if (this.bg2.x - temp <= 0) {
|
||||||
|
this.bg1.x = this.bg2.x;
|
||||||
|
this.bg2.x = this.bg1.x + this.bg1.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bg1.x -= temp;
|
||||||
|
this.bg2.x -= temp;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全屏适配
|
||||||
|
|
||||||
|
因为是整个背景都在跑动,所以两个背景节点都是需要做`widget`拉伸的,第一张背景上下左右都设置为0就可以了,第二种背景往右再偏移一个屏幕。但是这样是不够的,因为不是所有用户的屏幕都是按我们的设计分辨率来的,所以跑着跑着可能你的背景就露馅了
|
||||||
|
|
||||||
|
![overflow](./resourse/overflow.jpg)
|
||||||
|
|
||||||
|
所以我们在onload的时候还是得手动去对齐一下bg2的位置,因为它在布局编辑器里面只是往右移动了我们的设计分辨率一样宽的距离。(bg1肯定是对的,因为使用了widget对齐了当前屏幕)
|
||||||
|
|
||||||
|
```js
|
||||||
|
onLoad() {
|
||||||
|
const viewSize = cc.view.getVisibleSize();
|
||||||
|
this.bg2.getComponent(cc.Widget).left = viewSize.width
|
||||||
|
this.bg2.getComponent(cc.Widget).right = -viewSize.width
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 效果预览
|
||||||
|
|
||||||
|
源码获取请点击**查看原文**,长按二维码查看效果👇
|
||||||
|
|
||||||
|
![ewm](./resourse/preview_ewm.png)
|
||||||
|
|
||||||
|
我是异名,你的阅读是我的动力
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
doc/Joystick/resourse/Joystick.gif
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
doc/Joystick/resourse/calc_point_pos.png
Executable file
After Width: | Height: | Size: 106 KiB |
BIN
doc/Joystick/resourse/handle_point.png
Executable file
After Width: | Height: | Size: 103 KiB |
BIN
doc/Joystick/resourse/preview_ewm.png
Executable file
After Width: | Height: | Size: 6.3 KiB |
64
doc/Joystick/遥控杆.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# 效果演示
|
||||||
|
|
||||||
|
这是游戏里面很常用的一个功能模块,通过操控遥控杆控制物体的移动
|
||||||
|
|
||||||
|
![Joystick](./resourse/Joystick.gif)
|
||||||
|
|
||||||
|
# 实现思路
|
||||||
|
|
||||||
|
遥控杆的构造分为中间的控制点和外层的圆形,首先给遥控杆绑定个触控事件,然后在`touch_move`的时候让触控杆保持在圆形中,同时把鼠标的位置偏移信息传给需要移动的物体。
|
||||||
|
|
||||||
|
## 控制杆逻辑
|
||||||
|
|
||||||
|
用户点击的时候分两种情况,一种是用户的点击位置能够让控制点完全落在圆形内,这个时候让控制点直接移动到用户点击的位置即可;另外一种是用户的点击位置会让控制点会落在圆形外,那就要做一个计算,让控制点的位置维持在圆形内。
|
||||||
|
|
||||||
|
![handle_point](./resourse/handle_point.png)
|
||||||
|
|
||||||
|
这个计算也是很简单,因为我们知道外面圆形的半径,触控点的半径,那它们只要相减就能得出触控点距离圆心最远的距离R。当超过这个距离的时候我们已知斜边是R,同时可以得出用户点击点的向量夹角,那就可以通过三角函数轻易算出控制点的位置。
|
||||||
|
|
||||||
|
![calc_point_pos](./resourse/calc_point_pos.png)
|
||||||
|
|
||||||
|
这个逻辑在`TOUCH_START`和`TOUCH_MOVE`中都需要,以下代码是实现:
|
||||||
|
```js
|
||||||
|
touchStartEvent(event) {
|
||||||
|
let touchPos = this.node.convertToNodeSpaceAR(event.getLocation());
|
||||||
|
const distance = touchPos.len();
|
||||||
|
const radius = this.node.width / 2 - this.controlDot.width / 2;
|
||||||
|
|
||||||
|
// 以x轴正方向为基准,计算偏移量
|
||||||
|
this.radian = cc.v2(1, 0).signAngle(touchPos);
|
||||||
|
const offsetX = Math.cos(this.radian) * radius;
|
||||||
|
const offsetY = Math.sin(this.radian) * radius;
|
||||||
|
this.controlDot.setPosition(radius > distance ? touchPos : cc.v2(offsetX, offsetY));
|
||||||
|
|
||||||
|
this.movableFlag = true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 物体移动
|
||||||
|
|
||||||
|
在用户点击的时候我们已经拿到了用户拖动的方向`radian`,有了方向再加上物体移动的步长,这个其实也就是物体移动的速度,那就可以算出物体在x方向和y方向的移动增量,直接相加即可。
|
||||||
|
我们可以加一个标志位`movableFlag`,当用户操控控制杆的时候就把这个标志位置为`true`,然后在`update`中根据这个标志位使物体进行位置偏移。
|
||||||
|
|
||||||
|
```js
|
||||||
|
speed: number = 150;
|
||||||
|
update(dt) {
|
||||||
|
if (!this.movableFlag) return;
|
||||||
|
this.movableStar.x += Math.cos(this.radian) * dt * this.speed;
|
||||||
|
this.movableStar.y += Math.sin(this.radian) * dt * this.speed;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
大多数情况下还需要添加限制条件,让物体的移动不能超出画布,那这就可以通过获取当前屏幕的宽高,然后在计算位置偏移的时候多加一个判断,超出画布就不增加偏移,不超出画布就正常偏移就可以了
|
||||||
|
|
||||||
|
# 效果预览
|
||||||
|
|
||||||
|
源码获取请点击**查看原文**,长按二维码查看效果👇
|
||||||
|
|
||||||
|
![ewm](./resourse/preview_ewm.png)
|
||||||
|
|
||||||
|
我是异名,你的阅读是我的动力
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
134
doc/Scratch_ticket/Scratch_ticket.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# 效果演示
|
||||||
|
|
||||||
|
奖券,优惠券什么的就需要用到刮刮卡的效果了,大家生活里都使用过。
|
||||||
|
|
||||||
|
![Scratch_ticket](./resource/Scratch_ticket.gif)
|
||||||
|
|
||||||
|
# 实现思路
|
||||||
|
|
||||||
|
一个完整的刮刮卡需要有这三个功能,刮开涂层、刮开比例、重置。整个功能其实是对`mask`组件的一次深入应用。
|
||||||
|
|
||||||
|
## 刮开涂层
|
||||||
|
|
||||||
|
首先创立一个mask组件,长宽都设置为0,勾选反向遮罩,然后再拖一个涂层的`sprite`节点作为它的子节点。为什么这样做呢,我先介绍一下遮罩👇
|
||||||
|
|
||||||
|
![mask_intro](./resource/mask_intro.png)
|
||||||
|
|
||||||
|
上面可以很好地看明白,其实遮罩就是一个盖板,盖住了就看不到它的子节点了,但是如果我在遮罩中间掏个洞,那我就可以透过这个洞看到里面的东西。正向遮罩就是我看不到后面的东西,反向遮罩就是我可以看到后面的东西。如果我们把遮罩的长宽都设置为0,那等于这个遮罩什么用都没有,因为它的面积是0,所以它的子节点就能够完全暴露在画面上,所以我们能够看到涂层图片。
|
||||||
|
|
||||||
|
然后我们把刮的操作理解为更改这个遮罩的形状,本来它的面积是0,但是当用户刮的时候,我就给它赋予一个长宽,比如我把它的长宽变成10,那这个遮罩就从一个点(长宽为0)变成了一个长宽为10的正方形。而我们设置了这个遮罩类型为反向遮罩,所以我们就能透过这个正方形看到刮刮卡底层的内容了。这就解析了为什么我们需要勾选反向遮罩。这两步的图解如下👇
|
||||||
|
|
||||||
|
![step_1&2](./resource/step_1&2.png)
|
||||||
|
|
||||||
|
|
||||||
|
那问题来了,我在刮的时候,我生成的形状不能是矩形呀,我的动作是不规则的。其实mask是用Graphics实现的,刮的动作其实不就是在划线吗,所以每次刮的时候,我们只需要记录当前点和上一个点,调用`moveTo`和`lineTo`把用户的滑动轨迹画出来就可以了。我们在`touch_start`的时候只有一个点,那就用`circle`去画一个圆就可以了。代码如下:
|
||||||
|
|
||||||
|
```js
|
||||||
|
touchStartEvent(event) {
|
||||||
|
let point = this.ticketNode.convertToNodeSpaceAR(event.getLocation());
|
||||||
|
this.clearMask(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
touchMoveEvent(event) {
|
||||||
|
let point = this.ticketNode.convertToNodeSpaceAR(event.getLocation());
|
||||||
|
this.clearMask(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDrawPoints: cc.Vec2[] = [];
|
||||||
|
clearMask(pos) {
|
||||||
|
let mask: any = this.maskNode.getComponent(cc.Mask);
|
||||||
|
let stencil = mask._graphics;
|
||||||
|
const len = this.tempDrawPoints.length;
|
||||||
|
this.tempDrawPoints.push(pos);
|
||||||
|
|
||||||
|
if (len <= 1) {
|
||||||
|
// 只有一个点,用圆来清除涂层
|
||||||
|
stencil.circle(pos.x, pos.y, CLEAR_LINE_WIDTH / 2);
|
||||||
|
stencil.fill();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 存在多个点,用线段来清除涂层
|
||||||
|
let prevPos = this.tempDrawPoints[len - 2];
|
||||||
|
let curPos = this.tempDrawPoints[len - 1];
|
||||||
|
|
||||||
|
stencil.moveTo(prevPos.x, prevPos.y);
|
||||||
|
stencil.lineTo(curPos.x, curPos.y);
|
||||||
|
stencil.lineWidth = CLEAR_LINE_WIDTH;
|
||||||
|
stencil.lineCap = cc.Graphics.LineCap.ROUND;
|
||||||
|
stencil.lineJoin = cc.Graphics.LineJoin.ROUND;
|
||||||
|
stencil.strokeColor = cc.color(255, 255, 255, 255);
|
||||||
|
stencil.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 统计刮开比例
|
||||||
|
|
||||||
|
上面解决了如何刮涂层,但是刮纯粹是图像上的操作,如何统计刮开比例呢。我采用的方法就是把遮罩分出N个小正方体,然后每次画的时候不是要动态去画圆和画线段吗,那顺便遍历一下这N个小正方体,如果正方体和里面包括圆心或者和线段有相交,那就把这个正方体标记一下。最后在`touch_end`的时候去统计已经标记过的正方体数量就可以统计出比例了。我还在代码里面留了个`debug`开关,开发的时候可以去实时查看是否有相交喔。
|
||||||
|
|
||||||
|
![calc](./resource/calc.gif)
|
||||||
|
|
||||||
|
圆心点在正方形内是直接计算,线段和正方形相交是调用了`cc.Intersection.lineRect`方法,这块的代码如下:
|
||||||
|
```js
|
||||||
|
// 生成小格子,用来辅助统计涂层的刮开比例
|
||||||
|
for (let x = 0; x < this.ticketNode.width; x += CALC_RECT_WIDTH) {
|
||||||
|
for (let y = 0; y < this.ticketNode.height; y += CALC_RECT_WIDTH) {
|
||||||
|
this.polygonPointsList.push({
|
||||||
|
rect: cc.rect(x - this.ticketNode.width / 2, y - this.ticketNode.height / 2, CALC_RECT_WIDTH, CALC_RECT_WIDTH),
|
||||||
|
isHit: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录点所在的格子
|
||||||
|
this.polygonPointsList.forEach((item) => {
|
||||||
|
if (item.isHit) return;
|
||||||
|
const xFlag = pos.x > item.rect.x && pos.x < item.rect.x + item.rect.width;
|
||||||
|
const yFlag = pos.y > item.rect.y && pos.y < item.rect.y + item.rect.height;
|
||||||
|
if (xFlag && yFlag) item.isHit = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 记录线段经过的格子
|
||||||
|
this.polygonPointsList.forEach((item) => {
|
||||||
|
item.isHit = item.isHit || cc.Intersection.lineRect(prevPos, curPos, item.rect);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 重置
|
||||||
|
|
||||||
|
重置格子,直接再生成一批就可以了。然后清除已画区域直接调用`Graphics`的`clear`方法就可以了
|
||||||
|
|
||||||
|
```js
|
||||||
|
polygonPointsList: { rect: cc.Rect; isHit: boolean }[] = [];
|
||||||
|
reset() {
|
||||||
|
let mask: any = this.maskNode.getComponent(cc.Mask);
|
||||||
|
mask._graphics.clear();
|
||||||
|
|
||||||
|
this.tempDrawPoints = [];
|
||||||
|
this.polygonPointsList = [];
|
||||||
|
this.progerss.string = '已经刮开了 0%';
|
||||||
|
this.ticketNode.getComponent(cc.Graphics).clear();
|
||||||
|
|
||||||
|
// 生成小格子,用来辅助统计涂层的刮开比例
|
||||||
|
for (let x = 0; x < this.ticketNode.width; x += CALC_RECT_WIDTH) {
|
||||||
|
for (let y = 0; y < this.ticketNode.height; y += CALC_RECT_WIDTH) {
|
||||||
|
this.polygonPointsList.push({
|
||||||
|
rect: cc.rect(x - this.ticketNode.width / 2, y - this.ticketNode.height / 2, CALC_RECT_WIDTH, CALC_RECT_WIDTH),
|
||||||
|
isHit: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 效果预览
|
||||||
|
|
||||||
|
**源码**获取请点击**查看原文**,长按二维码**查看效果**👇
|
||||||
|
|
||||||
|
![ewm](./resource/preview_ewm.png)
|
||||||
|
|
||||||
|
我是异名,你的阅读是我的动力
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
doc/Scratch_ticket/resource/Scratch_ticket.gif
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
doc/Scratch_ticket/resource/calc.gif
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
doc/Scratch_ticket/resource/mask_intro.png
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
doc/Scratch_ticket/resource/preview_ewm.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
doc/Scratch_ticket/resource/step_1&2.png
Normal file
After Width: | Height: | Size: 113 KiB |