/** * MoveDashedLine.ts * Author: David de ShineYoung * Creation Date: 2025/10/27 * Description: 绘制带箭头指示的虚线移动路径 * */ import { _decorator, Component, Graphics, Vec3, Color, Node, Vec2 } from 'cc'; const { ccclass, property } = _decorator; @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}[] = []; onLoad() { this.calculatePathData(); } start() { } setLineColor(color: Color) { this.lineColor = color; } startSchedule() { this.schedule(this.updateLine); // 约60帧 } private calculatePathData() { this._pathSegments = []; this._totalPathLength = 0; for (let i = 0; i < this.paths.length - 1; i++) { const start = this.paths[i].toVec3(); const end = this.paths[i + 1].toVec3(); const length = Vec3.distance(start, end); this._pathSegments.push({ start: start.clone(), end: end.clone(), length: length }); this._totalPathLength += length; } } private updateLine() { this._currentOffset += this.moveSpeed; // 当偏移超过路径长度时,循环 if (this._currentOffset > this._totalPathLength) { this._currentOffset = 0; } //this.drawDashedLine(); 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; const segmentDirection = new Vec3(); Vec3.subtract(segmentDirection, segment.end, segment.start); Vec3.normalize(segmentDirection, segmentDirection); // 计算箭头中心点 const arrowCenter = new Vec3(); Vec3.multiplyScalar(arrowCenter, segmentDirection, arrowSegmentPosition); Vec3.add(arrowCenter, segment.start, arrowCenter); // 计算垂直于路径方向的向量(用于确定箭头宽度) const perpendicular = new Vec3(-segmentDirection.y, segmentDirection.x, 0); // 计算箭头的三个点 const arrowSize = this.arrowSize; const halfWidth = arrowSize * 0.3; // 箭头宽度的一半 // 箭头尖端点(沿路径方向前方) const arrowTip = new Vec3(); Vec3.multiplyScalar(arrowTip, segmentDirection, arrowSize * 0.5); Vec3.add(arrowTip, arrowCenter, arrowTip); // 箭头左底部点(沿路径方向后方,垂直方向左侧) const arrowLeft = new Vec3(); Vec3.multiplyScalar(arrowLeft, segmentDirection, -arrowSize * 0.3); Vec3.add(arrowLeft, arrowCenter, arrowLeft); // 使用Vec3.multiplyScalar和Vec3.add替代multiplyScalarAndAdd const leftOffset = new Vec3(); Vec3.multiplyScalar(leftOffset, perpendicular, halfWidth); Vec3.add(arrowLeft, arrowLeft, leftOffset); // 箭头右底部点(沿路径方向后方,垂直方向右侧) const arrowRight = new Vec3(); Vec3.multiplyScalar(arrowRight, segmentDirection, -arrowSize * 0.3); Vec3.add(arrowRight, arrowCenter, arrowRight); // 使用Vec3.multiplyScalar和Vec3.add替代multiplyScalarAndAdd const rightOffset = new Vec3(); Vec3.multiplyScalar(rightOffset, perpendicular, -halfWidth); 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.lineTo(arrowTip.x, arrowTip.y); this.graphics.stroke(); break; } accumulatedLength += segment.length; } } // 设置新的路径点 public setPaths(newPaths: Vec2[]) { this.paths = newPaths; this.calculatePathData(); this._currentOffset = 0; this.arrowSpacingFactor = 50; //this.drawDashedLine(); this.drawArrows(); } // 设置移动速度 public setMoveSpeed(speed: number) { this.moveSpeed = speed; } onDestroy() { this.unschedule(this.updateLine); } }