diff --git a/main.py b/main.py index feabd6b..1e8b4c2 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ from raycasting import * from object_renderer import * #from sprite_object import * from object_handler import * +from pathfinding import * class Game: def __init__(self): @@ -25,6 +26,7 @@ class Game: #self.static_sprites = SpriteObject(self) #self.animated_sprites = AnimatedSprite(self) self.object_handler = ObjectHandler(self) + self.pathfinding = PathFinding(self) def update(self): self.player.update() diff --git a/npc.py b/npc.py new file mode 100644 index 0000000..d6f953d --- /dev/null +++ b/npc.py @@ -0,0 +1,144 @@ +from sprite_object import * +from random import randint, random, choice + +class NPC(AnimatedSprite): + def __init__(self, game, path='resources/sprites/npc/soldier/0.png', pos=(10.5, 5.5), + scale=0.6, shift=0.38, animation_time=180): + super().__init__(game, path, pos, scale, shift, animation_time) + self.attack_images = self.get_images(self.path + '/attack') + self.death_images = self.get_images(self.path + '/death') + self.idle_images = self.get_images(self.path + '/idle') + self.pain_images = self.get_images(self.path + '/pain') + self.walk_images = self.get_images(self.path + '/walk') + + self.attack_dist = randint(3, 6) + self.speed = 0.02 + self.size = 10 + self.health = 100 + self.attack_damage = 10 + self.accuracy = 0.15 + self.alive = True + self.pain = False + self.ray_cast_value = False + self.player_search_trigger = False + + def update(self): + self.check_animation_time() + self.get_sprite() + self.run_logic() + if DEBUG_MODE: + self.draw_ray_cast() + + def check_wall(self, x, y): + return (x, y) not in self.game.map.world_map + + def check_collision(self, dx, dy): + if self.check_wall(int(self.x + dx * self.size), int(self.y)): + self.x += dx + if self.check_wall(int(self.x), int(self.y + dy * self.size)): + self.y += dy + + def movement(self): + next_pos = self.game.pathfinding.get_path(self.map_pos, self.game.player.map_pos) + next_x, next_y = next_pos + if DEBUG_MODE: + pg.draw.rect(self.game.screen, 'blue', (100 * next_x, 100 * next_y, 100, 100)) + if next_pos not in self.game.object_handler.npc_positions: + angle = math.atan2(next_y + 0.5 - self.y, next_x + 0.5 - self.x) + dx = math.cos(angle) * self.speed + dy = math.sin(angle) * self.speed + self.check_collision(dx, dy) + + def attack(self): + if self.animation_trigger: + pass + + def run_logic(self): + if self.alive: + self.ray_cast_value = self.ray_cast_player_npc() + self.animate(self.idle_images) + if self.ray_cast_value: + self.player_search_trigger = True + + if self.dist < self.attack_dist: + self.animate(self.attack_images) + self.attack() + else: + self.animate(self.walk_images) + self.movement() + elif self.player_search_trigger: + self.animate(self.walk_images) + self.movement() + + @property + def map_pos(self): + return int(self.x), int(self.y) + + def ray_cast_player_npc(self): + if self.game.player.map_pos == self.map_pos: + return True + + wall_dist_v, wall_dist_h = 0, 0 + player_dist_v, player_dist_h = 0, 0 + + ox, oy = self.game.player.pos + x_map, y_map = self.game.player.map_pos + + ray_angle = self.theta + sin_a = math.sin(ray_angle) + cos_a = math.cos(ray_angle) + + # horizontal + y_horz, dy = (y_map + 1, 1) if sin_a > 0 else (y_map - 1e-6, -1) + + depth_horz = (y_horz - oy) / sin_a + x_horz = ox + depth_horz * cos_a + + delta_depth = dy / sin_a + dx = delta_depth * cos_a + + for i in range(MAX_DEPTH): + tile_horz = int(x_horz), int(y_horz) + if tile_horz == self.map_pos: + player_dist_h = depth_horz + break + if tile_horz in self.game.map.world_map: + wall_dist_h = depth_horz + break + x_horz += dx + y_horz += dy + depth_horz += delta_depth + + # verticals + x_vert, dx = (x_map + 1, 1) if cos_a > 0 else (x_map - 1e-6, -1) + + depth_vert = (x_vert - ox) / cos_a + y_vert = oy + depth_vert * sin_a + + delta_depth = dx / cos_a + dy = delta_depth * sin_a + + for i in range(MAX_DEPTH): + tile_vert = int(x_vert), int(y_vert) + if tile_vert == self.map_pos: + player_dist_v = depth_vert + break + if tile_vert in self.game.map.world_map: + wall_dist_v = depth_vert + break + x_vert += dx + y_vert += dy + depth_vert += delta_depth + + player_dist = max(player_dist_v, player_dist_h) + wall_dist = max(wall_dist_v, wall_dist_h) + + if 0 < player_dist < wall_dist or not wall_dist: + return True + return False + + def draw_ray_cast(self): + pg.draw.circle(self.game.screen, 'red', (100 * self.x, 100 * self.y), 15) + if self.ray_cast_player_npc(): + pg.draw.line(self.game.screen, 'orange', (100 * self.game.player.x, 100 * self.game.player.y), + (100 * self.x, 100 * self.y), 2) \ No newline at end of file diff --git a/object_handler.py b/object_handler.py index 932f384..5b44be6 100644 --- a/object_handler.py +++ b/object_handler.py @@ -1,4 +1,5 @@ from sprite_object import * +from npc import * class ObjectHandler: def __init__(self, game): @@ -6,7 +7,11 @@ class ObjectHandler: self.sprite_list = [] self.static_sprite_path = 'resources/sprites/static_sprites/' self.animated_sprite_path = 'resources/sprites/animated_sprites/' + self.npc_list = [] + self.npc_sprite_path = 'resources/sprites/npc/' add_sprite = self.add_sprite + add_npc = self.add_npc + self.npc_positions = {} # Sprite Map add_sprite(SpriteObject(game)) @@ -21,8 +26,17 @@ class ObjectHandler: add_sprite(AnimatedSprite(game, path=self.animated_sprite_path + 'red_light/0.png', pos=(3.5, 14.5))) add_sprite(AnimatedSprite(game, path=self.animated_sprite_path + 'red_light/0.png', pos=(3.5, 18.5))) + # NPC Map + add_npc(NPC(game)) + + def update(self): + self.npc_positions = {npc.map_pos for npc in self.npc_list if npc.alive} [sprite.update() for sprite in self.sprite_list] + [npc.update() for npc in self.npc_list] + + def add_npc(self, npc): + self.npc_list.append(npc) def add_sprite(self, sprite): self.sprite_list.append(sprite) \ No newline at end of file diff --git a/pathfinding.py b/pathfinding.py new file mode 100644 index 0000000..2df004c --- /dev/null +++ b/pathfinding.py @@ -0,0 +1,45 @@ +from collections import deque + + +class PathFinding: + def __init__(self, game): + self.game = game + self.map = game.map.mini_map + self.ways = [-1, 0], [0, -1], [1, 0], [0, 1], [-1, -1], [1, -1], [1, 1], [-1, 1] + self.graph = {} + self.get_graph() + + def get_path(self, start, goal): + self.visited = self.bfs(start, goal, self.graph) + path = [goal] + step = self.visited.get(goal, start) + + while step and step != start: + path.append(step) + step = self.visited[step] + return path[-1] + + def bfs(self, start, goal, graph): + queue = deque([start]) + visited = {start: None} + + while queue: + cur_node = queue.popleft() + if cur_node == goal: + break + next_nodes = graph[cur_node] + + for next_node in next_nodes: + if next_node not in visited and next_node not in self.game.object_handler.npc_positions: + queue.append(next_node) + visited[next_node] = cur_node + return visited + + def get_next_nodes(self, x, y): + return [(x + dx, y + dy) for dx, dy in self.ways if (x + dx, y + dy) not in self.game.map.world_map] + + def get_graph(self): + for y, row in enumerate(self.map): + for x, col in enumerate(row): + if not col: + self.graph[(x, y)] = self.graph.get((x, y), []) + self.get_next_nodes(x, y) \ No newline at end of file diff --git a/resources/sprites/npc/soldier/0.png b/resources/sprites/npc/soldier/0.png new file mode 100644 index 0000000..d6cff0b Binary files /dev/null and b/resources/sprites/npc/soldier/0.png differ diff --git a/resources/sprites/npc/soldier/attack/0.png b/resources/sprites/npc/soldier/attack/0.png new file mode 100644 index 0000000..9321377 Binary files /dev/null and b/resources/sprites/npc/soldier/attack/0.png differ diff --git a/resources/sprites/npc/soldier/attack/1.png b/resources/sprites/npc/soldier/attack/1.png new file mode 100644 index 0000000..3338205 Binary files /dev/null and b/resources/sprites/npc/soldier/attack/1.png differ diff --git a/resources/sprites/npc/soldier/death/POSSM0.png b/resources/sprites/npc/soldier/death/POSSM0.png new file mode 100644 index 0000000..f6081b5 Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSM0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSN0.png b/resources/sprites/npc/soldier/death/POSSN0.png new file mode 100644 index 0000000..696e1bc Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSN0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSO0.png b/resources/sprites/npc/soldier/death/POSSO0.png new file mode 100644 index 0000000..a5e9bcb Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSO0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSP0.png b/resources/sprites/npc/soldier/death/POSSP0.png new file mode 100644 index 0000000..870a004 Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSP0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSQ0.png b/resources/sprites/npc/soldier/death/POSSQ0.png new file mode 100644 index 0000000..80b4ba7 Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSQ0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSR0.png b/resources/sprites/npc/soldier/death/POSSR0.png new file mode 100644 index 0000000..d41fe50 Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSR0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSS0.png b/resources/sprites/npc/soldier/death/POSSS0.png new file mode 100644 index 0000000..f51b66c Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSS0.png differ diff --git a/resources/sprites/npc/soldier/death/POSST0.png b/resources/sprites/npc/soldier/death/POSST0.png new file mode 100644 index 0000000..2b6624a Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSST0.png differ diff --git a/resources/sprites/npc/soldier/death/POSSU0.png b/resources/sprites/npc/soldier/death/POSSU0.png new file mode 100644 index 0000000..2a71484 Binary files /dev/null and b/resources/sprites/npc/soldier/death/POSSU0.png differ diff --git a/resources/sprites/npc/soldier/idle/0.png b/resources/sprites/npc/soldier/idle/0.png new file mode 100644 index 0000000..844fb0f Binary files /dev/null and b/resources/sprites/npc/soldier/idle/0.png differ diff --git a/resources/sprites/npc/soldier/idle/1.png b/resources/sprites/npc/soldier/idle/1.png new file mode 100644 index 0000000..a0b9a80 Binary files /dev/null and b/resources/sprites/npc/soldier/idle/1.png differ diff --git a/resources/sprites/npc/soldier/idle/2.png b/resources/sprites/npc/soldier/idle/2.png new file mode 100644 index 0000000..c6e2f01 Binary files /dev/null and b/resources/sprites/npc/soldier/idle/2.png differ diff --git a/resources/sprites/npc/soldier/idle/3.png b/resources/sprites/npc/soldier/idle/3.png new file mode 100644 index 0000000..fa0f205 Binary files /dev/null and b/resources/sprites/npc/soldier/idle/3.png differ diff --git a/resources/sprites/npc/soldier/idle/4.png b/resources/sprites/npc/soldier/idle/4.png new file mode 100644 index 0000000..ff3060f Binary files /dev/null and b/resources/sprites/npc/soldier/idle/4.png differ diff --git a/resources/sprites/npc/soldier/idle/5.png b/resources/sprites/npc/soldier/idle/5.png new file mode 100644 index 0000000..2bc8346 Binary files /dev/null and b/resources/sprites/npc/soldier/idle/5.png differ diff --git a/resources/sprites/npc/soldier/idle/6.png b/resources/sprites/npc/soldier/idle/6.png new file mode 100644 index 0000000..1869802 Binary files /dev/null and b/resources/sprites/npc/soldier/idle/6.png differ diff --git a/resources/sprites/npc/soldier/idle/7.png b/resources/sprites/npc/soldier/idle/7.png new file mode 100644 index 0000000..9cb1e7f Binary files /dev/null and b/resources/sprites/npc/soldier/idle/7.png differ diff --git a/resources/sprites/npc/soldier/pain/0.png b/resources/sprites/npc/soldier/pain/0.png new file mode 100644 index 0000000..ff6665e Binary files /dev/null and b/resources/sprites/npc/soldier/pain/0.png differ diff --git a/resources/sprites/npc/soldier/walk/0.png b/resources/sprites/npc/soldier/walk/0.png new file mode 100644 index 0000000..d6cff0b Binary files /dev/null and b/resources/sprites/npc/soldier/walk/0.png differ diff --git a/resources/sprites/npc/soldier/walk/1.png b/resources/sprites/npc/soldier/walk/1.png new file mode 100644 index 0000000..a85d0de Binary files /dev/null and b/resources/sprites/npc/soldier/walk/1.png differ diff --git a/resources/sprites/npc/soldier/walk/2.png b/resources/sprites/npc/soldier/walk/2.png new file mode 100644 index 0000000..844fb0f Binary files /dev/null and b/resources/sprites/npc/soldier/walk/2.png differ diff --git a/resources/sprites/npc/soldier/walk/3.png b/resources/sprites/npc/soldier/walk/3.png new file mode 100644 index 0000000..2f2a489 Binary files /dev/null and b/resources/sprites/npc/soldier/walk/3.png differ diff --git a/sound.py b/sound.py new file mode 100644 index 0000000..8662fc1 --- /dev/null +++ b/sound.py @@ -0,0 +1,11 @@ +import pygame as pg + +class Sound: + def __init__(self, game): + self.game = game + pg.mixer.init() + self.path = 'resources/sounds/' + #self.sound_type = pg.mixer.Sound(self.path + 'sound_type.extention') + + # Then to call the sound use + # self.sound_type.play() \ No newline at end of file