如何处理 ECS 模式中的冲突?
How to handle collisions in ECS pattern?
我正在用打字稿和 ECS 模式制作游戏。但我不明白如何处理实体之间的碰撞。
我的实体 Player
包含一组组件:
- LayerComponent - 组件,保留渲染层的名称;
- PositionComponent - 保持位置的组件;
- AppearanceComponent - 保留渲染选项的组件;
- BoxColliderComponent - 组件,它保持碰撞手柄的 AABB 大小。
我也有实体 Enemy
具有相同的组件集。这些实体在 LayerComponent
中的值不同。
LayerComponent
中的Player
实体保留Player
值,Enemy
实体保留Enemy
值。
我不知道如何处理这些实体之间的冲突。这些实体不应相互移动。
目前我已经创建了系统 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
);
}
}
我不知道是否应该存在错误或者这段代码是否有效,所以我假设你的问题严格来说是关于将碰撞检测系统的代码放在哪里:
在这种情况下,你必须考虑你的碰撞检测系统和你的运动系统之间的相互作用。大多数时候,方法类似于
1 - Apply movement without taking collisions into account
2 - Detect collisions
3 - Adjust the movement you made depending on the collisions you just detected
因此,由于您的碰撞检测与您的运动系统紧密结合,因此我将其保留在那里是有意义的。然而,仍然隔离你的碰撞检测系统可能是个好主意,所以你可以做的就是简单地从你的运动系统中调用你的碰撞检测系统,可以说让你的碰撞系统成为你的运动系统的 'sub-system' .
另一种选择是确实将它们分开,但随后您的碰撞检测系统将需要自行重新调整实体的位置。这可能没问题,但它可能会给你的代码增加一些复杂性(我猜你需要在你的组件中存储更多数据)并且它会打破只有你的运动系统改变你的实体位置的假设(这可能是保留是一件好事,但无论如何都没有必要。
希望对您有所帮助
我正在用打字稿和 ECS 模式制作游戏。但我不明白如何处理实体之间的碰撞。
我的实体 Player
包含一组组件:
- LayerComponent - 组件,保留渲染层的名称;
- PositionComponent - 保持位置的组件;
- AppearanceComponent - 保留渲染选项的组件;
- BoxColliderComponent - 组件,它保持碰撞手柄的 AABB 大小。
我也有实体 Enemy
具有相同的组件集。这些实体在 LayerComponent
中的值不同。
LayerComponent
中的Player
实体保留Player
值,Enemy
实体保留Enemy
值。
我不知道如何处理这些实体之间的冲突。这些实体不应相互移动。
目前我已经创建了系统 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
);
}
}
我不知道是否应该存在错误或者这段代码是否有效,所以我假设你的问题严格来说是关于将碰撞检测系统的代码放在哪里:
在这种情况下,你必须考虑你的碰撞检测系统和你的运动系统之间的相互作用。大多数时候,方法类似于
1 - Apply movement without taking collisions into account
2 - Detect collisions
3 - Adjust the movement you made depending on the collisions you just detected
因此,由于您的碰撞检测与您的运动系统紧密结合,因此我将其保留在那里是有意义的。然而,仍然隔离你的碰撞检测系统可能是个好主意,所以你可以做的就是简单地从你的运动系统中调用你的碰撞检测系统,可以说让你的碰撞系统成为你的运动系统的 'sub-system' .
另一种选择是确实将它们分开,但随后您的碰撞检测系统将需要自行重新调整实体的位置。这可能没问题,但它可能会给你的代码增加一些复杂性(我猜你需要在你的组件中存储更多数据)并且它会打破只有你的运动系统改变你的实体位置的假设(这可能是保留是一件好事,但无论如何都没有必要。
希望对您有所帮助