将 Collider 应用于 Node 的 触摸事件

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);
}