Flutter自定义计时器开发:车辆沿赛道随计时移动并转向
自定义计时器+赛道车辆移动实现方案
1. 赛道路径定义
先把赛道的路径结构化,推荐两种方式:
- 多点折线路径:适合规则或简单赛道,按顺序记录关键节点坐标
// 闭环赛道的关键节点数组 const trackPath = [ { x: 50, y: 300 }, { x: 200, y: 100 }, { x: 400, y: 100 }, { x: 550, y: 300 }, { x: 400, y: 500 }, { x: 200, y: 500 }, { x: 50, y: 300 } // 回到起点形成闭环 ]; - 贝塞尔曲线路径:适合平滑曲线赛道,每段记录起点、两个控制点、终点坐标,能实现更自然的弯道效果。
2. 计时与车辆进度映射
核心逻辑是把「剩余时间占总时长的比例」转换成车辆在赛道上的前进进度:
const totalTime = 60; // 总计时时长(秒) let remainingTime = 60; // 计算进度:0=起点,1=跑完一圈回到起点 const progress = 1 - (remainingTime / totalTime); // 多圈场景用取模实现循环:progress % 1
3. 车辆位置计算
折线路径场景
根据进度定位到对应路径段,再用线性插值算出当前坐标:
function getCarPosition(path, progress) { const segmentCount = path.length - 1; // 闭环路径的段数 const currentSegment = Math.floor(progress * segmentCount); const segmentProgress = (progress * segmentCount) - currentSegment; const startPoint = path[currentSegment]; const endPoint = path[currentSegment + 1]; // 线性插值计算当前坐标 return { x: startPoint.x + (endPoint.x - startPoint.x) * segmentProgress, y: startPoint.y + (endPoint.y - startPoint.y) * segmentProgress }; }
贝塞尔曲线场景
用贝塞尔公式直接计算对应进度的坐标,或者预计算路径上的密集采样点,再通过插值获取位置,保证平滑度。
4. 车辆朝向调整
让车辆朝向与路径前进方向一致,核心是计算路径段的方向向量:
function getCarRotation(path, progress) { const segmentCount = path.length - 1; const currentSegment = Math.floor(progress * segmentCount); const startPoint = path[currentSegment]; const endPoint = path[currentSegment + 1]; // 计算方向向量的角度(弧度转角度) const dx = endPoint.x - startPoint.x; const dy = endPoint.y - startPoint.y; return Math.atan2(dy, dx) * 180 / Math.PI; }
如果是平滑曲线赛道,可以取当前点前后的采样点计算切线方向,让转向更顺滑。
5. 计时更新与渲染循环
用requestAnimationFrame实现持续渲染,同步更新计时和车辆状态:
let startTime = Date.now(); const totalTimeMs = totalTime * 1000; function update() { const elapsedMs = Date.now() - startTime; remainingTime = Math.max(0, totalTime - elapsedMs / 1000); const progress = 1 - (remainingTime / totalTime); const carPos = getCarPosition(trackPath, progress); const carRotation = getCarRotation(trackPath, progress); // 更新DOM车辆元素的位置与旋转角度 const carEl = document.getElementById('car'); carEl.style.left = `${carPos.x}px`; carEl.style.top = `${carPos.y}px`; carEl.style.transform = `rotate(${carRotation}deg)`; if (remainingTime > 0) { requestAnimationFrame(update); } } // 启动计时器与动画 update();
6. 优化建议
- 预计算路径总长度,用「距离进度」代替段数进度,避免折线赛道各段长度不同导致车辆速度不均
- 复杂场景优先用Canvas渲染,比DOM元素动画更流畅
- 加入缓动函数(如线性插值lerp)让车辆的位置和旋转变化更平滑
内容的提问来源于stack exchange,提问作者L.Goyal
相关产品推荐
相关产品推荐

