You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
5.2 KiB
150 lines
5.2 KiB
import { _decorator, Component, EventTouch, find, Node, UITransform, v3, Vec3 } from 'cc';
|
|
import { MoveDirection, SuDacePlayer } from './SuDacePlayer';
|
|
import { SuDace } from './SuDace';
|
|
|
|
const { ccclass, property } = _decorator;
|
|
|
|
/**
|
|
* 浮动摇杆:在固定 touchArea 内按下,以触点为摇杆中心;拖拽方向量化四向,驱动 SuDacePlayer.setMoveInput。
|
|
* 可选 stickRoot / knob 用于在按下处显示底板与拖动的摇杆头。
|
|
*/
|
|
@ccclass('SuDaceJoystick')
|
|
export class SuDaceJoystick extends Component {
|
|
@property(SuDacePlayer)
|
|
player: SuDacePlayer = null;
|
|
|
|
/** 固定可触摸区域(全屏透明层或左下角区域等) */
|
|
@property(Node)
|
|
touchArea: Node = null;
|
|
|
|
/** 按下时显示在触点处的摇杆底板节点(可选) */
|
|
@property(Node)
|
|
stickRoot: Node = null;
|
|
|
|
/** 相对 stickRoot 中心拖动的摇杆头(可选) */
|
|
@property(Node)
|
|
knob: Node = null;
|
|
|
|
@property({ tooltip: '摇杆头最大偏移(与 UI 像素一致,用于显示)' })
|
|
maxKnobOffset = 80;
|
|
|
|
@property({ tooltip: '死区半径(相对本次按下中心),低于此视为无方向输入' })
|
|
deadRadius = 24;
|
|
|
|
private readonly _centerUi = v3();
|
|
private readonly _tmpLocal = v3();
|
|
private _trackingId = -1;
|
|
|
|
protected onEnable(): void {
|
|
this.resolvePlayerRef();
|
|
if (this.stickRoot) {
|
|
this.stickRoot.active = false;
|
|
}
|
|
const surface = this.touchArea ?? this.node;
|
|
surface.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
surface.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
|
surface.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
|
|
surface.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
|
|
}
|
|
|
|
protected onDisable(): void {
|
|
const surface = this.touchArea ?? this.node;
|
|
surface.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
|
|
surface.off(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
|
surface.off(Node.EventType.TOUCH_END, this.onTouchEnd, this);
|
|
surface.off(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this);
|
|
this.endSession();
|
|
}
|
|
|
|
private onTouchStart(event: EventTouch): void {
|
|
if (this._trackingId !== -1) return;
|
|
if (!this.player?.isValid) return;
|
|
|
|
const tid = event.getID();
|
|
this._trackingId = tid !== null && tid !== undefined ? tid : 0;
|
|
const loc = event.getUILocation();
|
|
this._centerUi.set(loc.x, loc.y, 0);
|
|
|
|
if (this.stickRoot) {
|
|
this.stickRoot.active = true;
|
|
this.placeNodeAtUi(this.stickRoot, this._centerUi);
|
|
}
|
|
this.updateKnob(0, 0);
|
|
this.player.setMoveInput(MoveDirection.NONE);
|
|
}
|
|
|
|
private onTouchMove(event: EventTouch): void {
|
|
if (!this.isOurTouch(event) || !this.player?.isValid) return;
|
|
|
|
const loc = event.getUILocation();
|
|
const dx = loc.x - this._centerUi.x;
|
|
const dy = loc.y - this._centerUi.y;
|
|
this.updateKnob(dx, dy);
|
|
this.player.setMoveInput(this.quantizeFourWay(dx, dy));
|
|
}
|
|
|
|
private onTouchEnd(event: EventTouch): void {
|
|
if (!this.isOurTouch(event)) return;
|
|
this.endSession();
|
|
}
|
|
|
|
private isOurTouch(event: EventTouch): boolean {
|
|
if (this._trackingId === -1) return false;
|
|
const id = event.getID();
|
|
const eid = id !== null && id !== undefined ? id : 0;
|
|
return eid === this._trackingId;
|
|
}
|
|
|
|
private endSession(): void {
|
|
this._trackingId = -1;
|
|
if (this.stickRoot?.isValid) {
|
|
this.stickRoot.active = false;
|
|
}
|
|
if (this.player?.isValid) {
|
|
this.player.setMoveInput(MoveDirection.NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 兜底回填 player,避免预制体覆盖丢失引用时摇杆失效。
|
|
*/
|
|
private resolvePlayerRef(): void {
|
|
if (this.player?.isValid) return;
|
|
this.player = SuDace.instance?.map?.player ?? find('map/player', this.node)?.getComponent(SuDacePlayer) ?? null;
|
|
}
|
|
|
|
/** 将 stickRoot 等节点放到 UI 坐标对应位置(相对其父节点 UITransform) */
|
|
private placeNodeAtUi(target: Node, uiWorld: Readonly<Vec3>): void {
|
|
const parent = target.parent;
|
|
const ui = parent?.getComponent(UITransform);
|
|
if (!ui) return;
|
|
ui.convertToNodeSpaceAR(uiWorld, this._tmpLocal);
|
|
target.setPosition(this._tmpLocal);
|
|
}
|
|
|
|
private updateKnob(dx: number, dy: number): void {
|
|
if (!this.knob?.isValid) return;
|
|
const max = this.maxKnobOffset;
|
|
let kx = dx;
|
|
let ky = dy;
|
|
const len = Math.sqrt(dx * dx + dy * dy);
|
|
if (len > max && len > 1e-6) {
|
|
const s = max / len;
|
|
kx *= s;
|
|
ky *= s;
|
|
}
|
|
this.knob.setPosition(kx, ky, 0);
|
|
}
|
|
|
|
/** 将相对中心的偏移量量化为四方向(主轴优先) */
|
|
private quantizeFourWay(ox: number, oy: number): MoveDirection {
|
|
const r = this.deadRadius;
|
|
if (ox * ox + oy * oy < r * r) {
|
|
return MoveDirection.NONE;
|
|
}
|
|
if (Math.abs(ox) >= Math.abs(oy)) {
|
|
return ox > 0 ? MoveDirection.RIGHT : MoveDirection.LEFT;
|
|
}
|
|
return oy > 0 ? MoveDirection.UP : MoveDirection.DOWN;
|
|
}
|
|
}
|
|
|