实现刮刮乐的效果,直接上效果图
第一种思路:
两张图片,一张底板显示,一张遮罩显示。利用获取遮罩的像素,记录鼠标在遮罩上的位置,获取该位置一定范围内的像素点集合,更改像素点的颜色。(理论上,因为涉及到像素级操作,未做实践。)
第二种思路:
两张图片,一张底板img_full显示,一张遮罩img_bg显示。如图摆放滑动鼠标给底板图片动态绘制mask就可以实现效果了。
private r: number = 3;
private $bgMask: Laya.Sprite = new Laya.Sprite();
private isTouched: boolean = false;
test(): void{
this.$bgImage.mask = this.$bgMask;
this.$bgImage.on(Laya.Event.MOUSE_DOWN, this, (event: Laya.Event)=>{
this.isTouched = true;
});
this.$bgImage.on(Laya.Event.MOUSE_MOVE, this, (event: Laya.Event)=>{
if(this.isTouched)
this.drawCircle(event.stageX, event.stageY);
});
this.$bgImage.on(Laya.Event.MOUSE_UP, this, (event: Laya.Event)=>{
this.isTouched = false;
});
}
drawCircle(x: number, y: number): void{
this.$bgMask.graphics.drawCircle(x, y, this.r, "red");
}
第三种思路:
参考官方新手引导示例,利用叠加模式来实现。实现代码如下,以下代码包含两种方式去实现绘制路线的平滑,一种是两点之间绘制直线;一种是绘制圆或者矩形等,再分解过程形成片段,利用自定义形成贝塞尔平滑点集合,再利用这些形成的点进行绘制(注意:该种方法在绘制区域面大的时候回导致掉帧严重!!!)。
/**
* 刮刮乐效果
*/
private ScratchImage(): void{
const Sprite = Laya.Sprite;
// 绘制底图
this.image = GameUtils.findObj("showDi", this.owner);
// 引导所在容器
this.guideContainer = new Sprite();
this.owner.addChild(this.guideContainer);
this.guideContainer.cacheAs = "bitmap";
// 绘制遮罩区,含透明度,可见游戏背景
var maskArea = new Sprite();
this.guideContainer.addChild(maskArea);
maskArea.width = this.image.width;
maskArea.height = this.image.height;
// 遮罩的位置
var maskPos = new Laya.Vector2((1280 -maskArea.width)*0.5, (720-maskArea.height)*0.5);
maskArea.pos(maskPos.x, maskPos.y);
maskArea.loadImage("achievement/top_bg_full@2x.png");
maskArea.alpha = 1;
// 用来绘制的对象,利用叠加模式,从遮罩区域抠出可交互区
this.interactionArea = new Sprite();
this.guideContainer.addChild(this.interactionArea);
this.interactionArea.blendMode = "destination-out";
Laya.stage.on(Laya.Event.MOUSE_DOWN, this, function (e){
this.isTouched = true;
// this.penultPos = new Laya.Vector2(e.stageX, e.stageY);
this.prevX = e.stageX;
this.prevY = e.stageY
});
// 鼠标是否不在目标图片范围内
var isOut = false;
Laya.stage.on(Laya.Event.MOUSE_MOVE, this, function (e){
if(this.isTouched == true){
// this.scratchSmopthPath(e.stageX, e.stageY);
// 世界坐标转换为this.image的局部坐标
var pos = this.image.globalToLocal(new Laya.Point(e.stageX, e.stageY));
// 当鼠标不在图片显示范围内,重新设置划线的起点坐标
if(pos.x < 0 || pos.x > this.image.width || pos.y >this.image.width || pos.y < 0){
isOut = true;
}else{
if(isOut){
isOut = false;
this.prevX = e.stageX;
this.prevY = e.stageY
}
this.nextStep(e.stageX, e.stageY);
}
}
});
Laya.stage.on(Laya.Event.MOUSE_UP, this, function (e){
this.isTouched = false;
// this.twoPoint = false;
this.prevX = 0;
this.prevY = 0;
});
}
/**
* 刮奖操作 注意:在刮奖结束后要把画的线清理,false掉跟遮罩相关的容器对象!避免卡顿掉帧!
* (当玩家手指滑到刮奖区外边, 首先要判断玩家操作区域是否在底图范围内。
* 若不存在,则更新this.prevX,this.prevY 的信息为重新进入矩形的交点坐标)
*/
nextStep(x, y) {
this.interactionArea.graphics.drawLine(this.prevX, this.prevY, x, y, "#000000", 20);
this.prevX = x
this.prevY = y;
}
/*private twoPoint: boolean = false;
privatelastPos: Laya.Vector2;//最后一个点
private penultPos: Laya.Vector2;//倒数第二个点
// 另一种方式(平滑滑动绘图的区域)绘图实现刮刮乐
private scratchSmopthPath(nowX:number, nowY:number): void{
var posMouse = new Laya.Vector2(nowX, nowY);
if(this.twoPoint && this..calcDistance(posMouse, this.lastPos) > 10){
var distancePoint = this.calcDistance(posMouse, this.lastPos);
var segments = Math.ceil(distancePoint / 3);//计算出平滑的段数
segments = segments < 1 ? 1 : segments;
var pointList: Array<Laya.Vector2> = this.Beizier(this.penultPos, this.lastPos, posMouse, segments);//进行贝塞尔平滑
this.lastPos = posMouse;
this.penultPos = pointList[pointList.length-2]
for (var i = 0; i < pointList.length; i++)
{
this.interactionArea.graphics.drawCircle(pointList[i].x, pointList[i].y, 10, "#000000");
}
}else{
this.twoPoint = true;
this.lastPos = posMouse;
}
}*/
/**
* 获取形成贝塞尔平滑的点坐标数组集集合
* @param start 起点
* @param mid 中点
* @param end 终点
* @param segments 需要平滑的段数
*/
Beizier( start: Laya.Vector2, mid:Laya.Vector2, end:Laya.Vector2, segments: number): Array<Laya.Vector2>
{
var posList = new Array<Laya.Vector2>();
var d:number = 1 / segments;
posList.push(mid);
for (var i = 0; i < segments-1; i++)
{
var t: number = d * (i + 1);
var points: Laya.Vector2 = new Laya.Vector2((1 - t)*(1 - t)*mid.x+2*t*(1 - t)*start.x+t*t*end.x, (1 - t)*(1 - t)*mid.y+2*t*(1 - t)*start.y+t*t*end.y);
posList.push(points);
}
posList.push(end);
return posList;
}
/**
* 计算两个点之间的距离
* @param a 第一个点的2d坐标
* @param b 第二个点的2d坐标
*/
calcDistance(a: Laya.Vector2, b: Laya.Vector2): number{
return Math.sqrt(Math.pow(a.x-b.x,2)+Math.pow(a.y-b.y,2));
}
优化方向:
目前实现是在底板图片范围内均可以绘制,没有考虑相应区域是否已经绘制过,所以后续可以朝着是否可以记录已经绘制过的区域进行剔除,不进行绘制新的图案?目前未想到相应的实现方法。另一方面未实现,玩家在刮了一定范围后让mask直接清除,完全显示底板?