Path finding
2
main.py
|
@ -7,6 +7,7 @@ from raycasting import *
|
||||||
from object_renderer import *
|
from object_renderer import *
|
||||||
#from sprite_object import *
|
#from sprite_object import *
|
||||||
from object_handler import *
|
from object_handler import *
|
||||||
|
from pathfinding import *
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -25,6 +26,7 @@ class Game:
|
||||||
#self.static_sprites = SpriteObject(self)
|
#self.static_sprites = SpriteObject(self)
|
||||||
#self.animated_sprites = AnimatedSprite(self)
|
#self.animated_sprites = AnimatedSprite(self)
|
||||||
self.object_handler = ObjectHandler(self)
|
self.object_handler = ObjectHandler(self)
|
||||||
|
self.pathfinding = PathFinding(self)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.player.update()
|
self.player.update()
|
||||||
|
|
144
npc.py
Normal file
|
@ -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)
|
|
@ -1,4 +1,5 @@
|
||||||
from sprite_object import *
|
from sprite_object import *
|
||||||
|
from npc import *
|
||||||
|
|
||||||
class ObjectHandler:
|
class ObjectHandler:
|
||||||
def __init__(self, game):
|
def __init__(self, game):
|
||||||
|
@ -6,7 +7,11 @@ class ObjectHandler:
|
||||||
self.sprite_list = []
|
self.sprite_list = []
|
||||||
self.static_sprite_path = 'resources/sprites/static_sprites/'
|
self.static_sprite_path = 'resources/sprites/static_sprites/'
|
||||||
self.animated_sprite_path = 'resources/sprites/animated_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_sprite = self.add_sprite
|
||||||
|
add_npc = self.add_npc
|
||||||
|
self.npc_positions = {}
|
||||||
|
|
||||||
# Sprite Map
|
# Sprite Map
|
||||||
add_sprite(SpriteObject(game))
|
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, 14.5)))
|
||||||
add_sprite(AnimatedSprite(game, path=self.animated_sprite_path + 'red_light/0.png', pos=(3.5, 18.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):
|
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]
|
[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):
|
def add_sprite(self, sprite):
|
||||||
self.sprite_list.append(sprite)
|
self.sprite_list.append(sprite)
|
45
pathfinding.py
Normal file
|
@ -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)
|
BIN
resources/sprites/npc/soldier/0.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
resources/sprites/npc/soldier/attack/0.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
resources/sprites/npc/soldier/attack/1.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
resources/sprites/npc/soldier/death/POSSM0.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
resources/sprites/npc/soldier/death/POSSN0.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
resources/sprites/npc/soldier/death/POSSO0.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
resources/sprites/npc/soldier/death/POSSP0.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
resources/sprites/npc/soldier/death/POSSQ0.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
resources/sprites/npc/soldier/death/POSSR0.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
resources/sprites/npc/soldier/death/POSSS0.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/sprites/npc/soldier/death/POSST0.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
resources/sprites/npc/soldier/death/POSSU0.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
resources/sprites/npc/soldier/idle/0.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
resources/sprites/npc/soldier/idle/1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
resources/sprites/npc/soldier/idle/2.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
resources/sprites/npc/soldier/idle/3.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
resources/sprites/npc/soldier/idle/4.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
resources/sprites/npc/soldier/idle/5.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
resources/sprites/npc/soldier/idle/6.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
resources/sprites/npc/soldier/idle/7.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
resources/sprites/npc/soldier/pain/0.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
resources/sprites/npc/soldier/walk/0.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
resources/sprites/npc/soldier/walk/1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
resources/sprites/npc/soldier/walk/2.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
resources/sprites/npc/soldier/walk/3.png
Normal file
After Width: | Height: | Size: 35 KiB |
11
sound.py
Normal file
|
@ -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()
|