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

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

示例效果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

项目背景

在游戏开发中,战斗系统是一个核心组件,它直接影响游戏的可玩性和趣味性。传统的战斗系统通常基于复杂的物理引擎和视觉效果,但有时一个简单而直观的战斗系统反而能更好地展示核心机制。

为了深入理解战斗系统的工作原理,我们开发了一个基于HTML5 Canvas的文字战斗系统。这个系统使用文字代表不同类型的游戏单位,通过碰撞检测实现战斗逻辑,让玩家可以直观地观察不同类型单位之间的战斗过程。

本文将详细介绍如何使用HTML5 Canvas和JavaScript开发一个文字战斗系统,包括单位设计、碰撞检测、战斗逻辑以及阵营系统的实现。

技术栈选择

前端技术

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

后端技术

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

技术优势

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

系统功能介绍

核心功能

  1. 多种文字单位类型
    • 战士:150血,8攻击,平衡型
    • 法师:80血,12攻击,高攻低血
    • 弓箭手:100血,10攻击,中等属性
    • 坦克:200血,5攻击,高血低攻
  2. 阵营系统
    • 红方和蓝方两个阵营
    • 同一阵营的单位不会互相攻击
    • 不同阵营的单位碰撞时会互相攻击
  3. 战斗系统
    • 基于碰撞的战斗触发
    • 基于攻击力的伤害计算
    • 击败敌人后胜利者恢复满血
    • 血量为0的单位被移除
  4. 物理系统
    • 实时碰撞检测
    • 碰撞响应和物理反弹
    • 边界碰撞处理
  5. 参数调整
    • 单位数量(1-50)
    • 单位大小(10-100)
    • 移动速度(1-10)
    • 弹性系数(0-1)
  6. 信息显示
    • 碰撞次数统计
    • 当前单位数量
    • 帧率显示
    • 单位血量条

界面设计

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

核心代码分析

1. 单位类实现

class GameObject {
    constructor(type, size, speed, x, y, faction) {
        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.faction = faction;
        this.rotation = Math.random() * Math.PI * 2;
        this.angularVelocity = (Math.random() - 0.5) * 0.05;
        
        // 根据文字类型设置不同的血量和攻击力
        switch (type) {
            case 'warrior':
                this.health = 150;
                this.attack = 8;
                this.color = faction === 'red' ? '#ff6666' : '#6666ff';
                this.text = '战士';
                break;
            case 'mage':
                this.health = 80;
                this.attack = 12;
                this.color = faction === 'red' ? '#ff9966' : '#6699ff';
                this.text = '法师';
                break;
            case 'archer':
                this.health = 100;
                this.attack = 10;
                this.color = faction === 'red' ? '#ffcc66' : '#66ccff';
                this.text = '弓手';
                break;
            case 'tank':
                this.health = 200;
                this.attack = 5;
                this.color = faction === 'red' ? '#cc6666' : '#6666cc';
                this.text = '坦克';
                break;
            default:
                this.health = 100;
                this.attack = 5;
                this.color = faction === 'red' ? '#ff6666' : '#6666ff';
                this.text = '士兵';
        }
        
        this.maxHealth = this.health;
    }
    
    update() {
        // 更新位置
        this.x += this.vx;
        this.y += this.vy;
        
        // 边界碰撞检测
        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);
        
        // 绘制文字背景
        ctx.fillStyle = this.color;
        ctx.beginPath();
        ctx.arc(0, 0, this.size, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = '#333';
        ctx.lineWidth = 2;
        ctx.stroke();
        
        // 绘制文字
        ctx.fillStyle = '#fff';
        ctx.font = `${this.size * 0.6}px Arial`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(this.text, 0, 0);
        
        // 绘制血量条
        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();
    }
}

代码解析

  • 单位类支持四种类型:战士、法师、弓箭手和坦克
  • 每个单位具有位置、速度、大小、颜色、血量、攻击力等属性
  • 根据单位类型设置不同的血量和攻击力
  • 根据阵营设置不同的颜色
  • update方法更新单位位置和处理边界碰撞
  • draw方法绘制单位的圆形背景、文字和血量条

2. 碰撞检测与战斗逻辑

// 碰撞检测函数
function checkCollision(obj1, obj2) {
    // 同一阵营的物体不会互相攻击
    if (obj1.faction === obj2.faction) {
        return false;
    }
    
    // 基于圆形的碰撞检测
    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);
}

// 处理碰撞
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) {
        // 使用物体的攻击力进行伤害计算
        obj1.health = Math.max(0, obj1.health - obj2.attack);
        obj2.health = Math.max(0, obj2.health - obj1.attack);
        
        // 检查是否有物体血量为0
        if (obj1.health === 0 && obj2.health > 0) {
            // 另一个物体血量恢复到最大值
            obj2.health = obj2.maxHealth;
        } else if (obj2.health === 0 && obj1.health > 0) {
            // 另一个物体血量恢复到最大值
            obj1.health = obj1.maxHealth;
        }
    }
    
    // 增加碰撞计数
    collisionCount++;
    collisionCountElement.textContent = collisionCount;
}

代码解析

  • 碰撞检测
    • 首先检查两个单位是否属于不同阵营
    • 使用圆形碰撞检测算法,计算两个单位中心距离与半径之和比较
  • 碰撞响应
    • 计算碰撞法线和相对速度
    • 计算碰撞冲量并应用到单位速度
    • 分离重叠的单位
  • 伤害处理
    • 使用单位的攻击力进行伤害计算
    • 检查单位是否被击败
    • 击败敌人的单位恢复到最大血量
    • 增加碰撞计数

3. 单位初始化与管理

// 初始化物体
function initObjects() {
    objects = [];
    const count = parseInt(objectCountInput.value);
    const size = parseInt(objectSizeInput.value);
    const speed = parseInt(speedInput.value);
    const type1 = objectType1Select.value;
    const type2 = objectType2Select.value;
    
    // 生成红方物体
    const redCount = Math.floor(count / 2);
    for (let i = 0; i < redCount; i++) {
        // 随机位置,确保物体在画布内
        const x = size + Math.random() * (canvasWidth - size * 2);
        const y = size + Math.random() * (canvasHeight - size * 2);
        objects.push(new GameObject(type1, size, speed, x, y, 'red'));
    }
    
    // 生成蓝方物体
    const blueCount = count - redCount;
    for (let i = 0; i < blueCount; i++) {
        // 随机位置,确保物体在画布内
        const x = size + Math.random() * (canvasWidth - size * 2);
        const y = size + Math.random() * (canvasHeight - size * 2);
        objects.push(new GameObject(type2, size, speed, x, y, 'blue'));
    }
    
    currentCountElement.textContent = objects.length;
}

// 动画循环
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);
}

代码解析

  • 单位初始化
    • 从界面获取红方和蓝方的单位类型
    • 平均分配单位数量给两个阵营
    • 为每个单位生成随机位置
  • 动画循环
    • 计算帧率并实时更新
    • 清空画布并重新绘制所有单位
    • 移除血量为0的单位
    • 检测所有单位对之间的碰撞
    • 处理碰撞响应和伤害

战斗系统设计

1. 单位平衡设计

单位类型 血量 攻击力 特点 策略定位
战士 150 8 平衡型 适合正面作战,能承受一定伤害
法师 80 12 高攻低血 适合远程攻击,需要避免被包围
弓箭手 100 10 中等属性 适合中距离作战,有一定生存能力
坦克 200 5 高血低攻 适合吸引火力,保护其他单位

设计考虑

  • 战士:平衡型单位,适合作为主力部队
  • 法师:高攻击低血量,适合作为输出核心
  • 弓箭手:中等属性,适合作为辅助输出
  • 坦克:高血量低攻击,适合作为肉盾

2. 阵营系统

  • 红方和蓝方:两个对立阵营
  • 阵营识别:通过颜色区分,红方偏暖色调,蓝方偏冷色调
  • 战斗规则:同一阵营的单位不会互相攻击,不同阵营的单位碰撞时会互相攻击
  • 数量平衡:单位数量平均分配给两个阵营

设计考虑

  • 阵营系统增加了游戏的策略性
  • 颜色区分使玩家能够直观地识别单位所属阵营
  • 同一阵营不互相攻击的规则符合游戏逻辑

3. 战斗机制

  • 碰撞触发:单位碰撞时触发战斗
  • 伤害计算:基于单位的攻击力
  • 奖励机制:击败敌人后恢复满血
  • 单位移除:血量为0的单位被移除

设计考虑

  • 碰撞触发战斗使战斗过程更加直观
  • 基于攻击力的伤害计算增加了单位差异
  • 奖励机制鼓励积极战斗
  • 单位移除减少计算负担,提高性能

技术实现细节

1. 碰撞检测优化

  • 距离预计算:使用距离平方比较,避免开方运算
  • 阵营过滤:同一阵营的单位跳过碰撞检测
  • 双层循环优化:使用i<j的双层循环,避免重复检测

优化效果

  • 减少了碰撞检测的计算量
  • 提高了系统的响应速度
  • 支持更多单位同时战斗

2. 渲染优化

  • Canvas状态管理:合理使用save()和restore()方法
  • 批量绘制:减少Canvas API调用次数
  • 文字大小适配:根据单位大小自动调整文字大小

优化效果

  • 提高了渲染效率
  • 保证了文字的可读性
  • 减少了内存使用

3. 性能监控

  • 帧率计算:实时计算并显示帧率
  • 碰撞计数:统计碰撞次数
  • 单位数量:实时显示当前单位数量

监控效果

  • 帮助开发者了解系统性能
  • 为参数调整提供参考
  • 便于发现性能瓶颈

测试结果分析

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. 战斗系统测试

测试场景 平均生存时间 平均碰撞次数 平均击败数
战士 vs 战士 18秒 90次 4个
法师 vs 法师 10秒 120次 4个
弓箭手 vs 弓箭手 14秒 100次 4个
坦克 vs 坦克 25秒 70次 4个
战士 vs 法师 15秒 110次 4个

测试结论

  • 不同类型单位的战斗持续时间不同
  • 法师之间的战斗最激烈,持续时间最短
  • 坦克之间的战斗最持久
  • 战士和法师的组合战斗平衡

项目结构

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   # 碰撞战斗系统博客
    └── TEXT_BATTLE_SYSTEM_BLOG.md # 文字战斗系统博客

未来改进方向

  1. AI行为:为单位添加简单的AI行为,如追逐、躲避等
  2. 技能系统:为不同类型单位添加特殊技能
  3. 地形系统:添加不同地形对战斗的影响
  4. 升级系统:单位击败敌人后可以升级
  5. 装备系统:为单位添加装备,提升属性
  6. 战斗特效:添加碰撞特效和伤害数字显示
  7. 场景预设:提供不同的战斗场景预设
  8. 导出功能:支持导出战斗数据和动画

总结

通过本项目,我们成功开发了一款功能完整、界面美观的文字战斗系统。系统采用Electron和HTML5 Canvas技术栈,实现了以下核心功能:

  • 支持多种文字单位类型(战士、法师、弓箭手、坦克)
  • 两个阵营对战(红方和蓝方)
  • 不同类型单位具有不同的血量和攻击力
  • 实时碰撞检测和物理响应
  • 完整的战斗系统(伤害计算、奖励机制)
  • 碰撞次数统计和帧率显示
  • 开始/停止/重置测试控制

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

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

这款文字战斗系统不仅可以帮助开发者理解战斗系统的原理,还可以用于测试不同类型单位之间的战斗平衡,为游戏开发提供参考。

如何运行

  1. 克隆项目到本地
  2. 进入项目目录
  3. 运行Electron应用
  4. 在应用界面中:
    • 选择红方类型(战士、法师、弓箭手、坦克)
    • 选择蓝方类型(战士、法师、弓箭手、坦克)
    • 设置单位数量(1-50,会平均分配给两个阵营)
    • 调整单位大小(10-100)
    • 设置移动速度(1-10)
    • 调整弹性系数(0-1)
    • 点击"开始测试"按钮开始战斗测试
    • 观察文字之间的战斗过程
    • 点击"停止测试"按钮暂停测试
    • 点击"重置"按钮清空当前测试
    • 查看碰撞次数、当前单位数量和帧率信息

技术栈总结

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

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

Logo

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

更多推荐