节点移动用 tween 好,还是写在 update 里好

节点移动用 tween 好,还是写在 update 里好?求前辈指点

简单功能用tween,封装的内容还算多,用起来比较省力,复杂的数量多的用update,因为tween没有update好管理。大概就这样吧

之前一直用的 tween,现在有个需求就是用 tween 的话,假如节点还没有移动到目标位置前,目标位置改变了,有办法同步 tween 的目标位置吗

tween 的话我感觉很好用贝塞尔曲线,update 里用贝塞尔曲线有什么思路吗

你要用贝塞尔曲线首先就得确定起点和终点啊 :sob:,已经不是update的问题了。如果只是要曲线的话,那你就用接近的目标位置的逻辑去做,起点位置和目标位置的向量和时间的进度,算出当前时间应该在的位置,然后根据时间加一个xy轴的偏移量函数就可以了吧。

实在不会的话交给ai吧,我看挺好的
在CocosCreator里实现一个物体平滑地沿曲线追击另一个移动中的物体,这个想法很棒。我为你准备了一套结合方向计算、曲线插值和帧率平滑处理的方案。

下面的流程图梳理了在 update 函数中每一帧需要执行的核心步骤,这能帮你直观地把控整个追击逻辑的脉络:

mermaid

下面,我们具体看看每个环节如何用代码实现。

:wrench: 核心脚本实现

你需要创建一个脚本(比如 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();
        }
    }
}

:gear: 参数说明与使用技巧

在CocosCreator编辑器中,你需要将移动的目标节点拖拽到脚本的 Target Node 属性上,然后可以根据需要调整以下参数:

参数 说明 推荐值
Move Speed 移动速度,单位是世界单位/秒 3-10
Use Curve 是否启用曲线移动,不启用则为直线追击 true
Curve Intensity 曲线强度,值越大路径弯曲越明显 1.0-2.0
Enable Debug 启用调试绘制,可以看到移动路径 开发时开启

使用技巧:

  • 平滑性保障 :代码中使用了 deltaTime 来确保帧率变化时移动速度保持一致。

  • 性能优化 :如果场景中有大量追击物体,可以考虑禁用调试绘制,或使用对象池管理路径点数组。

  • 曲线调整 :通过调整 curveIntensity 可以控制路径的弯曲程度,值为0时接近直线。

:bulb: 扩展思路

除了基本的追击功能,你还可以考虑以下扩展方向:

  • 预判移动 :如果目标移动有规律,可以计算其移动方向进行预判,让追击者拦截目标而非单纯跟随。

  • 多段复杂曲线 :对于复杂路径,可以使用包含多个控制点的更高阶贝塞尔曲线或样条曲线。

  • 动态调整速度 :根据与目标的距离动态调整速度,距离远时速度快,接近时减速,实现更自然的移动效果。

希望这个方案能帮助你实现理想中的追击效果。如果你在实现过程中遇到任何问题,或者需要进一步调整细节,随时可以告诉我。

好的老哥 !:yum: :yum: