import { _decorator, Camera, Component, EventMouse, EventTouch, Input, input, sys, Vec2, view, } from 'cc'; import { CAMERA_ORTHO_HALF, DESIGN_WIDTH } from '../core/GridConstants'; import { getEmbeddedOrthoHalf } from '../core/EmbeddedView'; const { ccclass } = _decorator; /** 与 UIMain 相同:165×165 按钮 + 右侧 20px 边距,按 2560 设计宽缩到 960 */ const HUD_BTN = Math.round(165 * DESIGN_WIDTH / 2560); const HUD_PAD_RIGHT = Math.round(20 * DESIGN_WIDTH / 2560); const HUD_RIGHT_SLACK = 8; function markMapViewDirty(): void { (globalThis as { __tfrhPreserveMapView?: boolean }).__tfrhPreserveMapView = true; } function clearMapViewDirty(): void { (globalThis as { __tfrhPreserveMapView?: boolean }).__tfrhPreserveMapView = false; } /** 对齐 Unity ViewController:Orthographic 缩放与拖拽 */ @ccclass('ViewController') export class ViewController extends Component { static instance: ViewController | null = null; /** Unity zoomSpeed=2 → 世界半高步进 200 */ zoomSpeed = 200; /** Unity minZoom=3, maxZoom=10(×100 世界单位) */ minOrtho = 300; maxOrtho = 1000; private camera: Camera | null = null; private dragOrigin = new Vec2(); private readonly uiLoc = new Vec2(); private dragging = false; onLoad() { if (ViewController.instance && ViewController.instance !== this) { this.destroy(); return; } ViewController.instance = this; this.camera = this.getComponent(Camera); if (this.camera && this.camera.orthoHeight <= 0) { this.camera.orthoHeight = CAMERA_ORTHO_HALF; } input.on(Input.EventType.TOUCH_START, this.onTouchStart, this); input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this); input.on(Input.EventType.TOUCH_CANCEL, this.onTouchEnd, this); input.on(Input.EventType.MOUSE_DOWN, this.onMouseDown, this); input.on(Input.EventType.MOUSE_MOVE, this.onMouseMove, this); input.on(Input.EventType.MOUSE_UP, this.onMouseUp, this); } onDestroy() { if (ViewController.instance === this) ViewController.instance = null; input.off(Input.EventType.TOUCH_START, this.onTouchStart, this); input.off(Input.EventType.TOUCH_MOVE, this.onTouchMove, this); input.off(Input.EventType.TOUCH_END, this.onTouchEnd, this); input.off(Input.EventType.TOUCH_CANCEL, this.onTouchEnd, this); input.off(Input.EventType.MOUSE_DOWN, this.onMouseDown, this); input.off(Input.EventType.MOUSE_MOVE, this.onMouseMove, this); input.off(Input.EventType.MOUSE_UP, this.onMouseUp, this); } zoomIn() { const cam = this.camera; if (!cam || cam.orthoHeight <= this.minOrtho) return; cam.orthoHeight = Math.max(this.minOrtho, cam.orthoHeight - this.zoomSpeed); markMapViewDirty(); } zoomOut() { const cam = this.camera; if (!cam || cam.orthoHeight >= this.maxOrtho) return; cam.orthoHeight = Math.min(this.maxOrtho, cam.orthoHeight + this.zoomSpeed); markMapViewDirty(); } resetZoom() { const cam = this.camera; if (!cam) return; cam.orthoHeight = getEmbeddedOrthoHalf(); cam.node.setPosition(0, 0, cam.node.position.z); clearMapViewDirty(); } private usingMouseInput(): boolean { return !sys.isMobile && sys.hasFeature(sys.Feature.EVENT_MOUSE); } private onTouchStart(e: EventTouch) { // 桌面端鼠标会同时模拟 touch,只走 MOUSE_* 以免双倍位移、并过滤非左键 if (this.usingMouseInput()) return; e.getUILocation(this.uiLoc); if (this.isPointerOnUI(this.uiLoc)) return; this.dragging = true; this.dragOrigin.set(this.uiLoc); } private onTouchEnd() { if (this.usingMouseInput()) return; this.dragging = false; } private onTouchMove(e: EventTouch) { if (this.usingMouseInput() || !this.dragging) return; e.getUILocation(this.uiLoc); this.applyDrag(this.uiLoc); this.dragOrigin.set(this.uiLoc); } private onMouseDown(e: EventMouse) { if (e.getButton() !== EventMouse.BUTTON_LEFT) return; e.getUILocation(this.uiLoc); if (this.isPointerOnUI(this.uiLoc)) return; this.dragging = true; this.dragOrigin.set(this.uiLoc); } private onMouseUp() { this.dragging = false; } private onMouseMove(e: EventMouse) { if (!this.dragging) return; e.getUILocation(this.uiLoc); this.applyDrag(this.uiLoc); this.dragOrigin.set(this.uiLoc); } private applyDrag(cur: Vec2) { if (!this.camera) return; const dx = cur.x - this.dragOrigin.x; const dy = cur.y - this.dragOrigin.y; const ortho = this.camera.orthoHeight; const { width, height } = view.getVisibleSize(); if (width <= 0 || height <= 0) return; const worldPerPixelX = (2 * ortho * (width / height)) / width; const worldPerPixelY = (2 * ortho) / height; const pos = this.camera.node.position; let nx = pos.x - dx * worldPerPixelX; let ny = pos.y - dy * worldPerPixelY; const limit = ortho - 20; if (Math.abs(nx) > limit) nx = pos.x; if (Math.abs(ny) > limit) ny = pos.y; this.camera.node.setPosition(nx, ny, pos.z); markMapViewDirty(); } /** 仅拦截右侧 HUD 按钮列;坐标为 UI 空间(原点左下,与 visibleSize 一致) */ private isPointerOnUI(loc: Vec2): boolean { const vis = view.getVisibleSize(); const right = vis.width > 0 ? vis.width : DESIGN_WIDTH; return loc.x >= right - HUD_BTN - HUD_PAD_RIGHT - HUD_RIGHT_SLACK; } }