Closed Lehnart closed 3 years ago
To better understand your request, could you tell me:
If it's not happening every frame, you might want to think about a temporary Component. For example, consider this Processer which lerps an Entity towards a position. The Component is temporary, and is removed after the movement is finished:
class WaypointProcessor(esper.Processor):
"""Lerp towards a position"""
@staticmethod
def lerp(source, target, factor):
return (target - source) / factor
def process(self, dt):
for ent, (rend, wayp) in self.world.get_components(Renderable, Waypoint):
sprx, spry = rend.sprite.position
sprx += self.lerp(sprx, wayp.x, wayp.speed)
spry += self.lerp(spry, wayp.y, wayp.speed)
if abs(wayp.x - sprx) < 1 and abs(wayp.y - spry) < 1:
sprx = wayp.x
spry = wayp.y
self.world.remove_component(ent, Waypoint)
rend.sprite.position = sprx, spry
Thanks for the detailed solution. It effectively solves a part of my problem.
However, I still have the problem for example if I want to check if I can move to the destination or if there is an enemy, a wall, etc. Here is a snippet of code I'm using:
class MoveProcessor(esper.Processor):
def process(self, dt):
messages: List[MoveEvent] = self.world.receive(MoveEvent)
for msg in messages:
ent, move = msg.entity, msg.movement
# If there is an enemy on destination, I don't want to move but I want to fight.
fighting_entities = self.get_entities_at(x + dx, y + dy, FighterComponent)
if self.world.has_component(ent, FighterComponent) and fighting_entities:
self.world.publish(FightEvent(ent, fighting_entities[0]))
continue
# If the destination is not _movable_, nothing happens
if not self.get_entities_at(x + dx, y + dy, MovableComponent):
continue
# else, I can move to (x,y). I omit the code here.
...
def get_entities_at(self, x: int, y: int, *component_types):
entities = []
for ent, (pos, *_) in self.world.get_components(PositionComponent, *component_types):
if pos.xy() == (x, y):
entities.append(ent)
return entities
In this case, where I want to know what is at a given position before doing an action, I have to check the components of the entity at (x,y). I'm not sure temporary components will help me with this. I'll be glad if you have a proposition to handle this :).
There are several ways to do this, but putting the logic inside of esper wouldn't be the best solution.
For enemies that are moving each frame, you really don't have much choice but to iterate over all of them each frame.
If the enemies only move sometimes, then you could add a HasMoved
component to them, at the time of movement. Then the below code could only return those enemies.
class CollisionProcessor(esper.Processor):
@staticmethod
def _check_overlap(body_a, body_b):
.....
def process(self, dt):
for ent, (player, rend, vel) in world.get_components(Player, Renderable, Velocity):
for eent, (_, erend, evel) in world.get_components(Enemy, Renderable, Velocity):
if self._check_overlap(rend, erend):
do_something()
For static things, like walls, etc., a good idea is to implement some kind of Spatial Hashing. This is expensive to create, but very fast to query. A good idea is to do this query only at the start of a level/map. Static objects would have a StaticBody
Component. Something like:
class CollisionProcessor(esper.Processor):
def __init__(self):
self.spatial_hash = None
@staticmethod
def _check_overlap(body_a, body_b):
....
def process(self, dt):
world = self.world
if self.spatial_hash is None:
bodies = [body for (ent, body) in world.get_component(StaticBody)]
self.spatial_hash = Space(bodies=bodies)
for ent, (rend, vel) in world.get_components(Renderable, Velocity):
"""Check Scene collisions for all moving entities."""
for hit_body in self.spatial.get_hits(rend.aabb):
do_correction_for_collision_with_walls()
Ok got it! Thanks for your answer.
Hey,
With an ECS architecture, I always have a PositionComponent in my game. This PositionComponent is quite simple, 2 integers representing a (x,y) position.
When using this component, I every time do something like :
It is not really performant neither elegant code.
I'd like a way to access specifically entities with a PositionComponent with given x and y. More technically, I think it could be interesting to have components that can be indexed by a parameter. In my example, the parameter will be
(x,y)
. I guess the code would become something like :I didn't think about the technical implementation, just laying the idea here :)