{"id":97,"date":"2025-12-12T15:14:36","date_gmt":"2025-12-12T15:14:36","guid":{"rendered":"https:\/\/theroyalscode.com\/students\/l_rankins\/?p=97"},"modified":"2025-12-12T15:14:36","modified_gmt":"2025-12-12T15:14:36","slug":"blog-15-doom-maddness","status":"publish","type":"post","link":"https:\/\/theroyalscode.com\/students\/l_rankins\/2025\/12\/12\/blog-15-doom-maddness\/","title":{"rendered":"BLOG 15: DOOM MADDNESS"},"content":{"rendered":"\n<p>Today I made Doom with a tutorial<\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pygame as pg\nimport sys\nfrom settings import *\nfrom map import *\nfrom player import *\nfrom raycasting import *\nfrom object_renderer import *\nfrom sprite_object import *\nfrom object_handler import *\nfrom weapon import *\nfrom sound import *\nfrom pathfinding import *\n\n\nclass Game:\n    def __init__(self):\n        pg.init()\n        pg.mouse.set_visible(False)\n        self.screen = pg.display.set_mode(RES)\n        pg.event.set_grab(True)\n        self.clock = pg.time.Clock()\n        self.delta_time = 1\n        self.global_trigger = False\n        self.global_event = pg.USEREVENT + 0\n        pg.time.set_timer(self.global_event, 40)\n        self.new_game()\n\n    def new_game(self):\n        self.map = Map(self)\n        self.player = Player(self)\n        self.object_renderer = ObjectRenderer(self)\n        self.raycasting = RayCasting(self)\n        self.object_handler = ObjectHandler(self)\n        self.weapon = Weapon(self)\n        self.sound = Sound(self)\n        self.pathfinding = PathFinding(self)\n        pg.mixer.music.play(-1)\n\n    def update(self):\n        self.player.update()\n        self.raycasting.update()\n        self.object_handler.update()\n        self.weapon.update()\n        pg.display.flip()\n        self.delta_time = self.clock.tick(FPS)\n        pg.display.set_caption(f'{self.clock.get_fps() :.1f}')\n\n    def draw(self):\n        # self.screen.fill('black')\n        self.object_renderer.draw()\n        self.weapon.draw()\n        # self.map.draw()\n        # self.player.draw()\n\n    def check_events(self):\n        self.global_trigger = False\n        for event in pg.event.get():\n            if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):\n                pg.quit()\n                sys.exit()\n            elif event.type == self.global_event:\n                self.global_trigger = True\n            self.player.single_fire_event(event)\n\n    def run(self):\n        while True:\n            self.check_events()\n            self.update()\n            self.draw()\n\n\nif __name__ == '__main__':\n    game = Game()\n    game.run()\n<\/code><\/pre>\n\n\n\n<p><strong>This one runs the game and handles all the functions<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pygame as pg\n\n_ = False\nmini_map = &#91;\n    &#91;1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n    &#91;1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;1, _, _, 3, 3, 3, 3, _, _, _, 2, 2, 2, _, _, 1],\n    &#91;1, _, _, _, _, _, 4, _, _, _, _, _, 2, _, _, 1],\n    &#91;1, _, _, _, _, _, 4, _, _, _, _, _, 2, _, _, 1],\n    &#91;1, _, _, 3, 3, 3, 3, _, _, _, _, _, _, _, _, 1],\n    &#91;1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;1, _, _, _, 4, _, _, _, 4, _, _, _, _, _, _, 1],\n    &#91;1, 1, 1, 3, 1, 3, 1, 1, 1, 3, _, _, 3, 1, 1, 1],\n    &#91;1, 1, 1, 1, 1, 1, 1, 1, 1, 3, _, _, 3, 1, 1, 1],\n    &#91;1, 1, 1, 1, 1, 1, 1, 1, 1, 3, _, _, 3, 1, 1, 1],\n    &#91;1, 1, 3, 1, 1, 1, 1, 1, 1, 3, _, _, 3, 1, 1, 1],\n    &#91;1, 4, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;1, _, _, 2, _, _, _, _, _, 3, 4, _, 4, 3, _, 1],\n    &#91;1, _, _, 5, _, _, _, _, _, _, 3, _, 3, _, _, 1],\n    &#91;1, _, _, 2, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 1],\n    &#91;1, 4, _, _, _, _, _, _, 4, _, _, 4, _, _, _, 1],\n    &#91;1, 1, 3, 3, _, _, 3, 3, 1, 3, 3, 1, 3, 1, 1, 1],\n    &#91;1, 1, 1, 3, _, _, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1],\n    &#91;1, 3, 3, 4, _, _, 4, 3, 3, 3, 3, 3, 3, 3, 3, 1],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 3],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 3],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 3],\n    &#91;3, _, _, 5, _, _, _, 5, _, _, _, 5, _, _, _, 3],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 3],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 3],\n    &#91;3, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 3],\n    &#91;3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],\n]\n\n\nclass Map:\n    def __init__(self, game):\n        self.game = game\n        self.mini_map = mini_map\n        self.world_map = {}\n        self.rows = len(self.mini_map)\n        self.cols = len(self.mini_map&#91;0])\n        self.get_map()\n\n    def get_map(self):\n        for j, row in enumerate(self.mini_map):\n            for i, value in enumerate(row):\n                if value:\n                    self.world_map&#91;(i, j)] = value\n\n    def draw(self):\n        &#91;pg.draw.rect(self.game.screen, 'darkgray', (pos&#91;0] * 100, pos&#91;1] * 100, 100, 100), 2)\n         for pos in self.world_map]<\/code><\/pre>\n\n\n\n<p><strong>This one draws the map and mini map<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from sprite_object import *\nfrom random import randint, random\n\n\nclass NPC(AnimatedSprite):\n    def __init__(self, game, path='resources\/sprites\/npc\/soldier\/0.png', pos=(10.5, 5.5),\n                 scale=0.6, shift=0.38, animation_time=180):\n        super().__init__(game, path, pos, scale, shift, animation_time)\n        self.attack_images = self.get_images(self.path + '\/attack')\n        self.death_images = self.get_images(self.path + '\/death')\n        self.idle_images = self.get_images(self.path + '\/idle')\n        self.pain_images = self.get_images(self.path + '\/pain')\n        self.walk_images = self.get_images(self.path + '\/walk')\n\n        self.attack_dist = randint(3, 6)\n        self.speed = 0.03\n        self.size = 20\n        self.health = 100\n        self.attack_damage = 10\n        self.accuracy = 0.15\n        self.alive = True\n        self.pain = False\n        self.ray_cast_value = False\n        self.frame_counter = 0\n        self.player_search_trigger = False\n\n    def update(self):\n        self.check_animation_time()\n        self.get_sprite()\n        self.run_logic()\n        # self.draw_ray_cast()\n\n    def check_wall(self, x, y):\n        return (x, y) not in self.game.map.world_map\n\n    def check_wall_collision(self, dx, dy):\n        if self.check_wall(int(self.x + dx * self.size), int(self.y)):\n            self.x += dx\n        if self.check_wall(int(self.x), int(self.y + dy * self.size)):\n            self.y += dy\n\n    def movement(self):\n        next_pos = self.game.pathfinding.get_path(self.map_pos, self.game.player.map_pos)\n        next_x, next_y = next_pos\n\n        # pg.draw.rect(self.game.screen, 'blue', (100 * next_x, 100 * next_y, 100, 100))\n        if next_pos not in self.game.object_handler.npc_positions:\n            angle = math.atan2(next_y + 0.5 - self.y, next_x + 0.5 - self.x)\n            dx = math.cos(angle) * self.speed\n            dy = math.sin(angle) * self.speed\n            self.check_wall_collision(dx, dy)\n\n    def attack(self):\n        if self.animation_trigger:\n            self.game.sound.npc_shot.play()\n            if random() &lt; self.accuracy:\n                self.game.player.get_damage(self.attack_damage)\n\n    def animate_death(self):\n        if not self.alive:\n            if self.game.global_trigger and self.frame_counter &lt; len(self.death_images) - 1:\n                self.death_images.rotate(-1)\n                self.image = self.death_images&#91;0]\n                self.frame_counter += 1\n\n    def animate_pain(self):\n        self.animate(self.pain_images)\n        if self.animation_trigger:\n            self.pain = False\n\n    def check_hit_in_npc(self):\n        if self.ray_cast_value and self.game.player.shot:\n            if HALF_WIDTH - self.sprite_half_width &lt; self.screen_x &lt; HALF_WIDTH + self.sprite_half_width:\n                self.game.sound.npc_pain.play()\n                self.game.player.shot = False\n                self.pain = True\n                self.health -= self.game.weapon.damage\n                self.check_health()\n\n    def check_health(self):\n        if self.health &lt; 1:\n            self.alive = False\n            self.game.sound.npc_death.play()\n\n    def run_logic(self):\n        if self.alive:\n            self.ray_cast_value = self.ray_cast_player_npc()\n            self.check_hit_in_npc()\n\n            if self.pain:\n                self.animate_pain()\n\n            elif self.ray_cast_value:\n                self.player_search_trigger = True\n\n                if self.dist &lt; self.attack_dist:\n                    self.animate(self.attack_images)\n                    self.attack()\n                else:\n                    self.animate(self.walk_images)\n                    self.movement()\n\n            elif self.player_search_trigger:\n                self.animate(self.walk_images)\n                self.movement()\n\n            else:\n                self.animate(self.idle_images)\n        else:\n            self.animate_death()\n\n    @property\n    def map_pos(self):\n        return int(self.x), int(self.y)\n\n    def ray_cast_player_npc(self):\n        if self.game.player.map_pos == self.map_pos:\n            return True\n\n        wall_dist_v, wall_dist_h = 0, 0\n        player_dist_v, player_dist_h = 0, 0\n\n        ox, oy = self.game.player.pos\n        x_map, y_map = self.game.player.map_pos\n\n        ray_angle = self.theta\n\n        sin_a = math.sin(ray_angle)\n        cos_a = math.cos(ray_angle)\n\n        # horizontals\n        y_hor, dy = (y_map + 1, 1) if sin_a > 0 else (y_map - 1e-6, -1)\n\n        depth_hor = (y_hor - oy) \/ sin_a\n        x_hor = ox + depth_hor * cos_a\n\n        delta_depth = dy \/ sin_a\n        dx = delta_depth * cos_a\n\n        for i in range(MAX_DEPTH):\n            tile_hor = int(x_hor), int(y_hor)\n            if tile_hor == self.map_pos:\n                player_dist_h = depth_hor\n                break\n            if tile_hor in self.game.map.world_map:\n                wall_dist_h = depth_hor\n                break\n            x_hor += dx\n            y_hor += dy\n            depth_hor += delta_depth\n\n        # verticals\n        x_vert, dx = (x_map + 1, 1) if cos_a > 0 else (x_map - 1e-6, -1)\n\n        depth_vert = (x_vert - ox) \/ cos_a\n        y_vert = oy + depth_vert * sin_a\n\n        delta_depth = dx \/ cos_a\n        dy = delta_depth * sin_a\n\n        for i in range(MAX_DEPTH):\n            tile_vert = int(x_vert), int(y_vert)\n            if tile_vert == self.map_pos:\n                player_dist_v = depth_vert\n                break\n            if tile_vert in self.game.map.world_map:\n                wall_dist_v = depth_vert\n                break\n            x_vert += dx\n            y_vert += dy\n            depth_vert += delta_depth\n\n        player_dist = max(player_dist_v, player_dist_h)\n        wall_dist = max(wall_dist_v, wall_dist_h)\n\n        if 0 &lt; player_dist &lt; wall_dist or not wall_dist:\n            return True\n        return False\n\n    def draw_ray_cast(self):\n        pg.draw.circle(self.game.screen, 'red', (100 * self.x, 100 * self.y), 15)\n        if self.ray_cast_player_npc():\n            pg.draw.line(self.game.screen, 'orange', (100 * self.game.player.x, 100 * self.game.player.y),\n                         (100 * self.x, 100 * self.y), 2)\n\n\nclass SoldierNPC(NPC):\n    def __init__(self, game, path='resources\/sprites\/npc\/soldier\/0.png', pos=(10.5, 5.5),\n                 scale=0.6, shift=0.38, animation_time=180):\n        super().__init__(game, path, pos, scale, shift, animation_time)\n\nclass CacoDemonNPC(NPC):\n    def __init__(self, game, path='resources\/sprites\/npc\/caco_demon\/0.png', pos=(10.5, 6.5),\n                 scale=0.7, shift=0.27, animation_time=250):\n        super().__init__(game, path, pos, scale, shift, animation_time)\n        self.attack_dist = 1.0\n        self.health = 150\n        self.attack_damage = 25\n        self.speed = 0.05\n        self.accuracy = 0.35\n\nclass CyberDemonNPC(NPC):\n    def __init__(self, game, path='resources\/sprites\/npc\/cyber_demon\/0.png', pos=(11.5, 6.0),\n                 scale=1.0, shift=0.04, animation_time=210):\n        super().__init__(game, path, pos, scale, shift, animation_time)\n        self.attack_dist = 6\n        self.health = 350\n        self.attack_damage = 15\n        self.speed = 0.055\n        self.accuracy = 0.25\n<\/code><\/pre>\n\n\n\n<p><strong>This one spawns the enemy&#8217;s and handles their AI<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from sprite_object import *\nfrom npc import *\nfrom random import choices, randrange\n\n\nclass ObjectHandler:\n    def __init__(self, game):\n        self.game = game\n        self.sprite_list = &#91;]\n        self.npc_list = &#91;]\n        self.npc_sprite_path = 'resources\/sprites\/npc\/'\n        self.static_sprite_path = 'resources\/sprites\/static_sprites\/'\n        self.anim_sprite_path = 'resources\/sprites\/animated_sprites\/'\n        add_sprite = self.add_sprite\n        add_npc = self.add_npc\n        self.npc_positions = {}\n\n        # spawn npc\n        self.enemies = 20  # npc count\n        self.npc_types = &#91;SoldierNPC, CacoDemonNPC, CyberDemonNPC]\n        self.weights = &#91;70, 20, 10]\n        self.restricted_area = {(i, j) for i in range(10) for j in range(10)}\n        self.spawn_npc()\n\n        # sprite map\n        add_sprite(AnimatedSprite(game))\n        add_sprite(AnimatedSprite(game, pos=(1.5, 1.5)))\n        add_sprite(AnimatedSprite(game, pos=(1.5, 7.5)))\n        add_sprite(AnimatedSprite(game, pos=(5.5, 3.25)))\n        add_sprite(AnimatedSprite(game, pos=(5.5, 4.75)))\n        add_sprite(AnimatedSprite(game, pos=(7.5, 2.5)))\n        add_sprite(AnimatedSprite(game, pos=(7.5, 5.5)))\n        add_sprite(AnimatedSprite(game, pos=(14.5, 1.5)))\n        add_sprite(AnimatedSprite(game, pos=(14.5, 4.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(14.5, 5.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(14.5, 7.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(12.5, 7.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(9.5, 7.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(14.5, 12.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(9.5, 20.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(10.5, 20.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(3.5, 14.5)))\n        add_sprite(AnimatedSprite(game, path=self.anim_sprite_path + 'red_light\/0.png', pos=(3.5, 18.5)))\n        add_sprite(AnimatedSprite(game, pos=(14.5, 24.5)))\n        add_sprite(AnimatedSprite(game, pos=(14.5, 30.5)))\n        add_sprite(AnimatedSprite(game, pos=(1.5, 30.5)))\n        add_sprite(AnimatedSprite(game, pos=(1.5, 24.5)))\n\n        # npc map\n        # add_npc(SoldierNPC(game, pos=(11.0, 19.0)))\n        # add_npc(SoldierNPC(game, pos=(11.5, 4.5)))\n        # add_npc(SoldierNPC(game, pos=(13.5, 6.5)))\n        # add_npc(SoldierNPC(game, pos=(2.0, 20.0)))\n        # add_npc(SoldierNPC(game, pos=(4.0, 29.0)))\n        # add_npc(CacoDemonNPC(game, pos=(5.5, 14.5)))\n        # add_npc(CacoDemonNPC(game, pos=(5.5, 16.5)))\n        # add_npc(CyberDemonNPC(game, pos=(14.5, 25.5)))\n\n    def spawn_npc(self):\n        for i in range(self.enemies):\n                npc = choices(self.npc_types, self.weights)&#91;0]\n                pos = x, y = randrange(self.game.map.cols), randrange(self.game.map.rows)\n                while (pos in self.game.map.world_map) or (pos in self.restricted_area):\n                    pos = x, y = randrange(self.game.map.cols), randrange(self.game.map.rows)\n                self.add_npc(npc(self.game, pos=(x + 0.5, y + 0.5)))\n\n    def check_win(self):\n        if not len(self.npc_positions):\n            self.game.object_renderer.win()\n            pg.display.flip()\n            pg.time.delay(1500)\n            self.game.new_game()\n\n    def update(self):\n        self.npc_positions = {npc.map_pos for npc in self.npc_list if npc.alive}\n        &#91;sprite.update() for sprite in self.sprite_list]\n        &#91;npc.update() for npc in self.npc_list]\n        self.check_win()\n\n    def add_npc(self, npc):\n        self.npc_list.append(npc)\n\n    def add_sprite(self, sprite):\n        self.sprite_list.append(sprite)<\/code><\/pre>\n\n\n\n<p><strong>This one draws the various objects in the map<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pygame as pg\nfrom settings import *\n\n\nclass ObjectRenderer:\n    def __init__(self, game):\n        self.game = game\n        self.screen = game.screen\n        self.wall_textures = self.load_wall_textures()\n        self.sky_image = self.get_texture('resources\/textures\/sky.png', (WIDTH, HALF_HEIGHT))\n        self.sky_offset = 0\n        self.blood_screen = self.get_texture('resources\/textures\/blood_screen.png', RES)\n        self.digit_size = 90\n        self.digit_images = &#91;self.get_texture(f'resources\/textures\/digits\/{i}.png', &#91;self.digit_size] * 2)\n                             for i in range(11)]\n        self.digits = dict(zip(map(str, range(11)), self.digit_images))\n        self.game_over_image = self.get_texture('resources\/textures\/game_over.png', RES)\n        self.win_image = self.get_texture('resources\/textures\/win.png', RES)\n\n    def draw(self):\n        self.draw_background()\n        self.render_game_objects()\n        self.draw_player_health()\n\n    def win(self):\n        self.screen.blit(self.win_image, (0, 0))\n\n    def game_over(self):\n        self.screen.blit(self.game_over_image, (0, 0))\n\n    def draw_player_health(self):\n        health = str(self.game.player.health)\n        for i, char in enumerate(health):\n            self.screen.blit(self.digits&#91;char], (i * self.digit_size, 0))\n        self.screen.blit(self.digits&#91;'10'], ((i + 1) * self.digit_size, 0))\n\n    def player_damage(self):\n        self.screen.blit(self.blood_screen, (0, 0))\n\n    def draw_background(self):\n        self.sky_offset = (self.sky_offset + 4.5 * self.game.player.rel) % WIDTH\n        self.screen.blit(self.sky_image, (-self.sky_offset, 0))\n        self.screen.blit(self.sky_image, (-self.sky_offset + WIDTH, 0))\n        # floor\n        pg.draw.rect(self.screen, FLOOR_COLOR, (0, HALF_HEIGHT, WIDTH, HEIGHT))\n\n    def render_game_objects(self):\n        list_objects = sorted(self.game.raycasting.objects_to_render, key=lambda t: t&#91;0], reverse=True)\n        for depth, image, pos in list_objects:\n            self.screen.blit(image, pos)\n\n    @staticmethod\n    def get_texture(path, res=(TEXTURE_SIZE, TEXTURE_SIZE)):\n        texture = pg.image.load(path).convert_alpha()\n        return pg.transform.scale(texture, res)\n\n    def load_wall_textures(self):\n        return {\n            1: self.get_texture('resources\/textures\/1.png'),\n            2: self.get_texture('resources\/textures\/2.png'),\n            3: self.get_texture('resources\/textures\/3.png'),\n            4: self.get_texture('resources\/textures\/4.png'),\n            5: self.get_texture('resources\/textures\/5.png'),\n        }<\/code><\/pre>\n\n\n\n<p><strong>this one renders the objects<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from collections import deque\nfrom functools import lru_cache\n\n\nclass PathFinding:\n    def __init__(self, game):\n        self.game = game\n        self.map = game.map.mini_map\n        self.ways = &#91;-1, 0], &#91;0, -1], &#91;1, 0], &#91;0, 1], &#91;-1, -1], &#91;1, -1], &#91;1, 1], &#91;-1, 1]\n        self.graph = {}\n        self.get_graph()\n\n    @lru_cache\n    def get_path(self, start, goal):\n        self.visited = self.bfs(start, goal, self.graph)\n        path = &#91;goal]\n        step = self.visited.get(goal, start)\n\n        while step and step != start:\n            path.append(step)\n            step = self.visited&#91;step]\n        return path&#91;-1]\n\n    def bfs(self, start, goal, graph):\n        queue = deque(&#91;start])\n        visited = {start: None}\n\n        while queue:\n            cur_node = queue.popleft()\n            if cur_node == goal:\n                break\n            next_nodes = graph&#91;cur_node]\n\n            for next_node in next_nodes:\n                if next_node not in visited and next_node not in self.game.object_handler.npc_positions:\n                    queue.append(next_node)\n                    visited&#91;next_node] = cur_node\n        return visited\n\n    def get_next_nodes(self, x, y):\n        return &#91;(x + dx, y + dy) for dx, dy in self.ways if (x + dx, y + dy) not in self.game.map.world_map]\n\n    def get_graph(self):\n        for y, row in enumerate(self.map):\n            for x, col in enumerate(row):\n                if not col:\n                    self.graph&#91;(x, y)] = self.graph.get((x, y), &#91;]) + self.get_next_nodes(x, y)<\/code><\/pre>\n\n\n\n<p><strong>this one is for the AIs path finding<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>from settings import *\nimport pygame as pg\nimport math\n\n\nclass Player:\n    def __init__(self, game):\n        self.game = game\n        self.x, self.y = PLAYER_POS\n        self.angle = PLAYER_ANGLE\n        self.shot = False\n        self.health = PLAYER_MAX_HEALTH\n        self.rel = 0\n        self.health_recovery_delay = 700\n        self.time_prev = pg.time.get_ticks()\n        # diagonal movement correction\n        self.diag_move_corr = 1 \/ math.sqrt(2)\n\n    def recover_health(self):\n        if self.check_health_recovery_delay() and self.health &lt; PLAYER_MAX_HEALTH:\n            self.health += 1\n\n    def check_health_recovery_delay(self):\n        time_now = pg.time.get_ticks()\n        if time_now - self.time_prev > self.health_recovery_delay:\n            self.time_prev = time_now\n            return True\n\n    def check_game_over(self):\n        if self.health &lt; 1:\n            self.game.object_renderer.game_over()\n            pg.display.flip()\n            pg.time.delay(1500)\n            self.game.new_game()\n\n    def get_damage(self, damage):\n        self.health -= damage\n        self.game.object_renderer.player_damage()\n        self.game.sound.player_pain.play()\n        self.check_game_over()\n\n    def single_fire_event(self, event):\n        if event.type == pg.MOUSEBUTTONDOWN:\n            if event.button == 1 and not self.shot and not self.game.weapon.reloading:\n                self.game.sound.shotgun.play()\n                self.shot = True\n                self.game.weapon.reloading = True\n\n    def movement(self):\n        sin_a = math.sin(self.angle)\n        cos_a = math.cos(self.angle)\n        dx, dy = 0, 0\n        speed = PLAYER_SPEED * self.game.delta_time\n        speed_sin = speed * sin_a\n        speed_cos = speed * cos_a\n\n        keys = pg.key.get_pressed()\n        num_key_pressed = -1\n        if keys&#91;pg.K_w]:\n            num_key_pressed += 1\n            dx += speed_cos\n            dy += speed_sin\n        if keys&#91;pg.K_s]:\n            num_key_pressed += 1\n            dx += -speed_cos\n            dy += -speed_sin\n        if keys&#91;pg.K_a]:\n            num_key_pressed += 1\n            dx += speed_sin\n            dy += -speed_cos\n        if keys&#91;pg.K_d]:\n            num_key_pressed += 1\n            dx += -speed_sin\n            dy += speed_cos\n\n        # diag move correction\n        if num_key_pressed:\n            dx *= self.diag_move_corr\n            dy *= self.diag_move_corr\n\n        self.check_wall_collision(dx, dy)\n\n        # if keys&#91;pg.K_LEFT]:\n        #     self.angle -= PLAYER_ROT_SPEED * self.game.delta_time\n        # if keys&#91;pg.K_RIGHT]:\n        #     self.angle += PLAYER_ROT_SPEED * self.game.delta_time\n        self.angle %= math.tau\n\n    def check_wall(self, x, y):\n        return (x, y) not in self.game.map.world_map\n\n    def check_wall_collision(self, dx, dy):\n        scale = PLAYER_SIZE_SCALE \/ self.game.delta_time\n        if self.check_wall(int(self.x + dx * scale), int(self.y)):\n            self.x += dx\n        if self.check_wall(int(self.x), int(self.y + dy * scale)):\n            self.y += dy\n\n    def draw(self):\n        pg.draw.line(self.game.screen, 'yellow', (self.x * 100, self.y * 100),\n                    (self.x * 100 + WIDTH * math.cos(self.angle),\n                     self.y * 100 + WIDTH * math. sin(self.angle)), 2)\n        pg.draw.circle(self.game.screen, 'green', (self.x * 100, self.y * 100), 15)\n\n    def mouse_control(self):\n        mx, my = pg.mouse.get_pos()\n        if mx &lt; MOUSE_BORDER_LEFT or mx > MOUSE_BORDER_RIGHT:\n            pg.mouse.set_pos(&#91;HALF_WIDTH, HALF_HEIGHT])\n        self.rel = pg.mouse.get_rel()&#91;0]\n        self.rel = max(-MOUSE_MAX_REL, min(MOUSE_MAX_REL, self.rel))\n        self.angle += self.rel * MOUSE_SENSITIVITY * self.game.delta_time\n\n    def update(self):\n        self.movement()\n        self.mouse_control()\n        self.recover_health()\n\n    @property\n    def pos(self):\n        return self.x, self.y\n\n    @property\n    def map_pos(self):\n        return int(self.x), int(self.y)<\/code><\/pre>\n\n\n\n<p><strong>this one handles the player character and how they move<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pygame as pg\nimport math\nfrom settings import *\n\n\nclass RayCasting:\n    def __init__(self, game):\n        self.game = game\n        self.ray_casting_result = &#91;]\n        self.objects_to_render = &#91;]\n        self.textures = self.game.object_renderer.wall_textures\n\n    def get_objects_to_render(self):\n        self.objects_to_render = &#91;]\n        for ray, values in enumerate(self.ray_casting_result):\n            depth, proj_height, texture, offset = values\n\n            if proj_height &lt; HEIGHT:\n                wall_column = self.textures&#91;texture].subsurface(\n                    offset * (TEXTURE_SIZE - SCALE), 0, SCALE, TEXTURE_SIZE\n                )\n                wall_column = pg.transform.scale(wall_column, (SCALE, proj_height))\n                wall_pos = (ray * SCALE, HALF_HEIGHT - proj_height \/\/ 2)\n            else:\n                texture_height = TEXTURE_SIZE * HEIGHT \/ proj_height\n                wall_column = self.textures&#91;texture].subsurface(\n                    offset * (TEXTURE_SIZE - SCALE), HALF_TEXTURE_SIZE - texture_height \/\/ 2,\n                    SCALE, texture_height\n                )\n                wall_column = pg.transform.scale(wall_column, (SCALE, HEIGHT))\n                wall_pos = (ray * SCALE, 0)\n\n            self.objects_to_render.append((depth, wall_column, wall_pos))\n\n    def ray_cast(self):\n        self.ray_casting_result = &#91;]\n        texture_vert, texture_hor = 1, 1\n        ox, oy = self.game.player.pos\n        x_map, y_map = self.game.player.map_pos\n\n        ray_angle = self.game.player.angle - HALF_FOV + 0.0001\n        for ray in range(NUM_RAYS):\n            sin_a = math.sin(ray_angle)\n            cos_a = math.cos(ray_angle)\n\n            # horizontals\n            y_hor, dy = (y_map + 1, 1) if sin_a > 0 else (y_map - 1e-6, -1)\n\n            depth_hor = (y_hor - oy) \/ sin_a\n            x_hor = ox + depth_hor * cos_a\n\n            delta_depth = dy \/ sin_a\n            dx = delta_depth * cos_a\n\n            for i in range(MAX_DEPTH):\n                tile_hor = int(x_hor), int(y_hor)\n                if tile_hor in self.game.map.world_map:\n                    texture_hor = self.game.map.world_map&#91;tile_hor]\n                    break\n                x_hor += dx\n                y_hor += dy\n                depth_hor += delta_depth\n\n            # verticals\n            x_vert, dx = (x_map + 1, 1) if cos_a > 0 else (x_map - 1e-6, -1)\n\n            depth_vert = (x_vert - ox) \/ cos_a\n            y_vert = oy + depth_vert * sin_a\n\n            delta_depth = dx \/ cos_a\n            dy = delta_depth * sin_a\n\n            for i in range(MAX_DEPTH):\n                tile_vert = int(x_vert), int(y_vert)\n                if tile_vert in self.game.map.world_map:\n                    texture_vert = self.game.map.world_map&#91;tile_vert]\n                    break\n                x_vert += dx\n                y_vert += dy\n                depth_vert += delta_depth\n\n            # depth, texture offset\n            if depth_vert &lt; depth_hor:\n                depth, texture = depth_vert, texture_vert\n                y_vert %= 1\n                offset = y_vert if cos_a > 0 else (1 - y_vert)\n            else:\n                depth, texture = depth_hor, texture_hor\n                x_hor %= 1\n                offset = (1 - x_hor) if sin_a > 0 else x_hor\n\n            # remove fishbowl effect\n            depth *= math.cos(self.game.player.angle - ray_angle)\n\n            # projection\n            proj_height = SCREEN_DIST \/ (depth + 0.0001)\n\n            # ray casting result\n            self.ray_casting_result.append((depth, proj_height, texture, offset))\n\n            ray_angle += DELTA_ANGLE\n\n    def update(self):\n        self.ray_cast()\n        self.get_objects_to_render()<\/code><\/pre>\n\n\n\n<p><strong>this one is the ray caster which makes the map 3Dish<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import math\n\n# game settings\nRES = WIDTH, HEIGHT = 1600, 900\n# RES = WIDTH, HEIGHT = 1920, 1080\nHALF_WIDTH = WIDTH \/\/ 2\nHALF_HEIGHT = HEIGHT \/\/ 2\nFPS = 0\n\nPLAYER_POS = 1.5, 5  # mini_map\nPLAYER_ANGLE = 0\nPLAYER_SPEED = 0.004\nPLAYER_ROT_SPEED = 0.002\nPLAYER_SIZE_SCALE = 60\nPLAYER_MAX_HEALTH = 100\n\nMOUSE_SENSITIVITY = 0.0003\nMOUSE_MAX_REL = 40\nMOUSE_BORDER_LEFT = 100\nMOUSE_BORDER_RIGHT = WIDTH - MOUSE_BORDER_LEFT\n\nFLOOR_COLOR = (30, 30, 30)\n\nFOV = math.pi \/ 3\nHALF_FOV = FOV \/ 2\nNUM_RAYS = WIDTH \/\/ 2\nHALF_NUM_RAYS = NUM_RAYS \/\/ 2\nDELTA_ANGLE = FOV \/ NUM_RAYS\nMAX_DEPTH = 20\n\nSCREEN_DIST = HALF_WIDTH \/ math.tan(HALF_FOV)\nSCALE = WIDTH \/\/ NUM_RAYS\n\nTEXTURE_SIZE = 256\nHALF_TEXTURE_SIZE = TEXTURE_SIZE \/\/ 2<\/code><\/pre>\n\n\n\n<p><strong>This is the settings<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pygame as pg\n\n\nclass Sound:\n    def __init__(self, game):\n        self.game = game\n        pg.mixer.init()\n        self.path = 'resources\/sound\/'\n        self.shotgun = pg.mixer.Sound(self.path + 'shotgun.wav')\n        self.npc_pain = pg.mixer.Sound(self.path + 'npc_pain.wav')\n        self.npc_death = pg.mixer.Sound(self.path + 'npc_death.wav')\n        self.npc_shot = pg.mixer.Sound(self.path + 'npc_attack.wav')\n        self.npc_shot.set_volume(0.2)\n        self.player_pain = pg.mixer.Sound(self.path + 'player_pain.wav')\n        self.theme = pg.mixer.music.load(self.path + 'theme.mp3')\n        pg.mixer.music.set_volume(0.3)<\/code><\/pre>\n\n\n\n<p><strong>This is the sound<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import pygame as pg\nfrom settings import *\nimport os\nfrom collections import deque\n\n\nclass SpriteObject:\n    def __init__(self, game, path='resources\/sprites\/static_sprites\/candlebra.png',\n                 pos=(10.5, 3.5), scale=0.7, shift=0.27):\n        self.game = game\n        self.player = game.player\n        self.x, self.y = pos\n        self.image = pg.image.load(path).convert_alpha()\n        self.IMAGE_WIDTH = self.image.get_width()\n        self.IMAGE_HALF_WIDTH = self.image.get_width() \/\/ 2\n        self.IMAGE_RATIO = self.IMAGE_WIDTH \/ self.image.get_height()\n        self.dx, self.dy, self.theta, self.screen_x, self.dist, self.norm_dist = 0, 0, 0, 0, 1, 1\n        self.sprite_half_width = 0\n        self.SPRITE_SCALE = scale\n        self.SPRITE_HEIGHT_SHIFT = shift\n\n    def get_sprite_projection(self):\n        proj = SCREEN_DIST \/ self.norm_dist * self.SPRITE_SCALE\n        proj_width, proj_height = proj * self.IMAGE_RATIO, proj\n\n        image = pg.transform.scale(self.image, (proj_width, proj_height))\n\n        self.sprite_half_width = proj_width \/\/ 2\n        height_shift = proj_height * self.SPRITE_HEIGHT_SHIFT\n        pos = self.screen_x - self.sprite_half_width, HALF_HEIGHT - proj_height \/\/ 2 + height_shift\n\n        self.game.raycasting.objects_to_render.append((self.norm_dist, image, pos))\n\n    def get_sprite(self):\n        dx = self.x - self.player.x\n        dy = self.y - self.player.y\n        self.dx, self.dy = dx, dy\n        self.theta = math.atan2(dy, dx)\n\n        delta = self.theta - self.player.angle\n        if (dx > 0 and self.player.angle > math.pi) or (dx &lt; 0 and dy &lt; 0):\n            delta += math.tau\n\n        delta_rays = delta \/ DELTA_ANGLE\n        self.screen_x = (HALF_NUM_RAYS + delta_rays) * SCALE\n\n        self.dist = math.hypot(dx, dy)\n        self.norm_dist = self.dist * math.cos(delta)\n        if -self.IMAGE_HALF_WIDTH &lt; self.screen_x &lt; (WIDTH + self.IMAGE_HALF_WIDTH) and self.norm_dist > 0.5:\n            self.get_sprite_projection()\n\n    def update(self):\n        self.get_sprite()\n\n\nclass AnimatedSprite(SpriteObject):\n    def __init__(self, game, path='resources\/sprites\/animated_sprites\/green_light\/0.png',\n                 pos=(11.5, 3.5), scale=0.8, shift=0.16, animation_time=120):\n        super().__init__(game, path, pos, scale, shift)\n        self.animation_time = animation_time\n        self.path = path.rsplit('\/', 1)&#91;0]\n        self.images = self.get_images(self.path)\n        self.animation_time_prev = pg.time.get_ticks()\n        self.animation_trigger = False\n\n    def update(self):\n        super().update()\n        self.check_animation_time()\n        self.animate(self.images)\n\n    def animate(self, images):\n        if self.animation_trigger:\n            images.rotate(-1)\n            self.image = images&#91;0]\n\n    def check_animation_time(self):\n        self.animation_trigger = False\n        time_now = pg.time.get_ticks()\n        if time_now - self.animation_time_prev > self.animation_time:\n            self.animation_time_prev = time_now\n            self.animation_trigger = True\n\n    def get_images(self, path):\n        images = deque()\n        for file_name in os.listdir(path):\n            if os.path.isfile(os.path.join(path, file_name)):\n                img = pg.image.load(path + '\/' + file_name).convert_alpha()\n                images.append(img)\n        return images\n<\/code><\/pre>\n\n\n\n<p><strong>This handles what the npcs and such looks like<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Weapon(AnimatedSprite):\n    def __init__(self, game, path='resources\/sprites\/weapon\/shotgun\/0.png', scale=0.4, animation_time=90):\n        super().__init__(game=game, path=path, scale=scale, animation_time=animation_time)\n        self.images = deque(\n            &#91;pg.transform.smoothscale(img, (self.image.get_width() * scale, self.image.get_height() * scale))\n             for img in self.images])\n        self.weapon_pos = (HALF_WIDTH - self.images&#91;0].get_width() \/\/ 2, HEIGHT - self.images&#91;0].get_height())\n        self.reloading = False\n        self.num_images = len(self.images)\n        self.frame_counter = 0\n        self.damage = 50\n\n    def animate_shot(self):\n        if self.reloading:\n            self.game.player.shot = False\n            if self.animation_trigger:\n                self.images.rotate(-1)\n                self.image = self.images&#91;0]\n                self.frame_counter += 1\n                if self.frame_counter == self.num_images:\n                    self.reloading = False\n                    self.frame_counter = 0\n\n    def draw(self):\n        self.game.screen.blit(self.images&#91;0], self.weapon_pos)\n\n    def update(self):\n        self.check_animation_time()\n        self.animate_shot()<\/code><\/pre>\n\n\n\n<p><strong>This one is for the gun<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today I made Doom with a tutorial This one runs the game and handles all the functions This one draws [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[1],"tags":[],"class_list":["post-97","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/posts\/97","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/comments?post=97"}],"version-history":[{"count":1,"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/posts\/97\/revisions"}],"predecessor-version":[{"id":98,"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/posts\/97\/revisions\/98"}],"wp:attachment":[{"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/media?parent=97"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/categories?post=97"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/theroyalscode.com\/students\/l_rankins\/wp-json\/wp\/v2\/tags?post=97"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}