跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
TypeScript大前端算法

HarmonyOS 实战:动态轨道随机生成与碰撞检测

在跑酷类游戏中,程序化动态轨道生成是提升留存的核心设计。方案基于 HarmonyOS ArkTS 语言,通过定义圆形节点数据结构,利用随机角度与距离算法构建自然路径。重点解决边界反弹逻辑防止越界,以及基于最近邻检测的碰撞避免机制。配合 Canvas 渲染与状态管理,实现点击延伸轨道的交互效果,适用于跑酷或节奏类游戏开发。

DataScient发布于 2026/3/29更新于 2026/4/263 浏览
HarmonyOS 实战:动态轨道随机生成与碰撞检测

HarmonyOS 实战:动态轨道随机生成与碰撞检测

在跑酷类、节奏跳跃或几何闯关游戏中,无限轨道是提升玩家留存率的核心设计。实现这一功能的关键,在于程序化生成一条自然、可玩且不重复的随机路径。

本文将基于 HarmonyOS ArkTS 语言,从零构建一个动态轨道生成系统,实现点击一次延伸一段新轨道的交互逻辑。我们将重点解决如何定义轨道节点、生成合理方向与距离、实现边界反弹以及检测并避免圆环重叠等问题。

为什么需要动态轨道生成?

静态轨道虽然简单,但存在三大致命缺陷:

问题后果动态生成解决方案
路径固定玩家背板后失去挑战每次游戏路径都不同
关卡有限用户流失率高无限延伸,永不重复
多端适配难手机平板需分别设计一套算法,自动适配

在 OpenHarmony 生态下,程序化内容生成(PCG)已成为小游戏开发的标准范式。我们的目标是用最基础的数学与算法,构建一个轻量、高效且可扩展的轨道生成器。

定义 CircleSegment 类

轨道由一系列圆形节点组成。我们首先定义其数据结构:

class CircleSegment {
  x: number;
  y: number;
  radius: number;

  constructor(x: number, y: number, r: number = 25) {
    this.x = x;
    this.y = y;
    this.radius = r;
  }
}

这里 x, y 代表圆心坐标,单位是 px;radius 是圆环半径,默认 25px。使用标准 ES6 class 语法,ArkTS 完全支持,无外部依赖,便于后续序列化与 Canvas 绘制。

随机生成方向与距离

控制可玩性

经测试,相邻圆环中心距应落在 82px ~ 145px 区间,这样既确保玩家能轻松跳跃,又不失挑战性:

const MIN_SPACING: number = 82; // 最小可跳距离
const MAX_SPACING: number = 145; // 最大舒适距离
const distance: number = MIN_SPACING + Math.random() * (MAX_SPACING - MIN_SPACING);

引入角度扰动

若轨道沿固定方向延伸,会形成直线。为此,我们引入角度扰动机制:

let currentAngle: number = Math.PI / 4; // 初始方向:45°
const MAX_ANGLE_DELTA: number = Math.PI / 8; // ±22.5°
// 新方向 = 当前方向 + 随机扰动
currentAngle += (Math.random() * 2 - 1) * MAX_ANGLE_DELTA;

关键点在于 Math.random() * 2 - 1 生成 [-1, 1) 的浮点数,乘以 MAX_ANGLE_DELTA 后,轨道呈现自然弯曲,避免机械感。

计算新坐标

根据极坐标公式,计算新圆心位置:

const newX: number = lastX + distance * Math.cos(currentAngle);
const newY: number = lastY + distance * Math.sin(currentAngle);

以 lastX, lastY 为原点,偏移 distance 距离,得到新位置。

边界反弹算法

当新坐标超出屏幕边界时,不能简单裁剪,否则轨道会贴墙走。我们采用物理反弹策略:

const SAFE_MARGIN: number = 100; // 安全边距
if (newX < SAFE_MARGIN || newX > screenWidth - SAFE_MARGIN) {
  currentAngle = Math.PI - currentAngle; // X 轴反弹
}
if (newY < SAFE_MARGIN || newY > screenHeight - SAFE_MARGIN) {
  currentAngle = -currentAngle; // Y 轴反弹
}

// 再次计算新坐标(反弹后)
const finalX: number = lastX + distance * Math.cos(currentAngle);
const finalY: number = lastY + distance * Math.sin(currentAngle);

// 最终裁剪到安全区域
const clampedX: number = Math.max(SAFE_MARGIN, Math.min(screenWidth - SAFE_MARGIN, finalX));
const clampedY: number = Math.max(SAFE_MARGIN, Math.min(screenHeight - SAFE_MARGIN, finalY));

反弹后轨道折返,更自然;SAFE_MARGIN 避免圆环紧贴屏幕边缘。

防止重叠:碰撞检测

若新圆环与已有节点重叠(中心距小于阈值),会导致视觉混乱。我们实现 isTooClose 函数:

function isTooClose(candidate: CircleSegment, circles: CircleSegment[]): boolean {
  // 仅检查最近 5 个节点,提升性能
  const recent: CircleSegment[] = circles.slice(-5);
  for (const c of recent) {
    const dx: number = candidate.x - c.x;
    const dy: number = candidate.y - c.y;
    const dist: number = Math.sqrt(dx * dx + dy * dy);
    if (dist < 70) {
      return true;
    }
  }
  return false;
}

设计考量上,只查最近 5 个节点,避免 O(n) 全遍历;距离阈值设为 70px,小于最小间距 82px,确保不重叠。

重试机制 + Fallback

为保证生成成功率,我们采用 5 次重试加 fallback 策略:

for (let attempt: number = 0; attempt < 5; attempt++) {
  const cand: CircleSegment = generateCandidate();
  if (!isTooClose(cand, circles)) {
    circles.push(cand);
    return;
  }
}
// Fallback:沿原方向微调生成
const fallbackX: number = last.x + (MIN_SPACING + 10) * Math.cos(currentAngle);
const fallbackY: number = last.y + (MIN_SPACING + 10) * Math.sin(currentAngle);
circles.push(newCircleSegment(fallbackX, fallbackY, 25));

极端情况下(如角落密集),可能无法找到合适位置,fallback 确保流程不中断。

完整可运行代码

文件路径:entry/src/main/ets/pages/Index.ets

// entry/src/main/ets/pages/Index.ets
// 适配 OpenHarmony API 9/10

// 圆形节点数据模型
class CircleSegment {
  x: number;
  y: number;
  radius: number;

  constructor(x: number, y: number, radius: number = 25.0) {
    this.x = x;
    this.y = y;
    this.radius = radius;
  }
}

@Entry
@Component
struct TrackDemoApp {
  build() {
    Column() {
      TrackGenerator()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000');
  }
}

@Component
struct TrackGenerator {
  // 核心状态:必须用 @State 装饰,确保 UI 重绘
  @State private circles: CircleSegment[] = [];
  private currentAngle: number = Math.PI / 4;
  private initialized: boolean = false;
  private canvasWidth: number = 0;
  private canvasHeight: number = 0;

  // 轨道配置常量
  private readonly MIN_SPACING: number = 90.0;
  private readonly MAX_SPACING: number = 130.0;
  private readonly MAX_ANGLE_DELTA: number = Math.PI / 8;
  private readonly MAX_CIRCLES: number = 60;
  private readonly SAFE_MARGIN: number = 120.0;

  // 随机数工具
  private nextDouble(): number {
    return Math.random();
  }

  // 初始化轨道:使用固定屏幕尺寸,避免异步依赖
  private initializeTrack(): void {
    if (this.initialized) return;
    this.initialized = true;
    // 降级:使用固定尺寸初始化,适配所有设备
    this.canvasWidth = 1080;
    this.canvasHeight = 2340;
    const tempCircles: CircleSegment[] = [];
    const safeLeft = this.SAFE_MARGIN;
    const safeRight = this.canvasWidth - this.SAFE_MARGIN;
    const safeTop = this.SAFE_MARGIN;
    const safeBottom = this.canvasHeight - this.SAFE_MARGIN;

    let x = this.canvasWidth * 0.4;
    let y = this.canvasHeight * 0.4;

    for (let i = 0; i < 8; i++) {
      const radius = 18.0 + this.nextDouble() * 20.0;
      if (i === 0) {
        tempCircles.push(new CircleSegment(x, y, radius));
      } else {
        const angleDelta = (2 * this.nextDouble() - 1) * this.MAX_ANGLE_DELTA;
        this.currentAngle += angleDelta;
        const distance = this.MIN_SPACING + this.nextDouble() * (this.MAX_SPACING - this.MIN_SPACING);
        let newX = x + distance * Math.cos(this.currentAngle);
        let newY = y + distance * Math.sin(this.currentAngle);

        // 边界反弹
        if (newX < safeLeft || newX > safeRight) {
          this.currentAngle = Math.PI - this.currentAngle;
          newX = x + distance * Math.cos(this.currentAngle);
        }
        if (newY < safeTop || newY > safeBottom) {
          this.currentAngle = -this.currentAngle;
          newY = y + distance * Math.sin(this.currentAngle);
        }

        // 限制在安全区域内
        newX = Math.max(safeLeft, Math.min(safeRight, newX));
        newY = Math.max(safeTop, Math.min(safeBottom, newY));
        tempCircles.push(new CircleSegment(newX, newY, radius));
        x = newX;
        y = newY;
      }
    }
    // 关键:直接赋值给 @State 数组,触发 UI 重绘
    this.circles = tempCircles;
  }

  // 延伸轨道:确保重绘
  private extendTrack(): void {
    if (this.circles.length === 0 || this.canvasWidth === 0) return;
    const tempCircles = [...this.circles];
    const last = tempCircles[tempCircles.length - 1];
    const safeLeft = this.SAFE_MARGIN;
    const safeRight = this.canvasWidth - this.SAFE_MARGIN;
    const safeTop = this.SAFE_MARGIN;
    const safeBottom = this.canvasHeight - this.SAFE_MARGIN;

    let newNodeAdded = false;
    for (let attempt = 0; attempt < 5; attempt++) {
      const angleDelta = (2 * this.nextDouble() - 1) * this.MAX_ANGLE_DELTA;
      this.currentAngle += angleDelta;
      const distance = this.MIN_SPACING + this.nextDouble() * (this.MAX_SPACING - this.MIN_SPACING);
      let newX = last.x + distance * Math.cos(this.currentAngle);
      let newY = last.y + distance * Math.sin(this.currentAngle);

      // 边界反弹
      if (newX < safeLeft || newX > safeRight) {
        this.currentAngle = Math.PI - this.currentAngle;
        newX = last.x + distance * Math.cos(this.currentAngle);
      }
      if (newY < safeTop || newY > safeBottom) {
        this.currentAngle = -this.currentAngle;
        newY = last.y + distance * Math.sin(this.currentAngle);
      }

      // 限制在安全区域内
      newX = Math.max(safeLeft, Math.min(safeRight, newX));
      newY = Math.max(safeTop, Math.min(safeBottom, newY));

      const newRadius = 18.0 + this.nextDouble() * 20.0;
      const candidate = new CircleSegment(newX, newY, newRadius);

      // 检查是否与最近 5 个节点太近
      let tooClose = false;
      const recentCircles = tempCircles.slice(Math.max(0, tempCircles.length - 5));
      for (const c of recentCircles) {
        const dx = candidate.x - c.x;
        const dy = candidate.y - c.y;
        if (Math.sqrt(dx * dx + dy * dy) < 70.0) {
          tooClose = true;
          break;
        }
      }

      if (!tooClose) {
        tempCircles.push(candidate);
        if (tempCircles.length > this.MAX_CIRCLES) {
          tempCircles.shift();
        }
        newNodeAdded = true;
        break;
      }
    }
    if (newNodeAdded) {
      this.circles = tempCircles;
    }
  }

  // 绘制轨道和节点
  private drawTrack(ctx: CanvasRenderingContext2D): void {
    if (this.circles.length === 0) return;
    const total = this.circles.length;

    // 绘制连接线
    for (let i = 0; i < this.circles.length - 1; i++) {
      const a = this.circles[i];
      const b = this.circles[i + 1];
      const progress = i / total;
      const alpha = Math.max(30, Math.min(150, Math.floor(150 * (1 - progress))));
      ctx.beginPath();
      ctx.moveTo(a.x, a.y);
      ctx.lineTo(b.x, b.y);
      ctx.strokeStyle = `rgba(200, 200, 255, ${alpha / 255})`;
      ctx.lineWidth = 2.5;
      ctx.stroke();
    }

    // 绘制圆形节点
    for (let i = 0; i < this.circles.length; i++) {
      const c = this.circles[i];
      const progress = i / total;
      const r = Math.max(100, Math.min(255, Math.floor(100 + 100 * progress)));
      const g = Math.max(50, Math.min(150, Math.floor(150 - 100 * progress)));
      const bVal = 255;
      const alpha = Math.max(100, Math.min(200, Math.floor(200 * (1 - progress))));

      // 填充色
      ctx.beginPath();
      ctx.arc(c.x, c.y, c.radius, 0, 2 * Math.PI);
      ctx.fillStyle = `rgba(${r}, ${g}, ${bVal}, ${alpha / 255})`;
      ctx.fill();

      // 描边
      ctx.beginPath();
      ctx.arc(c.x, c.y, c.radius, 0, 2 * Math.PI);
      ctx.strokeStyle = `rgb(${r}, ${g}, ${bVal})`;
      ctx.lineWidth = 3;
      ctx.stroke();
    }
  }

  // 生命周期:在组件即将显示时初始化
  aboutToAppear() {
    this.initializeTrack();
  }

  build() {
    Stack() {
      // 画布绘制轨道
      Canvas(this.drawTrack.bind(this))
        .width('100%')
        .height('100%')
        .gesture(TapGesture().onAction(() => {
          this.extendTrack();
        }))
      // 提示文字
      Text('点击屏幕延伸轨道')
        .fontSize(20)
        .fontColor('#B3FFFFFF')
        .position({ x: 0, y: 60 })
        .width('100%')
        .textAlign(TextAlign.Center)
    }
    .width('100%')
    .height('100%');
  }
}

关键技术总结

技术点实现方式说明
CircleSegment自定义 class轻量节点结构
随机生成Math.random()控制距离与角度
边界处理反弹 + clamp防止越界,增强自然感
碰撞检测isTooClose + 重试避免重叠
内存管理环形队列(MAX=50)内存恒定
渲染Canvas 批量绘制性能最优

该方案具备良好的可玩性、稳定性与扩展性,可直接用于跑酷、节奏跳跃等小游戏开发。

目录

  1. HarmonyOS 实战:动态轨道随机生成与碰撞检测
  2. 为什么需要动态轨道生成?
  3. 定义 CircleSegment 类
  4. 随机生成方向与距离
  5. 控制可玩性
  6. 引入角度扰动
  7. 计算新坐标
  8. 边界反弹算法
  9. 防止重叠:碰撞检测
  10. 重试机制 + Fallback
  11. 完整可运行代码
  12. 关键技术总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 免费在线 SQL 转 ER 图工具推荐
  • 双足机器人 2-RSS-1U 并联踝关节设计与运动学解析
  • OpenClaw 大龙虾机器人本地部署与配置指南
  • Java 高性能开发实战:Redis 7 持久化机制详解
  • Linux 环境变量:作用、分类及自定义配置技巧
  • 基于 Java 的同城家政智能派单系统构建
  • 2026 RAG 技术演进:DeepSeek 与 Neo4j 构建企业智能体系
  • Python 使用 Turtle 库绘制动态彩色爱心动画
  • OpenClaw 搭建 QQ AI 办公机器人:关键词触发与邮件集成
  • Windows 系统 WSL2 Ubuntu 部署 OpenClaw
  • 位运算实战:两整数之和与只出现一次的数字
  • 自然语言处理在金融领域的应用与实战
  • 归并排序详解:分治策略与 C 语言实现
  • LIBERO 数据集:终身机器人学习与知识迁移基准
  • Git 默认编辑器配置及修改历史提交信息指南
  • OpenClaw 开源仓库:30+ 真实场景用例解析
  • C++ 容器详解:std::list 与 std::forward_list 对比分析
  • MySQL 索引机制实战:从 ORM 到 EXPLAIN 分析
  • MATLAB 实现基于天牛须搜索算法(BAS)的无人机三维路径规划
  • OpenClaw 开源 AI 智能体项目精选与部署指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online