|
|
|
|
@ -1,4 +1,4 @@ |
|
|
|
|
import { Color, Graphics, Node, UITransform, Vec3, isValid, sp } from 'cc'; |
|
|
|
|
import { _decorator, Color, Component, Graphics, Node, UITransform, Vec3, isValid, sp } from 'cc'; |
|
|
|
|
import { isElementGlyphType } from 'db://assets/mx/module/merge'; |
|
|
|
|
import { GBundle } from '../../game/ConfigRes'; |
|
|
|
|
import { AI_VIEW_LOCAL_CAMP } from '../ai/runtime/AiCampArea'; |
|
|
|
|
@ -8,6 +8,8 @@ import type { MapManager } from '../map/legacy/MapManager'; |
|
|
|
|
import { BattleSpinePlayback } from '../presentation/BattleSpinePlayback'; |
|
|
|
|
import type { TextGrid } from './TextGrid'; |
|
|
|
|
|
|
|
|
|
const { ccclass } = _decorator; |
|
|
|
|
|
|
|
|
|
/** 引导虚线框预制体路径 */ |
|
|
|
|
const FRAME_PATH = 'prefab/障碍物/'; |
|
|
|
|
/** 引导虚线框预制体名 */ |
|
|
|
|
@ -22,15 +24,29 @@ const DASH_LEN = 12; |
|
|
|
|
const DASH_HALF_W = 2.5; |
|
|
|
|
/** 描边相对半宽加宽 */ |
|
|
|
|
const DASH_OUTLINE_EXTRA = 1.5; |
|
|
|
|
/** 背包端取字块顶边中点的向上偏移 */ |
|
|
|
|
const BAG_TOP_PAD = 2; |
|
|
|
|
/** 气泡端取底边中点的向下偏移 */ |
|
|
|
|
const TIP_BOTTOM_PAD = 2; |
|
|
|
|
/** 流动速度:像素/秒(背包→地图) */ |
|
|
|
|
const FLOW_SPEED = 48; |
|
|
|
|
/** 交替色:黑 */ |
|
|
|
|
const DASH_BLACK = new Color(15, 15, 15, 255); |
|
|
|
|
/** 交替色:白 */ |
|
|
|
|
const DASH_WHITE = new Color(255, 255, 255, 255); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 连线层每帧推进斑马相位。 |
|
|
|
|
*/ |
|
|
|
|
@ccclass('AreaUnlockGuideFlowTick') |
|
|
|
|
class AreaUnlockGuideFlowTick extends Component { |
|
|
|
|
/** 宿主 */ |
|
|
|
|
host: AreaUnlockGuidePresenter | null = null; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @param dt 帧间隔 |
|
|
|
|
*/ |
|
|
|
|
update(dt: number): void { |
|
|
|
|
this.host?.onFlowTick(dt); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 世界坐标复用 */ |
|
|
|
|
const _bagWorld = new Vec3(); |
|
|
|
|
/** 气泡世界坐标复用 */ |
|
|
|
|
@ -72,6 +88,18 @@ export class AreaUnlockGuidePresenter { |
|
|
|
|
private _frame: Node | null = null; |
|
|
|
|
/** 当前标识的背包槽;null 表示未展示 */ |
|
|
|
|
private _slotIndex: number | null = null; |
|
|
|
|
/** 连线本地起点 x */ |
|
|
|
|
private _fromX = 0; |
|
|
|
|
/** 连线本地起点 y */ |
|
|
|
|
private _fromY = 0; |
|
|
|
|
/** 连线本地终点 x */ |
|
|
|
|
private _toX = 0; |
|
|
|
|
/** 连线本地终点 y */ |
|
|
|
|
private _toY = 0; |
|
|
|
|
/** 斑马流动相位(像素) */ |
|
|
|
|
private _flowPhase = 0; |
|
|
|
|
/** 是否正在播流动 */ |
|
|
|
|
private _flowing = false; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 绑定连线层父节点(须同时覆盖地图与背包,如 BattleGame 根)。 |
|
|
|
|
@ -87,6 +115,8 @@ export class AreaUnlockGuidePresenter { |
|
|
|
|
/** 释放节点与池资源 */ |
|
|
|
|
unbind(): void { |
|
|
|
|
this.clear(); |
|
|
|
|
const tick = this._lineLayer?.getComponent(AreaUnlockGuideFlowTick); |
|
|
|
|
if (tick) tick.host = null; |
|
|
|
|
if (this._lineLayer?.isValid) { |
|
|
|
|
this._lineLayer.destroy(); |
|
|
|
|
} |
|
|
|
|
@ -104,12 +134,30 @@ export class AreaUnlockGuidePresenter { |
|
|
|
|
clear(): void { |
|
|
|
|
const prevHost = this._frame?.parent; |
|
|
|
|
this._slotIndex = null; |
|
|
|
|
this._stopFlow(); |
|
|
|
|
this._releaseFrame(); |
|
|
|
|
if (prevHost?.isValid) this._purgeOrphanFrames(prevHost); |
|
|
|
|
if (this._graphics?.isValid) this._graphics.clear(); |
|
|
|
|
if (this._lineLayer?.isValid) this._lineLayer.active = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 连线层帧 tick:推进相位并重绘。 |
|
|
|
|
* @param dt 帧间隔 |
|
|
|
|
*/ |
|
|
|
|
onFlowTick(dt: number): void { |
|
|
|
|
if (!this._flowing) return; |
|
|
|
|
if (!this._lineLayer?.isValid || !this._lineLayer.active) return; |
|
|
|
|
if (!this._graphics?.isValid) return; |
|
|
|
|
const step = Math.max(0, Number(dt) || 0) * FLOW_SPEED; |
|
|
|
|
if (step <= 0) return; |
|
|
|
|
// 相位减小:斑马段沿起点(背包)→终点(地图)滑动
|
|
|
|
|
this._flowPhase -= step; |
|
|
|
|
const period = DASH_LEN * 2; |
|
|
|
|
if (this._flowPhase < 0) this._flowPhase = (this._flowPhase % period + period) % period; |
|
|
|
|
this._redrawLine(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 按背包与当前解锁条件刷新引导。 |
|
|
|
|
* 原槽仍可解锁时保持不变,避免频繁乱跳。 |
|
|
|
|
@ -295,12 +343,15 @@ export class AreaUnlockGuidePresenter { |
|
|
|
|
} |
|
|
|
|
this._lineLayer = layer; |
|
|
|
|
this._graphics = layer.getComponent(Graphics); |
|
|
|
|
let tick = layer.getComponent(AreaUnlockGuideFlowTick); |
|
|
|
|
if (!tick) tick = layer.addComponent(AreaUnlockGuideFlowTick); |
|
|
|
|
tick.host = this; |
|
|
|
|
this._disableNodeHit(layer); |
|
|
|
|
layer.active = false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 从背包字顶边中点连到气泡底边中点。 |
|
|
|
|
* 背包字中心 → 地图待解锁格中心(气泡已挂在格心)。 |
|
|
|
|
* @param bagNode 背包字块 |
|
|
|
|
* @param tip 格子锁气泡 |
|
|
|
|
*/ |
|
|
|
|
@ -311,46 +362,73 @@ export class AreaUnlockGuidePresenter { |
|
|
|
|
const layerUt = layer.getComponent(UITransform); |
|
|
|
|
if (!layerUt) return; |
|
|
|
|
|
|
|
|
|
const bagUt = bagNode.getComponent(UITransform); |
|
|
|
|
const tipUt = tip.getComponent(UITransform); |
|
|
|
|
const bagHalfH = bagUt ? bagUt.contentSize.height * 0.5 * Math.abs(bagNode.worldScale.y) : 30; |
|
|
|
|
const tipHalfH = tipUt ? tipUt.contentSize.height * 0.5 * Math.abs(tip.worldScale.y) : 20; |
|
|
|
|
|
|
|
|
|
_bagWorld.set(bagNode.worldPosition.x, bagNode.worldPosition.y + bagHalfH + BAG_TOP_PAD, bagNode.worldPosition.z); |
|
|
|
|
_tipWorld.set(tip.worldPosition.x, tip.worldPosition.y - tipHalfH - TIP_BOTTOM_PAD, tip.worldPosition.z); |
|
|
|
|
_bagWorld.set(bagNode.worldPosition.x, bagNode.worldPosition.y, bagNode.worldPosition.z); |
|
|
|
|
_tipWorld.set(tip.worldPosition.x, tip.worldPosition.y, tip.worldPosition.z); |
|
|
|
|
layerUt.convertToNodeSpaceAR(_bagWorld, _fromLocal); |
|
|
|
|
layerUt.convertToNodeSpaceAR(_tipWorld, _toLocal); |
|
|
|
|
|
|
|
|
|
g.clear(); |
|
|
|
|
this._fillZebraDash(g, _fromLocal.x, _fromLocal.y, _toLocal.x, _toLocal.y); |
|
|
|
|
const nx = _fromLocal.x; |
|
|
|
|
const ny = _fromLocal.y; |
|
|
|
|
const mx = _toLocal.x; |
|
|
|
|
const my = _toLocal.y; |
|
|
|
|
const moved = Math.abs(nx - this._fromX) > 1 |
|
|
|
|
|| Math.abs(ny - this._fromY) > 1 |
|
|
|
|
|| Math.abs(mx - this._toX) > 1 |
|
|
|
|
|| Math.abs(my - this._toY) > 1; |
|
|
|
|
this._fromX = nx; |
|
|
|
|
this._fromY = ny; |
|
|
|
|
this._toX = mx; |
|
|
|
|
this._toY = my; |
|
|
|
|
if (!this._flowing || moved) this._flowPhase = 0; |
|
|
|
|
this._redrawLine(); |
|
|
|
|
layer.active = true; |
|
|
|
|
layer.setSiblingIndex(layer.parent!.children.length - 1); |
|
|
|
|
this._flowing = true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 按当前相位重绘斑马线 */ |
|
|
|
|
private _redrawLine(): void { |
|
|
|
|
const g = this._graphics; |
|
|
|
|
if (!g?.isValid) return; |
|
|
|
|
g.clear(); |
|
|
|
|
this._fillZebraDash(g, this._fromX, this._fromY, this._toX, this._toY, this._flowPhase); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** 停止流动(清相位,保留层节点) */ |
|
|
|
|
private _stopFlow(): void { |
|
|
|
|
this._flowing = false; |
|
|
|
|
this._flowPhase = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 黑白段交替实心绳:每段先画反色描边再画本色。 |
|
|
|
|
* 黑白段交替实心绳:相位沿背包→地图方向滑动。 |
|
|
|
|
* @param g Graphics |
|
|
|
|
* @param x1 起点 x |
|
|
|
|
* @param y1 起点 y |
|
|
|
|
* @param x2 终点 x |
|
|
|
|
* @param y2 终点 y |
|
|
|
|
* @param phase 流动相位(像素) |
|
|
|
|
*/ |
|
|
|
|
private _fillZebraDash(g: Graphics, x1: number, y1: number, x2: number, y2: number): void { |
|
|
|
|
private _fillZebraDash(g: Graphics, x1: number, y1: number, x2: number, y2: number, phase: number): void { |
|
|
|
|
const dx = x2 - x1; |
|
|
|
|
const dy = y2 - y1; |
|
|
|
|
const len = Math.sqrt(dx * dx + dy * dy); |
|
|
|
|
if (len < 1) return; |
|
|
|
|
const ux = dx / len; |
|
|
|
|
const uy = dy / len; |
|
|
|
|
let d = 0; |
|
|
|
|
const period = DASH_LEN * 2; |
|
|
|
|
const offset = ((phase % period) + period) % period; |
|
|
|
|
let d = -offset; |
|
|
|
|
let useBlack = true; |
|
|
|
|
while (d < len) { |
|
|
|
|
const d0 = Math.max(d, 0); |
|
|
|
|
const d1 = Math.min(d + DASH_LEN, len); |
|
|
|
|
const fill = useBlack ? DASH_BLACK : DASH_WHITE; |
|
|
|
|
const outline = useBlack ? DASH_WHITE : DASH_BLACK; |
|
|
|
|
this._fillDashSeg(g, x1, y1, ux, uy, d, d1, DASH_HALF_W + DASH_OUTLINE_EXTRA, outline); |
|
|
|
|
this._fillDashSeg(g, x1, y1, ux, uy, d, d1, DASH_HALF_W, fill); |
|
|
|
|
d = d1; |
|
|
|
|
if (d1 > d0) { |
|
|
|
|
const fill = useBlack ? DASH_BLACK : DASH_WHITE; |
|
|
|
|
const outline = useBlack ? DASH_WHITE : DASH_BLACK; |
|
|
|
|
this._fillDashSeg(g, x1, y1, ux, uy, d0, d1, DASH_HALF_W + DASH_OUTLINE_EXTRA, outline); |
|
|
|
|
this._fillDashSeg(g, x1, y1, ux, uy, d0, d1, DASH_HALF_W, fill); |
|
|
|
|
} |
|
|
|
|
d += DASH_LEN; |
|
|
|
|
useBlack = !useBlack; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|