├── README.md ├── Tower_Defense-main.zip ├── run.py ├── requirements.txt ├── enemies ├── scorpion.py ├── wizard.py ├── club.py ├── sword.py └── enemy.py ├── main_menu └── main_menu.py ├── make_requirements_and_scaffold.py ├── towers ├── supportTower.py ├── tower.py └── archerTower.py └── menu └── menu.py /README.md: -------------------------------------------------------------------------------- 1 | # Tower_Defense 2 | Tower defense game done with pygame 3 | -------------------------------------------------------------------------------- /Tower_Defense-main.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Otutu11/Tower_Defense/HEAD/Tower_Defense-main.zip -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | if __name__ == "__main__": 4 | pygame.init() 5 | win = pygame.display.set_mode((1350, 700)) 6 | from main_menu.main_menu import MainMenu 7 | mainMenu = MainMenu(win) 8 | mainMenu.run() 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Runtime 2 | pygame>=2.5.0 3 | numpy>=1.24 4 | pyyaml>=6.0.1 5 | loguru>=0.7.2 6 | 7 | # Optional (uncomment if you use them) 8 | # pillow>=10.0 9 | # pysdl2>=0.9.16 10 | # screeninfo>=0.8.1 11 | 12 | # Dev tools (optional) 13 | # black>=24.0 14 | # isort>=5.13 15 | # mypy>=1.10 16 | # pytest>=8.0 17 | -------------------------------------------------------------------------------- /enemies/scorpion.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | from .enemy import Enemy 4 | 5 | imgs = [] 6 | for x in range(20): 7 | add_str = str(x) 8 | if x < 10: 9 | add_str = "0" + add_str 10 | imgs.append(pygame.transform.scale( 11 | pygame.image.load(os.path.join("game_assets/enemies/1", "1_enemies_1_run_0" + add_str + ".png")), 12 | (64, 64))) 13 | 14 | 15 | class Scorpion(Enemy): 16 | def __init__(self): 17 | super().__init__() 18 | self.name = "scorpion" 19 | self.money = 1 20 | self.max_health = 1 21 | self.health = self.max_health 22 | self.imgs = imgs[:] 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /enemies/wizard.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | from .enemy import Enemy 4 | 5 | imgs = [] 6 | 7 | 8 | for x in range(20): 9 | add_str = str(x) 10 | if x < 10: 11 | add_str = "0" + add_str 12 | imgs.append(pygame.transform.scale( 13 | pygame.image.load(os.path.join("game_assets/enemies/2", "2_enemies_1_run_0" + add_str + ".png")), 14 | (64, 64))) 15 | 16 | 17 | class Wizard(Enemy): 18 | def __init__(self): 19 | super().__init__() 20 | self.name = "wizard" 21 | self.money = 3 22 | self.max_health = 3 23 | self.health = self.max_health 24 | self.imgs = imgs[:] 25 | 26 | 27 | -------------------------------------------------------------------------------- /enemies/club.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | from .enemy import Enemy 4 | 5 | imgs = [] 6 | for x in range(20): 7 | add_str = str(x) 8 | if x < 10: 9 | add_str = "0" + add_str 10 | imgs.append(pygame.transform.scale( 11 | pygame.image.load(os.path.join("game_assets/enemies/5", "5_enemies_1_run_0" + add_str + ".png")).convert_alpha(), 12 | (64, 64))) 13 | 14 | 15 | class Club(Enemy): 16 | 17 | def __init__(self): 18 | super().__init__() 19 | self.name = "club" 20 | self.money = 5 21 | self.imgs = imgs[:] 22 | self.max_health = 5 23 | self.health = self.max_health 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /enemies/sword.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | from .enemy import Enemy 4 | 5 | imgs = [] 6 | for x in range(20): 7 | add_str = str(x) 8 | if x < 10: 9 | add_str = "0" + add_str 10 | imgs.append(pygame.transform.scale( 11 | pygame.image.load(os.path.join("game_assets/enemies/8", "8_enemies_1_run_0" + add_str + ".png")).convert_alpha(), 12 | (100, 100))) 13 | 14 | 15 | class Sword(Enemy): 16 | 17 | def __init__(self): 18 | super().__init__() 19 | self.name = "sword" 20 | self.money = 200 21 | self.imgs = imgs[:] 22 | self.max_health = 100 23 | self.health = self.max_health 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /main_menu/main_menu.py: -------------------------------------------------------------------------------- 1 | from game import Game 2 | import pygame 3 | import os 4 | 5 | start_btn = pygame.image.load(os.path.join("game_assets", "button_play.png")).convert_alpha() 6 | logo = pygame.image.load(os.path.join("game_assets", "logo.png")).convert_alpha() 7 | 8 | class MainMenu: 9 | def __init__(self, win): 10 | self.width = 1350 11 | self.height = 700 12 | self.bg = pygame.image.load(os.path.join("game_assets", "bg.png")) 13 | self.bg = pygame.transform.scale(self.bg, (self.width, self.height)) 14 | self.win = win 15 | self.btn = (self.width/2 - start_btn.get_width()/2, 350, start_btn.get_width(), start_btn.get_height()) 16 | 17 | def run(self): 18 | run = True 19 | 20 | while run: 21 | for event in pygame.event.get(): 22 | if event.type == pygame.QUIT: 23 | run = False 24 | 25 | if event.type == pygame.MOUSEBUTTONUP: 26 | # check if hit start btn 27 | x, y = pygame.mouse.get_pos() 28 | 29 | if self.btn[0] <= x <= self.btn[0] + self.btn[2]: 30 | if self.btn[1] <= y <= self.btn[1] + self.btn[3]: 31 | game = Game(self.win) 32 | game.run() 33 | del game 34 | self.draw() 35 | 36 | pygame.quit() 37 | 38 | def draw(self): 39 | self.win.blit(self.bg, (0,0)) 40 | self.win.blit(logo, (self.width/2 - logo.get_width()/2, 0)) 41 | self.win.blit(start_btn, (self.btn[0], self.btn[1])) 42 | pygame.display.update() 43 | 44 | 45 | -------------------------------------------------------------------------------- /make_requirements_and_scaffold.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Create a Tower_Defense scaffold with requirements.txt and core folders.""" 3 | import argparse, pathlib 4 | 5 | FOLDERS = ["enemies", "main_menu", "menu", "towers"] 6 | REQUIREMENTS = "# Runtime\npygame>=2.5.0\nnumpy>=1.24\npyyaml>=6.0.1\nloguru>=0.7.2\n\n# Optional (uncomment if you use them)\n# pillow>=10.0\n# pysdl2>=0.9.16\n# screeninfo>=0.8.1\n\n# Dev tools (optional)\n# black>=24.0\n# isort>=5.13\n# mypy>=1.10\n# pytest>=8.0\n" 7 | STARTER_MAIN = "import pygame\nimport sys\n\nWIDTH, HEIGHT = 960, 540\nFPS = 60\n\ndef main():\n pygame.init()\n screen = pygame.display.set_mode((WIDTH, HEIGHT))\n pygame.display.set_caption(\"Tower Defense \u2014 Starter\")\n clock = pygame.time.Clock()\n\n running = True\n while running:\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n running = False\n\n screen.fill((20, 24, 28))\n pygame.display.flip()\n clock.tick(FPS)\n\n pygame.quit()\n sys.exit()\n\nif __name__ == \"__main__\":\n main()\n" 8 | 9 | def write(base: pathlib.Path, rel: str, content: str): 10 | p = base / rel 11 | p.parent.mkdir(parents=True, exist_ok=True) 12 | p.write_text(content, encoding="utf-8") 13 | return p 14 | 15 | def make(base: pathlib.Path): 16 | base.mkdir(parents=True, exist_ok=True) 17 | for name in FOLDERS: 18 | (base / name).mkdir(parents=True, exist_ok=True) 19 | write(base, f"{name}/__init__.py", "# package\n") 20 | write(base, "requirements.txt", REQUIREMENTS) 21 | write(base, "main.py", STARTER_MAIN) 22 | print(f"✓ Created scaffold at: {base}") 23 | 24 | def main(): 25 | ap = argparse.ArgumentParser() 26 | ap.add_argument("--path", default="./Tower_Defense") 27 | args = ap.parse_args() 28 | make(pathlib.Path(args.path).resolve()) 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /towers/supportTower.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .tower import Tower 3 | import os 4 | import math 5 | import time 6 | 7 | 8 | range_imgs = [pygame.transform.scale(pygame.image.load(os.path.join("game_assets/support_towers", "4.png")).convert_alpha(), (90,90)), 9 | pygame.transform.scale(pygame.image.load(os.path.join("game_assets/support_towers", "5.png")).convert_alpha(), (90, 90))] 10 | 11 | 12 | class RangeTower(Tower): 13 | """ 14 | Add extra range to each surrounding tower 15 | """ 16 | def __init__(self, x, y): 17 | super().__init__(x,y) 18 | self.range = 75 19 | self.effect = [0.2, 0.4] 20 | self.tower_imgs = range_imgs[:] 21 | self.width = self.height = 90 22 | self.name = "range" 23 | self.price = [2000] 24 | 25 | def draw(self, win): 26 | super().draw_radius(win) 27 | super().draw(win) 28 | 29 | def support(self, towers): 30 | """ 31 | will modify towers according to abillity 32 | :param towers: list 33 | :return: None 34 | """ 35 | effected = [] 36 | for tower in towers: 37 | x = tower.x 38 | y = tower.y 39 | 40 | dis = math.sqrt((self.x - x) ** 2 + (self.y - y) ** 2) 41 | 42 | if dis <= self.range + tower.width/2: 43 | effected.append(tower) 44 | 45 | for tower in effected: 46 | tower.range = tower.original_range + round(tower.range * self.effect[self.level -1]) 47 | 48 | 49 | damage_imgs = [pygame.transform.scale(pygame.image.load(os.path.join("game_assets/support_towers", "8.png")).convert_alpha(), (90,90)), 50 | pygame.transform.scale(pygame.image.load(os.path.join("game_assets/support_towers", "9.png")).convert_alpha(), (90,90))] 51 | 52 | 53 | class DamageTower(RangeTower): 54 | """ 55 | add damage to surrounding towers 56 | """ 57 | def __init__(self, x, y): 58 | super().__init__(x,y) 59 | self.range = 100 60 | self.tower_imgs = damage_imgs[:] 61 | self.effect = [0.5, 1] 62 | self.name = "damage" 63 | self.price = [2000] 64 | 65 | def support(self, towers): 66 | """ 67 | will modify towers according to ability 68 | :param towers: list 69 | :return: None 70 | """ 71 | effected = [] 72 | for tower in towers: 73 | x = tower.x 74 | y = tower.y 75 | 76 | dis = math.sqrt((self.x - x) ** 2 + (self.y - y) ** 2) 77 | 78 | if dis <= self.range + tower.width/2: 79 | effected.append(tower) 80 | 81 | for tower in effected: 82 | tower.damage = tower.original_damage + round(tower.original_damage * self.effect[self.level -1]) 83 | -------------------------------------------------------------------------------- /towers/tower.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from menu.menu import Menu 3 | import os 4 | import math 5 | 6 | menu_bg = pygame.transform.scale(pygame.image.load(os.path.join("game_assets", "menu.png")).convert_alpha(), (120, 70)) 7 | upgrade_btn = pygame.transform.scale(pygame.image.load(os.path.join("game_assets", "upgrade.png")).convert_alpha(), (50, 50)) 8 | 9 | 10 | class Tower: 11 | """ 12 | Abstract class for towers 13 | """ 14 | def __init__(self,x,y): 15 | self.x = x 16 | self.y = y 17 | self.width = 0 18 | self.height = 0 19 | self.sell_price = [0,0,0] 20 | self.price = [0,0,0] 21 | self.level = 1 22 | self.selected = False 23 | # define menu and buttons 24 | self.menu = Menu(self, self.x, self.y, menu_bg, [2000, "MAX"]) 25 | self.menu.add_btn(upgrade_btn, "Upgrade") 26 | 27 | self.tower_imgs = [] 28 | self.damage = 1 29 | 30 | self.place_color = (0,0,255, 100) 31 | 32 | def draw(self, win): 33 | """ 34 | draws the tower 35 | :param win: surface 36 | :return: None 37 | """ 38 | img = self.tower_imgs[self.level - 1] 39 | win.blit(img, (self.x-img.get_width()//2, self.y-img.get_height()//2)) 40 | 41 | # draw menu 42 | if self.selected: 43 | self.menu.draw(win) 44 | 45 | def draw_radius(self,win): 46 | if self.selected: 47 | # draw range circle 48 | surface = pygame.Surface((self.range * 4, self.range * 4), pygame.SRCALPHA, 32) 49 | pygame.draw.circle(surface, (128, 128, 128, 100), (self.range, self.range), self.range, 0) 50 | 51 | win.blit(surface, (self.x - self.range, self.y - self.range)) 52 | 53 | def draw_placement(self,win): 54 | # draw range circle 55 | surface = pygame.Surface((self.range * 4, self.range * 4), pygame.SRCALPHA, 32) 56 | pygame.draw.circle(surface, self.place_color, (50,50), 50, 0) 57 | 58 | win.blit(surface, (self.x - 50, self.y - 50)) 59 | 60 | def click(self, X, Y): 61 | """ 62 | returns if tower has been clicked on 63 | and selects tower if it was clicked 64 | :param X: int 65 | :param Y: int 66 | :return: bool 67 | """ 68 | img = self.tower_imgs[self.level - 1] 69 | if X <= self.x - img.get_width()//2 + self.width and X >= self.x - img.get_width()//2: 70 | if Y <= self.y + self.height - img.get_height()//2 and Y >= self.y - img.get_height()//2: 71 | return True 72 | return False 73 | 74 | def sell(self): 75 | """ 76 | call to sell the tower, returns sell price 77 | :return: int 78 | """ 79 | return self.sell_price[self.level-1] 80 | 81 | def upgrade(self): 82 | """ 83 | upgrades the tower for a given cost 84 | :return: None 85 | """ 86 | if self.level < len(self.tower_imgs): 87 | self.level += 1 88 | self.damage += 1 89 | 90 | def get_upgrade_cost(self): 91 | """ 92 | returns the upgrade cost, if 0 then can't upgrade anymore 93 | :return: int 94 | """ 95 | return self.price[self.level-1] 96 | 97 | def move(self, x, y): 98 | """ 99 | moves tower to given x and y 100 | :param x: int 101 | :param y: int 102 | :return: None 103 | """ 104 | self.x = x 105 | self.y = y 106 | self.menu.x = x 107 | self.menu.y = y 108 | self.menu.update() 109 | 110 | def collide(self, otherTower): 111 | x2 = otherTower.x 112 | y2 = otherTower.y 113 | 114 | dis = math.sqrt((x2 - self.x)**2 + (y2 - self.y)**2) 115 | if dis >= 100: 116 | return False 117 | else: 118 | return True 119 | 120 | 121 | -------------------------------------------------------------------------------- /towers/archerTower.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | from .tower import Tower 3 | import os 4 | import math 5 | from menu.menu import Menu 6 | 7 | 8 | menu_bg = pygame.transform.scale(pygame.image.load(os.path.join("game_assets", "menu.png")).convert_alpha(), (120, 70)) 9 | upgrade_btn = pygame.transform.scale(pygame.image.load(os.path.join("game_assets", "upgrade.png")).convert_alpha(), (50, 50)) 10 | 11 | 12 | tower_imgs1 = [] 13 | archer_imgs1 = [] 14 | # load archer tower images 15 | for x in range(7,10): 16 | tower_imgs1.append(pygame.transform.scale( 17 | pygame.image.load(os.path.join("game_assets/archer_towers/archer_1", str(x) + ".png")).convert_alpha(), 18 | (90, 90))) 19 | 20 | # load archer images 21 | for x in range(37,43): 22 | archer_imgs1.append( 23 | pygame.image.load(os.path.join("game_assets/archer_towers/archer_top", str(x) + ".png")).convert_alpha()) 24 | 25 | 26 | class ArcherTowerLong(Tower): 27 | def __init__(self, x,y): 28 | super().__init__(x, y) 29 | self.tower_imgs = tower_imgs1[:] 30 | self.archer_imgs = archer_imgs1[:] 31 | self.archer_count = 0 32 | self.range = 200 33 | self.original_range = self.range 34 | self.inRange = False 35 | self.left = True 36 | self.damage = 1 37 | self.original_damage = self.damage 38 | self.width = self.height = 90 39 | self.moving = False 40 | self.name = "archer" 41 | 42 | self.menu = Menu(self, self.x, self.y, menu_bg, [2000, 5000,"MAX"]) 43 | self.menu.add_btn(upgrade_btn, "Upgrade") 44 | 45 | def get_upgrade_cost(self): 46 | """ 47 | gets the upgrade cost 48 | :return: int 49 | """ 50 | return self.menu.get_item_cost() 51 | 52 | def draw(self, win): 53 | """ 54 | draw the arhcer tower and animated archer 55 | :param win: surface 56 | :return: int 57 | """ 58 | super().draw_radius(win) 59 | super().draw(win) 60 | 61 | if self.inRange and not self.moving: 62 | self.archer_count += 1 63 | if self.archer_count >= len(self.archer_imgs) * 10: 64 | self.archer_count = 0 65 | else: 66 | self.archer_count = 0 67 | 68 | archer = self.archer_imgs[self.archer_count // 10] 69 | if self.left == True: 70 | add = -25 71 | else: 72 | add = -archer.get_width() + 10 73 | win.blit(archer, ((self.x + add), (self.y - archer.get_height() - 25))) 74 | 75 | def change_range(self, r): 76 | """ 77 | change range of archer tower 78 | :param r: int 79 | :return: None 80 | """ 81 | self.range = r 82 | 83 | def attack(self, enemies): 84 | """ 85 | attacks an enemy in the enemy list, modifies the list 86 | :param enemies: list of enemies 87 | :return: None 88 | """ 89 | money = 0 90 | self.inRange = False 91 | enemy_closest = [] 92 | for enemy in enemies: 93 | x = enemy.x 94 | y = enemy.y 95 | 96 | dis = math.sqrt((self.x - enemy.img.get_width()/2 - x)**2 + (self.y -enemy.img.get_height()/2 - y)**2) 97 | if dis < self.range: 98 | self.inRange = True 99 | enemy_closest.append(enemy) 100 | 101 | enemy_closest.sort(key=lambda x: x.path_pos) 102 | enemy_closest = enemy_closest[::-1] 103 | if len(enemy_closest) > 0: 104 | first_enemy = enemy_closest[0] 105 | if self.archer_count == 50: 106 | if first_enemy.hit(self.damage) == True: 107 | money = first_enemy.money * 2 108 | enemies.remove(first_enemy) 109 | 110 | if first_enemy.x > self.x and not(self.left): 111 | self.left = True 112 | for x, img in enumerate(self.archer_imgs): 113 | self.archer_imgs[x] = pygame.transform.flip(img, True, False) 114 | elif self.left and first_enemy.x < self.x: 115 | self.left = False 116 | for x, img in enumerate(self.archer_imgs): 117 | self.archer_imgs[x] = pygame.transform.flip(img, True, False) 118 | 119 | return money 120 | 121 | 122 | tower_imgs = [] 123 | archer_imgs = [] 124 | # load archer tower images 125 | for x in range(10,13): 126 | tower_imgs.append(pygame.transform.scale( 127 | pygame.image.load(os.path.join("game_assets/archer_towers/archer_2", str(x) + ".png")), 128 | (90, 90))) 129 | 130 | # load archer images 131 | for x in range(43,49): 132 | archer_imgs.append( 133 | pygame.image.load(os.path.join("game_assets/archer_towers/archer_top_2", str(x) + ".png"))) 134 | 135 | 136 | class ArcherTowerShort(ArcherTowerLong): 137 | def __init__(self, x,y): 138 | super().__init__(x, y) 139 | self.tower_imgs = tower_imgs[:] 140 | self.archer_imgs = archer_imgs[:] 141 | self.archer_count = 0 142 | self.range = 120 143 | self.original_range = self.range 144 | self.inRange = False 145 | self.left = True 146 | self.damage = 2 147 | self.original_damage = self.damage 148 | 149 | self.menu = Menu(self, self.x, self.y, menu_bg, [2500, 5500, "MAX"]) 150 | self.menu.add_btn(upgrade_btn, "Upgrade") 151 | self.name = "archer2" 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /menu/menu.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import os 3 | pygame.font.init() 4 | 5 | star = pygame.transform.scale(pygame.image.load(os.path.join("game_assets", "star.png")).convert_alpha(), (50,50)) 6 | star2 = pygame.transform.scale(pygame.image.load(os.path.join("game_assets", "star.png")).convert_alpha(), (20,20)) 7 | 8 | 9 | class Button: 10 | """ 11 | Button class for menu objects 12 | """ 13 | def __init__(self, menu, img, name): 14 | self.name = name 15 | self.img = img 16 | self.x = menu.x - 50 17 | self.y = menu.y - 110 18 | self.menu = menu 19 | self.width = self.img.get_width() 20 | self.height = self.img.get_height() 21 | 22 | def click(self, X, Y): 23 | """ 24 | returns if the positon has collided with the menu 25 | :param X: int 26 | :param Y: int 27 | :return: bool 28 | """ 29 | if X <= self.x + self.width and X >= self.x: 30 | if Y <= self.y + self.height and Y >= self.y: 31 | return True 32 | return False 33 | 34 | def draw(self, win): 35 | """ 36 | draws the button image 37 | :param win: surface 38 | :return: None 39 | """ 40 | win.blit(self.img, (self.x, self.y)) 41 | 42 | def update(self): 43 | """ 44 | updates button position 45 | :return: None 46 | """ 47 | self.x = self.menu.x - 50 48 | self.y = self.menu.y - 110 49 | 50 | 51 | class PlayPauseButton(Button): 52 | def __init__(self, play_img, pause_img, x, y): 53 | self.img = play_img 54 | self.play = play_img 55 | self.pause = pause_img 56 | self.x = x 57 | self.y = y 58 | self.width = self.img.get_width() 59 | self.height = self.img.get_height() 60 | self.paused = True 61 | 62 | def draw(self, win): 63 | if self.paused: 64 | win.blit(self.play, (self.x, self.y)) 65 | else: 66 | win.blit(self.pause, (self.x, self.y)) 67 | 68 | 69 | class VerticalButton(Button): 70 | """ 71 | Button class for menu objects 72 | """ 73 | def __init__(self, x, y, img, name, cost): 74 | self.name = name 75 | self.img = img 76 | self.x = x 77 | self.y = y 78 | self.width = self.img.get_width() 79 | self.height = self.img.get_height() 80 | self.cost = cost 81 | 82 | 83 | class Menu: 84 | """ 85 | menu for holding items 86 | """ 87 | def __init__(self, tower, x, y, img, item_cost): 88 | self.x = x 89 | self.y = y 90 | self.width = img.get_width() 91 | self.height = img.get_height() 92 | self.item_cost = item_cost 93 | self.buttons = [] 94 | self.items = 0 95 | self.bg = img 96 | self.font = pygame.font.SysFont("comicsans", 25) 97 | self.tower = tower 98 | 99 | def add_btn(self, img, name): 100 | """ 101 | adds buttons to menu 102 | :param img: surface 103 | :param name: str 104 | :return: None 105 | """ 106 | self.items += 1 107 | self.buttons.append(Button(self, img, name)) 108 | 109 | def get_item_cost(self): 110 | """ 111 | gets cost of upgrade to next level 112 | :return: int 113 | """ 114 | return self.item_cost[self.tower.level - 1] 115 | 116 | def draw(self, win): 117 | """ 118 | draws btns and menu bg 119 | :param win: surface 120 | :return: None 121 | """ 122 | win.blit(self.bg, (self.x - self.bg.get_width()/2, self.y-120)) 123 | for item in self.buttons: 124 | item.draw(win) 125 | win.blit(star, (item.x + item.width + 5, item.y-9)) 126 | text = self.font.render(str(self.item_cost[self.tower.level - 1]), 1, (255,255,255)) 127 | win.blit(text, (item.x + item.width + 30 - text.get_width()/2, item.y + star.get_height() -8)) 128 | 129 | def get_clicked(self, X, Y): 130 | """ 131 | return the clicked item from the menu 132 | :param X: int 133 | :param Y: int 134 | :return: str 135 | """ 136 | for btn in self.buttons: 137 | if btn.click(X,Y): 138 | return btn.name 139 | 140 | return None 141 | 142 | def update(self): 143 | """ 144 | update menu and button location 145 | :return: None 146 | """ 147 | for btn in self.buttons: 148 | btn.update() 149 | 150 | 151 | class VerticalMenu(Menu): 152 | """ 153 | Vertical Menu for side bar of game 154 | """ 155 | def __init__(self, x, y, img): 156 | self.x = x 157 | self.y = y 158 | self.width = img.get_width() 159 | self.height = img.get_height() 160 | self.buttons = [] 161 | self.items = 0 162 | self.bg = img 163 | self.font = pygame.font.SysFont("comicsans", 25) 164 | 165 | def add_btn(self, img, name, cost): 166 | """ 167 | adds buttons to menu 168 | :param img: surface 169 | :param name: str 170 | :return: None 171 | """ 172 | self.items += 1 173 | btn_x = self.x - 40 174 | btn_y = self.y-100 + (self.items-1)*120 175 | self.buttons.append(VerticalButton(btn_x, btn_y, img, name, cost)) 176 | 177 | def get_item_cost(self, name): 178 | """ 179 | gets cost of item 180 | :param name: str 181 | :return: int 182 | """ 183 | for btn in self.buttons: 184 | if btn.name == name: 185 | return btn.cost 186 | return -1 187 | 188 | def draw(self, win): 189 | """ 190 | draws btns and menu bg 191 | :param win: surface 192 | :return: None 193 | """ 194 | win.blit(self.bg, (self.x - self.bg.get_width()/2, self.y-120)) 195 | for item in self.buttons: 196 | item.draw(win) 197 | win.blit(star2, (item.x+2, item.y + item.height)) 198 | text = self.font.render(str(item.cost), 1, (255,255,255)) 199 | win.blit(text, (item.x + item.width/2 - text.get_width()/2 + 7, item.y + item.height + 5)) 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /enemies/enemy.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import os 4 | 5 | class Enemy: 6 | def __init__(self): 7 | self.width = 64 8 | self.height = 64 9 | self.animation_count = 0 10 | self.health = 1 11 | self.vel = 3 12 | self.path = [(-10, 224),(19, 224), (177, 235), (282, 283), (526, 277), (607, 217), (641, 105), (717, 57), (796, 83), (855, 222), (973, 284), (1046, 366), (1022, 458), (894, 492), (740, 504), (580, 542), (148, 541), (10, 442), (-20, 335), (-75, 305), (-100, 345)] 13 | self.x = self.path[0][0] 14 | self.y = self.path[0][1] 15 | self.img = pygame.image.load(os.path.join("game_assets/enemies/1", "1_enemies_1_run_000.png")).convert_alpha() 16 | self.dis = 0 17 | self.path_pos = 0 18 | self.move_count = 0 19 | self.move_dis = 0 20 | self.imgs = [] 21 | self.flipped = False 22 | self.max_health = 0 23 | self.speed_increase = 1.2 24 | 25 | def draw(self, win): 26 | """ 27 | Draws the enemy with the given images 28 | :param win: surface 29 | :return: None 30 | """ 31 | self.img = self.imgs[self.animation_count] 32 | 33 | win.blit(self.img, (self.x - self.img.get_width()/2, self.y- self.img.get_height()/2 - 35)) 34 | self.draw_health_bar(win) 35 | 36 | def draw_health_bar(self, win): 37 | """ 38 | draw health bar above enemy 39 | :param win: surface 40 | :return: None 41 | """ 42 | length = 50 43 | move_by = round(length / self.max_health) 44 | health_bar = move_by * self.health 45 | 46 | pygame.draw.rect(win, (255,0,0), (self.x-30, self.y-75, length, 10), 0) 47 | pygame.draw.rect(win, (0, 255, 0), (self.x-30, self.y - 75, health_bar, 10), 0) 48 | 49 | def collide(self, X, Y): 50 | """ 51 | Returns if position has hit enemy 52 | :param x: int 53 | :param y: int 54 | :return: Bool 55 | """ 56 | if X <= self.x + self.width and X >= self.x: 57 | if Y <= self.y + self.height and Y >= self.y: 58 | return True 59 | return False 60 | 61 | def move(self): 62 | """ 63 | Move enemy 64 | :return: None 65 | """ 66 | self.animation_count += 1 67 | if self.animation_count >= len(self.imgs): 68 | self.animation_count = 0 69 | 70 | x1, y1 = self.path[self.path_pos] 71 | if self.path_pos + 1 >= len(self.path): 72 | x2, y2 = (-10, 355) 73 | else: 74 | x2, y2 = self.path[self.path_pos+1] 75 | 76 | dirn = ((x2-x1)*2, (y2-y1)*2) 77 | length = math.sqrt((dirn[0])**2 + (dirn[1])**2) 78 | dirn = (dirn[0]/length, dirn[1]/length) 79 | 80 | if dirn[0] < 0 and not(self.flipped): 81 | self.flipped = True 82 | for x, img in enumerate(self.imgs): 83 | self.imgs[x] = pygame.transform.flip(img, True, False) 84 | 85 | move_x, move_y = ((self.x + dirn[0]), (self.y + dirn[1])) 86 | 87 | self.x = move_x 88 | self.y = move_y 89 | 90 | # Go to next point 91 | if dirn[0] >= 0: # moving right 92 | if dirn[1] >= 0: # moving down 93 | if self.x >= x2 and self.y >= y2: 94 | self.path_pos += 1 95 | else: 96 | if self.x >= x2 and self.y <= y2: 97 | self.path_pos += 1 98 | else: # moving left 99 | if dirn[1] >= 0: # moving down 100 | if self.x <= x2 and self.y >= y2: 101 | self.path_pos += 1 102 | else: 103 | if self.x <= x2 and self.y >= y2: 104 | self.path_pos += 1 105 | 106 | def hit(self, damage): 107 | """ 108 | Returns if an enemy has died and removes one health 109 | each call 110 | :return: Bool 111 | """ 112 | self.health -= damage 113 | if self.health <= 0: 114 | return True 115 | return False 116 | 117 | self.x = self.path[0][0] 118 | self.y = self.path[0][1] 119 | self.img = None 120 | self.dis = 0 121 | self.path_pos = 0 122 | self.move_count = 0 123 | self.move_dis = 0 124 | self.imgs = [] 125 | self.flipped = False 126 | self.max_health = 0 127 | 128 | def draw(self, win): 129 | """ 130 | Draws the enemy with the given images 131 | :param win: surface 132 | :return: None 133 | """ 134 | self.img = self.imgs[self.animation_count] 135 | 136 | win.blit(self.img, (self.x - self.img.get_width()/2, self.y- self.img.get_height()/2 - 35)) 137 | self.draw_health_bar(win) 138 | 139 | def draw_health_bar(self, win): 140 | """ 141 | draw health bar above enemy 142 | :param win: surface 143 | :return: None 144 | """ 145 | length = 50 146 | move_by = length / self.max_health 147 | health_bar = round(move_by * self.health) 148 | 149 | pygame.draw.rect(win, (255,0,0), (self.x-30, self.y-75, length, 10), 0) 150 | pygame.draw.rect(win, (0, 255, 0), (self.x-30, self.y - 75, health_bar, 10), 0) 151 | 152 | def collide(self, X, Y): 153 | """ 154 | Returns if position has hit enemy 155 | :param x: int 156 | :param y: int 157 | :return: Bool 158 | """ 159 | if X <= self.x + self.width and X >= self.x: 160 | if Y <= self.y + self.height and Y >= self.y: 161 | return True 162 | return False 163 | 164 | def move(self): 165 | """ 166 | Move enemy 167 | :return: None 168 | """ 169 | self.animation_count += 1 170 | if self.animation_count >= len(self.imgs): 171 | self.animation_count = 0 172 | 173 | x1, y1 = self.path[self.path_pos] 174 | x1 = x1 + 75 175 | if self.path_pos + 1 >= len(self.path): 176 | x2, y2 = (-10, 355) 177 | else: 178 | x2, y2 = self.path[self.path_pos+1] 179 | 180 | x2 = x2+75 181 | 182 | dirn = ((x2-x1)*2, (y2-y1)*2) 183 | length = math.sqrt((dirn[0])**2 + (dirn[1])**2) 184 | dirn = (dirn[0]/length * self.speed_increase, dirn[1]/length * self.speed_increase) 185 | 186 | if dirn[0] < 0 and not(self.flipped): 187 | self.flipped = True 188 | for x, img in enumerate(self.imgs): 189 | self.imgs[x] = pygame.transform.flip(img, True, False) 190 | 191 | move_x, move_y = ((self.x + dirn[0]), (self.y + dirn[1])) 192 | 193 | self.x = move_x 194 | self.y = move_y 195 | 196 | # Go to next point 197 | if dirn[0] >= 0: # moving right 198 | if dirn[1] >= 0: # moving down 199 | if self.x >= x2 and self.y >= y2: 200 | self.path_pos += 1 201 | else: 202 | if self.x >= x2 and self.y <= y2: 203 | self.path_pos += 1 204 | else: # moving left 205 | if dirn[1] >= 0: # moving down 206 | if self.x <= x2 and self.y >= y2: 207 | self.path_pos += 1 208 | else: 209 | if self.x <= x2 and self.y >= y2: 210 | self.path_pos += 1 211 | 212 | def hit(self, damage): 213 | """ 214 | Returns if an enemy has died and removes one health 215 | each call 216 | :return: Bool 217 | """ 218 | self.health -= damage 219 | if self.health <= 0: 220 | return True 221 | return False 222 | --------------------------------------------------------------------------------