diff --git a/survival/__init__.py b/survival/__init__.py index c457f84..35625fc 100644 --- a/survival/__init__.py +++ b/survival/__init__.py @@ -1,20 +1,22 @@ import pygame from settings import SCREEN_WIDTH, SCREEN_HEIGHT +from survival import esper from survival.camera import Camera +from survival.components.camera_target_component import CameraTargetComponent +from survival.components.input_component import InputComponent +from survival.components.movement_component import MovementComponent +from survival.components.position_component import PositionComponent +from survival.components.sprite_component import SpriteComponent from survival.game_map import GameMap - - -def draw_game(delta): - win.fill((0, 0, 0)) - game_map.draw(camera) - pygame.display.update() - - -def update_game(delta, pressed_keys): - game_map.update(camera, delta, pressed_keys) - pass - +from survival.player_generator import PlayerGenerator +from survival.resource_generator import ResourceGenerator +from survival.systems.camera_system import CameraSystem +from survival.systems.collision_system import CollisionSystem +from survival.systems.draw_system import DrawSystem +from survival.systems.input_system import InputSystem +from survival.systems.movement_system import MovementSystem +from survival.world_generator import WorldGenerator if __name__ == '__main__': pygame.init() @@ -26,6 +28,12 @@ if __name__ == '__main__': game_map = GameMap(int(SCREEN_WIDTH / 32) * 2, 2 * int(SCREEN_HEIGHT / 32) + 1) camera = Camera(game_map.width * 32, game_map.height * 32, win) + + world = WorldGenerator().create_world(camera, game_map) + player = PlayerGenerator().create_player(world, game_map) + + ResourceGenerator(world, game_map).generate_resources() + run = True while run: @@ -40,5 +48,7 @@ if __name__ == '__main__': keys = pygame.key.get_pressed() - draw_game(ms) - update_game(ms, keys) + win.fill((0, 0, 0)) + game_map.draw(camera) + world.process(ms) + pygame.display.update() diff --git a/survival/camera.py b/survival/camera.py index bc6348b..d03769b 100644 --- a/survival/camera.py +++ b/survival/camera.py @@ -21,8 +21,8 @@ class Camera: SCREEN_WIDTH - self.camera.left, SCREEN_HEIGHT - self.camera.top) def update(self, target): - x = -target.pos[0] + int(SCREEN_WIDTH / 2) - y = -target.pos[1] + int(SCREEN_HEIGHT / 2) + x = -target.position[0] + int(SCREEN_WIDTH / 2) + y = -target.position[1] + int(SCREEN_HEIGHT / 2) x = min(0, x) y = min(0, y) diff --git a/survival/components/camera_target_component.py b/survival/components/camera_target_component.py new file mode 100644 index 0000000..6ca95f8 --- /dev/null +++ b/survival/components/camera_target_component.py @@ -0,0 +1,3 @@ +class CameraTargetComponent: + def __init__(self, target): + self.target = target diff --git a/survival/components/collision_component.py b/survival/components/collision_component.py new file mode 100644 index 0000000..6a4bc16 --- /dev/null +++ b/survival/components/collision_component.py @@ -0,0 +1,3 @@ +class CollisionComponent: + def __init__(self): + pass diff --git a/survival/components/input_component.py b/survival/components/input_component.py new file mode 100644 index 0000000..c2e1300 --- /dev/null +++ b/survival/components/input_component.py @@ -0,0 +1,3 @@ +class InputComponent: + def __init__(self): + self.input_data = None diff --git a/survival/components/movement_component.py b/survival/components/movement_component.py new file mode 100644 index 0000000..92a7896 --- /dev/null +++ b/survival/components/movement_component.py @@ -0,0 +1,4 @@ +class MovementComponent: + def __init__(self): + self.speed = 30 + self.timer = 0 diff --git a/survival/components/moving_component.py b/survival/components/moving_component.py new file mode 100644 index 0000000..0de446e --- /dev/null +++ b/survival/components/moving_component.py @@ -0,0 +1,5 @@ +class MovingComponent: + def __init__(self, direction, target): + self.direction = direction + self.movement_target = target + self.checked_collision = False diff --git a/survival/components/position_component.py b/survival/components/position_component.py new file mode 100644 index 0000000..5164100 --- /dev/null +++ b/survival/components/position_component.py @@ -0,0 +1,4 @@ +class PositionComponent: + def __init__(self, pos, grid_pos): + self.position = pos + self.grid_position = grid_pos diff --git a/survival/components/sprite_component.py b/survival/components/sprite_component.py new file mode 100644 index 0000000..9d6ef95 --- /dev/null +++ b/survival/components/sprite_component.py @@ -0,0 +1,9 @@ +from survival.image import Image + + +class SpriteComponent: + def __init__(self, path): + self.image = Image(path) + + def set_scale(self, scale): + self.image.set_scale(scale) diff --git a/survival/entity_layer.py b/survival/entity_layer.py new file mode 100644 index 0000000..d934b6f --- /dev/null +++ b/survival/entity_layer.py @@ -0,0 +1,22 @@ +class EntityLayer: + def __init__(self, width, height): + self.width = width + self.height = height + self.tiles = [[None for x in range(self.width)] for y in range(self.height)] + + def draw(self, camera, visible_area): + pass + + def add_entity(self, entity, pos): + self.tiles[pos[1]][pos[0]] = entity + + def move_entity(self, from_pos, to_pos): + ent = self.tiles[from_pos[1]][from_pos[0]] + self.tiles[from_pos[1]][from_pos[0]] = None + self.tiles[to_pos[1]][to_pos[0]] = ent + + def remove_entity(self, pos): + self.tiles[pos[1]][pos[0]] = None + + def is_colliding(self, pos): + return self.tiles[pos[1]][pos[0]] is not None diff --git a/survival/esper.py b/survival/esper.py new file mode 100644 index 0000000..04f5d52 --- /dev/null +++ b/survival/esper.py @@ -0,0 +1,368 @@ +import time as _time + +from functools import lru_cache as _lru_cache +from typing import List as _List +from typing import Type as _Type +from typing import TypeVar as _TypeVar +from typing import Any as _Any +from typing import Tuple as _Tuple +from typing import Iterable as _Iterable + + +version = '1.3' + +C = _TypeVar('C') +P = _TypeVar('P') + + +class Processor: + """Base class for all Processors to inherit from. + + Processor instances must contain a `process` method. Other than that, + you are free to add any additional methods that are necessary. The process + method will be called by each call to `World.process`, so you will + generally want to iterate over entities with one (or more) calls to the + appropriate world methods there, such as + `for ent, (rend, vel) in self.world.get_components(Renderable, Velocity):` + """ + world = None + + def process(self, *args, **kwargs): + raise NotImplementedError + + +class World: + """A World object keeps track of all Entities, Components, and Processors. + + A World contains a database of all Entity/Component assignments. The World + is also responsible for executing all Processors assigned to it for each + frame of your game. + """ + def __init__(self, timed=False): + self._processors = [] + self._next_entity_id = 0 + self._components = {} + self._entities = {} + self._dead_entities = set() + if timed: + self.process_times = {} + self._process = self._timed_process + + def clear_cache(self) -> None: + self.get_component.cache_clear() + self.get_components.cache_clear() + + def clear_database(self) -> None: + """Remove all Entities and Components from the World.""" + self._next_entity_id = 0 + self._dead_entities.clear() + self._components.clear() + self._entities.clear() + self.clear_cache() + + def add_processor(self, processor_instance: Processor, priority=0) -> None: + """Add a Processor instance to the World. + + :param processor_instance: An instance of a Processor, + subclassed from the Processor class + :param priority: A higher number is processed first. + """ + assert issubclass(processor_instance.__class__, Processor) + processor_instance.priority = priority + processor_instance.world = self + self._processors.append(processor_instance) + self._processors.sort(key=lambda proc: proc.priority, reverse=True) + + def remove_processor(self, processor_type: Processor) -> None: + """Remove a Processor from the World, by type. + + :param processor_type: The class type of the Processor to remove. + """ + for processor in self._processors: + if type(processor) == processor_type: + processor.world = None + self._processors.remove(processor) + + def get_processor(self, processor_type: _Type[P]) -> P: + """Get a Processor instance, by type. + + This method returns a Processor instance by type. This could be + useful in certain situations, such as wanting to call a method on a + Processor, from within another Processor. + + :param processor_type: The type of the Processor you wish to retrieve. + :return: A Processor instance that has previously been added to the World. + """ + for processor in self._processors: + if type(processor) == processor_type: + return processor + + def create_entity(self, *components) -> int: + """Create a new Entity. + + This method returns an Entity ID, which is just a plain integer. + You can optionally pass one or more Component instances to be + assigned to the Entity. + + :param components: Optional components to be assigned to the + entity on creation. + :return: The next Entity ID in sequence. + """ + self._next_entity_id += 1 + + # TODO: duplicate add_component code here for performance + for component in components: + self.add_component(self._next_entity_id, component) + + # self.clear_cache() + return self._next_entity_id + + def delete_entity(self, entity: int, immediate=False) -> None: + """Delete an Entity from the World. + + Delete an Entity and all of it's assigned Component instances from + the world. By default, Entity deletion is delayed until the next call + to *World.process*. You can request immediate deletion, however, by + passing the "immediate=True" parameter. This should generally not be + done during Entity iteration (calls to World.get_component/s). + + Raises a KeyError if the given entity does not exist in the database. + :param entity: The Entity ID you wish to delete. + :param immediate: If True, delete the Entity immediately. + """ + if immediate: + for component_type in self._entities[entity]: + self._components[component_type].discard(entity) + + if not self._components[component_type]: + del self._components[component_type] + + del self._entities[entity] + self.clear_cache() + + else: + self._dead_entities.add(entity) + + def entity_exists(self, entity: int) -> bool: + """Check if a specific entity exists. + + Empty entities(with no components) and dead entities(destroyed + by delete_entity) will not count as existent ones. + :param entity: The Entity ID to check existance for. + :return: True if the entity exists, False otherwise. + """ + return entity in self._entities and entity not in self._dead_entities + + def component_for_entity(self, entity: int, component_type: _Type[C]) -> C: + """Retrieve a Component instance for a specific Entity. + + Retrieve a Component instance for a specific Entity. In some cases, + it may be necessary to access a specific Component instance. + For example: directly modifying a Component to handle user input. + + Raises a KeyError if the given Entity and Component do not exist. + :param entity: The Entity ID to retrieve the Component for. + :param component_type: The Component instance you wish to retrieve. + :return: The Component instance requested for the given Entity ID. + """ + return self._entities[entity][component_type] + + def components_for_entity(self, entity: int) -> _Tuple[C, ...]: + """Retrieve all Components for a specific Entity, as a Tuple. + + Retrieve all Components for a specific Entity. The method is probably + not appropriate to use in your Processors, but might be useful for + saving state, or passing specific Components between World instances. + Unlike most other methods, this returns all of the Components as a + Tuple in one batch, instead of returning a Generator for iteration. + + Raises a KeyError if the given entity does not exist in the database. + :param entity: The Entity ID to retrieve the Components for. + :return: A tuple of all Component instances that have been + assigned to the passed Entity ID. + """ + return tuple(self._entities[entity].values()) + + def has_component(self, entity: int, component_type: _Any) -> bool: + """Check if a specific Entity has a Component of a certain type. + + :param entity: The Entity you are querying. + :param component_type: The type of Component to check for. + :return: True if the Entity has a Component of this type, + otherwise False + """ + return component_type in self._entities[entity] + + def has_components(self, entity: int, *component_types: _Any) -> bool: + """Check if an Entity has all of the specified Component types. + + :param entity: The Entity you are querying. + :param component_types: Two or more Component types to check for. + :return: True if the Entity has all of the Components, + otherwise False + """ + return all(comp_type in self._entities[entity] for comp_type in component_types) + + def add_component(self, entity: int, component_instance: _Any, type_alias: _Type = None) -> None: + """Add a new Component instance to an Entity. + + Add a Component instance to an Entiy. If a Component of the same type + is already assigned to the Entity, it will be replaced. + + :param entity: The Entity to associate the Component with. + :param component_instance: A Component instance. + """ + component_type = type_alias or type(component_instance) + + if component_type not in self._components: + self._components[component_type] = set() + + self._components[component_type].add(entity) + + if entity not in self._entities: + self._entities[entity] = {} + + self._entities[entity][component_type] = component_instance + self.clear_cache() + + def remove_component(self, entity: int, component_type: _Any) -> int: + """Remove a Component instance from an Entity, by type. + + A Component instance can be removed by providing it's type. + For example: world.delete_component(enemy_a, Velocity) will remove + the Velocity instance from the Entity enemy_a. + + Raises a KeyError if either the given entity or Component type does + not exist in the database. + :param entity: The Entity to remove the Component from. + :param component_type: The type of the Component to remove. + """ + self._components[component_type].discard(entity) + + if not self._components[component_type]: + del self._components[component_type] + + del self._entities[entity][component_type] + + if not self._entities[entity]: + del self._entities[entity] + + self.clear_cache() + return entity + + def _get_component(self, component_type: _Type[C]) -> _Iterable[_Tuple[int, C]]: + """Get an iterator for Entity, Component pairs. + + :param component_type: The Component type to retrieve. + :return: An iterator for (Entity, Component) tuples. + """ + entity_db = self._entities + + for entity in self._components.get(component_type, []): + yield entity, entity_db[entity][component_type] + + def _get_components(self, *component_types: _Type) -> _Iterable[_Tuple[int, ...]]: + """Get an iterator for Entity and multiple Component sets. + + :param component_types: Two or more Component types. + :return: An iterator for Entity, (Component1, Component2, etc) + tuples. + """ + entity_db = self._entities + comp_db = self._components + + try: + for entity in set.intersection(*[comp_db[ct] for ct in component_types]): + yield entity, [entity_db[entity][ct] for ct in component_types] + except KeyError: + pass + + @_lru_cache() + def get_component(self, component_type: _Type[C]) -> _List[_Tuple[int, C]]: + return [query for query in self._get_component(component_type)] + + @_lru_cache() + def get_components(self, *component_types: _Type): + return [query for query in self._get_components(*component_types)] + + def try_component(self, entity: int, component_type: _Type): + """Try to get a single component type for an Entity. + + This method will return the requested Component if it exists, but + will pass silently if it does not. This allows a way to access + optional Components that may or may not exist, without having to + first querty the Entity to see if it has the Component type. + + :param entity: The Entity ID to retrieve the Component for. + :param component_type: The Component instance you wish to retrieve. + :return: A iterator containg the single Component instance requested, + which is empty if the component doesn't exist. + """ + if component_type in self._entities[entity]: + yield self._entities[entity][component_type] + else: + return None + + def try_components(self, entity: int, *component_types: _Type): + """Try to get a multiple component types for an Entity. + + This method will return the requested Components if they exist, but + will pass silently if they do not. This allows a way to access + optional Components that may or may not exist, without first having + to query if the entity has the Component types. + + :param entity: The Entity ID to retrieve the Component for. + :param component_types: The Components types you wish to retrieve. + :return: A iterator containg the multiple Component instances requested, + which is empty if the components do not exist. + """ + if all(comp_type in self._entities[entity] for comp_type in component_types): + yield [self._entities[entity][comp_type] for comp_type in component_types] + else: + return None + + def _clear_dead_entities(self): + """Finalize deletion of any Entities that are marked dead. + + In the interest of performance, this method duplicates code from the + `delete_entity` method. If that method is changed, those changes should + be duplicated here as well. + """ + for entity in self._dead_entities: + + for component_type in self._entities[entity]: + self._components[component_type].discard(entity) + + if not self._components[component_type]: + del self._components[component_type] + + del self._entities[entity] + + self._dead_entities.clear() + self.clear_cache() + + def _process(self, *args, **kwargs): + for processor in self._processors: + processor.process(*args, **kwargs) + + def _timed_process(self, *args, **kwargs): + """Track Processor execution time for benchmarking.""" + for processor in self._processors: + start_time = _time.process_time() + processor.process(*args, **kwargs) + process_time = int(round((_time.process_time() - start_time) * 1000, 2)) + self.process_times[processor.__class__.__name__] = process_time + + def process(self, *args, **kwargs): + """Call the process method on all Processors, in order of their priority. + + Call the *process* method on all assigned Processors, respecting their + optional priority setting. In addition, any Entities that were marked + for deletion since the last call to *World.process*, will be deleted + at the start of this method call. + + :param args: Optional arguments that will be passed through to the + *process* method of all Processors. + """ + self._clear_dead_entities() + self._process(*args, **kwargs) diff --git a/survival/game_map.py b/survival/game_map.py index 3fc9937..dfc0365 100644 --- a/survival/game_map.py +++ b/survival/game_map.py @@ -1,3 +1,5 @@ +from survival.components.position_component import PositionComponent +from survival.entity_layer import EntityLayer from survival.player import Player from survival.tile_layer import TileLayer @@ -6,16 +8,21 @@ class GameMap: def __init__(self, width, height): self.width = width self.height = height - self.player = Player() - self.layers = [] - self.layers.append(TileLayer(width, height)) + self.tile_layer = TileLayer(width, height) + self.entity_layer = EntityLayer(width, height) def draw(self, camera): visible_area = camera.get_visible_area() - for layer in self.layers: - layer.draw(camera, visible_area) - self.player.draw(camera) + self.tile_layer.draw(camera, visible_area) - def update(self, camera, delta, pressed_keys): - self.player.update(delta, pressed_keys) - camera.update(self.player) + def add_entity(self, entity, pos): + self.entity_layer.add_entity(entity, pos.grid_position) + + def move_entity(self, from_pos, to_pos): + self.entity_layer.move_entity(from_pos, to_pos) + + def remove_entity(self, pos): + self.entity_layer.remove_entity(pos) + + def is_colliding(self, pos): + return self.entity_layer.is_colliding(pos) diff --git a/survival/game_object.py b/survival/game_object.py deleted file mode 100644 index dd27093..0000000 --- a/survival/game_object.py +++ /dev/null @@ -1,20 +0,0 @@ -import pygame -from pygame.rect import Rect - - -class GameObject: - - def __init__(self, pos, texture): - self.pos = pos - self.last_pos = pos - self.texture = pygame.image.load(texture) - self.texture = pygame.transform.scale(self.texture, (64, 64)) - self.width = self.texture.get_width() - self.height = self.texture.get_height() - self.velocity = [0, 0] - - def draw(self, window): - window.blit(self.texture, self.pos) - - def get_rect(self): - return Rect(self.pos[0], self.pos[1], self.width, self.height) diff --git a/survival/player.py b/survival/player.py index 57d1a97..a689663 100644 --- a/survival/player.py +++ b/survival/player.py @@ -7,25 +7,17 @@ from survival.image import Image class Player: def __init__(self): - self.pos = [0, 0] - self.velocity = [0, 0] - self.image = Image('stevenson.png') - self.image.set_scale(2) - self.origin = (0, 0) - self.speed = 30 - self.movement_target = [self.pos[0], self.pos[1]] - self.timer = 0 + # self.pos = [1024, 512] + # self.velocity = [0, 0] + # self.image = Image('stevenson.png') + # self.image.set_scale(2) + # self.speed = 30 + # self.movement_target = [self.pos[0], self.pos[1]] + # self.timer = 0 + pass def draw(self, camera): - if self.is_moving(): - if self.velocity[0] == 1: - self.image.origin = (96, 0) - elif self.velocity[0] == -1: - self.image.origin = (64, 0) - elif self.velocity[1] == 1: - self.image.origin = (0, 0) - else: - self.image.origin = (32, 0) + self.image.pos = self.pos camera.draw(self.image) @@ -59,7 +51,7 @@ class Player: self.timer += delta if self.timer > 1000: - #self.move_in_random_direction() + self.move_in_random_direction() self.timer = 0 if pressed_keys[pygame.K_LEFT]: diff --git a/survival/player_generator.py b/survival/player_generator.py new file mode 100644 index 0000000..df7a317 --- /dev/null +++ b/survival/player_generator.py @@ -0,0 +1,23 @@ +from survival.components.camera_target_component import CameraTargetComponent +from survival.components.input_component import InputComponent +from survival.components.movement_component import MovementComponent +from survival.components.position_component import PositionComponent +from survival.components.sprite_component import SpriteComponent + + +class PlayerGenerator: + + def create_player(self, world, game_map): + player = world.create_entity() + pos = PositionComponent([0, 0], [0, 0]) + world.add_component(player, pos) + world.add_component(player, MovementComponent()) + world.add_component(player, InputComponent()) + camera_target = CameraTargetComponent(pos) + world.add_component(player, camera_target) + game_map.add_entity(player, pos) + sprite = SpriteComponent('stevenson.png') + sprite.set_scale(1) + world.add_component(player, sprite) + + return player diff --git a/survival/resource_generator.py b/survival/resource_generator.py new file mode 100644 index 0000000..ba5aa3f --- /dev/null +++ b/survival/resource_generator.py @@ -0,0 +1,31 @@ +import random + +from survival.components.position_component import PositionComponent +from survival.components.sprite_component import SpriteComponent +from survival.settings import RESOURCES_AMOUNT + + +class ResourceGenerator: + def __init__(self, world, game_map): + self.world = world + self.map = game_map + + def generate_resources(self): + for x in range(RESOURCES_AMOUNT): + obj = self.world.create_entity() + sprites = ['apple.png', 'water.png', 'wood.png', 'stone.png'] + + empty_grid_pos = self.get_empty_grid_position() + empty_pos = [empty_grid_pos[0] * 32, empty_grid_pos[1] * 32] + + pos = PositionComponent(empty_pos, empty_grid_pos) + sprite = SpriteComponent(random.choice(sprites)) + self.world.add_component(obj, pos) + self.world.add_component(obj, sprite) + self.map.add_entity(obj, pos) + + def get_empty_grid_position(self): + free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)] + while self.map.is_colliding(free_pos): + free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)] + return free_pos diff --git a/survival/settings.py b/survival/settings.py index 30e3c68..577806c 100644 --- a/survival/settings.py +++ b/survival/settings.py @@ -1,2 +1,3 @@ SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 +RESOURCES_AMOUNT = 300 diff --git a/survival/systems/camera_system.py b/survival/systems/camera_system.py new file mode 100644 index 0000000..e553f92 --- /dev/null +++ b/survival/systems/camera_system.py @@ -0,0 +1,12 @@ +from survival import esper +from survival.components.camera_target_component import CameraTargetComponent +from survival.components.position_component import PositionComponent + + +class CameraSystem(esper.Processor): + def __init__(self, camera): + self.camera = camera + + def process(self, dt): + for ent, (camera_target, pos) in self.world.get_components(CameraTargetComponent, PositionComponent): + self.camera.update(pos) diff --git a/survival/systems/collision_system.py b/survival/systems/collision_system.py new file mode 100644 index 0000000..5a7055e --- /dev/null +++ b/survival/systems/collision_system.py @@ -0,0 +1,25 @@ +from survival import esper +from survival.components.moving_component import MovingComponent +from survival.components.position_component import PositionComponent + + +class CollisionSystem(esper.Processor): + def __init__(self, game_map): + self.map = game_map + + def process(self, dt): + for ent, (pos, moving) in self.world.get_components(PositionComponent, MovingComponent): + if moving.checked_collision: + continue + + moving.checked_collision = True + + if self.check_collision(moving.movement_target): + self.world.remove_component(ent, MovingComponent) + + else: + self.map.move_entity(pos.grid_position, moving.movement_target) + pos.grid_position = moving.movement_target + + def check_collision(self, pos): + return self.map.is_colliding(pos) diff --git a/survival/systems/draw_system.py b/survival/systems/draw_system.py new file mode 100644 index 0000000..cf70fe7 --- /dev/null +++ b/survival/systems/draw_system.py @@ -0,0 +1,13 @@ +from survival import esper +from survival.components.position_component import PositionComponent +from survival.components.sprite_component import SpriteComponent + + +class DrawSystem(esper.Processor): + def __init__(self, camera): + self.camera = camera + + def process(self, dt): + for ent, (sprite, pos) in self.world.get_components(SpriteComponent, PositionComponent): + sprite.image.pos = pos.position + self.camera.draw(sprite.image) diff --git a/survival/systems/input_system.py b/survival/systems/input_system.py new file mode 100644 index 0000000..c8d7cc8 --- /dev/null +++ b/survival/systems/input_system.py @@ -0,0 +1,26 @@ +import pygame + +from survival import esper +from survival.components.input_component import InputComponent +from survival.components.moving_component import MovingComponent +from survival.components.position_component import PositionComponent + + +class InputSystem(esper.Processor): + def __init__(self): + self.map = None + + def process(self, dt): + for ent, (inp, pos) in self.world.get_components(InputComponent, PositionComponent): + keys = pygame.key.get_pressed() + + if self.world.has_component(ent, MovingComponent): + continue + if keys[pygame.K_LEFT]: + self.world.add_component(ent, MovingComponent([-1, 0], [pos.grid_position[0] - 1, pos.grid_position[1]])) + elif keys[pygame.K_RIGHT]: + self.world.add_component(ent, MovingComponent([1, 0], [pos.grid_position[0] + 1, pos.grid_position[1]])) + elif keys[pygame.K_DOWN]: + self.world.add_component(ent, MovingComponent([0, 1], [pos.grid_position[0], pos.grid_position[1] + 1])) + elif keys[pygame.K_UP]: + self.world.add_component(ent, MovingComponent([0, -1], [pos.grid_position[0], pos.grid_position[1] - 1])) diff --git a/survival/systems/movement_system.py b/survival/systems/movement_system.py new file mode 100644 index 0000000..70ad744 --- /dev/null +++ b/survival/systems/movement_system.py @@ -0,0 +1,35 @@ +from survival import esper +from survival.components.movement_component import MovementComponent +from survival.components.moving_component import MovingComponent +from survival.components.position_component import PositionComponent +from survival.components.sprite_component import SpriteComponent + + +class MovementSystem(esper.Processor): + def __init__(self): + self.map = None + + def process(self, dt): + for ent, (mov, pos, moving, sprite) in self.world.get_components(MovementComponent, PositionComponent, + MovingComponent, + SpriteComponent): + if moving.direction[0] != 0: + pos.position[0] += moving.direction[0] * mov.speed * dt / 100 + if abs(moving.movement_target[0] * 32 - pos.position[0]) < 0.1 * mov.speed: + pos.position = [moving.movement_target[0] * 32, moving.movement_target[1] * 32] + self.world.remove_component(ent, MovingComponent) + else: + pos.position[1] += moving.direction[1] * mov.speed * dt / 100 + if abs(pos.position[1] - moving.movement_target[1] * 32) < 0.1 * mov.speed: + pos.position = [moving.movement_target[0] * 32, moving.movement_target[1] * 32] + self.world.remove_component(ent, MovingComponent) + + if moving.direction[0] == 1: + sprite.image.origin = (96, 0) + elif moving.direction[0] == -1: + sprite.image.origin = (64, 0) + elif moving.direction[1] == 1: + sprite.image.origin = (0, 0) + else: + sprite.image.origin = (32, 0) + diff --git a/survival/tile_layer.py b/survival/tile_layer.py index b07465a..fa4aa6c 100644 --- a/survival/tile_layer.py +++ b/survival/tile_layer.py @@ -10,8 +10,11 @@ class TileLayer: self.image = Image('atlas.png') def draw(self, camera, visible_area): - for y in range(int(visible_area.top/32), int(visible_area.height/32) + 1): - for x in range(int(visible_area.left/32), int(visible_area.width/32) + 1): + for y in range(int(visible_area.top / 32), int(visible_area.height / 32) + 1): + for x in range(int(visible_area.left / 32), int(visible_area.width / 32) + 1): + if y >= self.height or x >= self.width: + continue + self.image.pos = (x * 32, y * 32) self.image.origin = self.tiles[y][x].origin camera.draw(self.image) diff --git a/survival/world_generator.py b/survival/world_generator.py new file mode 100644 index 0000000..02e75ff --- /dev/null +++ b/survival/world_generator.py @@ -0,0 +1,19 @@ +from survival import esper +from survival.systems.camera_system import CameraSystem +from survival.systems.collision_system import CollisionSystem +from survival.systems.draw_system import DrawSystem +from survival.systems.input_system import InputSystem +from survival.systems.movement_system import MovementSystem + + +class WorldGenerator: + + def create_world(self, camera, game_map): + world = esper.World() + world.add_processor(InputSystem()) + world.add_processor(CameraSystem(camera)) + world.add_processor(MovementSystem(), priority=1) + world.add_processor(CollisionSystem(game_map), priority=2) + world.add_processor(DrawSystem(camera)) + + return world