解析游戏开发中的ECS设计模式:实体、组件、系统的完美协同

news/2024/5/18 15:18:42 标签: 设计模式, javascript, 游戏程序

ECS(Entity-Component-System)是一种设计模式,通常用于构建和管理具有大量实体和复杂交互的系统,尤其在游戏开发中得到广泛应用。这个模式的核心思想是将系统中的组件、实体和系统进行分离,以提高代码的可维护性、可扩展性和性能。

实体(Entity):

实体是系统中的基本对象,可以是游戏中的角色、物体或其他有意义的实体。
实体本身通常只是一个标识符,没有行为或状态。

组件(Component):

组件是实体的属性或数据单元,描述了实体的特征和状态。
不同的组件可以包含不同类型的数据,例如位置、渲染信息、健康状态等。
一个实体可以关联多个组件,组件之间是相互独立的。

系统(System):

系统是处理实体和组件的逻辑单元,负责执行特定的功能或行为。
系统基于组件的存在与否,以及它们的状态来执行逻辑。
系统通常是独立于特定实体的,可以处理多个具有相似组件结构的实体。
ECS 模式的优势包括:

松散耦合(Loose Coupling): 实体、组件和系统之间的分离降低了各部分之间的依赖关系,使得系统更易于理解和维护。
可重用性(Reusability): 组件和系统的设计使得它们可以轻松地被重用在不同的实体和场景中。
性能优化(Performance Optimization): ECS 模式支持数据驱动的设计,允许系统更有效地管理和处理大量数据,提高系统性能。

在应用 ECS 模式时,开发者通常需要定义组件、创建实体,实现系统,并在主循环中更新系统。 ECS
提供了一种结构化的方式来管理和处理系统中的复杂性,特别适用于需要频繁变化和交互的应用场景。

简易ECS示例

javascript"><!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>简易ECS示例</title>
    <style>
      canvas {
        border: 1px solid #000;
      }
    </style>
  </head>
  <body>
    <canvas id="gameCanvas" width="400" height="400"></canvas>
    <script>
      // 实体类,用于表示游戏中的实体
      class Entity {
        constructor(id) {
          this.id = id;
          this.components = {};
        }

        // 添加组件到实体
        addComponent(component) {
          this.components[component.constructor.name] = component;
        }

        // 获取实体的指定组件
        getComponent(componentName) {
          return this.components[componentName];
        }
      }

      // 组件基类,所有组件都继承自这个基类
      class Component {
        constructor() {
          this.name = this.constructor.name;
        }
      }

      // 位置组件,表示实体的位置
      class Position extends Component {
        constructor(x, y) {
          super();
          this.x = x;
          this.y = y;
        }
      }

      // 渲染组件,表示实体的渲染样式
      class Render extends Component {
        constructor(color) {
          super();
          this.color = color;
        }
      }

      // 移动系统,处理具有位置组件的实体的移动逻辑
      class MoveSystem {
        constructor() {
          this.componentTypes = ["Position"];
        }

        // 更新实体的位置
        update(entities) {
          for (const entity of entities) {
            if (this.entityHasComponents(entity)) {
              const position = entity.getComponent("Position");
              if (position.x >= 380) position.x = 0;
              if (position.y >= 380) position.y = 0;
              position.x += 1;
              position.y += 1;
            }
          }
        }

        // 检查实体是否具有指定的组件类型
        entityHasComponents(entity) {
          return this.componentTypes.every((type) => entity.components[type]);
        }
      }

      // 渲染系统,处理具有位置和渲染组件的实体的渲染逻辑
      class RenderSystem {
        constructor() {
          this.componentTypes = ["Position", "Render"];
          this.canvas = document.getElementById("gameCanvas");
          this.context = this.canvas.getContext("2d");
        }

        // 更新实体的渲染
        update(entities) {
          // 清空画布
          this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

          for (const entity of entities) {
            if (this.entityHasComponents(entity)) {
              // 获取位置和渲染组件
              const position = entity.getComponent("Position");
              const render = entity.getComponent("Render");

              // 设置颜色并绘制实体
              this.context.fillStyle = render.color;
              this.context.fillRect(position.x, position.y, 20, 20);
            }
          }
        }

        // 检查实体是否具有指定的组件类型
        entityHasComponents(entity) {
          return this.componentTypes.every((type) => entity.components[type]);
        }
      }

      // 创建两个实体,并为它们添加位置和渲染组件
      const entity1 = new Entity(1);
      entity1.addComponent(new Position(50, 50));
      entity1.addComponent(new Render("#ff0000")); // 使用十六进制颜色表示 "red"

      const entity2 = new Entity(2);
      entity2.addComponent(new Position(100, 100));
      entity2.addComponent(new Render("#0000ff")); // 使用十六进制颜色表示 "blue"

      // 创建移动系统和渲染系统
      const moveSystem = new MoveSystem();
      const renderSystem = new RenderSystem();

      // 游戏循环函数,负责更新移动和渲染系统
      function gameLoop() {
        moveSystem.update([entity1, entity2]);
        renderSystem.update([entity1, entity2]);
        requestAnimationFrame(gameLoop);
      }

      // 启动游戏循环
      gameLoop();
    </script>
  </body>
</html>

ecs


http://www.niftyadmin.cn/n/5314018.html

相关文章

#{}和${}的区别?

#{}是占位符&#xff0c;预编译处理&#xff1b;${}是拼接符&#xff0c;字符串替换&#xff0c;没有预编译处理。Mybatis在处理#{}时&#xff0c;#{}传入参数是以字符串传入&#xff0c;会将SQL中的#{}替换为?号&#xff0c;调用PreparedStatement的set方法来赋值。Mybatis在…

.NET学习教程二——.net基础定义+VS常用设置

一&#xff1a;注释 C#编写代码注意事项&#xff1a; 1、写代码切忌括号错误! 2、代码中所有的标点都是英文半角的标点 3、c#代码中每行代码以分号结束. 作用&#xff1a; 1、注销 2、解释 C#的3种注释符 1、单行注释// 2、多行注释/* 注释内容*/ 3、文档注释 ///多用…

家政服务系统有哪些优势及特点

&#x1f308;家政系统小程序&#xff0c;有哪些功能优势&#xff01; 1、平台依托&#xff1a;&#x1f30d;小程序极速开发。 2、人员保障&#xff1a;顾客轻松注册&#xff0c;家政服务人员也有独立账号。 3、地域无忧&#xff1a;后台自定义开放城市范围。&#x1f31f; 4、…

Flink编程实践

(一)开发WordCount 程序. 在 Linux 系统中实现WordCount 程序,并打包成 JAR 文件,提交到 Flink 中运行。 创建目录用来存放代码: mkdir -p ~/flinkapp/src/main/java使用gedit编辑器在“./flinkapp/src/main/java”目录下建立三个代码文件,其中WordCountData.java用于…

MYSQL的SWITCH语句和循环语句

1. 请解释MySQL中的CASE语句和IF语句的区别。 MySQL中的CASE语句和IF语句都可以用于条件判断&#xff0c;但它们的使用方式和语法有所不同。 CASE语句&#xff1a;CASE语句是一种更灵活的条件判断结构&#xff0c;它可以处理多个条件和结果。CASE语句的基本语法如下&#xff…

海思SD3403,SS928/926,hi3519dv500,hi3516dv500移植yolov7,yolov8(7)

上一篇用MindStudio转换完om模型,就可以在板卡里进行推理验证了。 SDK里有相关推理的demo,只要om模型转换没有遇到问题,是可以做推理验证。 首先SDK里推理验证方式有两种,一个是用H264实时视频流的方式,还有一种是通过图片的方式。 H264方式需要准备好FFMPEG,通过本地…

数据结构入门到入土——链表(完)LinkedList

目录 一&#xff0c;双向链表 1.单向链表的缺点 2.什么是双向链表&#xff1f; 3.自主实现双向链表 接口实现&#xff1a; 二&#xff0c;LinkedList 1.LinkedList的使用 1.1 什么是LinkedList&#xff1f; 1.2 LinkedList的使用 1.LinkedList的构造 2.LinkedList的…

【Android】 ConstraintLayout实操

由于最近比较悠闲&#xff0c;重新学习了constraintlayout&#xff0c;看着官网学的&#xff0c;官网网站如下&#xff1a;https://developer.android.com/training/constraint-layout?hlzh-cn#alignment 其实之前也小小的学过一波constraintlayout&#xff0c;不过因为用线性…