0

0

JavaScript Canvas 游戏:使用类管理多个敌人实例的教程

碧海醫心

碧海醫心

发布时间:2025-10-09 10:07:37

|

426人浏览过

|

来源于php中文网

原创

javascript canvas 游戏:使用类管理多个敌人实例的教程

在JavaScript Canvas游戏中,当需要管理多个独立移动的敌人或其他游戏实体时,直接使用全局变量会导致所有实体共享相同的状态,从而表现出同步且非预期的行为。本文将深入探讨这一常见问题,并提供一个基于JavaScript类的面向对象解决方案,通过为每个实体创建独立实例来有效管理其各自的位置、速度和行为,确保每个敌人都能独立运动并响应环境,从而构建出更复杂和动态的游戏场景。

问题描述:全局状态与多实体管理

在开发基于JavaScript Canvas的游戏时,一个常见的挑战是如何有效地管理多个游戏实体,例如多个敌人。如果每个敌人的位置、速度等属性都依赖于全局变量,那么当游戏中出现多个敌人时,它们将无法独立行动。

考虑以下初始代码片段,它尝试绘制并移动一个敌人:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var x = 0; // 全局X坐标
var y = 0; // 全局Y坐标
var x_add = 2; // 全局X方向速度
var y_add = 2; // 全局Y方向速度

function animate(){
    draw();
    setTimeout(animate, 10);
};

function draw(){
    ctx.clearRect(0,0,1500, 500);
    draw_enemy(900, 100, "red", 40, 50); // 绘制一个敌人
};

function draw_enemy(start_x, start_y, fill, w, h){
    // 边界检测和速度反转逻辑
    if(x + w + start_x >= 1000){ // 注意:这里使用了硬编码的画布宽度
        x_add = -2;
    }
    if(y + h + start_y >= 500){ // 注意:这里使用了硬编码的画布高度
        y_add = -2;
    }
    if(y  + start_y <= 0){
        y_add = 2;
    }
    if(x + start_x <= 0){
        x_add = 2;
    }
    x += x_add; // 更新全局X坐标
    y += y_add; // 更新全局Y坐标
    ctx.fillStyle = fill;
    ctx.fillRect(x + start_x, y + start_y, w, h);
};

animate();

以及对应的HTML结构:

<html>
    <head>
        <title>local storage test</title>
        <style>
        </style>
    </head>
    <body>
        <div style="text-align: center">
            <canvas id="canvas" width="1000" height="500" style="border: 1px solid black; padding: 5 px">
            </canvas>
        </div>        
        <script src="script.js">
        </script>
    </body>
</html>

这段代码的问题在于,x、y、x_add 和 y_add 都是全局变量。当只绘制一个敌人时,一切正常。然而,如果尝试绘制两个或更多敌人,例如在 draw 函数中调用 draw_enemy 两次,所有敌人都会共享这些全局变量。这意味着:

立即学习Java免费学习笔记(深入)”;

  1. 它们将使用相同的 x 和 y 坐标,导致它们在屏幕上重叠或表现出相同的位移。
  2. 当任何一个敌人触碰到边界时,它会修改全局的 x_add 或 y_add,从而影响所有其他敌人的移动方向,使得它们的行为变得同步且随机。

这种共享全局状态的方式使得每个敌人都无法拥有独立的运动轨迹和行为逻辑,这显然不符合游戏设计的需求。

解决方案:使用JavaScript类进行面向对象管理

为了解决上述问题,最佳实践是采用面向对象编程的思想,使用JavaScript的 class 语法来定义敌人的蓝图。通过类,我们可以创建多个独立的敌人实例,每个实例都拥有自己的属性(如位置、速度、颜色、大小等)和方法(如绘制、更新)。

1. 定义 Enemy 类

首先,我们创建一个 Enemy 类,它将封装每个敌人的所有相关属性和行为。

AdsGo AI
AdsGo AI

全自动 AI 广告专家,助您在数分钟内完成广告搭建、优化及扩量

下载
class Enemy {
  constructor(color, initialX, initialY, width, height, initialVx, initialVy) {
    // 为每个敌人实例初始化独立的属性
    this.x = initialX !== undefined ? initialX : 50 + Math.random() * (canvas.width - 100);
    this.y = initialY !== undefined ? initialY : 50 + Math.random() * (canvas.height - 100);
    this.w = width !== undefined ? width : 40;
    this.h = height !== undefined ? height : 50;
    this.c = color !== undefined ? color : 'red'; // 颜色
    this.vx = initialVx !== undefined ? initialVx : 2; // X方向速度
    this.vy = initialVy !== undefined ? initialVy : 2; // Y方向速度
  }

  // 绘制敌人的方法
  draw() {
    ctx.fillStyle = this.c;
    ctx.fillRect(this.x, this.y, this.w, this.h);
  }

  // 更新敌人状态(位置和边界检测)的方法
  update() {
    // 边界检测:使用this.x, this.y, this.w, this.h确保是当前实例的属性
    if (this.x + this.w >= canvas.width || this.x <= 0) {
      this.vx *= -1; // 碰到左右边界反转X方向速度
      // 修正位置,防止卡在边界
      this.x = this.x + this.w >= canvas.width ? canvas.width - this.w : 0;
    }
    if (this.y + this.h >= canvas.height || this.y <= 0) {
      this.vy *= -1; // 碰到上下边界反转Y方向速度
      // 修正位置,防止卡在边界
      this.y = this.y + this.h >= canvas.height ? canvas.height - this.h : 0;
    }

    // 更新位置:使用this.vx, this.vy确保是当前实例的速度
    this.x += this.vx;
    this.y += this.vy;

    this.draw(); // 更新后立即绘制
  }
}

在 Enemy 类中:

  • constructor 方法用于初始化每个敌人实例的属性,如 x、y(位置)、w、h(宽度、高度)、c(颜色)以及 vx、vy(X、Y方向的速度)。通过 this 关键字,确保这些属性是每个实例独有的。
  • draw() 方法负责根据当前实例的 x、y、w、h 和 c 属性在 Canvas 上绘制敌人。
  • update() 方法负责更新敌人实例的位置,并执行边界检测。同样,它使用 this 关键字来访问和修改当前实例的属性。

2. 管理多个敌人实例

为了管理多个敌人,我们需要一个数组来存储 Enemy 类的实例。

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

let enemies = []; // 存储所有敌人实例的数组

// 创建多个敌人实例并添加到数组中
function createEnemies(count = 5) {
  for (let i = 0; i < count; i++) {
    // 可以传入不同的参数来创建具有不同初始状态的敌人
    enemies.push(new Enemy('red')); 
  }
}

createEnemies(3); // 创建3个红色敌人
enemies.push(new Enemy('green', 100, 200, 50, 50, 3, -3)); // 创建一个绿色敌人,并指定其初始状态
enemies.push(new Enemy('blue', 500, 150, 60, 40, -2, 4)); // 创建一个蓝色敌人

3. 改进游戏循环

现在,游戏的主循环(animate 和 draw 函数)需要进行修改,以便遍历 enemies 数组中的所有敌人实例,并分别调用它们的 update 方法。

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除整个画布
  // 遍历敌人数组,对每个敌人实例调用其update方法
  enemies.forEach(enemy => enemy.update());
}

function animate() {
  draw();
  // 推荐使用 requestAnimationFrame() 替代 setTimeout(),以获得更流畅的动画
  // requestAnimationFrame(animate);
  setTimeout(animate, 10); 
}

animate(); // 启动动画

完整示例代码

HTML (index.html):

<!DOCTYPE html>
<html>
    <head>
        <title>JavaScript Canvas 游戏:多敌人管理</title>
        <style>
            body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; min-height: 100vh; background-color: #f0f0f0; }
            canvas { border: 1px solid black; background-color: #eee; }
        </style>
    </head>
    <body>
        <canvas id="canvas" width="1000" height="500"></canvas>
        <script src="script.js"></script>
    </body>
</html>

JavaScript (script.js):

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

let enemies = []; // 存储所有敌人实例的数组

class Enemy {
  constructor(color, initialX, initialY, width, height, initialVx, initialVy) {
    this.w = width !== undefined ? width : 40;
    this.h = height !== undefined ? height : 50;
    this.x = initialX !== undefined ? initialX : Math.random() * (canvas.width - this.w);
    this.y = initialY !== undefined ? initialY : Math.random() * (canvas.height - this.h);
    this.c = color !== undefined ? color : 'red';
    this.vx = initialVx !== undefined ? initialVx : 2 * (Math.random() > 0.5 ? 1 : -1); // 随机初始速度方向
    this.vy = initialVy !== undefined ? initialVy : 2 * (Math.random() > 0.5 ? 1 : -1);
  }

  draw() {
    ctx.fillStyle = this.c;
    ctx.fillRect(this.x, this.y, this.w, this.h);
  }

  update() {
    // 边界检测和速度反转
    if (this.x + this.w >= canvas.width) {
      this.x = canvas.width - this.w; // 修正位置
      this.vx *= -1;
    }
    if (this.x <= 0) {
      this.x = 0; // 修正位置
      this.vx *= -1;
    }
    if (this.y + this.h >= canvas.height) {
      this.y = canvas.height - this.h; // 修正位置
      this.vy *= -1;
    }
    if (this.y <= 0) {
      this.y = 0; // 修正位置
      this.vy *= -1;
    }

    this.x += this.vx;
    this.y += this.vy;

    this.draw();
  }
}

// 创建多个敌人实例
function createEnemies(count = 5) {
  for (let i = 0; i < count; i++) {
    enemies.push(new Enemy('red')); // 默认创建红色敌人
  }
}

createEnemies(3); // 创建3个红色敌人
enemies.push(new Enemy('green', 100, 200, 50, 50, 3, -3)); // 创建一个绿色敌人,并指定其初始状态
enemies.push(new Enemy('blue', 500, 150, 60, 40, -2, 4)); // 创建一个蓝色敌人

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  enemies.forEach(enemy => enemy.update()); // 遍历并更新所有敌人
}

function animate() {
  draw();
  // 推荐使用 requestAnimationFrame() 替代 setTimeout() 以获得更流畅的动画
  requestAnimationFrame(animate); 
  // setTimeout(animate, 10); 
}

animate();

注意事项与最佳实践

  1. 使用 canvas.width 和 canvas.height: 在进行边界检测时,始终使用 canvas.width 和 canvas.height 而不是硬编码的数值。这使得你的游戏能够更好地适应不同大小的 Canvas,增加代码的灵活性和可维护性。
  2. requestAnimationFrame() vs. setTimeout(): 对于游戏循环,强烈推荐使用 requestAnimationFrame() 而非 setTimeout()。requestAnimationFrame() 会在浏览器下一次重绘之前调用指定的回调函数,它与浏览器的刷新率同步,能够提供更平滑、更高效的动画,并节省CPU资源(当页面在后台时会暂停)。
  3. 构造函数的灵活性: 在 Enemy 类的 constructor 中,你可以根据需要传入更多参数,例如不同的颜色、初始位置、生命值、防御力等。这使得创建各种具有独特属性的敌人变得非常容易。
  4. 数组迭代方法: forEach 是一种简洁且常用的数组迭代方法,它比传统的 for 循环更具可读性。在处理游戏实体数组时,它是一个很好的选择。
  5. 碰撞修正: 在边界检测中,除了反转速度外,最好也修正一下对象的位置,将其精确地放置在边界上,以避免在某些帧率下对象“卡”在边界内或穿透边界的问题。示例代码中已加入了简单的位置修正。

总结

通过采用面向对象的设计方法,利用JavaScript的 class 语法,我们可以为游戏中的每个实体创建独立的实例。每个实例都拥有自己的状态和行为,并通过一个数组进行统一管理。这种方法不仅解决了多实体共享全局状态的问题,使得每个敌人都能独立运动,还大大提高了代码的模块化、可读性和可维护性,为构建更复杂、更动态的Canvas游戏奠定了坚实的基础。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
php中foreach用法
php中foreach用法

本专题整合了php中foreach用法的相关介绍,阅读专题下面的文章了解更多详细教程。

288

2025.12.04

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

58

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

65

2025.11.27

全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

100

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

126

2025.09.18

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

931

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

32

2025.12.06

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

532

2023.06.20

chatgpt使用指南
chatgpt使用指南

本专题整合了chatgpt使用教程、新手使用说明等等相关内容,阅读专题下面的文章了解更多详细内容。

0

2026.03.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 3.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号