节点移动用 tween 好,还是写在 update 里好?求前辈指点
简单功能用tween,封装的内容还算多,用起来比较省力,复杂的数量多的用update,因为tween没有update好管理。大概就这样吧
之前一直用的 tween,现在有个需求就是用 tween 的话,假如节点还没有移动到目标位置前,目标位置改变了,有办法同步 tween 的目标位置吗
tween 的话我感觉很好用贝塞尔曲线,update 里用贝塞尔曲线有什么思路吗
你要用贝塞尔曲线首先就得确定起点和终点啊
,已经不是update的问题了。如果只是要曲线的话,那你就用接近的目标位置的逻辑去做,起点位置和目标位置的向量和时间的进度,算出当前时间应该在的位置,然后根据时间加一个xy轴的偏移量函数就可以了吧。
实在不会的话交给ai吧,我看挺好的
在CocosCreator里实现一个物体平滑地沿曲线追击另一个移动中的物体,这个想法很棒。我为你准备了一套结合方向计算、曲线插值和帧率平滑处理的方案。
下面的流程图梳理了在 update 函数中每一帧需要执行的核心步骤,这能帮你直观地把控整个追击逻辑的脉络:
mermaid
下面,我们具体看看每个环节如何用代码实现。
核心脚本实现
你需要创建一个脚本(比如 ChaseComponent.ts ),将其挂载到需要移动的物体上。
typescript
typescript
复制
import { _decorator, Component, Vec3, v3, v2, math, Graphics } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('ChaseComponent')
export class ChaseComponent extends Component {
// 目标节点(会移动的那个点)
@property({ type: Component })
public targetNode: Component = null!;
// 移动速度(单位/秒)
@property
public moveSpeed: number = 5;
// 启用曲线移动(否则直接朝目标移动)
@property
public useCurve: boolean = true;
// 曲线强度(控制点偏移系数)
@property({ visible: function () { return this.useCurve; } })
public curveIntensity: number = 1.5;
// 启用调试绘制
@property
public enableDebug: boolean = false;
// 私有属性
private _graphics: Graphics | null = null;
private _pathPoints: Vec3[] = [];
private _currentPosition: Vec3 = new Vec3();
private _lastTargetPos: Vec3 = new Vec3();
onLoad() {
if (this.enableDebug) {
this._graphics = this.getComponent(Graphics) || this.addComponent(Graphics);
}
// 记录初始位置
this.node.getWorldPosition(this._currentPosition);
this._pathPoints.push(this._currentPosition.clone());
if (this.targetNode) {
this.targetNode.node.getWorldPosition(this._lastTargetPos);
}
}
update(deltaTime: number) {
if (!this.targetNode) return;
// 1. 获取当前和目标的世界坐标
const currentPos = this._currentPosition;
const targetPos = new Vec3();
this.targetNode.node.getWorldPosition(targetPos);
// 2. 计算方向向量
const direction = new Vec3();
Vec3.subtract(direction, targetPos, currentPos);
// 3. 计算移动距离
const distanceToMove = this.moveSpeed * deltaTime;
const distanceToTarget = Vec3.distance(currentPos, targetPos);
// 如果已经非常接近目标,可以停止移动
if (distanceToTarget < distanceToMove) {
Vec3.copy(currentPos, targetPos);
this.node.setWorldPosition(currentPos);
return;
}
// 4. 应用移动
if (this.useCurve) {
this._moveWithCurve(currentPos, targetPos, direction, distanceToMove, deltaTime);
} else {
// 直接线性移动
direction.normalize();
Vec3.scaleAndAdd(currentPos, currentPos, direction, distanceToMove);
this.node.setWorldPosition(currentPos);
}
// 5. 调整朝向(可选:让物体面朝移动方向)
if (direction.length() > 0.01) {
const angle = Math.atan2(direction.y, direction.x) * math.RAD2DEG;
this.node.setWorldRotationFromEuler(0, 0, angle);
}
// 6. 记录路径点用于调试
this._recordPathPoint(currentPos);
// 7. 记录上一帧目标位置
Vec3.copy(this._lastTargetPos, targetPos);
}
// 曲线移动实现
private _moveWithCurve(currentPos: Vec3, targetPos: Vec3, direction: Vec3, distanceToMove: number, deltaTime: number) {
// 计算控制点(在方向向量的垂直方向上取一个点,使路径弯曲)
const midPoint = new Vec3();
Vec3.lerp(midPoint, currentPos, targetPos, 0.5);
// 计算垂直向量(用于生成曲线控制点)
const normal = v3(-direction.y, direction.x, 0);
normal.normalize();
// 根据曲线强度调整控制点距离
const curveDistance = direction.length() * 0.3 * this.curveIntensity;
Vec3.scaleAndAdd(midPoint, midPoint, normal, curveDistance);
// 使用二次贝塞尔曲线计算新位置
const t = distanceToMove / Vec3.distance(currentPos, targetPos);
const newPos = this._quadraticBezier(currentPos, midPoint, targetPos, Math.min(t, 1.0));
Vec3.copy(currentPos, newPos);
this.node.setWorldPosition(currentPos);
}
// 二次贝塞尔曲线公式
private _quadraticBezier(p0: Vec3, p1: Vec3, p2: Vec3, t: number): Vec3 {
const result = new Vec3();
const tt = t * t;
const u = 1 - t;
const uu = u * u;
// B(t) = (1-t)²P₀ + 2(1-t)tP₁ + t²P₂
Vec3.scaleAndAdd(result, result, p0, uu);
Vec3.scaleAndAdd(result, result, p1, 2 * u * t);
Vec3.scaleAndAdd(result, result, p2, tt);
return result;
}
// 记录路径点
private _recordPathPoint(point: Vec3) {
this._pathPoints.push(point.clone());
// 保持路径点数量合理
if (this._pathPoints.length > 100) {
this._pathPoints.shift();
}
// 调试绘制
if (this.enableDebug && this._graphics) {
this._drawDebugPath();
}
}
// 绘制调试路径
private _drawDebugPath() {
if (!this._graphics || this._pathPoints.length < 2) return;
this._graphics.clear();
this._graphics.lineWidth = 2;
this._graphics.strokeColor.fromHEX('#ff0000');
// 绘制路径线
this._graphics.moveTo(this._pathPoints[0].x, this._pathPoints[0].y);
for (let i = 1; i < this._pathPoints.length; i++) {
this._graphics.lineTo(this._pathPoints[i].x, this._pathPoints[i].y);
}
this._graphics.stroke();
// 绘制路径点
this._graphics.fillColor.fromHEX('#00ff00');
for (let i = 0; i < this._pathPoints.length; i += 5) {
this._graphics.circle(this._pathPoints[i].x, this._pathPoints[i].y, 3);
this._graphics.fill();
}
}
}
参数说明与使用技巧
在CocosCreator编辑器中,你需要将移动的目标节点拖拽到脚本的 Target Node 属性上,然后可以根据需要调整以下参数:
| 参数 | 说明 | 推荐值 |
|---|---|---|
| Move Speed | 移动速度,单位是世界单位/秒 | 3-10 |
| Use Curve | 是否启用曲线移动,不启用则为直线追击 | true |
| Curve Intensity | 曲线强度,值越大路径弯曲越明显 | 1.0-2.0 |
| Enable Debug | 启用调试绘制,可以看到移动路径 | 开发时开启 |
使用技巧:
-
平滑性保障 :代码中使用了
deltaTime来确保帧率变化时移动速度保持一致。 -
性能优化 :如果场景中有大量追击物体,可以考虑禁用调试绘制,或使用对象池管理路径点数组。
-
曲线调整 :通过调整
curveIntensity可以控制路径的弯曲程度,值为0时接近直线。
扩展思路
除了基本的追击功能,你还可以考虑以下扩展方向:
-
预判移动 :如果目标移动有规律,可以计算其移动方向进行预判,让追击者拦截目标而非单纯跟随。
-
多段复杂曲线 :对于复杂路径,可以使用包含多个控制点的更高阶贝塞尔曲线或样条曲线。
-
动态调整速度 :根据与目标的距离动态调整速度,距离远时速度快,接近时减速,实现更自然的移动效果。
希望这个方案能帮助你实现理想中的追击效果。如果你在实现过程中遇到任何问题,或者需要进一步调整细节,随时可以告诉我。
好的老哥 !
