diff --git a/assets/atlas.png b/assets/atlas.png index 46a2db2..f4c4682 100644 Binary files a/assets/atlas.png and b/assets/atlas.png differ diff --git a/assets/chest.png b/assets/chest.png new file mode 100644 index 0000000..563166c Binary files /dev/null and b/assets/chest.png differ diff --git a/assets/home.png b/assets/home.png new file mode 100644 index 0000000..68837dd Binary files /dev/null and b/assets/home.png differ diff --git a/assets/ui.png b/assets/ui.png new file mode 100644 index 0000000..999de9f Binary files /dev/null and b/assets/ui.png differ diff --git a/survival/__init__.py b/survival/__init__.py index 6a9abf7..372290d 100644 --- a/survival/__init__.py +++ b/survival/__init__.py @@ -2,14 +2,19 @@ import pygame from settings import SCREEN_WIDTH, SCREEN_HEIGHT from survival.camera import Camera +from survival.components.inventory_component import InventoryComponent from survival.game_map import GameMap +from survival.generators.building_generator import BuildingGenerator from survival.generators.player_generator import PlayerGenerator from survival.generators.resource_generator import ResourceGenerator from survival.generators.world_generator import WorldGenerator +from survival.systems.draw_system import DrawSystem if __name__ == '__main__': pygame.init() + pygame.font.init() + win = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("AI Project") @@ -20,8 +25,10 @@ if __name__ == '__main__': world = WorldGenerator().create_world(camera, game_map) player = PlayerGenerator().create_player(world, game_map) + world.get_processor(DrawSystem).initialize_interface(world.component_for_entity(player, InventoryComponent)) + building = BuildingGenerator().create_home(world, game_map) - ResourceGenerator(world, game_map).generate_resources() + ResourceGenerator(world, game_map).generate_resources(player) run = True diff --git a/survival/biomes/biome_preset.py b/survival/biomes/biome_preset.py index b11cd3c..f1289b3 100644 --- a/survival/biomes/biome_preset.py +++ b/survival/biomes/biome_preset.py @@ -1,10 +1,11 @@ import random +from typing import List from survival.tile import Tile class BiomePreset: - def __init__(self, name, min_height: float, min_moisture: float, min_heat: float, tiles: list[Tile]): + def __init__(self, name, min_height: float, min_moisture: float, min_heat: float, tiles: List[Tile]): self.name = name self.min_height = min_height self.min_moisture = min_moisture diff --git a/survival/components/OnCollisionComponent.py b/survival/components/OnCollisionComponent.py new file mode 100644 index 0000000..49e3feb --- /dev/null +++ b/survival/components/OnCollisionComponent.py @@ -0,0 +1,15 @@ +from functools import partial + + +class OnCollisionComponent: + def __init__(self, callbacks=None): + if callbacks is None: + callbacks = [] + self.callbacks = callbacks + + def callAll(self): + for func in self.callbacks: + func() + + def addCallback(self, fn, **kwargs): + self.callbacks.append(partial(fn, **kwargs)) diff --git a/survival/components/inventory_component.py b/survival/components/inventory_component.py index 18d39f9..5fe81fb 100644 --- a/survival/components/inventory_component.py +++ b/survival/components/inventory_component.py @@ -1,9 +1,9 @@ class InventoryComponent: - def __init__(self, maxitems): + def __init__(self, maxitems=10): self.maxitems = maxitems self.items = {} - def addItem(self, item, count): + def add_item(self, item, count): if item not in self.items: self.items[item] = count else: @@ -11,14 +11,11 @@ class InventoryComponent: if self.items[item] > self.maxitems: self.items[item] = self.maxitems - def removeItem(self, item, count): - if self.items: + def remove_item(self, item, count): + if item in self.items: self.items[item] = self.items[item] - count - if self.items[item] < 0: - self.items[item] = 0 + if self.items[item] < 0: + self.items[item] = 0 - def hasItem(self, item): - if self.items[item] != 0: - return True - else: - return False + def has_item(self, item): + return item in self.items and self.items[item] != 0 diff --git a/survival/components/resource_component.py b/survival/components/resource_component.py new file mode 100644 index 0000000..0a95b73 --- /dev/null +++ b/survival/components/resource_component.py @@ -0,0 +1,3 @@ +class ResourceComponent: + def __init__(self, resource_type): + self.resource_type = resource_type diff --git a/survival/entity_layer.py b/survival/entity_layer.py index d934b6f..cd7a9cd 100644 --- a/survival/entity_layer.py +++ b/survival/entity_layer.py @@ -18,5 +18,8 @@ class EntityLayer: def remove_entity(self, pos): self.tiles[pos[1]][pos[0]] = None + def get_entity(self, pos) -> int: + return self.tiles[pos[1]][pos[0]] + def is_colliding(self, pos): return self.tiles[pos[1]][pos[0]] is not None diff --git a/survival/game_map.py b/survival/game_map.py index f8dca9e..36c04af 100644 --- a/survival/game_map.py +++ b/survival/game_map.py @@ -22,6 +22,9 @@ class GameMap: def remove_entity(self, pos): self.entity_layer.remove_entity(pos) + def get_entity(self, pos) -> int: + return self.entity_layer.get_entity(pos) + def is_colliding(self, pos): return pos[0] < 0 or pos[0] >= self.width or pos[1] < 0 or pos[1] >= self.height or self.entity_layer.is_colliding(pos) diff --git a/survival/generators/building_generator.py b/survival/generators/building_generator.py index bdfeacf..303eeac 100644 --- a/survival/generators/building_generator.py +++ b/survival/generators/building_generator.py @@ -5,14 +5,25 @@ from survival.components.sprite_component import SpriteComponent class BuildingGenerator: - def create_home(self, world, game_map): + def create_home(self, world, game_map, position = [10,10]): home = world.create_entity() - pos = PositionComponent([32, 32], [32, 32]) - world.add_component(home, pos) + home_pos = PositionComponent([position[0]*32, position[1]*32], position) + world.add_component(home, home_pos) + game_map.add_entity(home, home_pos) world.add_component(home, InventoryComponent()) - - game_map.add_entity(home, pos) - sprite = SpriteComponent('stone.png') - sprite.set_scale(2) + sprite = SpriteComponent('home.png') world.add_component(home, sprite) world.add_component(home, CollisionComponent()) + + for x_pos in [-1, 1]: + chest = world.create_entity() + chest_pos = PositionComponent( + [(position[0]+x_pos)*32, position[1]*32], + [position[0]+x_pos, position[1]] + ) + world.add_component(chest, chest_pos) + game_map.add_entity(chest, chest_pos) + world.add_component(chest, InventoryComponent()) + sprite = SpriteComponent('chest.png') + world.add_component(chest, sprite) + world.add_component(chest, CollisionComponent()) diff --git a/survival/generators/player_generator.py b/survival/generators/player_generator.py index 4a24660..46561c1 100644 --- a/survival/generators/player_generator.py +++ b/survival/generators/player_generator.py @@ -1,5 +1,7 @@ +from survival.components.OnCollisionComponent import OnCollisionComponent from survival.components.camera_target_component import CameraTargetComponent from survival.components.input_component import InputComponent +from survival.components.inventory_component import InventoryComponent from survival.components.movement_component import MovementComponent from survival.components.position_component import PositionComponent from survival.components.sprite_component import SpriteComponent @@ -14,6 +16,8 @@ class PlayerGenerator: world.add_component(player, pos) world.add_component(player, MovementComponent()) world.add_component(player, InputComponent()) + world.add_component(player, OnCollisionComponent()) + world.add_component(player, InventoryComponent()) camera_target = CameraTargetComponent(pos) world.add_component(player, camera_target) game_map.add_entity(player, pos) diff --git a/survival/generators/resource_generator.py b/survival/generators/resource_generator.py index fe36c9a..b9a8309 100644 --- a/survival/generators/resource_generator.py +++ b/survival/generators/resource_generator.py @@ -1,27 +1,47 @@ import random +from enum import Enum +from survival import GameMap +from survival.components.OnCollisionComponent import OnCollisionComponent +from survival.components.inventory_component import InventoryComponent from survival.components.position_component import PositionComponent +from survival.components.resource_component import ResourceComponent from survival.components.sprite_component import SpriteComponent +from survival.esper import World from survival.settings import RESOURCES_AMOUNT +class ResourceType(Enum): + FOOD = 1 + WATER = 2 + WOOD = 3 + + class ResourceGenerator: def __init__(self, world, game_map): self.world = world self.map = game_map - def generate_resources(self): + def generate_resources(self, player: int): for x in range(RESOURCES_AMOUNT): obj = self.world.create_entity() - sprites = ['apple.png', 'water.png', 'wood.png'] + sprites = { + ResourceType.FOOD: 'apple.png', + ResourceType.WATER: 'water.png', + ResourceType.WOOD: 'wood.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)) + resource_type = random.choice(list(ResourceType)) + sprite = SpriteComponent(sprites[resource_type]) + col = OnCollisionComponent() + col.addCallback(self.remove_resource, world=self.world, game_map=self.map, entity=obj, player=player) self.world.add_component(obj, pos) self.world.add_component(obj, sprite) + self.world.add_component(obj, col) + self.world.add_component(obj, ResourceComponent(resource_type)) self.map.add_entity(obj, pos) def get_empty_grid_position(self): @@ -29,3 +49,12 @@ class ResourceGenerator: while self.map.is_colliding(free_pos): free_pos = [random.randrange(self.map.width), random.randrange(self.map.height)] return free_pos + + @staticmethod + def remove_resource(world: World, game_map: GameMap, entity: int, player: int): + pos = world.component_for_entity(entity, PositionComponent) + resource = world.component_for_entity(entity, ResourceComponent) + inventory = world.component_for_entity(player, InventoryComponent) + inventory.add_item(resource.resource_type, 1) + game_map.remove_entity(pos.grid_position) + world.delete_entity(entity, immediate=True) diff --git a/survival/generators/tile_generator.py b/survival/generators/tile_generator.py index 64f69ce..a6e2ad9 100644 --- a/survival/generators/tile_generator.py +++ b/survival/generators/tile_generator.py @@ -1,4 +1,5 @@ import random +from typing import List from survival.biomes.biome_data import BiomeData from survival.biomes.biome_preset import BiomePreset @@ -38,7 +39,7 @@ class TileGenerator: return Tile(origin=tile.origin, cost=tile.cost) @staticmethod - def generate_random_tiles(width: int, height: int) -> list[list[Tile]]: + def generate_random_tiles(width: int, height: int) -> List[List[Tile]]: return [[TileGenerator.get_random_tile() for _ in range(width)] for _ in range(height)] @staticmethod diff --git a/survival/generators/world_generator.py b/survival/generators/world_generator.py index 586564b..bb9b59e 100644 --- a/survival/generators/world_generator.py +++ b/survival/generators/world_generator.py @@ -15,7 +15,7 @@ class WorldGenerator: world = esper.World() world.add_processor(InputSystem(camera)) world.add_processor(CameraSystem(camera)) - world.add_processor(MovementSystem(), priority=1) + world.add_processor(MovementSystem(game_map), priority=1) world.add_processor(CollisionSystem(game_map), priority=2) world.add_processor(DrawSystem(camera)) world.add_processor(TimeSystem()) diff --git a/survival/graph_search.py b/survival/graph_search.py index 77b4138..76c9f6e 100644 --- a/survival/graph_search.py +++ b/survival/graph_search.py @@ -1,5 +1,6 @@ from enum import Enum from queue import PriorityQueue +from typing import Tuple, List from survival import GameMap from survival.components.position_component import PositionComponent @@ -13,7 +14,7 @@ class Action(Enum): class State: - def __init__(self, position: tuple[int, int], direction: Direction): + def __init__(self, position: Tuple[int, int], direction: Direction): self.position = position self.direction = direction @@ -32,12 +33,12 @@ class Node: return self.cost == other.cost -def get_moved_position(position: tuple[int, int], direction: Direction): +def get_moved_position(position: Tuple[int, int], direction: Direction): vector = Direction.get_vector(direction) return position[0] + vector[0], position[1] + vector[1] -def get_states(state: State, game_map: GameMap) -> list[tuple[Action, State, int]]: +def get_states(state: State, game_map: GameMap) -> List[Tuple[Action, State, int]]: states = list() states.append((Action.ROTATE_LEFT, State(state.position, state.direction.rotate_left(state.direction)), 1)) @@ -63,7 +64,7 @@ def build_path(node: Node): return actions -def heuristic(new_node: Node, goal: tuple[int, int]): +def heuristic(new_node: Node, goal: Tuple[int, int]): return abs(new_node.state.position[0] - goal[0]) + abs(new_node.state.position[1] - goal[1]) diff --git a/survival/image.py b/survival/image.py index 3c93b9c..c1727cf 100644 --- a/survival/image.py +++ b/survival/image.py @@ -4,12 +4,12 @@ import pygame class Image: - def __init__(self, filename): + def __init__(self, filename, pos=(0, 0), scale=1): self.texture = pygame.image.load(os.path.join('..', 'assets', filename)).convert_alpha() self.image = self.texture self.origin = (0, 0) - self.pos = (0, 0) - self.scale = 1 + self.pos = pos + self.set_scale(scale) def set_scale(self, scale): self.image = pygame.transform.scale(self.texture, @@ -20,3 +20,8 @@ class Image: window.blit(self.image, camera.apply(self.pos), pygame.Rect(self.origin[0] * self.scale, self.origin[1] * self.scale, 32 * self.scale, 32 * self.scale)) + + def draw_static(self, window): + window.blit(self.image, self.pos, + pygame.Rect(self.origin[0] * self.scale, self.origin[1] * self.scale, 32 * self.scale, + 32 * self.scale)) diff --git a/survival/settings.py b/survival/settings.py index fd6e84f..0deb510 100644 --- a/survival/settings.py +++ b/survival/settings.py @@ -1,4 +1,4 @@ -SCREEN_WIDTH = 1920 -SCREEN_HEIGHT = 1080 +SCREEN_WIDTH = 1000 +SCREEN_HEIGHT = 600 RESOURCES_AMOUNT = 300 DIRECTION_CHANGE_DELAY = 200 diff --git a/survival/systems/collision_system.py b/survival/systems/collision_system.py index 5e0bea2..b9cc0f7 100644 --- a/survival/systems/collision_system.py +++ b/survival/systems/collision_system.py @@ -1,6 +1,7 @@ import operator from survival import esper +from survival.components.OnCollisionComponent import OnCollisionComponent from survival.components.moving_component import MovingComponent from survival.components.position_component import PositionComponent from survival.enums import Direction @@ -11,7 +12,8 @@ class CollisionSystem(esper.Processor): self.map = game_map def process(self, dt): - for ent, (pos, moving) in self.world.get_components(PositionComponent, MovingComponent): + for ent, (pos, moving, onCol) in self.world.get_components(PositionComponent, MovingComponent, + OnCollisionComponent): if moving.target is not None: continue @@ -22,6 +24,15 @@ class CollisionSystem(esper.Processor): moving.direction_vector = vector if self.check_collision(moving.target): self.world.remove_component(ent, MovingComponent) + onCol.callAll() + colliding_object: int = self.map.get_entity(moving.target) + + if colliding_object is None or not self.world.entity_exists(colliding_object): + continue + + if self.world.has_component(colliding_object, OnCollisionComponent): + self.world.component_for_entity(colliding_object, OnCollisionComponent).callAll() + else: self.map.move_entity(pos.grid_position, moving.target) pos.grid_position = moving.target diff --git a/survival/systems/draw_system.py b/survival/systems/draw_system.py index 2cb35ae..38aac3e 100644 --- a/survival/systems/draw_system.py +++ b/survival/systems/draw_system.py @@ -1,14 +1,21 @@ from survival import esper from survival.components.position_component import PositionComponent from survival.components.sprite_component import SpriteComponent +from survival.user_interface import UserInterface class DrawSystem(esper.Processor): def __init__(self, camera): self.camera = camera + self.ui = None + + def initialize_interface(self, inventory): + self.ui = UserInterface(self.camera.window, inventory) def process(self, dt): for ent, (sprite, pos) in self.world.get_components(SpriteComponent, PositionComponent): sprite.image.pos = pos.position sprite.image.origin = (32 * pos.direction.value, 0) self.camera.draw(sprite.image) + self.ui.update() + self.ui.draw() diff --git a/survival/systems/movement_system.py b/survival/systems/movement_system.py index 47a5ad1..9299d29 100644 --- a/survival/systems/movement_system.py +++ b/survival/systems/movement_system.py @@ -1,4 +1,4 @@ -from survival import esper +from survival import esper, GameMap from survival.components.movement_component import MovementComponent from survival.components.moving_component import MovingComponent from survival.components.position_component import PositionComponent @@ -6,16 +6,16 @@ from survival.components.sprite_component import SpriteComponent class MovementSystem(esper.Processor): - def __init__(self): - self.map = None + def __init__(self, game_map: GameMap): + self.map = game_map def process(self, dt): for ent, (mov, pos, moving, sprite) in self.world.get_components(MovementComponent, PositionComponent, MovingComponent, SpriteComponent): - - pos.position[0] += moving.direction_vector[0] * mov.speed * dt / 100 - pos.position[1] += moving.direction_vector[1] * mov.speed * dt / 100 + cost = self.map.get_cost(moving.target) + pos.position[0] += moving.direction_vector[0] * mov.speed * dt / 100 / cost + pos.position[1] += moving.direction_vector[1] * mov.speed * dt / 100 / cost if abs(moving.target[0] * 32 - pos.position[0]) < 0.1 * mov.speed and abs( pos.position[1] - moving.target[1] * 32) < 0.1 * mov.speed: diff --git a/survival/user_interface.py b/survival/user_interface.py new file mode 100644 index 0000000..33e8841 --- /dev/null +++ b/survival/user_interface.py @@ -0,0 +1,42 @@ +import pygame.font + +from survival import settings +from survival.components.inventory_component import InventoryComponent +from survival.generators.resource_generator import ResourceType +from survival.image import Image + + +class UserInterface: + def __init__(self, window, inventory: InventoryComponent): + self.width = settings.SCREEN_WIDTH + self.height = settings.SCREEN_HEIGHT + self.window = window + self.pos = (self.width - 240, 50) + self.scale = 2 + self.inventory = inventory + self.images = { + ResourceType.FOOD: Image('apple.png', self.pos, self.scale), + ResourceType.WATER: Image('water.png', self.pos, self.scale), + ResourceType.WOOD: Image('wood.png', self.pos, self.scale) + } + i = 0 + for key, value in self.images.items(): + self.images[key].pos = (self.pos[0] + i * 32 * self.scale + 8 * i, self.pos[1]) + i += 1 + self.slot_image = Image('ui.png', self.pos, scale=2) + self.font = pygame.font.SysFont('Comic Sans MS', 20) + + def update(self): + pass + + def draw(self): + for key, image in self.images.items(): + items_count = self.inventory.items[key] if self.inventory.has_item(key) else 0 + + self.slot_image.pos = image.pos + self.slot_image.draw_static(self.window) + image.draw_static(self.window) + + textsurface = self.font.render(str(items_count), False, (255, 255, 255)) + self.window.blit(textsurface, (image.pos[0] + 48, image.pos[1] + 36)) +