https://forum.cocos.org/t/topic/146951
如同这个帖子所言,需求是一个Node会有 Polygon Collider,对Node 的 触摸事件要通过判定这个Collider 而不是这个Node UITransform 的 AABB box。
看了源码,实现支持 Collider 判定的组件。
组件有两种实现方式,一种是 扩展 UITransform, 一种是作为UITransform的附件工作。
1)扩展 UITransform
import { _decorator, cclegacy, Collider2D, PhysicsSystem2D, UITransform, Vec2 } from 'cc';
const { ccclass, property, disallowMultiple, executeInEditMode} = _decorator;
const _vec2a = new Vec2();
@ccclass('UITransformHitOverrider')
@disallowMultiple
@executeInEditMode
export class UITransformHitOverrider extends UITransform {
@property(Collider2D)
collider:Collider2D;
protected start(): void {
this.collider = this.getComponent(Collider2D);
if (this.collider == null) {
return;
}
//如果不加这个,mouse-joint.ts onTouchBegan() 会抛错
(this.collider as any)._body = UITransformHitOverrider.NOBODY;
}
/**
* @zh 屏幕空间中的点击测试。
* @en Hit test with point in Screen Space.
*
* @param screenPoint @en point in Screen Space. @zh 屏幕坐标中的点。
*/
public hitTest (screenPoint: Vec2, windowId = 0): boolean {
if (this.collider == null) {
return false;
}
_vec2a.set(screenPoint.x, screenPoint.y);
cclegacy.view._convertToUISpace(_vec2a);
const world = (PhysicsSystem2D.instance.physicsWorld as any);
const colliders:Collider2D[] = world.testPoint(_vec2a);
if (colliders.length <= 0) return;
if (colliders.includes(this.collider)) {
return true;
}
return false;
}
private static _nobody = null;
private static get NOBODY() {
if (this._nobody != null) {
return this._nobody;
}
this._nobody = {enabledInHierarchy:false, wakeUp:()=>{}, getMass:()=>{return 0;}}
return this._nobody;
}
}
2)作为 依附于 UITransform 的附组件实现
import { _decorator, cclegacy, Collider2D, Component, PhysicsSystem2D, UITransform, Vec2 } from 'cc';
const { ccclass, property, disallowMultiple, executeInEditMode} = _decorator;
const _vec2a = new Vec2();
@ccclass('UITransformHitRemora')
@disallowMultiple
@executeInEditMode
export class UITransformHitRemora extends Component {
@property(Collider2D)
collider:Collider2D;
_target:UITransform;
protected start(): void {
this._target = this.getComponent(UITransform);
if (!this._target) {
return;
}
this._target.hitTest = (screenPoint: Vec2, windowId = 0):boolean =>{
return this.hitTest(screenPoint, windowId);
}
this.collider = this.getComponent(Collider2D);
if (this.collider == null) {
return;
}
//如果不加这个,mouse-joint.ts onTouchBegan() 会抛错
(this.collider as any)._body = UITransformHitRemora.NOBODY;
}
/**
* @zh 屏幕空间中的点击测试。
* @en Hit test with point in Screen Space.
*
* @param screenPoint @en point in Screen Space. @zh 屏幕坐标中的点。
*/
public hitTest (screenPoint: Vec2, windowId = 0): boolean {
if (this.collider == null) {
return false;
}
_vec2a.set(screenPoint.x, screenPoint.y);
cclegacy.view._convertToUISpace(_vec2a);
const world = (PhysicsSystem2D.instance.physicsWorld as any);
const colliders:Collider2D[] = world.testPoint(_vec2a);
if (colliders.length <= 0) return;
if (colliders.includes(this.collider)) {
return true;
}
return false;
}
private static _nobody = null;
private static get NOBODY() {
if (this._nobody != null) {
return this._nobody;
}
this._nobody = {enabledInHierarchy:false, wakeUp:()=>{}, getMass:()=>{return 0;}}
return this._nobody;
}
}
另外,建议官方修改一下 mouse-joint.ts :
onTouchBegan (event: Touch): void {
this._isTouched = true;
const target = this._touchPoint.set(event.getUILocation());
const world = (PhysicsSystem2D.instance.physicsWorld as B2PhysicsWorld);
const colliders = world.testPoint(target);
if (colliders.length <= 0) return;
//以下强制不为空的设计是为什么?
const body = colliders[0].body;
body!.wakeUp();
const comp = this._jointComp as MouseJoint2D;
comp.connectedBody = body;
this._init();
this.setMaxForce(comp.maxForce * body!.getMass());
this.setTarget(target);
}