首页 > 解决方案 > 如何处理 ECS 模式中的冲突?

问题描述

我正在使用打字稿和 ECS 模式制作游戏。但我不明白如何处理实体之间的冲突。我有一组组Player件的实体:

Enemy我也有具有相同组件集的实体。这些实体的值不同LayerComponent。中的Player实体LayerComponent保持Player值,Enemy实体保持Enemy值。

我不知道如何处理这些实体之间的冲突。这些实体不应相互穿过。

目前我已经创建了 system PlayerPosition,它处理碰撞和块通过实体移动BoxColliderComponent。但我认为这是错误的,因为必须在他们自己的系统中处理冲突。

代码PlayerPosition

import { System } from 'ecs';
import { ecs, EntityType } from 'game';

import Vector2, { IVector2 } from 'services/vector2.service';

import MouseService from 'services/mouse.service';
import ELayers from 'constants/layers';
import Enemy from 'entities/enemy';

interface IIntersect {
  position: IVector2;
  height: number;
  width: number;
}

export default class PlayerPositionSystem extends System<EntityType> {
  readonly ctx: CanvasRenderingContext2D;
  readonly entities: EntityType[] = [];

  private readonly mouse: MouseService = new MouseService();

  constructor(ctx: CanvasRenderingContext2D) {
    super();
    this.ctx = ctx;
  }

  addEntity(entity: EntityType): void {
    if (this.test(entity)) {
      this.entities.push(entity);
    } else {
      console.warn(`The entity '${entity.id}' have no necessary component`);
    }
  }

  test(entity: EntityType): boolean {
    const position = entity.components.position;

    return !!position;
  }

  update(entity: EntityType): void {
    const component = entity.components.position;
    const colliderComponent = entity.components.boxCollider;
    const layerComponent = entity.components.layer;

    if (!component || !colliderComponent || !layerComponent) {
      return;
    }

    if (layerComponent.props.layer !== ELayers.player) {
      return;
    }

    const mouseCoordinates = this.mouse.getMouseCoordinate();
    const { position, velocity } = component.props;

    const distance = mouseCoordinates.distance(position);
    const deltaVector = mouseCoordinates.subtraction(position);
    const inversionDistance = 1 / distance;
    const direction = new Vector2(
      deltaVector.x * inversionDistance,
      deltaVector.y * inversionDistance
    );
    const newPosition = position.addition(
      new Vector2(
        distance > 5 ? direction.x * velocity : 0,
        distance > 5 ? direction.y * velocity : 0
      )
    );

    const currentObject: IIntersect = {
      position: new Vector2(newPosition.x, newPosition.y),
      height: colliderComponent.props.size.y,
      width: colliderComponent.props.size.x,
    };

    for (const object of this.entities) {
      if (object === entity) {
        continue;
      }

      const itemComponents = object.components;
      const itemPosition =
        itemComponents.position && itemComponents.position.props;
      const itemBoxCollider =
        itemComponents.boxCollider && itemComponents.boxCollider.props;

      if (!itemPosition || !itemBoxCollider) {
        continue;
      }

      const item: IIntersect = {
        ...itemPosition,
        height: itemBoxCollider.size.y,
        width: itemBoxCollider.size.x,
      };

      if (this.intersect(currentObject, item)) {
        const itemLayer = object.components.layer;
        if (itemLayer && itemLayer.props.layer === ELayers.enemy) {
          object.remove();
          const canvas = this.ctx.canvas;
          let x = Math.random() * canvas.width - 100;
          x = x < 0 ? 0 : x;
          let y = Math.random() * canvas.height - 100;
          y = y < 0 ? 0 : y;
          ecs.addEntity(Enemy({ velocity: 3, position: new Vector2(x, y) }));
        }

        let x = newPosition.x;
        let y = newPosition.y;

        if (
          this.intersect(
            {
              ...currentObject,
              position: new Vector2(x, position.y),
            },
            item
          )
        ) {
          x = position.x;
        }

        if (
          this.intersect(
            {
              ...currentObject,
              position: new Vector2(position.x, y),
            },
            item
          )
        ) {
          y = position.y;
        }

        newPosition.set(new Vector2(x, y));
      }
    }

    component.setProperties({ position: newPosition });
  }

  intersect(object: IIntersect, object2: IIntersect): boolean {
    const { position: pos1, height: h1, width: w1 } = object;
    const { position: pos2, height: h2, width: w2 } = object2;

    return (
      pos1.x + w1 > pos2.x &&
      pos2.x + w2 > pos1.x &&
      pos1.y + h1 > pos2.y &&
      pos2.y + h2 > pos1.y
    );
  }
}

标签: javascripttypescriptgame-engineentity-component-system

解决方案


我不知道是否应该存在错误或者此代码是否正常工作,所以我假设您的问题严格是关于将代码放置在哪里用于碰撞检测系统:

在这种情况下,您必须考虑碰撞检测系统和运动系统之间的交互。大多数情况下,方法类似于

1 - Apply movement without taking collisions into account
2 - Detect collisions
3 - Adjust the movement you made depending on the collisions you just detected

因此,由于您的碰撞检测与您的运动系统紧密耦合,因此我将其保留在那里是有意义的。但是,仍然隔离您的碰撞检测系统可能是一个好主意,所以您可以简单地从您的运动系统中调用您的碰撞检测系统,从而使您的碰撞系统成为您运动系统的“子系统” .

另一种选择是确实将它们分开,但是您的碰撞检测系统将需要自己重新调整实体的位置。这可能没问题,但它可能会给您的代码增加一些复杂性(我猜您需要在组件中存储更多数据)并且它会打破只有您的运动系统会改变您的实体位置的假设(这可能是保留是一件好事,但无论如何都没有必要)。

希望这可以帮助


推荐阅读