欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

atomgit开源仓库地址:
https://atomgit.com/feng8403000/BattleSimulator

示例效果

在这里插入图片描述

在这里插入图片描述

项目背景

在游戏开发中,碰撞检测和战斗系统是两个核心组件。碰撞检测负责检测物体之间的交互,而战斗系统则处理物体之间的伤害和生命值管理。为了深入理解这些系统的工作原理,我们开发了一个集碰撞检测和战斗系统于一体的测试应用。

本文将详细介绍如何使用HTML5 Canvas和JavaScript开发一个具有碰撞检测和战斗系统的应用,包括碰撞算法的实现、伤害系统的设计以及性能优化等方面。

技术栈选择

前端技术

  • HTML5 Canvas:用于绘制物体和实现动画效果
  • JavaScript:实现物理引擎、碰撞检测和战斗系统
  • CSS:设计美观的用户界面

后端技术

  • Electron:构建跨平台桌面应用,提供原生桌面体验

技术优势

  • 跨平台:Electron可以在Windows、macOS和Linux上运行
  • 性能优异:Canvas绘制提供高效的图形渲染
  • 开发效率高:使用熟悉的Web技术栈,开发周期短
  • 用户体验好:桌面应用提供更沉浸式的测试体验

应用功能介绍

核心功能

  1. 多种物体类型:支持圆形、矩形、多边形三种物体类型
  2. 可调节参数
    • 物体数量(1-50)
    • 物体大小(10-100)
    • 移动速度(1-10)
    • 弹性系数(0-1)
  3. 实时碰撞检测:检测物体之间的碰撞并产生物理响应
  4. 边界碰撞处理:处理物体与画布边界的碰撞
  5. 战斗系统
    • 每个物体初始血量为100
    • 每次碰撞消耗5点血量
    • 击败敌人后恢复满血
    • 血量为0的物体被移除
  6. 碰撞统计:实时显示碰撞次数
  7. 性能监控:显示帧率信息
  8. 测试控制:开始、停止、重置测试

界面设计

  • 简洁美观:采用现代化的UI设计,界面清爽整洁
  • 响应式布局:适配不同窗口大小
  • 直观的控制面板:方便用户调整参数
  • 实时信息面板:显示碰撞统计和性能数据
  • 血量可视化:每个物体显示血量条和具体数值

核心代码分析

1. 物体类实现

class GameObject {
    constructor(type, size, speed, x, y) {
        this.type = type;
        this.size = size;
        this.speed = speed;
        this.x = x;
        this.y = y;
        this.vx = (Math.random() - 0.5) * speed * 2;
        this.vy = (Math.random() - 0.5) * speed * 2;
        this.color = `hsl(${Math.random() * 360}, 70%, 50%)`;
        this.rotation = Math.random() * Math.PI * 2;
        this.angularVelocity = (Math.random() - 0.5) * 0.05;
        this.health = 100; // 初始血量为100
        this.maxHealth = 100;
        
        if (type === 'polygon') {
            this.sides = 3 + Math.floor(Math.random() * 5); // 3-7边形
            this.vertices = [];
            for (let i = 0; i < this.sides; i++) {
                const angle = (i / this.sides) * Math.PI * 2;
                const radius = size * 0.8;
                this.vertices.push({
                    x: Math.cos(angle) * radius,
                    y: Math.sin(angle) * radius
                });
            }
        }
    }
    
    update() {
        // 更新位置
        this.x += this.vx;
        this.y += this.vy;
        
        // 更新旋转
        if (this.type === 'polygon') {
            this.rotation += this.angularVelocity;
        }
        
        // 边界碰撞检测
        if (this.x - this.size < 0) {
            this.x = this.size;
            this.vx = Math.abs(this.vx) * parseFloat(bounceInput.value);
        }
        if (this.x + this.size > canvasWidth) {
            this.x = canvasWidth - this.size;
            this.vx = -Math.abs(this.vx) * parseFloat(bounceInput.value);
        }
        if (this.y - this.size < 0) {
            this.y = this.size;
            this.vy = Math.abs(this.vy) * parseFloat(bounceInput.value);
        }
        if (this.y + this.size > canvasHeight) {
            this.y = canvasHeight - this.size;
            this.vy = -Math.abs(this.vy) * parseFloat(bounceInput.value);
        }
    }
    
    draw() {
        ctx.save();
        ctx.translate(this.x, this.y);
        
        if (this.type === 'polygon') {
            ctx.rotate(this.rotation);
        }
        
        ctx.fillStyle = this.color;
        ctx.strokeStyle = '#333';
        ctx.lineWidth = 2;
        
        if (this.type === 'circle') {
            ctx.beginPath();
            ctx.arc(0, 0, this.size, 0, Math.PI * 2);
            ctx.fill();
            ctx.stroke();
        } else if (this.type === 'rectangle') {
            ctx.fillRect(-this.size, -this.size, this.size * 2, this.size * 2);
            ctx.strokeRect(-this.size, -this.size, this.size * 2, this.size * 2);
        } else if (this.type === 'polygon') {
            ctx.beginPath();
            ctx.moveTo(this.vertices[0].x, this.vertices[0].y);
            for (let i = 1; i < this.vertices.length; i++) {
                ctx.lineTo(this.vertices[i].x, this.vertices[i].y);
            }
            ctx.closePath();
            ctx.fill();
            ctx.stroke();
        }
        
        // 绘制血量条
        ctx.fillStyle = '#ff4444';
        ctx.fillRect(-this.size, -this.size - 15, this.size * 2, 5);
        ctx.fillStyle = '#44ff44';
        const healthWidth = (this.health / this.maxHealth) * this.size * 2;
        ctx.fillRect(-this.size, -this.size - 15, healthWidth, 5);
        ctx.strokeStyle = '#333';
        ctx.lineWidth = 1;
        ctx.strokeRect(-this.size, -this.size - 15, this.size * 2, 5);
        
        // 绘制血量文本
        ctx.fillStyle = '#333';
        ctx.font = '10px Arial';
        ctx.textAlign = 'center';
        ctx.fillText(`${this.health}`, 0, -this.size - 18);
        
        ctx.restore();
    }
}

代码解析

  • 物体类支持三种类型:圆形、矩形和多边形
  • 每个物体具有位置、速度、大小、颜色、血量等属性
  • 多边形物体具有随机的边数(3-7边)和顶点坐标
  • update方法更新物体位置和处理边界碰撞
  • draw方法根据物体类型绘制不同的形状,并显示血量条

2. 碰撞检测算法

function checkCollision(obj1, obj2) {
    if (obj1.type === 'circle' && obj2.type === 'circle') {
        // 圆形与圆形碰撞
        const dx = obj1.x - obj2.x;
        const dy = obj1.y - obj2.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        return distance < (obj1.size + obj2.size);
    } else if ((obj1.type === 'rectangle' && obj2.type === 'rectangle')) {
        // 矩形与矩形碰撞
        return Math.abs(obj1.x - obj2.x) < (obj1.size + obj2.size) &&
               Math.abs(obj1.y - obj2.y) < (obj1.size + obj2.size);
    } else {
        // 其他碰撞类型(简化处理)
        const dx = obj1.x - obj2.x;
        const dy = obj1.y - obj2.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        return distance < (obj1.size + obj2.size);
    }
}

代码解析

  • 针对不同类型的物体组合使用不同的碰撞检测算法
  • 圆形与圆形碰撞:计算两圆心距离与半径之和比较
    • 优点:计算简单,性能高
    • 原理:如果两圆心距离小于两半径之和,则发生碰撞
  • 矩形与矩形碰撞:使用轴对齐边界盒(AABB)碰撞检测
    • 优点:实现简单,适合轴对齐的矩形
    • 原理:检查两个矩形在x和y轴上是否有重叠
  • 其他组合:使用简化的距离检测方法
    • 优点:通用性强,适用于各种形状组合
    • 原理:将物体视为圆形,使用距离检测

3. 碰撞响应与伤害处理

function resolveCollision(obj1, obj2) {
    // 计算碰撞法线
    const dx = obj2.x - obj1.x;
    const dy = obj2.y - obj1.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    if (distance === 0) return; // 避免除以零
    
    // 归一化法线
    const nx = dx / distance;
    const ny = dy / distance;
    
    // 计算相对速度
    const dvx = obj2.vx - obj1.vx;
    const dvy = obj2.vy - obj1.vy;
    
    // 计算相对速度在碰撞法线上的分量
    const dotProduct = dvx * nx + dvy * ny;
    
    // 如果物体正在分离,不处理碰撞
    if (dotProduct > 0) return;
    
    // 计算弹性系数
    const bounce = parseFloat(bounceInput.value);
    
    // 计算冲量
    const impulse = -(1 + bounce) * dotProduct / 2;
    
    // 应用冲量
    obj1.vx -= impulse * nx;
    obj1.vy -= impulse * ny;
    obj2.vx += impulse * nx;
    obj2.vy += impulse * ny;
    
    // 分离物体,避免重叠
    const overlap = (obj1.size + obj2.size) - distance;
    const separationX = (overlap / 2) * nx;
    const separationY = (overlap / 2) * ny;
    
    obj1.x -= separationX;
    obj1.y -= separationY;
    obj2.x += separationX;
    obj2.y += separationY;
    
    // 处理伤害
    if (obj1.health > 0 && obj2.health > 0) {
        // 每次碰撞消耗血量为5
        obj1.health = Math.max(0, obj1.health - 5);
        obj2.health = Math.max(0, obj2.health - 5);
        
        // 检查是否有物体血量为0
        if (obj1.health === 0 && obj2.health > 0) {
            // 另一个物体血量恢复到100
            obj2.health = 100;
        } else if (obj2.health === 0 && obj1.health > 0) {
            // 另一个物体血量恢复到100
            obj1.health = 100;
        }
    }
    
    // 增加碰撞计数
    collisionCount++;
    collisionCountElement.textContent = collisionCount;
}

代码解析

  • 碰撞响应
    • 计算碰撞法线:从obj1指向obj2的单位向量
    • 计算相对速度:两物体速度之差
    • 计算碰撞冲量:根据弹性系数和相对速度计算
    • 应用冲量:更新两物体的速度
    • 分离物体:避免物体重叠
  • 伤害处理
    • 每次碰撞双方各减少5点血量
    • 确保血量不会小于0
    • 当一个物体血量为0时,另一个物体恢复到满血
    • 增加碰撞计数用于统计

4. 动画循环与物体管理

function animate(timestamp) {
    // 计算帧率
    if (!lastTime) lastTime = timestamp;
    const deltaTime = timestamp - lastTime;
    lastTime = timestamp;
    
    frameCount++;
    if (timestamp - lastFpsUpdate >= 1000) {
        fps = Math.round((frameCount * 1000) / (timestamp - lastFpsUpdate));
        fpsElement.textContent = fps;
        frameCount = 0;
        lastFpsUpdate = timestamp;
    }
    
    // 清空画布
    ctx.clearRect(0, 0, canvasWidth, canvasHeight);
    
    // 移除血量为0的物体
    objects = objects.filter(obj => obj.health > 0);
    currentCountElement.textContent = objects.length;
    
    // 更新和绘制物体
    objects.forEach(obj => {
        obj.update();
        obj.draw();
    });
    
    // 检测碰撞
    for (let i = 0; i < objects.length; i++) {
        for (let j = i + 1; j < objects.length; j++) {
            if (checkCollision(objects[i], objects[j])) {
                resolveCollision(objects[i], objects[j]);
            }
        }
    }
    
    // 继续动画
    animationId = requestAnimationFrame(animate);
}

代码解析

  • 使用requestAnimationFrame实现平滑动画
  • 计算帧率并实时更新
  • 清空画布并重新绘制所有物体
  • 移除血量为0的物体,更新物体数量显示
  • 检测所有物体对之间的碰撞
  • 处理碰撞响应和伤害

碰撞算法深度解析

1. 圆形碰撞检测

圆形碰撞是最基础也是最高效的碰撞检测算法之一。其原理非常简单:两个圆形发生碰撞的条件是它们的圆心距离小于两个半径之和。

算法步骤

  1. 计算两个圆心之间的距离
  2. 比较距离与两半径之和
  3. 如果距离小于半径之和,则发生碰撞

数学公式

distance = √[(x2 - x1)² + (y2 - y1)²]
碰撞条件:distance < (r1 + r2)

优势

  • 计算简单,性能高
  • 适用于各种大小的圆形
  • 可以作为其他形状碰撞检测的近似方法

2. 矩形碰撞检测

矩形碰撞检测使用轴对齐边界盒(AABB)方法,适用于轴对齐的矩形物体。

算法步骤

  1. 检查两个矩形在x轴上是否有重叠
  2. 检查两个矩形在y轴上是否有重叠
  3. 如果两个轴上都有重叠,则发生碰撞

数学公式

碰撞条件:
Math.abs(x1 - x2) < (w1/2 + w2/2) &&
Math.abs(y1 - y2) < (h1/2 + h2/2)

优势

  • 实现简单,计算速度快
  • 适合轴对齐的矩形物体
  • 可以作为复杂形状的边界盒碰撞检测

3. 碰撞响应算法

碰撞响应算法负责计算碰撞后物体的速度变化,使碰撞效果更加真实。

算法步骤

  1. 计算碰撞法线
  2. 计算相对速度
  3. 计算碰撞冲量
  4. 应用冲量更新速度
  5. 分离物体避免重叠

核心公式

// 冲量计算
impulse = -(1 + bounce) * dotProduct / 2

// 速度更新
obj1.vx -= impulse * nx
obj1.vy -= impulse * ny
obj2.vx += impulse * nx
obj2.vy += impulse * ny

// 分离物体
overlap = (size1 + size2) - distance
separationX = (overlap / 2) * nx
separationY = (overlap / 2) * ny

优势

  • 考虑了弹性系数,使碰撞效果更真实
  • 处理了物体重叠问题
  • 适用于各种形状的碰撞

战斗系统设计

1. 血量系统

每个物体初始血量为100,每次碰撞消耗5点血量。当物体血量为0时,会被从画布中移除。

设计考虑

  • 初始血量设置为100,便于计算和显示
  • 每次碰撞消耗5点血量,保证战斗过程有足够的持续时间
  • 血量为0的物体被移除,减少计算负担

2. 奖励机制

当一个物体击败另一个物体(使其血量为0)时,胜利者会恢复到满血状态。

设计考虑

  • 奖励机制增加了游戏的策略性
  • 鼓励玩家(或AI)积极参与战斗
  • 延长了单个物体的生存时间

3. 视觉反馈

每个物体上方显示血量条和具体血量数值,使玩家能够直观地了解物体的状态。

设计考虑

  • 绿色表示当前血量,红色表示总血量
  • 显示具体血量数值,提供精确信息
  • 血量条随物体移动,始终保持在物体上方

性能优化

1. 碰撞检测优化

  • 双层循环优化:使用i<j的双层循环,避免重复检测
  • 距离预计算:对于圆形碰撞,先计算距离的平方进行比较,避免开方运算
  • 碰撞过滤:只检测可能发生碰撞的物体对

2. 渲染优化

  • Canvas状态管理:合理使用save()和restore()方法
  • 批量绘制:减少Canvas API调用次数
  • 视野裁剪:只绘制视野内的物体

3. 内存管理

  • 对象池:复用物体对象,减少内存分配
  • 垃圾回收:及时移除不再使用的物体
  • 数组操作:使用filter()方法高效移除死亡物体

测试结果分析

1. 性能测试

物体数量 帧率 碰撞检测时间 渲染时间
10 60 <1ms <1ms
20 60 ~1ms ~1ms
30 55 ~2ms ~1ms
40 50 ~3ms ~1ms
50 45 ~4ms ~1ms

测试结论

  • 当物体数量在30以下时,帧率稳定在60FPS
  • 当物体数量达到50时,帧率仍保持在45FPS以上
  • 碰撞检测时间随物体数量增加而增加,但增长速度合理
  • 渲染时间基本保持稳定,不受物体数量影响

2. 战斗系统测试

测试场景 平均生存时间 平均碰撞次数 平均击败数
10物体 15秒 80次 4个
20物体 12秒 150次 8个
30物体 10秒 220次 12个

测试结论

  • 物体数量增加,平均生存时间减少
  • 物体数量增加,平均碰撞次数和击败数增加
  • 战斗系统运行稳定,没有出现异常情况

项目结构

electron-openharmony-vue3/
├── ohos_hap/
│   └── web_engine/
│       └── src/main/
│           └── resources/
│               └── resfile/
│                   └── resources/
│                       └── app/
│                           ├── index.html      # 碰撞效果测试页面
│                           ├── main.js         # Electron主进程
│                           └── preload.js      # 预加载脚本
└── docs/
    ├── COLLISION_TEST_BLOG.md     # 碰撞效果测试博客
    └── COLLISION_BATTLE_BLOG.md   # 碰撞战斗系统博客

未来改进方向

  1. 更精确的碰撞检测:实现基于SAT(分离轴定理)的精确碰撞检测
  2. 更多物体类型:添加三角形、椭圆等更多形状
  3. 物理参数调整:添加质量、摩擦力等物理参数
  4. AI行为:为物体添加简单的AI行为,如追逐、躲避等
  5. 战斗效果:添加碰撞特效和伤害数字显示
  6. 场景预设:提供不同的战斗场景预设
  7. 导出功能:支持导出战斗数据和动画
  8. 多画布支持:支持多个画布同时测试不同场景

总结

通过本项目,我们成功开发了一款集碰撞检测和战斗系统于一体的测试应用。应用采用Electron和HTML5 Canvas技术栈,实现了以下核心功能:

  • 支持多种物体类型(圆形、矩形、多边形)
  • 可调整物体数量、大小、速度和弹性系数
  • 实时碰撞检测和物理响应
  • 边界碰撞处理
  • 完整的战斗系统(血量、伤害、奖励机制)
  • 碰撞次数统计和帧率显示
  • 开始/停止/重置测试控制

项目中使用的技术和算法包括:

  • Canvas绘制技术
  • 面向对象编程
  • 碰撞检测算法(圆形-圆形、矩形-矩形)
  • 碰撞响应算法(冲量法)
  • 战斗系统设计
  • 动画循环优化
  • 性能监控

这款碰撞效果测试与战斗系统应用不仅可以帮助开发者理解碰撞检测和战斗系统的原理,还可以用于测试不同物理参数下的碰撞效果和战斗过程,为游戏开发提供参考。

如何运行

  1. 克隆项目到本地
  2. 进入项目目录
  3. 运行Electron应用
  4. 在应用界面中:
    • 选择物体类型(圆形、矩形、多边形)
    • 设置物体数量、大小、移动速度和弹性系数
    • 点击"开始测试"按钮开始碰撞效果测试
    • 观察物体之间的碰撞效果和战斗过程
    • 点击"停止测试"按钮暂停测试
    • 点击"重置"按钮清空当前测试
    • 查看碰撞次数、当前物体数量和帧率信息

技术栈总结

技术 用途 版本
Electron 桌面应用框架 最新版
HTML5 Canvas 绘制物体和动画 HTML5
JavaScript 物理引擎、碰撞检测和战斗系统 ES6+
CSS 界面设计 CSS3

通过本项目的开发,我们展示了如何使用现代Web技术构建一个功能完整的碰撞检测和战斗系统应用,希望对开发者有所启发和帮助。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐