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.
 
 
 
 
AdLike2048/assets/script/ui/BattleGame/MoveDashedLine.ts

211 lines
6.9 KiB

/**
* 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);
}
}