commit 81a92432776226a74f68c1f0add9e9844bc0fc14 Author: Vladislavs Pozdņaks Date: Thu Mar 28 06:41:00 2024 +0000 Doompart1 diff --git a/drawing.py b/drawing.py new file mode 100644 index 0000000..f1bf372 --- /dev/null +++ b/drawing.py @@ -0,0 +1,156 @@ +import pygame +from settings import * +from ray_casting import ray_casting +from map import mini_map +from collections import deque +from random import randrange +import sys + +class Drawing: + def __init__(self, sc, sc_map, player, clock): + self.sc = sc + self.sc_map = sc_map + self.player = player + self.clock = clock + self.font = pygame.font.SysFont('Arial', 36, bold=True) + self.font_win = pygame.font.Font('font/font.ttf', 144) + self.textures = {1: pygame.image.load('img/wall3.png').convert(), + 2: pygame.image.load('img/wall4.png').convert(), + 3: pygame.image.load('img/wall5.png').convert(), + 4: pygame.image.load('img/wall6.png').convert(), + 'S': pygame.image.load('img/sky2.png').convert() + } + # menu + self.menu_trigger = True + self.menu_picture = pygame.image.load('img/bg.jpg').convert() + # weapon parameters + self.weapon_base_sprite = pygame.image.load('sprites/weapons/shotgun/base/0.png').convert_alpha() + self.weapon_shot_animation = deque([pygame.image.load(f'sprites/weapons/shotgun/shot/{i}.png').convert_alpha() + for i in range(20)]) + self.weapon_rect = self.weapon_base_sprite.get_rect() + self.weapon_pos = (HALF_WIDTH - self.weapon_rect.width // 2, HEIGHT - self.weapon_rect.height) + self.shot_length = len(self.weapon_shot_animation) + self.shot_length_count = 0 + self.shot_animation_speed = 3 + self.shot_animation_count = 0 + self.shot_animation_trigger = True + self.shot_sound = pygame.mixer.Sound('sound/shotgun.wav') + # sfx parameters + self.sfx = deque([pygame.image.load(f'sprites/weapons/sfx/{i}.png').convert_alpha() for i in range(9)]) + self.sfx_length_count = 0 + self.sfx_length = len(self.sfx) + + def background(self, angle): + sky_offset = -10 * math.degrees(angle) % WIDTH + self.sc.blit(self.textures['S'], (sky_offset, 0)) + self.sc.blit(self.textures['S'], (sky_offset - WIDTH, 0)) + self.sc.blit(self.textures['S'], (sky_offset + WIDTH, 0)) + pygame.draw.rect(self.sc, DARKGRAY, (0, HALF_HEIGHT, WIDTH, HALF_HEIGHT)) + + def world(self, world_objects): + for obj in sorted(world_objects, key=lambda n: n[0], reverse=True): + if obj[0]: + _, object, object_pos = obj + self.sc.blit(object, object_pos) + + def fps(self, clock): + display_fps = str(int(clock.get_fps())) + render = self.font.render(display_fps, 0, DARKORANGE) + self.sc.blit(render, FPS_POS) + + def mini_map(self, player): + self.sc_map.fill(BLACK) + map_x, map_y = player.x // MAP_SCALE, player.y // MAP_SCALE + pygame.draw.line(self.sc_map, YELLOW, (map_x, map_y), (map_x + 12 * math.cos(player.angle), + map_y + 12 * math.sin(player.angle)), 2) + pygame.draw.circle(self.sc_map, RED, (int(map_x), int(map_y)), 5) + for x, y in mini_map: + pygame.draw.rect(self.sc_map, DARKBROWN, (x, y, MAP_TILE, MAP_TILE)) + self.sc.blit(self.sc_map, MAP_POS) + + def player_weapon(self, shots): + if self.player.shot: + if not self.shot_length_count: + self.shot_sound.play() + self.shot_projection = min(shots)[1] // 2 + self.bullet_sfx() + shot_sprite = self.weapon_shot_animation[0] + self.sc.blit(shot_sprite, self.weapon_pos) + self.shot_animation_count += 1 + if self.shot_animation_count == self.shot_animation_speed: + self.weapon_shot_animation.rotate(-1) + self.shot_animation_count = 0 + self.shot_length_count += 1 + self.shot_animation_trigger = False + if self.shot_length_count == self.shot_length: + self.player.shot = False + self.shot_length_count = 0 + self.sfx_length_count = 0 + self.shot_animation_trigger = True + else: + self.sc.blit(self.weapon_base_sprite, self.weapon_pos) + + def bullet_sfx(self): + if self.sfx_length_count < self.sfx_length: + sfx = pygame.transform.scale(self.sfx[0], (self.shot_projection, self.shot_projection)) + sfx_rect = sfx.get_rect() + self.sc.blit(sfx, (HALF_WIDTH - sfx_rect.w // 2, HALF_HEIGHT - sfx_rect.h // 2)) + self.sfx_length_count += 1 + self.sfx.rotate(-1) + + + def win(self): + render = self.font_win.render('YOU WIN!!!', 1, (randrange(40, 120), 0, 0)) + rect = pygame.Rect(0, 0, 1000, 300) + rect.center = HALF_WIDTH, HALF_HEIGHT + pygame.draw.rect(self.sc, BLACK, rect, border_radius=50) + self.sc.blit(render, (rect.centerx - 430, rect.centery - 140)) + pygame.display.flip() + self.clock.tick(15) + + def menu(self): + x = 0 + button_font = pygame.font.Font('font/font.ttf', 72) + label_font = pygame.font.Font('font/font1.otf', 400) + start = button_font.render('START', 1, pygame.Color('lightgray')) + button_start = pygame.Rect(0, 0, 400, 150) + button_start.center = HALF_WIDTH, HALF_HEIGHT + exit = button_font.render('EXIT', 1, pygame.Color('lightgray')) + button_exit = pygame.Rect(0, 0, 400, 150) + button_exit.center = HALF_WIDTH, HALF_HEIGHT + 200 + + while self.menu_trigger: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + self.sc.blit(self.menu_picture, (0, 0), (x % WIDTH, HALF_HEIGHT, WIDTH, HEIGHT)) + x += 1 + + pygame.draw.rect(self.sc, BLACK, button_start, border_radius=25, width=10) + self.sc.blit(start, (button_start.centerx - 130, button_start.centery - 70)) + + pygame.draw.rect(self.sc, BLACK, button_exit, border_radius=25, width=10) + self.sc.blit(exit, (button_exit.centerx - 85, button_exit.centery - 70)) + + color = randrange(40) + label = label_font.render('DOOMPy', 1, (color, color, color)) + self.sc.blit(label, (15, -30)) + + mouse_pos = pygame.mouse.get_pos() + mouse_click = pygame.mouse.get_pressed() + if button_start.collidepoint(mouse_pos): + pygame.draw.rect(self.sc, BLACK, button_start, border_radius=25) + self.sc.blit(start, (button_start.centerx - 130, button_start.centery - 70)) + if mouse_click[0]: + self.menu_trigger = False + elif button_exit.collidepoint(mouse_pos): + pygame.draw.rect(self.sc, BLACK, button_exit, border_radius=25) + self.sc.blit(exit, (button_exit.centerx - 85, button_exit.centery - 70)) + if mouse_click[0]: + pygame.quit() + sys.exit() + + pygame.display.flip() + self.clock.tick(20) \ No newline at end of file diff --git a/interaction.py b/interaction.py new file mode 100644 index 0000000..7d503f8 --- /dev/null +++ b/interaction.py @@ -0,0 +1,105 @@ +from settings import * +from map import world_map +from ray_casting import mapping +import math +import pygame +from numba import njit + +@njit(fastmath=True, cache=True) +def ray_casting_npc_player(npc_x, npc_y, blocked_doors, world_map, player_pos): + ox, oy = player_pos + xm, ym = mapping(ox, oy) + delta_x, delta_y = ox - npc_x, oy - npc_y + cur_angle = math.atan2(delta_y, delta_x) + cur_angle += math.pi + + sin_a = math.sin(cur_angle) + sin_a = sin_a if sin_a else 0.000001 + cos_a = math.cos(cur_angle) + cos_a = cos_a if cos_a else 0.000001 + + # verticals + x, dx = (xm + TILE, 1) if cos_a >= 0 else (xm, -1) + for i in range(0, int(abs(delta_x)) // TILE): + depth_v = (x - ox) / cos_a + yv = oy + depth_v * sin_a + tile_v = mapping(x + dx, yv) + if tile_v in world_map or tile_v in blocked_doors: + return False + x += dx * TILE + + # horizontals + y, dy = (ym + TILE, 1) if sin_a >= 0 else (ym, -1) + for i in range(0, int(abs(delta_y)) // TILE): + depth_h = (y - oy) / sin_a + xh = ox + depth_h * cos_a + tile_h = mapping(xh, y + dy) + if tile_h in world_map or tile_h in blocked_doors: + return False + y += dy * TILE + return True + + +class Interaction: + def __init__(self, player, sprites, drawing): + self.player = player + self.sprites = sprites + self.drawing = drawing + self.pain_sound = pygame.mixer.Sound('sound/pain.wav') + + def interaction_objects(self): + if self.player.shot and self.drawing.shot_animation_trigger: + for obj in sorted(self.sprites.list_of_objects, key=lambda obj: obj.distance_to_sprite): + if obj.is_on_fire[1]: + if obj.is_dead != 'immortal' and not obj.is_dead: + if ray_casting_npc_player(obj.x, obj.y, + self.sprites.blocked_doors, + world_map, self.player.pos): + if obj.flag == 'npc': + self.pain_sound.play() + obj.is_dead = True + obj.blocked = None + self.drawing.shot_animation_trigger = False + if obj.flag in {'door_h', 'door_v'} and obj.distance_to_sprite < TILE: + obj.door_open_trigger = True + obj.blocked = None + break + + def npc_action(self): + for obj in self.sprites.list_of_objects: + if obj.flag == 'npc' and not obj.is_dead: + if ray_casting_npc_player(obj.x, obj.y, + self.sprites.blocked_doors, + world_map, self.player.pos): + obj.npc_action_trigger = True + self.npc_move(obj) + else: + obj.npc_action_trigger = False + + def npc_move(self, obj): + if abs(obj.distance_to_sprite) > TILE: + dx = obj.x - self.player.pos[0] + dy = obj.y - self.player.pos[1] + obj.x = obj.x + 1 if dx < 0 else obj.x - 1 + obj.y = obj.y + 1 if dy < 0 else obj.y - 1 + + def clear_world(self): + deleted_objects = self.sprites.list_of_objects[:] + [self.sprites.list_of_objects.remove(obj) for obj in deleted_objects if obj.delete] + + def play_music(self): + pygame.mixer.pre_init(44100, -16, 2, 2048) + pygame.mixer.init() + pygame.mixer.music.load('sound/theme.mp3') + pygame.mixer.music.play(10) + + def check_win(self): + if not len([obj for obj in self.sprites.list_of_objects if obj.flag == 'npc' and not obj.is_dead]): + pygame.mixer.music.stop() + pygame.mixer.music.load('sound/win.mp3') + pygame.mixer.music.play() + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + self.drawing.win() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..c7a1180 --- /dev/null +++ b/main.py @@ -0,0 +1,37 @@ +from player import Player +from sprite_objects import * +from ray_casting import ray_casting_walls +from drawing import Drawing +from interaction import Interaction + +pygame.init() +sc = pygame.display.set_mode((WIDTH, HEIGHT)) +sc_map = pygame.Surface(MINIMAP_RES) + +sprites = Sprites() +clock = pygame.time.Clock() +player = Player(sprites) +drawing = Drawing(sc, sc_map, player, clock) +interaction = Interaction(player, sprites, drawing) + +drawing.menu() +pygame.mouse.set_visible(False) +interaction.play_music() + +while True: + + player.movement() + drawing.background(player.angle) + walls, wall_shot = ray_casting_walls(player, drawing.textures) + drawing.world(walls + [obj.object_locate(player) for obj in sprites.list_of_objects]) + drawing.fps(clock) + drawing.mini_map(player) + drawing.player_weapon([wall_shot, sprites.sprite_shot]) + + interaction.interaction_objects() + interaction.npc_action() + interaction.clear_world() + interaction.check_win() + + pygame.display.flip() + clock.tick() \ No newline at end of file diff --git a/map.py b/map.py new file mode 100644 index 0000000..ec1570b --- /dev/null +++ b/map.py @@ -0,0 +1,44 @@ +from settings import * +import pygame +from numba.core import types +from numba.typed import Dict +from numba import int32 + +_ = False +matrix_map = [ + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, _, _, _, _, _, 2, _, _, _, _, _, _, _, _, _, _, 4, _, _, _, _, _, 1], + [1, _, 2, 2, _, _, _, _, _, 2, 2, 2, _, _, _, 3, _, _, _, _, 4, _, _, 1], + [1, _, _, _, _, _, _, _, _, _, _, 2, 2, _, _, _, 3, _, _, _, _, _, _, 1], + [1, _, 2, 2, _, _, _, _, _, _, _, _, 2, _, 4, _, _, 3, _, _, _, 4, _, 1], + [1, _, _, _, _, _, 4, _, _, 2, 2, _, 2, _, _, _, _, _, _, 4, _, _, _, 1], + [1, _, 3, _, _, _, 2, _, _, 2, _, _, 2, _, _, _, 4, _, _, _, _, 4, _, 1], + [1, _, _, 3, _, _, 2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1], + [1, _, 3, _, _, _, _, _, _, _, 3, _, _, 3, 3, _, _, _, _, 3, 3, _, _, 1], + [1, _, 3, _, _, _, 3, 3, _, 3, _, _, _, 3, 3, _, _, _, _, 2, 3, _, _, 1], + [1, _, _, _, _, 3, _, 3, _, _, 3, _, _, _, _, _, _, _, _, _, _, _, _, 1], + [1, _, 4, _, 3, _, _, _, _, 3, _, _, 2, _, _, _, _, _, _, _, _, 2, _, 1], + [1, _, _, _, _, _, 4, _, _, _, _, _, 2, 2, _, _, _, _, _, _, 2, 2, _, 1], + [1, _, _, 4, _, _, _, _, 4, _, _, _, _, 2, 2, 2, 2, 2, 2, 2, 2, _, _, 1], + [1, _, _, _, _, _, _, _, _, _, 4, _, _, _, _, _, _, _, _, _, _, _, _, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +] + +WORLD_WIDTH = len(matrix_map[0]) * TILE +WORLD_HEIGHT = len(matrix_map) * TILE +world_map = Dict.empty(key_type=types.UniTuple(int32, 2), value_type=int32) +mini_map = set() +collision_walls = [] +for j, row in enumerate(matrix_map): + for i, char in enumerate(row): + if char: + mini_map.add((i * MAP_TILE, j * MAP_TILE)) + collision_walls.append(pygame.Rect(i * TILE, j * TILE, TILE, TILE)) + if char == 1: + world_map[(i * TILE, j * TILE)] = 1 + elif char == 2: + world_map[(i * TILE, j * TILE)] = 2 + elif char == 3: + world_map[(i * TILE, j * TILE)] = 3 + elif char == 4: + world_map[(i * TILE, j * TILE)] = 4 \ No newline at end of file diff --git a/player.py b/player.py new file mode 100644 index 0000000..12ef4ea --- /dev/null +++ b/player.py @@ -0,0 +1,100 @@ +from settings import * +import pygame +import math +from map import collision_walls + +class Player: + def __init__(self, sprites): + self.x, self.y = player_pos + self.sprites = sprites + self.angle = player_angle + self.sensitivity = 0.004 + # collision parameters + self.side = 50 + self.rect = pygame.Rect(*player_pos, self.side, self.side) + # weapon + self.shot = False + + @property + def pos(self): + return (self.x, self.y) + + @property + def collision_list(self): + return collision_walls + [pygame.Rect(*obj.pos, obj.side, obj.side) for obj in + self.sprites.list_of_objects if obj.blocked] + + def detect_collision(self, dx, dy): + next_rect = self.rect.copy() + next_rect.move_ip(dx, dy) + hit_indexes = next_rect.collidelistall(self.collision_list) + + if len(hit_indexes): + delta_x, delta_y = 0, 0 + for hit_index in hit_indexes: + hit_rect = self.collision_list[hit_index] + if dx > 0: + delta_x += next_rect.right - hit_rect.left + else: + delta_x += hit_rect.right - next_rect.left + if dy > 0: + delta_y += next_rect.bottom - hit_rect.top + else: + delta_y += hit_rect.bottom - next_rect.top + + if abs(delta_x - delta_y) < 10: + dx, dy = 0, 0 + elif delta_x > delta_y: + dy = 0 + elif delta_y > delta_x: + dx = 0 + self.x += dx + self.y += dy + + def movement(self): + self.keys_control() + self.mouse_control() + self.rect.center = self.x, self.y + self.angle %= DOUBLE_PI + + def keys_control(self): + sin_a = math.sin(self.angle) + cos_a = math.cos(self.angle) + keys = pygame.key.get_pressed() + if keys[pygame.K_ESCAPE]: + exit() + + if keys[pygame.K_w]: + dx = player_speed * cos_a + dy = player_speed * sin_a + self.detect_collision(dx, dy) + if keys[pygame.K_s]: + dx = -player_speed * cos_a + dy = -player_speed * sin_a + self.detect_collision(dx, dy) + if keys[pygame.K_a]: + dx = player_speed * sin_a + dy = -player_speed * cos_a + self.detect_collision(dx, dy) + if keys[pygame.K_d]: + dx = -player_speed * sin_a + dy = player_speed * cos_a + self.detect_collision(dx, dy) + + if keys[pygame.K_LEFT]: + self.angle -= 0.02 + if keys[pygame.K_RIGHT]: + self.angle += 0.02 + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + exit() + if event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1 and not self.shot: + self.shot = True + + def mouse_control(self): + if pygame.mouse.get_focused(): + difference = pygame.mouse.get_pos()[0] - HALF_WIDTH + pygame.mouse.set_pos((HALF_WIDTH, HALF_HEIGHT)) + self.angle += difference * self.sensitivity \ No newline at end of file