需要实现:
一个大的地图背景背分隔成了多个小的区块,需要对每一个区块可以选中并且改变区块颜色(不规则区块)。
思路:
为每一个区块注册点击事件实现选中方法,编写shader实现不规则图形色彩变化。
问题:
1、不规则图形如何注册点击事件。
2、同父亲节点下的区块节点如何防止吞噬事件(冒泡只能在父子节点)
3、如何编写shader实现不规则图形色彩变化。
解决方法:
问题1——————————————————————————————————
对于不规则图形,可以考虑判断点击点是否是透明像素。
先获取到图片的像素数据
/**
* 获取贴图中的像素
* @param tex 贴图资源,可以是单个贴图或者是合图的贴图
* @param rect 如果是单个贴图则不需要传裁剪矩形,合图中的贴图请传入spriteFrame.rect用来确定偏移位置和裁剪大小
* @returns Uint8Array 按rgba排列
*/
getPixels(sp: Sprite, rect?: Rect): Uint8Array {
let width = sp.getComponent(UITransform).width;
let height = sp.getComponent(UITransform).height;
const gfxTexture = sp.spriteFrame.getGFXTexture();
if (!gfxTexture) {
return null;
}
const bufferViews: ArrayBufferView[] = [];
const regions: gfx.BufferTextureCopy[] = [];
const region0 = new gfx.BufferTextureCopy();
region0.texOffset.x = rect?.x || 0;
region0.texOffset.y = rect?.y || 0;
region0.texExtent.width = width;
region0.texExtent.height = height;
regions.push(region0);
const buffer = new Uint8Array(width * height * 4);
bufferViews.push(buffer);
director.root?.device.copyTextureToBuffers(gfxTexture, bufferViews, regions)
return buffer;
}
然后将触摸屏幕坐标转化为世界坐标,继续转化为相对点击区块的本地坐标
// 获取屏幕坐标
let screenPos = event.getLocation();
let viewport = view.getViewportRect(); // 获取视口信息
let cocosScreenPos = new Vec3(
screenPos.x,
screenPos.y,
0
);
let xj=find("Canvas/Camera").getComponent(Camera)
let worldPos=new Vec3()
xj.screenToWorld(cocosScreenPos,worldPos)
let uiTransform = this.bg.getComponent(UITransform);
// 将屏幕坐标转换为Vec3类型,因为convertToNodeSpaceAR需要Vec3类型的参数
let pos=this.bg.getComponent(UITransform).convertToNodeSpaceAR(worldPos)
将本地坐标转化为纹理坐标,需要注意纹理坐标以左上角为原点
// 将本地坐标转换为纹理的 UV 坐标(归一化到 [0, 1])
const width = uiTransform.width;
const height = uiTransform.height;
const anchor = uiTransform.anchorPoint;
// 考虑锚点,计算x,y比例
const u = ((pos.x) / width) + anchor.x;
const v = ((pos.y) / height) + anchor.y;
// 4. 转换为纹理像素坐标(注意y坐标要翻转)
const texWidth = this._texture.width;
const texHeight = this._texture.height;
const x = Math.floor(u * texWidth);
const y = texHeight - Math.floor(v * texHeight);
获取到点击部分的像素并判断
// 获取 Alpha 值(RGBA 格式,每个像素占 4 个字节)
const idx = (y * texWidth + x) * 4;
const alpha = this._pixelData[idx + 3]; // Alpha 通道
// 判断是否透明
if (alpha < 10) { // Alpha 值范围 0~255,此处阈值设为 10
console.log('点击在透明区域');
} else {
console.log('点击在非透明区域');
}
问题2——————————————————————————————————
由于地图是由很多小区块无缝拼接而成,所以会有部分区块的矩形透明部分覆盖在其他区块上,需要同级节点触摸事件穿透。
onMapClick(event: EventTouch) {
event.preventSwallow=true//防止事件被吞噬
在事件回调中去设置不能吞噬事件,但是发现对于触摸结束事件无效。
查询官方文档发现对于触摸结束事件需要将触摸开始事件也设置为防止吞噬
修改后代码
this.bg.on(Node.EventType.TOUCH_START,(event: EventTouch)=>{event.preventSwallow=true},this)
this.bg.on(Node.EventType.TOUCH_END,(event: EventTouch)=>{this.onMapClick(event)},this)
问题三—————————————————————————————————
已解决:解决方案