/** * MoveDashedLine.ts * Author: David de ShineYoung * Creation Date: 2025/10/27 * Description: 绘制带箭头指示的虚线移动路径 * */ import { _decorator, Component, Graphics, Vec3, Color, Vec2 } from 'cc'; const { ccclass, property } = _decorator; const _arrowDir = new Vec3(); const _arrowCenter = new Vec3(); const _arrowTip = new Vec3(); const _arrowLeft = new Vec3(); const _arrowRight = new Vec3(); const _leftOffset = new Vec3(); const _rightOffset = new Vec3(); const _segStart = new Vec3(); const _segEnd = new Vec3(); @ccclass('MoveDashedLine') export class MoveDashedLine extends Component { @property(Graphics) graphics: Graphics = null!; @property([Vec2]) paths: Vec2[] = []; @property dashLength: number = 10; // 虚线每段长度 @property gapLength: number = 8; // 虚线间隙长度 @property lineWidth: number = 2; // 线宽 @property(Color) lineColor: Color = Color.WHITE; // 线条颜色 @property moveSpeed: number = 3; // 移动速度 @property arrowCount: number = 3; // 箭头数量 @property arrowSize: number = 20; // 箭头大小 @property arrowSpacingFactor: number = 20; // 箭头间距因子,值越大间距越大 private _currentOffset: number = 0; private _totalPathLength: number = 0; private _pathSegments: { start: Vec3; end: Vec3; length: number }[] = []; /** 引导线固定两点,setPathEndpoints 复用 */ private _pathA = new Vec2(); private _pathB = new Vec2(); onLoad() { this.calculatePathData(); } start() { } setLineColor(color: Color) { this.lineColor = color; } startSchedule() { this.schedule(this.updateLine); // 约60帧 } private calculatePathData() { this._pathSegments.length = 0; this._totalPathLength = 0; for (let i = 0; i < this.paths.length - 1; i++) { const p0 = this.paths[i]; const p1 = this.paths[i + 1]; _segStart.set(p0.x, p0.y, 0); _segEnd.set(p1.x, p1.y, 0); const length = Vec3.distance(_segStart, _segEnd); this._pathSegments.push({ start: _segStart.clone(), end: _segEnd.clone(), length, }); this._totalPathLength += length; } } private updateLine() { this._currentOffset += this.moveSpeed; // 当偏移超过路径长度时,循环 if (this._currentOffset > this._totalPathLength) { this._currentOffset = 0; } this.drawArrows(); } // 绘制箭头> private drawArrows() { if (!this.graphics || this._pathSegments.length === 0) return; // 清除之前的绘制内容 this.graphics.clear(); this.graphics.lineWidth = this.lineWidth; this.graphics.strokeColor = this.lineColor; this.graphics.fillColor = this.lineColor; // 使用arrowSpacingFactor作为箭头之间的最小间距(像素单位) const minArrowSpacing = Math.max(this.arrowSpacingFactor, this.arrowSize * 0.8); // 计算需要绘制的箭头数量 // 从起点开始绘制,每隔minArrowSpacing距离绘制一个箭头,直到终点 const totalArrows = Math.max(1, Math.floor(this._totalPathLength / minArrowSpacing)); // 计算实际的间距,确保箭头均匀分布在整个路径上 const actualSpacing = this._totalPathLength / totalArrows; for (let i = 0; i <= totalArrows; i++) { // 计算箭头位置:从起点到终点均匀分布 const arrowPosition = (this._currentOffset + actualSpacing * i) % this._totalPathLength; this.drawArrowAtPosition(arrowPosition); } } // 在指定位置绘制箭头> private drawArrowAtPosition(position: number) { if (!this.graphics) return; let accumulatedLength = 0; for (const segment of this._pathSegments) { if (accumulatedLength + segment.length > position) { const arrowSegmentPosition = position - accumulatedLength; Vec3.subtract(_arrowDir, segment.end, segment.start); Vec3.normalize(_arrowDir, _arrowDir); Vec3.multiplyScalar(_arrowCenter, _arrowDir, arrowSegmentPosition); Vec3.add(_arrowCenter, segment.start, _arrowCenter); const perpX = -_arrowDir.y; const perpY = _arrowDir.x; const arrowSize = this.arrowSize; const halfWidth = arrowSize * 0.3; Vec3.multiplyScalar(_arrowTip, _arrowDir, arrowSize * 0.5); Vec3.add(_arrowTip, _arrowCenter, _arrowTip); Vec3.multiplyScalar(_arrowLeft, _arrowDir, -arrowSize * 0.3); Vec3.add(_arrowLeft, _arrowCenter, _arrowLeft); _leftOffset.set(perpX * halfWidth, perpY * halfWidth, 0); Vec3.add(_arrowLeft, _arrowLeft, _leftOffset); Vec3.multiplyScalar(_arrowRight, _arrowDir, -arrowSize * 0.3); Vec3.add(_arrowRight, _arrowCenter, _arrowRight); _rightOffset.set(perpX * -halfWidth, perpY * -halfWidth, 0); Vec3.add(_arrowRight, _arrowRight, _rightOffset); this.graphics.moveTo(_arrowTip.x, _arrowTip.y); this.graphics.lineTo(_arrowLeft.x, _arrowLeft.y); this.graphics.lineTo(_arrowRight.x, _arrowRight.y); this.graphics.close(); this.graphics.fill(); break; } accumulatedLength += segment.length; } } /** 更新两端点(复用内部 Vec2,避免引导每帧 new) */ public setPathEndpoints(x1: number, y1: number, x2: number, y2: number): void { this._pathA.set(x1, y1); this._pathB.set(x2, y2); if (this.paths.length !== 2 || this.paths[0] !== this._pathA || this.paths[1] !== this._pathB) { this.paths = [this._pathA, this._pathB]; } this.calculatePathData(); this._currentOffset = 0; this.arrowSpacingFactor = 50; this.drawArrows(); } // 设置新的路径点 public setPaths(newPaths: Vec2[]) { this.paths = newPaths; this.calculatePathData(); this._currentOffset = 0; this.arrowSpacingFactor = 50; this.drawArrows(); } // 设置移动速度 public setMoveSpeed(speed: number) { this.moveSpeed = speed; } onDestroy() { this.unschedule(this.updateLine); } }