├── README.md ├── part1 ├── game │ ├── __pycache__ │ │ ├── game.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── game.py │ ├── settings.py │ └── world.py └── main.py ├── part2 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── game.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── game.py │ ├── settings.py │ └── world.py └── main.py ├── part3 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── camera.cpython-38.pyc │ │ ├── game.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── utils.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── camera.py │ ├── game.py │ ├── settings.py │ ├── utils.py │ └── world.py └── main.py ├── part4 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── building01.png │ │ ├── building02.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── camera.cpython-38.pyc │ │ ├── game.cpython-38.pyc │ │ ├── hud.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── utils.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── camera.py │ ├── game.py │ ├── hud.py │ ├── settings.py │ ├── utils.py │ └── world.py └── main.py ├── part5 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── building01.png │ │ ├── building02.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── camera.cpython-38.pyc │ │ ├── game.cpython-38.pyc │ │ ├── hud.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── utils.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── camera.py │ ├── game.py │ ├── hud.py │ ├── settings.py │ ├── utils.py │ └── world.py └── main.py ├── part6 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── building01.png │ │ ├── building02.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── camera.cpython-38.pyc │ │ ├── game.cpython-38.pyc │ │ ├── hud.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── utils.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── camera.py │ ├── game.py │ ├── hud.py │ ├── settings.py │ ├── utils.py │ └── world.py └── main.py ├── part7 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── building01.png │ │ ├── building02.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── buildings.cpython-38.pyc │ │ ├── camera.cpython-38.pyc │ │ ├── game.cpython-38.pyc │ │ ├── hud.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── utils.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── buildings.py │ ├── camera.py │ ├── game.py │ ├── hud.py │ ├── settings.py │ ├── utils.py │ └── world.py └── main.py ├── part8 ├── assets │ └── graphics │ │ ├── block.png │ │ ├── building01.png │ │ ├── building02.png │ │ ├── rock.png │ │ └── tree.png ├── game │ ├── __pycache__ │ │ ├── buildings.cpython-38.pyc │ │ ├── camera.cpython-38.pyc │ │ ├── game.cpython-38.pyc │ │ ├── hud.cpython-38.pyc │ │ ├── resource_manager.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── utils.cpython-38.pyc │ │ └── world.cpython-38.pyc │ ├── buildings.py │ ├── camera.py │ ├── game.py │ ├── hud.py │ ├── resource_manager.py │ ├── settings.py │ ├── utils.py │ └── world.py └── main.py └── part9 ├── assets └── graphics │ ├── block.png │ ├── building01.png │ ├── building02.png │ ├── rock.png │ ├── tree.png │ └── worker.png ├── game ├── __pycache__ │ ├── buildings.cpython-38.pyc │ ├── camera.cpython-38.pyc │ ├── game.cpython-38.pyc │ ├── hud.cpython-38.pyc │ ├── menu.cpython-38.pyc │ ├── resource_manager.cpython-38.pyc │ ├── settings.cpython-38.pyc │ ├── utils.cpython-38.pyc │ ├── workers.cpython-38.pyc │ └── world.cpython-38.pyc ├── buildings.py ├── camera.py ├── game.py ├── hud.py ├── menu.py ├── resource_manager.py ├── settings.py ├── utils.py ├── workers.py └── world.py └── main.py /README.md: -------------------------------------------------------------------------------- 1 | ## Pygame tutorials series 2 | 3 | Code from my youtube series on making a city builder game in pygame 4 | 5 | https://www.youtube.com/watch?v=wI_pvfwcPgQ 6 | 7 | ### Part 1 8 | * Project setup 9 | * Creating cartesian world grid 10 | * Creating isometric world grid 11 | 12 | ### Part 2 13 | * Rendering isometric images 14 | * Render as a batch 15 | * Render iteratively 16 | 17 | ### Part 3 18 | * Adding camera class 19 | * Extending world map 20 | * randomised tiles with perlin noise 21 | 22 | ### Part 4 23 | * Creating hud display class 24 | * building panel with selectable tiles 25 | * resources panel 26 | -------------------------------------------------------------------------------- /part1/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part1/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part1/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part1/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part1/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part1/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part1/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | 6 | 7 | class Game: 8 | 9 | def __init__(self, screen, clock): 10 | self.screen = screen 11 | self.clock = clock 12 | self.width, self.height = self.screen.get_size() 13 | 14 | self.world = World(10, 10, self.width, self.height) 15 | 16 | def run(self): 17 | self.playing = True 18 | while self.playing: 19 | self.clock.tick(60) 20 | self.events() 21 | self.update() 22 | self.draw() 23 | 24 | def events(self): 25 | for event in pg.event.get(): 26 | if event.type == pg.QUIT: 27 | pg.quit() 28 | sys.exit() 29 | if event.type == pg.KEYDOWN: 30 | if event.key == pg.K_ESCAPE: 31 | pg.quit() 32 | sys.exit() 33 | 34 | def update(self): 35 | pass 36 | 37 | def draw(self): 38 | self.screen.fill((0, 0, 0)) 39 | 40 | for x in range(self.world.grid_length_x): 41 | for y in range(self.world.grid_length_y): 42 | 43 | sq = self.world.world[x][y]["cart_rect"] 44 | rect = pg.Rect(sq[0][0], sq[0][1], TILE_SIZE, TILE_SIZE) 45 | pg.draw.rect(self.screen, (0, 0, 255), rect, 1) 46 | 47 | p = self.world.world[x][y]["iso_poly"] 48 | p = [(x + self.width/2, y + self.height/4) for x, y in p] 49 | pg.draw.polygon(self.screen, (255, 0, 0), p, 1) 50 | 51 | 52 | pg.display.flip() 53 | 54 | -------------------------------------------------------------------------------- /part1/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part1/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .settings import TILE_SIZE 4 | 5 | 6 | 7 | class World: 8 | 9 | def __init__(self, grid_length_x, grid_length_y, width, height): 10 | self.grid_length_x = grid_length_x 11 | self.grid_length_y = grid_length_y 12 | self.width = width 13 | self.height = height 14 | 15 | self.world = self.create_world() 16 | 17 | def create_world(self): 18 | 19 | world = [] 20 | 21 | for grid_x in range(self.grid_length_x): 22 | world.append([]) 23 | for grid_y in range(self.grid_length_y): 24 | world_tile = self.grid_to_world(grid_x, grid_y) 25 | world[grid_x].append(world_tile) 26 | 27 | return world 28 | 29 | def grid_to_world(self, grid_x, grid_y): 30 | 31 | rect = [ 32 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 33 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 34 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 35 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 36 | ] 37 | 38 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 39 | 40 | out = { 41 | "grid": [grid_x, grid_y], 42 | "cart_rect": rect, 43 | "iso_poly": iso_poly 44 | } 45 | 46 | return out 47 | 48 | def cart_to_iso(self, x, y): 49 | iso_x = x - y 50 | iso_y = (x + y)/2 51 | return iso_x, iso_y 52 | 53 | 54 | -------------------------------------------------------------------------------- /part1/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part2/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part2/assets/graphics/block.png -------------------------------------------------------------------------------- /part2/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part2/assets/graphics/rock.png -------------------------------------------------------------------------------- /part2/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part2/assets/graphics/tree.png -------------------------------------------------------------------------------- /part2/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part2/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part2/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part2/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part2/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part2/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part2/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | 6 | 7 | class Game: 8 | 9 | def __init__(self, screen, clock): 10 | self.screen = screen 11 | self.clock = clock 12 | self.width, self.height = self.screen.get_size() 13 | 14 | self.world = World(10, 10, self.width, self.height) 15 | 16 | def run(self): 17 | self.playing = True 18 | while self.playing: 19 | self.clock.tick(60) 20 | self.events() 21 | self.update() 22 | self.draw() 23 | 24 | def events(self): 25 | for event in pg.event.get(): 26 | if event.type == pg.QUIT: 27 | pg.quit() 28 | sys.exit() 29 | if event.type == pg.KEYDOWN: 30 | if event.key == pg.K_ESCAPE: 31 | pg.quit() 32 | sys.exit() 33 | 34 | def update(self): 35 | pass 36 | 37 | def draw(self): 38 | self.screen.fill((0, 0, 0)) 39 | 40 | self.screen.blit(self.world.grass_tiles, (0, 0)) 41 | 42 | for x in range(self.world.grid_length_x): 43 | for y in range(self.world.grid_length_y): 44 | 45 | # sq = self.world.world[x][y]["cart_rect"] 46 | # rect = pg.Rect(sq[0][0], sq[0][1], TILE_SIZE, TILE_SIZE) 47 | # pg.draw.rect(self.screen, (0, 0, 255), rect, 1) 48 | 49 | render_pos = self.world.world[x][y]["render_pos"] 50 | #self.screen.blit(self.world.tiles["block"], (render_pos[0] + self.width/2, render_pos[1] + self.height/4)) 51 | 52 | tile = self.world.world[x][y]["tile"] 53 | if tile != "": 54 | self.screen.blit(self.world.tiles[tile], 55 | (render_pos[0] + self.width/2, 56 | render_pos[1] + self.height/4 - (self.world.tiles[tile].get_height() - TILE_SIZE))) 57 | 58 | # p = self.world.world[x][y]["iso_poly"] 59 | # p = [(x + self.width/2, y + self.height/4) for x, y in p] 60 | # pg.draw.polygon(self.screen, (255, 0, 0), p, 1) 61 | 62 | 63 | pg.display.flip() 64 | 65 | -------------------------------------------------------------------------------- /part2/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part2/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | from .settings import TILE_SIZE 5 | 6 | 7 | 8 | class World: 9 | 10 | def __init__(self, grid_length_x, grid_length_y, width, height): 11 | self.grid_length_x = grid_length_x 12 | self.grid_length_y = grid_length_y 13 | self.width = width 14 | self.height = height 15 | 16 | self.grass_tiles = pg.Surface((width, height)) 17 | self.tiles = self.load_images() 18 | self.world = self.create_world() 19 | 20 | def create_world(self): 21 | 22 | world = [] 23 | 24 | for grid_x in range(self.grid_length_x): 25 | world.append([]) 26 | for grid_y in range(self.grid_length_y): 27 | world_tile = self.grid_to_world(grid_x, grid_y) 28 | world[grid_x].append(world_tile) 29 | 30 | render_pos = world_tile["render_pos"] 31 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.width/2, render_pos[1] + self.height/4)) 32 | 33 | 34 | return world 35 | 36 | def grid_to_world(self, grid_x, grid_y): 37 | 38 | rect = [ 39 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 40 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 41 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 42 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 43 | ] 44 | 45 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 46 | 47 | minx = min([x for x, y in iso_poly]) 48 | miny = min([y for x, y in iso_poly]) 49 | 50 | r = random.randint(1, 100) 51 | 52 | if r <= 5: 53 | tile = "tree" 54 | elif r <= 10: 55 | tile = "rock" 56 | else: 57 | tile = "" 58 | 59 | out = { 60 | "grid": [grid_x, grid_y], 61 | "cart_rect": rect, 62 | "iso_poly": iso_poly, 63 | "render_pos": [minx, miny], 64 | "tile": tile 65 | } 66 | 67 | return out 68 | 69 | def cart_to_iso(self, x, y): 70 | iso_x = x - y 71 | iso_y = (x + y)/2 72 | return iso_x, iso_y 73 | 74 | def load_images(self): 75 | 76 | block = pg.image.load("assets/graphics/block.png") 77 | tree = pg.image.load("assets/graphics/tree.png") 78 | rock = pg.image.load("assets/graphics/rock.png") 79 | 80 | return {"block": block, "tree": tree, "rock": rock} 81 | 82 | 83 | -------------------------------------------------------------------------------- /part2/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part3/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/assets/graphics/block.png -------------------------------------------------------------------------------- /part3/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/assets/graphics/rock.png -------------------------------------------------------------------------------- /part3/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/assets/graphics/tree.png -------------------------------------------------------------------------------- /part3/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part3/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part3/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part3/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part3/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part3/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part3/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part3/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | 8 | 9 | class Game: 10 | 11 | def __init__(self, screen, clock): 12 | self.screen = screen 13 | self.clock = clock 14 | self.width, self.height = self.screen.get_size() 15 | 16 | # world 17 | self.world = World(100, 100, self.width, self.height) 18 | 19 | # camera 20 | self.camera = Camera(self.width, self.height) 21 | 22 | def run(self): 23 | self.playing = True 24 | while self.playing: 25 | self.clock.tick(60) 26 | self.events() 27 | self.update() 28 | self.draw() 29 | 30 | def events(self): 31 | for event in pg.event.get(): 32 | if event.type == pg.QUIT: 33 | pg.quit() 34 | sys.exit() 35 | if event.type == pg.KEYDOWN: 36 | if event.key == pg.K_ESCAPE: 37 | pg.quit() 38 | sys.exit() 39 | 40 | def update(self): 41 | self.camera.update() 42 | 43 | def draw(self): 44 | self.screen.fill((0, 0, 0)) 45 | 46 | self.screen.blit(self.world.grass_tiles, (self.camera.scroll.x, self.camera.scroll.y)) 47 | 48 | for x in range(self.world.grid_length_x): 49 | for y in range(self.world.grid_length_y): 50 | 51 | # sq = self.world.world[x][y]["cart_rect"] 52 | # rect = pg.Rect(sq[0][0], sq[0][1], TILE_SIZE, TILE_SIZE) 53 | # pg.draw.rect(self.screen, (0, 0, 255), rect, 1) 54 | 55 | render_pos = self.world.world[x][y]["render_pos"] 56 | #self.screen.blit(self.world.tiles["block"], (render_pos[0] + self.width/2, render_pos[1] + self.height/4)) 57 | 58 | tile = self.world.world[x][y]["tile"] 59 | if tile != "": 60 | self.screen.blit(self.world.tiles[tile], 61 | (render_pos[0] + self.world.grass_tiles.get_width()/2 + self.camera.scroll.x, 62 | render_pos[1] - (self.world.tiles[tile].get_height() - TILE_SIZE) + self.camera.scroll.y)) 63 | 64 | # p = self.world.world[x][y]["iso_poly"] 65 | # p = [(x + self.width/2, y + self.height/4) for x, y in p] 66 | # pg.draw.polygon(self.screen, (255, 0, 0), p, 1) 67 | 68 | draw_text( 69 | self.screen, 70 | 'fps={}'.format(round(self.clock.get_fps())), 71 | 25, 72 | (255, 255, 255), 73 | (10, 10) 74 | ) 75 | 76 | pg.display.flip() 77 | 78 | -------------------------------------------------------------------------------- /part3/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part3/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part3/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | 7 | 8 | 9 | class World: 10 | 11 | def __init__(self, grid_length_x, grid_length_y, width, height): 12 | self.grid_length_x = grid_length_x 13 | self.grid_length_y = grid_length_y 14 | self.width = width 15 | self.height = height 16 | 17 | self.perlin_scale = grid_length_x/2 18 | 19 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 20 | self.tiles = self.load_images() 21 | self.world = self.create_world() 22 | 23 | def create_world(self): 24 | 25 | world = [] 26 | 27 | for grid_x in range(self.grid_length_x): 28 | world.append([]) 29 | for grid_y in range(self.grid_length_y): 30 | world_tile = self.grid_to_world(grid_x, grid_y) 31 | world[grid_x].append(world_tile) 32 | 33 | render_pos = world_tile["render_pos"] 34 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 35 | 36 | 37 | return world 38 | 39 | def grid_to_world(self, grid_x, grid_y): 40 | 41 | rect = [ 42 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 43 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 44 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 45 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 46 | ] 47 | 48 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 49 | 50 | minx = min([x for x, y in iso_poly]) 51 | miny = min([y for x, y in iso_poly]) 52 | 53 | r = random.randint(1, 100) 54 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 55 | 56 | if (perlin >= 15) or (perlin <= -35): 57 | tile = "tree" 58 | else: 59 | if r == 1: 60 | tile = "tree" 61 | elif r == 2: 62 | tile = "rock" 63 | else: 64 | tile = "" 65 | 66 | out = { 67 | "grid": [grid_x, grid_y], 68 | "cart_rect": rect, 69 | "iso_poly": iso_poly, 70 | "render_pos": [minx, miny], 71 | "tile": tile 72 | } 73 | 74 | return out 75 | 76 | def cart_to_iso(self, x, y): 77 | iso_x = x - y 78 | iso_y = (x + y)/2 79 | return iso_x, iso_y 80 | 81 | def load_images(self): 82 | 83 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 84 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 85 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 86 | 87 | return {"block": block, "tree": tree, "rock": rock} 88 | 89 | 90 | -------------------------------------------------------------------------------- /part3/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part4/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/assets/graphics/block.png -------------------------------------------------------------------------------- /part4/assets/graphics/building01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/assets/graphics/building01.png -------------------------------------------------------------------------------- /part4/assets/graphics/building02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/assets/graphics/building02.png -------------------------------------------------------------------------------- /part4/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/assets/graphics/rock.png -------------------------------------------------------------------------------- /part4/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/assets/graphics/tree.png -------------------------------------------------------------------------------- /part4/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part4/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part4/game/__pycache__/hud.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/game/__pycache__/hud.cpython-38.pyc -------------------------------------------------------------------------------- /part4/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part4/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part4/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part4/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part4/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part4/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | from .hud import Hud 8 | 9 | 10 | class Game: 11 | 12 | def __init__(self, screen, clock): 13 | self.screen = screen 14 | self.clock = clock 15 | self.width, self.height = self.screen.get_size() 16 | 17 | # world 18 | self.world = World(100, 100, self.width, self.height) 19 | 20 | # camera 21 | self.camera = Camera(self.width, self.height) 22 | 23 | # hud 24 | self.hud = Hud(self.width, self.height) 25 | 26 | def run(self): 27 | self.playing = True 28 | while self.playing: 29 | self.clock.tick(60) 30 | self.events() 31 | self.update() 32 | self.draw() 33 | 34 | def events(self): 35 | for event in pg.event.get(): 36 | if event.type == pg.QUIT: 37 | pg.quit() 38 | sys.exit() 39 | if event.type == pg.KEYDOWN: 40 | if event.key == pg.K_ESCAPE: 41 | pg.quit() 42 | sys.exit() 43 | 44 | def update(self): 45 | self.camera.update() 46 | self.hud.update() 47 | 48 | def draw(self): 49 | self.screen.fill((0, 0, 0)) 50 | 51 | self.screen.blit(self.world.grass_tiles, (self.camera.scroll.x, self.camera.scroll.y)) 52 | 53 | for x in range(self.world.grid_length_x): 54 | for y in range(self.world.grid_length_y): 55 | 56 | # sq = self.world.world[x][y]["cart_rect"] 57 | # rect = pg.Rect(sq[0][0], sq[0][1], TILE_SIZE, TILE_SIZE) 58 | # pg.draw.rect(self.screen, (0, 0, 255), rect, 1) 59 | 60 | render_pos = self.world.world[x][y]["render_pos"] 61 | #self.screen.blit(self.world.tiles["block"], (render_pos[0] + self.width/2, render_pos[1] + self.height/4)) 62 | 63 | tile = self.world.world[x][y]["tile"] 64 | if tile != "": 65 | self.screen.blit(self.world.tiles[tile], 66 | (render_pos[0] + self.world.grass_tiles.get_width()/2 + self.camera.scroll.x, 67 | render_pos[1] - (self.world.tiles[tile].get_height() - TILE_SIZE) + self.camera.scroll.y)) 68 | 69 | # p = self.world.world[x][y]["iso_poly"] 70 | # p = [(x + self.width/2, y + self.height/4) for x, y in p] 71 | # pg.draw.polygon(self.screen, (255, 0, 0), p, 1) 72 | 73 | self.hud.draw(self.screen) 74 | 75 | draw_text( 76 | self.screen, 77 | 'fps={}'.format(round(self.clock.get_fps())), 78 | 25, 79 | (255, 255, 255), 80 | (10, 10) 81 | ) 82 | 83 | pg.display.flip() 84 | 85 | -------------------------------------------------------------------------------- /part4/game/hud.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .utils import draw_text 4 | 5 | 6 | class Hud: 7 | 8 | def __init__(self, width, height): 9 | 10 | self.width = width 11 | self.height = height 12 | 13 | self.hud_colour = (198, 155, 93, 175) 14 | 15 | # resouces hud 16 | self.resouces_surface = pg.Surface((width, height * 0.02), pg.SRCALPHA) 17 | self.resouces_surface.fill(self.hud_colour) 18 | 19 | # building hud 20 | self.build_surface = pg.Surface((width * 0.15, height * 0.25), pg.SRCALPHA) 21 | self.build_surface.fill(self.hud_colour) 22 | 23 | # select hud 24 | self.select_surface = pg.Surface((width * 0.3, height * 0.2), pg.SRCALPHA) 25 | self.select_surface.fill(self.hud_colour) 26 | 27 | self.images = self.load_images() 28 | self.tiles = self.create_build_hud() 29 | 30 | self.selected_tile = None 31 | 32 | def create_build_hud(self): 33 | 34 | render_pos = [self.width * 0.84 + 10, self.height * 0.74 + 10] 35 | object_width = self.build_surface.get_width() // 5 36 | 37 | tiles = [] 38 | 39 | for image_name, image in self.images.items(): 40 | 41 | pos = render_pos.copy() 42 | image_tmp = image.copy() 43 | image_scale = self.scale_image(image_tmp, w=object_width) 44 | rect = image_scale.get_rect(topleft=pos) 45 | 46 | tiles.append( 47 | { 48 | "name": image_name, 49 | "icon": image_scale, 50 | "image": self.images[image_name], 51 | "rect": rect 52 | } 53 | ) 54 | 55 | render_pos[0] += image_scale.get_width() + 10 56 | 57 | return tiles 58 | 59 | def update(self): 60 | 61 | mouse_pos = pg.mouse.get_pos() 62 | mouse_action = pg.mouse.get_pressed() 63 | 64 | if mouse_action[2]: 65 | self.selected_tile = None 66 | 67 | for tile in self.tiles: 68 | if tile["rect"].collidepoint(mouse_pos): 69 | if mouse_action[0]: 70 | self.selected_tile = tile 71 | 72 | def draw(self, screen): 73 | 74 | if self.selected_tile is not None: 75 | img = self.selected_tile["image"].copy() 76 | img.set_alpha(100) 77 | screen.blit(img, pg.mouse.get_pos()) 78 | 79 | # resouce hud 80 | screen.blit(self.resouces_surface, (0, 0)) 81 | # build hud 82 | screen.blit(self.build_surface, (self.width * 0.84, self.height * 0.74)) 83 | # select hud 84 | screen.blit(self.select_surface, (self.width * 0.35, self.height * 0.79)) 85 | 86 | for tile in self.tiles: 87 | screen.blit(tile["icon"], tile["rect"].topleft) 88 | 89 | # resources 90 | pos = self.width - 400 91 | for resource in ["wood:", "stone:", "gold:"]: 92 | draw_text(screen, resource, 30, (255, 255, 255), (pos, 0)) 93 | pos += 100 94 | 95 | def load_images(self): 96 | 97 | # read images 98 | building1 = pg.image.load("assets/graphics/building01.png") 99 | building2 = pg.image.load("assets/graphics/building02.png") 100 | tree = pg.image.load("assets/graphics/tree.png") 101 | rock = pg.image.load("assets/graphics/rock.png") 102 | 103 | images = { 104 | "building1": building1, 105 | "building2": building2, 106 | "tree": tree, 107 | "rock": rock 108 | } 109 | 110 | return images 111 | 112 | def scale_image(self, image, w=None, h=None): 113 | 114 | if (w == None) and (h == None): 115 | pass 116 | elif h == None: 117 | scale = w / image.get_width() 118 | h = scale * image.get_height() 119 | image = pg.transform.scale(image, (int(w), int(h))) 120 | elif w == None: 121 | scale = h / image.get_height() 122 | w = scale * image.get_width() 123 | image = pg.transform.scale(image, (int(w), int(h))) 124 | else: 125 | image = pg.transform.scale(image, (int(w), int(h))) 126 | 127 | return image 128 | 129 | -------------------------------------------------------------------------------- /part4/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part4/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part4/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | 7 | 8 | 9 | class World: 10 | 11 | def __init__(self, grid_length_x, grid_length_y, width, height): 12 | self.grid_length_x = grid_length_x 13 | self.grid_length_y = grid_length_y 14 | self.width = width 15 | self.height = height 16 | 17 | self.perlin_scale = grid_length_x/2 18 | 19 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 20 | self.tiles = self.load_images() 21 | self.world = self.create_world() 22 | 23 | def create_world(self): 24 | 25 | world = [] 26 | 27 | for grid_x in range(self.grid_length_x): 28 | world.append([]) 29 | for grid_y in range(self.grid_length_y): 30 | world_tile = self.grid_to_world(grid_x, grid_y) 31 | world[grid_x].append(world_tile) 32 | 33 | render_pos = world_tile["render_pos"] 34 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 35 | 36 | 37 | return world 38 | 39 | def grid_to_world(self, grid_x, grid_y): 40 | 41 | rect = [ 42 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 43 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 44 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 45 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 46 | ] 47 | 48 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 49 | 50 | minx = min([x for x, y in iso_poly]) 51 | miny = min([y for x, y in iso_poly]) 52 | 53 | r = random.randint(1, 100) 54 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 55 | 56 | if (perlin >= 15) or (perlin <= -35): 57 | tile = "tree" 58 | else: 59 | if r == 1: 60 | tile = "tree" 61 | elif r == 2: 62 | tile = "rock" 63 | else: 64 | tile = "" 65 | 66 | out = { 67 | "grid": [grid_x, grid_y], 68 | "cart_rect": rect, 69 | "iso_poly": iso_poly, 70 | "render_pos": [minx, miny], 71 | "tile": tile 72 | } 73 | 74 | return out 75 | 76 | def cart_to_iso(self, x, y): 77 | iso_x = x - y 78 | iso_y = (x + y)/2 79 | return iso_x, iso_y 80 | 81 | def load_images(self): 82 | 83 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 84 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 85 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 86 | 87 | return {"block": block, "tree": tree, "rock": rock} 88 | 89 | 90 | -------------------------------------------------------------------------------- /part4/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part5/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/assets/graphics/block.png -------------------------------------------------------------------------------- /part5/assets/graphics/building01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/assets/graphics/building01.png -------------------------------------------------------------------------------- /part5/assets/graphics/building02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/assets/graphics/building02.png -------------------------------------------------------------------------------- /part5/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/assets/graphics/rock.png -------------------------------------------------------------------------------- /part5/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/assets/graphics/tree.png -------------------------------------------------------------------------------- /part5/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part5/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part5/game/__pycache__/hud.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/game/__pycache__/hud.cpython-38.pyc -------------------------------------------------------------------------------- /part5/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part5/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part5/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part5/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part5/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part5/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | from .hud import Hud 8 | 9 | 10 | class Game: 11 | 12 | def __init__(self, screen, clock): 13 | self.screen = screen 14 | self.clock = clock 15 | self.width, self.height = self.screen.get_size() 16 | 17 | # hud 18 | self.hud = Hud(self.width, self.height) 19 | 20 | # world 21 | self.world = World(self.hud, 50, 50, self.width, self.height) 22 | 23 | # camera 24 | self.camera = Camera(self.width, self.height) 25 | 26 | def run(self): 27 | self.playing = True 28 | while self.playing: 29 | self.clock.tick(60) 30 | self.events() 31 | self.update() 32 | self.draw() 33 | 34 | def events(self): 35 | for event in pg.event.get(): 36 | if event.type == pg.QUIT: 37 | pg.quit() 38 | sys.exit() 39 | if event.type == pg.KEYDOWN: 40 | if event.key == pg.K_ESCAPE: 41 | pg.quit() 42 | sys.exit() 43 | 44 | def update(self): 45 | self.camera.update() 46 | self.hud.update() 47 | self.world.update(self.camera) 48 | 49 | def draw(self): 50 | self.screen.fill((0, 0, 0)) 51 | self.world.draw(self.screen, self.camera) 52 | self.hud.draw(self.screen) 53 | 54 | draw_text( 55 | self.screen, 56 | 'fps={}'.format(round(self.clock.get_fps())), 57 | 25, 58 | (255, 255, 255), 59 | (10, 10) 60 | ) 61 | 62 | pg.display.flip() 63 | 64 | -------------------------------------------------------------------------------- /part5/game/hud.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .utils import draw_text 4 | 5 | 6 | class Hud: 7 | 8 | def __init__(self, width, height): 9 | 10 | self.width = width 11 | self.height = height 12 | 13 | self.hud_colour = (198, 155, 93, 175) 14 | 15 | # resouces hud 16 | self.resouces_surface = pg.Surface((width, height * 0.02), pg.SRCALPHA) 17 | self.resources_rect = self.resouces_surface.get_rect(topleft=(0, 0)) 18 | self.resouces_surface.fill(self.hud_colour) 19 | 20 | # building hud 21 | self.build_surface = pg.Surface((width * 0.15, height * 0.25), pg.SRCALPHA) 22 | self.build_rect = self.build_surface.get_rect(topleft=(self.width * 0.84, self.height * 0.74)) 23 | self.build_surface.fill(self.hud_colour) 24 | 25 | # select hud 26 | self.select_surface = pg.Surface((width * 0.3, height * 0.2), pg.SRCALPHA) 27 | self.select_rect = self.select_surface.get_rect(topleft=(self.width * 0.35, self.height * 0.79)) 28 | self.select_surface.fill(self.hud_colour) 29 | 30 | self.images = self.load_images() 31 | self.tiles = self.create_build_hud() 32 | 33 | self.selected_tile = None 34 | 35 | def create_build_hud(self): 36 | 37 | render_pos = [self.width * 0.84 + 10, self.height * 0.74 + 10] 38 | object_width = self.build_surface.get_width() // 5 39 | 40 | tiles = [] 41 | 42 | for image_name, image in self.images.items(): 43 | 44 | pos = render_pos.copy() 45 | image_tmp = image.copy() 46 | image_scale = self.scale_image(image_tmp, w=object_width) 47 | rect = image_scale.get_rect(topleft=pos) 48 | 49 | tiles.append( 50 | { 51 | "name": image_name, 52 | "icon": image_scale, 53 | "image": self.images[image_name], 54 | "rect": rect 55 | } 56 | ) 57 | 58 | render_pos[0] += image_scale.get_width() + 10 59 | 60 | return tiles 61 | 62 | def update(self): 63 | 64 | mouse_pos = pg.mouse.get_pos() 65 | mouse_action = pg.mouse.get_pressed() 66 | 67 | if mouse_action[2]: 68 | self.selected_tile = None 69 | 70 | for tile in self.tiles: 71 | if tile["rect"].collidepoint(mouse_pos): 72 | if mouse_action[0]: 73 | self.selected_tile = tile 74 | 75 | def draw(self, screen): 76 | 77 | # resouce hud 78 | screen.blit(self.resouces_surface, (0, 0)) 79 | # build hud 80 | screen.blit(self.build_surface, (self.width * 0.84, self.height * 0.74)) 81 | # select hud 82 | screen.blit(self.select_surface, (self.width * 0.35, self.height * 0.79)) 83 | 84 | for tile in self.tiles: 85 | screen.blit(tile["icon"], tile["rect"].topleft) 86 | 87 | # resources 88 | pos = self.width - 400 89 | for resource in ["wood:", "stone:", "gold:"]: 90 | draw_text(screen, resource, 30, (255, 255, 255), (pos, 0)) 91 | pos += 100 92 | 93 | def load_images(self): 94 | 95 | # read images 96 | building1 = pg.image.load("assets/graphics/building01.png") 97 | building2 = pg.image.load("assets/graphics/building02.png") 98 | tree = pg.image.load("assets/graphics/tree.png") 99 | rock = pg.image.load("assets/graphics/rock.png") 100 | 101 | images = { 102 | "building1": building1, 103 | "building2": building2, 104 | "tree": tree, 105 | "rock": rock 106 | } 107 | 108 | return images 109 | 110 | def scale_image(self, image, w=None, h=None): 111 | 112 | if (w == None) and (h == None): 113 | pass 114 | elif h == None: 115 | scale = w / image.get_width() 116 | h = scale * image.get_height() 117 | image = pg.transform.scale(image, (int(w), int(h))) 118 | elif w == None: 119 | scale = h / image.get_height() 120 | w = scale * image.get_width() 121 | image = pg.transform.scale(image, (int(w), int(h))) 122 | else: 123 | image = pg.transform.scale(image, (int(w), int(h))) 124 | 125 | return image 126 | 127 | -------------------------------------------------------------------------------- /part5/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part5/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part5/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | 7 | 8 | 9 | class World: 10 | 11 | def __init__(self, hud, grid_length_x, grid_length_y, width, height): 12 | self.hud = hud 13 | self.grid_length_x = grid_length_x 14 | self.grid_length_y = grid_length_y 15 | self.width = width 16 | self.height = height 17 | 18 | self.perlin_scale = grid_length_x/2 19 | 20 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 21 | self.tiles = self.load_images() 22 | self.world = self.create_world() 23 | 24 | self.temp_tile = None 25 | 26 | def update(self, camera): 27 | 28 | mouse_pos = pg.mouse.get_pos() 29 | mouse_action = pg.mouse.get_pressed() 30 | 31 | self.temp_tile = None 32 | if self.hud.selected_tile is not None: 33 | 34 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 35 | 36 | if self.can_place_tile(grid_pos): 37 | img = self.hud.selected_tile["image"].copy() 38 | img.set_alpha(100) 39 | 40 | render_pos = self.world[grid_pos[0]][grid_pos[1]]["render_pos"] 41 | iso_poly = self.world[grid_pos[0]][grid_pos[1]]["iso_poly"] 42 | collision = self.world[grid_pos[0]][grid_pos[1]]["collision"] 43 | 44 | self.temp_tile = { 45 | "image": img, 46 | "render_pos": render_pos, 47 | "iso_poly": iso_poly, 48 | "collision": collision 49 | } 50 | 51 | if mouse_action[0] and not collision: 52 | self.world[grid_pos[0]][grid_pos[1]]["tile"] = self.hud.selected_tile["name"] 53 | self.world[grid_pos[0]][grid_pos[1]]["collision"] = True 54 | self.hud.selected_tile = None 55 | 56 | 57 | def draw(self, screen, camera): 58 | 59 | screen.blit(self.grass_tiles, (camera.scroll.x, camera.scroll.y)) 60 | 61 | for x in range(self.grid_length_x): 62 | for y in range(self.grid_length_y): 63 | render_pos = self.world[x][y]["render_pos"] 64 | tile = self.world[x][y]["tile"] 65 | if tile != "": 66 | screen.blit(self.tiles[tile], 67 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 68 | render_pos[1] - (self.tiles[tile].get_height() - TILE_SIZE) + camera.scroll.y)) 69 | 70 | if self.temp_tile is not None: 71 | iso_poly = self.temp_tile["iso_poly"] 72 | iso_poly = [(x + self.grass_tiles.get_width()/2 + camera.scroll.x, y + camera.scroll.y) for x, y in iso_poly] 73 | if self.temp_tile["collision"]: 74 | pg.draw.polygon(screen, (255, 0, 0), iso_poly, 3) 75 | else: 76 | pg.draw.polygon(screen, (255, 255, 255), iso_poly, 3) 77 | render_pos = self.temp_tile["render_pos"] 78 | screen.blit( 79 | self.temp_tile["image"], 80 | ( 81 | render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 82 | render_pos[1] - (self.temp_tile["image"].get_height() - TILE_SIZE) + camera.scroll.y 83 | ) 84 | ) 85 | 86 | def create_world(self): 87 | 88 | world = [] 89 | 90 | for grid_x in range(self.grid_length_x): 91 | world.append([]) 92 | for grid_y in range(self.grid_length_y): 93 | world_tile = self.grid_to_world(grid_x, grid_y) 94 | world[grid_x].append(world_tile) 95 | 96 | render_pos = world_tile["render_pos"] 97 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 98 | 99 | 100 | return world 101 | 102 | def grid_to_world(self, grid_x, grid_y): 103 | 104 | rect = [ 105 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 106 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 107 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 108 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 109 | ] 110 | 111 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 112 | 113 | minx = min([x for x, y in iso_poly]) 114 | miny = min([y for x, y in iso_poly]) 115 | 116 | r = random.randint(1, 100) 117 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 118 | 119 | if (perlin >= 15) or (perlin <= -35): 120 | tile = "tree" 121 | else: 122 | if r == 1: 123 | tile = "tree" 124 | elif r == 2: 125 | tile = "rock" 126 | else: 127 | tile = "" 128 | 129 | out = { 130 | "grid": [grid_x, grid_y], 131 | "cart_rect": rect, 132 | "iso_poly": iso_poly, 133 | "render_pos": [minx, miny], 134 | "tile": tile, 135 | "collision": False if tile == "" else True 136 | } 137 | 138 | return out 139 | 140 | def cart_to_iso(self, x, y): 141 | iso_x = x - y 142 | iso_y = (x + y)/2 143 | return iso_x, iso_y 144 | 145 | def mouse_to_grid(self, x, y, scroll): 146 | # transform to world position (removing camera scroll and offset) 147 | world_x = x - scroll.x - self.grass_tiles.get_width()/2 148 | world_y = y - scroll.y 149 | # transform to cart (inverse of cart_to_iso) 150 | cart_y = (2*world_y - world_x)/2 151 | cart_x = cart_y + world_x 152 | # transform to grid coordinates 153 | grid_x = int(cart_x // TILE_SIZE) 154 | grid_y = int(cart_y // TILE_SIZE) 155 | return grid_x, grid_y 156 | 157 | def load_images(self): 158 | 159 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 160 | # read images 161 | building1 = pg.image.load("assets/graphics/building01.png").convert_alpha() 162 | building2 = pg.image.load("assets/graphics/building02.png").convert_alpha() 163 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 164 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 165 | 166 | images = { 167 | "building1": building1, 168 | "building2": building2, 169 | "tree": tree, 170 | "rock": rock, 171 | "block": block 172 | } 173 | 174 | return images 175 | 176 | def can_place_tile(self, grid_pos): 177 | mouse_on_panel = False 178 | for rect in [self.hud.resources_rect, self.hud.build_rect, self.hud.select_rect]: 179 | if rect.collidepoint(pg.mouse.get_pos()): 180 | mouse_on_panel = True 181 | world_bounds = (0 <= grid_pos[0] <= self.grid_length_x) and (0 <= grid_pos[1] <= self.grid_length_x) 182 | 183 | if world_bounds and not mouse_on_panel: 184 | return True 185 | else: 186 | return False 187 | 188 | -------------------------------------------------------------------------------- /part5/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part6/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/assets/graphics/block.png -------------------------------------------------------------------------------- /part6/assets/graphics/building01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/assets/graphics/building01.png -------------------------------------------------------------------------------- /part6/assets/graphics/building02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/assets/graphics/building02.png -------------------------------------------------------------------------------- /part6/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/assets/graphics/rock.png -------------------------------------------------------------------------------- /part6/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/assets/graphics/tree.png -------------------------------------------------------------------------------- /part6/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part6/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part6/game/__pycache__/hud.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/game/__pycache__/hud.cpython-38.pyc -------------------------------------------------------------------------------- /part6/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part6/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part6/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part6/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part6/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part6/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | from .hud import Hud 8 | 9 | 10 | class Game: 11 | 12 | def __init__(self, screen, clock): 13 | self.screen = screen 14 | self.clock = clock 15 | self.width, self.height = self.screen.get_size() 16 | 17 | # hud 18 | self.hud = Hud(self.width, self.height) 19 | 20 | # world 21 | self.world = World(self.hud, 50, 50, self.width, self.height) 22 | 23 | # camera 24 | self.camera = Camera(self.width, self.height) 25 | 26 | def run(self): 27 | self.playing = True 28 | while self.playing: 29 | self.clock.tick(60) 30 | self.events() 31 | self.update() 32 | self.draw() 33 | 34 | def events(self): 35 | for event in pg.event.get(): 36 | if event.type == pg.QUIT: 37 | pg.quit() 38 | sys.exit() 39 | if event.type == pg.KEYDOWN: 40 | if event.key == pg.K_ESCAPE: 41 | pg.quit() 42 | sys.exit() 43 | 44 | def update(self): 45 | self.camera.update() 46 | self.hud.update() 47 | self.world.update(self.camera) 48 | 49 | def draw(self): 50 | self.screen.fill((0, 0, 0)) 51 | self.world.draw(self.screen, self.camera) 52 | self.hud.draw(self.screen) 53 | 54 | draw_text( 55 | self.screen, 56 | 'fps={}'.format(round(self.clock.get_fps())), 57 | 25, 58 | (255, 255, 255), 59 | (10, 10) 60 | ) 61 | 62 | pg.display.flip() 63 | 64 | -------------------------------------------------------------------------------- /part6/game/hud.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .utils import draw_text 4 | 5 | 6 | class Hud: 7 | 8 | def __init__(self, width, height): 9 | 10 | self.width = width 11 | self.height = height 12 | 13 | self.hud_colour = (198, 155, 93, 175) 14 | 15 | # resouces hud 16 | self.resouces_surface = pg.Surface((width, height * 0.02), pg.SRCALPHA) 17 | self.resources_rect = self.resouces_surface.get_rect(topleft=(0, 0)) 18 | self.resouces_surface.fill(self.hud_colour) 19 | 20 | # building hud 21 | self.build_surface = pg.Surface((width * 0.15, height * 0.25), pg.SRCALPHA) 22 | self.build_rect = self.build_surface.get_rect(topleft=(self.width * 0.84, self.height * 0.74)) 23 | self.build_surface.fill(self.hud_colour) 24 | 25 | # select hud 26 | self.select_surface = pg.Surface((width * 0.3, height * 0.2), pg.SRCALPHA) 27 | self.select_rect = self.select_surface.get_rect(topleft=(self.width * 0.35, self.height * 0.79)) 28 | self.select_surface.fill(self.hud_colour) 29 | 30 | self.images = self.load_images() 31 | self.tiles = self.create_build_hud() 32 | 33 | self.selected_tile = None 34 | self.examined_tile = None 35 | 36 | def create_build_hud(self): 37 | 38 | render_pos = [self.width * 0.84 + 10, self.height * 0.74 + 10] 39 | object_width = self.build_surface.get_width() // 5 40 | 41 | tiles = [] 42 | 43 | for image_name, image in self.images.items(): 44 | 45 | pos = render_pos.copy() 46 | image_tmp = image.copy() 47 | image_scale = self.scale_image(image_tmp, w=object_width) 48 | rect = image_scale.get_rect(topleft=pos) 49 | 50 | tiles.append( 51 | { 52 | "name": image_name, 53 | "icon": image_scale, 54 | "image": self.images[image_name], 55 | "rect": rect 56 | } 57 | ) 58 | 59 | render_pos[0] += image_scale.get_width() + 10 60 | 61 | return tiles 62 | 63 | def update(self): 64 | 65 | mouse_pos = pg.mouse.get_pos() 66 | mouse_action = pg.mouse.get_pressed() 67 | 68 | if mouse_action[2]: 69 | self.selected_tile = None 70 | 71 | for tile in self.tiles: 72 | if tile["rect"].collidepoint(mouse_pos): 73 | if mouse_action[0]: 74 | self.selected_tile = tile 75 | 76 | def draw(self, screen): 77 | 78 | # resouce hud 79 | screen.blit(self.resouces_surface, (0, 0)) 80 | # build hud 81 | screen.blit(self.build_surface, (self.width * 0.84, self.height * 0.74)) 82 | # select hud 83 | if self.examined_tile is not None: 84 | w, h = self.select_rect.width, self.select_rect.height 85 | screen.blit(self.select_surface, (self.width * 0.35, self.height * 0.79)) 86 | img = self.images[self.examined_tile["tile"]].copy() 87 | img_scale = self.scale_image(img, h=h*0.9) 88 | screen.blit(img_scale, (self.width * 0.35 + 10, self.height * 0.79 + 10)) 89 | draw_text(screen, self.examined_tile["tile"], 40, (255, 255, 255), self.select_rect.center) 90 | 91 | for tile in self.tiles: 92 | screen.blit(tile["icon"], tile["rect"].topleft) 93 | 94 | # resources 95 | pos = self.width - 400 96 | for resource in ["wood:", "stone:", "gold:"]: 97 | draw_text(screen, resource, 30, (255, 255, 255), (pos, 0)) 98 | pos += 100 99 | 100 | def load_images(self): 101 | 102 | # read images 103 | building1 = pg.image.load("assets/graphics/building01.png") 104 | building2 = pg.image.load("assets/graphics/building02.png") 105 | tree = pg.image.load("assets/graphics/tree.png") 106 | rock = pg.image.load("assets/graphics/rock.png") 107 | 108 | images = { 109 | "building1": building1, 110 | "building2": building2, 111 | "tree": tree, 112 | "rock": rock 113 | } 114 | 115 | return images 116 | 117 | def scale_image(self, image, w=None, h=None): 118 | 119 | if (w == None) and (h == None): 120 | pass 121 | elif h == None: 122 | scale = w / image.get_width() 123 | h = scale * image.get_height() 124 | image = pg.transform.scale(image, (int(w), int(h))) 125 | elif w == None: 126 | scale = h / image.get_height() 127 | w = scale * image.get_width() 128 | image = pg.transform.scale(image, (int(w), int(h))) 129 | else: 130 | image = pg.transform.scale(image, (int(w), int(h))) 131 | 132 | return image 133 | 134 | -------------------------------------------------------------------------------- /part6/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part6/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part6/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | 7 | 8 | 9 | class World: 10 | 11 | def __init__(self, hud, grid_length_x, grid_length_y, width, height): 12 | self.hud = hud 13 | self.grid_length_x = grid_length_x 14 | self.grid_length_y = grid_length_y 15 | self.width = width 16 | self.height = height 17 | 18 | self.perlin_scale = grid_length_x/2 19 | 20 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 21 | self.tiles = self.load_images() 22 | self.world = self.create_world() 23 | 24 | self.temp_tile = None 25 | self.examine_tile = None 26 | 27 | def update(self, camera): 28 | 29 | mouse_pos = pg.mouse.get_pos() 30 | mouse_action = pg.mouse.get_pressed() 31 | 32 | if mouse_action[2]: 33 | self.examine_tile = None 34 | self.hud.examined_tile = None 35 | 36 | self.temp_tile = None 37 | if self.hud.selected_tile is not None: 38 | 39 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 40 | 41 | if self.can_place_tile(grid_pos): 42 | img = self.hud.selected_tile["image"].copy() 43 | img.set_alpha(100) 44 | 45 | render_pos = self.world[grid_pos[0]][grid_pos[1]]["render_pos"] 46 | iso_poly = self.world[grid_pos[0]][grid_pos[1]]["iso_poly"] 47 | collision = self.world[grid_pos[0]][grid_pos[1]]["collision"] 48 | 49 | self.temp_tile = { 50 | "image": img, 51 | "render_pos": render_pos, 52 | "iso_poly": iso_poly, 53 | "collision": collision 54 | } 55 | 56 | if mouse_action[0] and not collision: 57 | self.world[grid_pos[0]][grid_pos[1]]["tile"] = self.hud.selected_tile["name"] 58 | self.world[grid_pos[0]][grid_pos[1]]["collision"] = True 59 | self.hud.selected_tile = None 60 | 61 | else: 62 | 63 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 64 | 65 | if self.can_place_tile(grid_pos): 66 | 67 | collision = self.world[grid_pos[0]][grid_pos[1]]["collision"] 68 | 69 | if mouse_action[0] and collision: 70 | self.examine_tile = grid_pos 71 | self.hud.examined_tile = self.world[grid_pos[0]][grid_pos[1]] 72 | 73 | 74 | def draw(self, screen, camera): 75 | 76 | screen.blit(self.grass_tiles, (camera.scroll.x, camera.scroll.y)) 77 | 78 | for x in range(self.grid_length_x): 79 | for y in range(self.grid_length_y): 80 | render_pos = self.world[x][y]["render_pos"] 81 | tile = self.world[x][y]["tile"] 82 | if tile != "": 83 | screen.blit(self.tiles[tile], 84 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 85 | render_pos[1] - (self.tiles[tile].get_height() - TILE_SIZE) + camera.scroll.y)) 86 | if self.examine_tile is not None: 87 | if (x == self.examine_tile[0]) and (y == self.examine_tile[1]): 88 | mask = pg.mask.from_surface(self.tiles[tile]).outline() 89 | mask = [(x + render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, y + render_pos[1] - (self.tiles[tile].get_height() - TILE_SIZE) + camera.scroll.y) for x, y in mask] 90 | pg.draw.polygon(screen, (255, 255, 255), mask, 3) 91 | 92 | if self.temp_tile is not None: 93 | iso_poly = self.temp_tile["iso_poly"] 94 | iso_poly = [(x + self.grass_tiles.get_width()/2 + camera.scroll.x, y + camera.scroll.y) for x, y in iso_poly] 95 | if self.temp_tile["collision"]: 96 | pg.draw.polygon(screen, (255, 0, 0), iso_poly, 3) 97 | else: 98 | pg.draw.polygon(screen, (255, 255, 255), iso_poly, 3) 99 | render_pos = self.temp_tile["render_pos"] 100 | screen.blit( 101 | self.temp_tile["image"], 102 | ( 103 | render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 104 | render_pos[1] - (self.temp_tile["image"].get_height() - TILE_SIZE) + camera.scroll.y 105 | ) 106 | ) 107 | 108 | def create_world(self): 109 | 110 | world = [] 111 | 112 | for grid_x in range(self.grid_length_x): 113 | world.append([]) 114 | for grid_y in range(self.grid_length_y): 115 | world_tile = self.grid_to_world(grid_x, grid_y) 116 | world[grid_x].append(world_tile) 117 | 118 | render_pos = world_tile["render_pos"] 119 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 120 | 121 | 122 | return world 123 | 124 | def grid_to_world(self, grid_x, grid_y): 125 | 126 | rect = [ 127 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 128 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 129 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 130 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 131 | ] 132 | 133 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 134 | 135 | minx = min([x for x, y in iso_poly]) 136 | miny = min([y for x, y in iso_poly]) 137 | 138 | r = random.randint(1, 100) 139 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 140 | 141 | if (perlin >= 15) or (perlin <= -35): 142 | tile = "tree" 143 | else: 144 | if r == 1: 145 | tile = "tree" 146 | elif r == 2: 147 | tile = "rock" 148 | else: 149 | tile = "" 150 | 151 | out = { 152 | "grid": [grid_x, grid_y], 153 | "cart_rect": rect, 154 | "iso_poly": iso_poly, 155 | "render_pos": [minx, miny], 156 | "tile": tile, 157 | "collision": False if tile == "" else True 158 | } 159 | 160 | return out 161 | 162 | def cart_to_iso(self, x, y): 163 | iso_x = x - y 164 | iso_y = (x + y)/2 165 | return iso_x, iso_y 166 | 167 | def mouse_to_grid(self, x, y, scroll): 168 | # transform to world position (removing camera scroll and offset) 169 | world_x = x - scroll.x - self.grass_tiles.get_width()/2 170 | world_y = y - scroll.y 171 | # transform to cart (inverse of cart_to_iso) 172 | cart_y = (2*world_y - world_x)/2 173 | cart_x = cart_y + world_x 174 | # transform to grid coordinates 175 | grid_x = int(cart_x // TILE_SIZE) 176 | grid_y = int(cart_y // TILE_SIZE) 177 | return grid_x, grid_y 178 | 179 | def load_images(self): 180 | 181 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 182 | # read images 183 | building1 = pg.image.load("assets/graphics/building01.png").convert_alpha() 184 | building2 = pg.image.load("assets/graphics/building02.png").convert_alpha() 185 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 186 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 187 | 188 | images = { 189 | "building1": building1, 190 | "building2": building2, 191 | "tree": tree, 192 | "rock": rock, 193 | "block": block 194 | } 195 | 196 | return images 197 | 198 | def can_place_tile(self, grid_pos): 199 | mouse_on_panel = False 200 | for rect in [self.hud.resources_rect, self.hud.build_rect, self.hud.select_rect]: 201 | if rect.collidepoint(pg.mouse.get_pos()): 202 | mouse_on_panel = True 203 | world_bounds = (0 <= grid_pos[0] <= self.grid_length_x) and (0 <= grid_pos[1] <= self.grid_length_x) 204 | 205 | if world_bounds and not mouse_on_panel: 206 | return True 207 | else: 208 | return False 209 | 210 | -------------------------------------------------------------------------------- /part6/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part7/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/assets/graphics/block.png -------------------------------------------------------------------------------- /part7/assets/graphics/building01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/assets/graphics/building01.png -------------------------------------------------------------------------------- /part7/assets/graphics/building02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/assets/graphics/building02.png -------------------------------------------------------------------------------- /part7/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/assets/graphics/rock.png -------------------------------------------------------------------------------- /part7/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/assets/graphics/tree.png -------------------------------------------------------------------------------- /part7/game/__pycache__/buildings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/buildings.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/__pycache__/hud.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/hud.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part7/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part7/game/buildings.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | 6 | class Lumbermill: 7 | 8 | def __init__(self, pos): 9 | image = pg.image.load("assets/graphics/building01.png") 10 | self.image = image 11 | self.name = "lumbermill" 12 | self.rect = self.image.get_rect(topleft=pos) 13 | self.counter = 0 14 | 15 | def update(self): 16 | self.counter += 1 17 | 18 | 19 | 20 | class Stonemasonry: 21 | 22 | def __init__(self, pos): 23 | image = pg.image.load("assets/graphics/building02.png") 24 | self.image = image 25 | self.name = "stonemasonry" 26 | self.rect = self.image.get_rect(topleft=pos) 27 | self.counter = 0 28 | 29 | def update(self): 30 | self.counter += 1 31 | 32 | 33 | -------------------------------------------------------------------------------- /part7/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part7/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | from .hud import Hud 8 | 9 | 10 | class Game: 11 | 12 | def __init__(self, screen, clock): 13 | self.screen = screen 14 | self.clock = clock 15 | self.width, self.height = self.screen.get_size() 16 | 17 | # entities 18 | self.entities = [] 19 | 20 | # hud 21 | self.hud = Hud(self.width, self.height) 22 | 23 | # world 24 | self.world = World(self.entities, self.hud, 50, 50, self.width, self.height) 25 | 26 | # camera 27 | self.camera = Camera(self.width, self.height) 28 | 29 | def run(self): 30 | self.playing = True 31 | while self.playing: 32 | self.clock.tick(60) 33 | self.events() 34 | self.update() 35 | self.draw() 36 | 37 | def events(self): 38 | for event in pg.event.get(): 39 | if event.type == pg.QUIT: 40 | pg.quit() 41 | sys.exit() 42 | if event.type == pg.KEYDOWN: 43 | if event.key == pg.K_ESCAPE: 44 | pg.quit() 45 | sys.exit() 46 | 47 | def update(self): 48 | self.camera.update() 49 | for e in self.entities: e.update() 50 | self.hud.update() 51 | self.world.update(self.camera) 52 | 53 | def draw(self): 54 | self.screen.fill((0, 0, 0)) 55 | self.world.draw(self.screen, self.camera) 56 | self.hud.draw(self.screen) 57 | 58 | draw_text( 59 | self.screen, 60 | 'fps={}'.format(round(self.clock.get_fps())), 61 | 25, 62 | (255, 255, 255), 63 | (10, 10) 64 | ) 65 | 66 | pg.display.flip() 67 | 68 | -------------------------------------------------------------------------------- /part7/game/hud.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .utils import draw_text 4 | 5 | 6 | class Hud: 7 | 8 | def __init__(self, width, height): 9 | 10 | self.width = width 11 | self.height = height 12 | 13 | self.hud_colour = (198, 155, 93, 175) 14 | 15 | # resouces hud 16 | self.resouces_surface = pg.Surface((width, height * 0.02), pg.SRCALPHA) 17 | self.resources_rect = self.resouces_surface.get_rect(topleft=(0, 0)) 18 | self.resouces_surface.fill(self.hud_colour) 19 | 20 | # building hud 21 | self.build_surface = pg.Surface((width * 0.15, height * 0.25), pg.SRCALPHA) 22 | self.build_rect = self.build_surface.get_rect(topleft=(self.width * 0.84, self.height * 0.74)) 23 | self.build_surface.fill(self.hud_colour) 24 | 25 | # select hud 26 | self.select_surface = pg.Surface((width * 0.3, height * 0.2), pg.SRCALPHA) 27 | self.select_rect = self.select_surface.get_rect(topleft=(self.width * 0.35, self.height * 0.79)) 28 | self.select_surface.fill(self.hud_colour) 29 | 30 | self.images = self.load_images() 31 | self.tiles = self.create_build_hud() 32 | 33 | self.selected_tile = None 34 | self.examined_tile = None 35 | 36 | def create_build_hud(self): 37 | 38 | render_pos = [self.width * 0.84 + 10, self.height * 0.74 + 10] 39 | object_width = self.build_surface.get_width() // 5 40 | 41 | tiles = [] 42 | 43 | for image_name, image in self.images.items(): 44 | 45 | pos = render_pos.copy() 46 | image_tmp = image.copy() 47 | image_scale = self.scale_image(image_tmp, w=object_width) 48 | rect = image_scale.get_rect(topleft=pos) 49 | 50 | tiles.append( 51 | { 52 | "name": image_name, 53 | "icon": image_scale, 54 | "image": self.images[image_name], 55 | "rect": rect 56 | } 57 | ) 58 | 59 | render_pos[0] += image_scale.get_width() + 10 60 | 61 | return tiles 62 | 63 | def update(self): 64 | 65 | mouse_pos = pg.mouse.get_pos() 66 | mouse_action = pg.mouse.get_pressed() 67 | 68 | if mouse_action[2]: 69 | self.selected_tile = None 70 | 71 | for tile in self.tiles: 72 | if tile["rect"].collidepoint(mouse_pos): 73 | if mouse_action[0]: 74 | self.selected_tile = tile 75 | 76 | def draw(self, screen): 77 | 78 | # resouce hud 79 | screen.blit(self.resouces_surface, (0, 0)) 80 | # build hud 81 | screen.blit(self.build_surface, (self.width * 0.84, self.height * 0.74)) 82 | # select hud 83 | if self.examined_tile is not None: 84 | w, h = self.select_rect.width, self.select_rect.height 85 | screen.blit(self.select_surface, (self.width * 0.35, self.height * 0.79)) 86 | img = self.examined_tile.image.copy() 87 | img_scale = self.scale_image(img, h=h*0.7) 88 | screen.blit(img_scale, (self.width * 0.35 + 10, self.height * 0.79 + 40)) 89 | draw_text(screen, self.examined_tile.name, 40, (255, 255, 255), self.select_rect.topleft) 90 | draw_text(screen, str(self.examined_tile.counter), 30, (255, 255, 255), self.select_rect.center) 91 | 92 | for tile in self.tiles: 93 | screen.blit(tile["icon"], tile["rect"].topleft) 94 | 95 | # resources 96 | pos = self.width - 400 97 | for resource in ["wood:", "stone:", "gold:"]: 98 | draw_text(screen, resource, 30, (255, 255, 255), (pos, 0)) 99 | pos += 100 100 | 101 | def load_images(self): 102 | 103 | # read images 104 | lumbermill = pg.image.load("assets/graphics/building01.png") 105 | stonemasonry = pg.image.load("assets/graphics/building02.png") 106 | 107 | images = { 108 | "lumbermill": lumbermill, 109 | "stonemasonry": stonemasonry 110 | } 111 | 112 | return images 113 | 114 | def scale_image(self, image, w=None, h=None): 115 | 116 | if (w == None) and (h == None): 117 | pass 118 | elif h == None: 119 | scale = w / image.get_width() 120 | h = scale * image.get_height() 121 | image = pg.transform.scale(image, (int(w), int(h))) 122 | elif w == None: 123 | scale = h / image.get_height() 124 | w = scale * image.get_width() 125 | image = pg.transform.scale(image, (int(w), int(h))) 126 | else: 127 | image = pg.transform.scale(image, (int(w), int(h))) 128 | 129 | return image 130 | 131 | -------------------------------------------------------------------------------- /part7/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part7/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part7/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | from .buildings import Lumbermill, Stonemasonry 7 | 8 | 9 | 10 | class World: 11 | 12 | def __init__(self, entities, hud, grid_length_x, grid_length_y, width, height): 13 | self.entities = entities 14 | self.hud = hud 15 | self.grid_length_x = grid_length_x 16 | self.grid_length_y = grid_length_y 17 | self.width = width 18 | self.height = height 19 | 20 | self.perlin_scale = grid_length_x/2 21 | 22 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 23 | self.tiles = self.load_images() 24 | self.world = self.create_world() 25 | 26 | self.buildings = [[None for x in range(self.grid_length_x)] for y in range(self.grid_length_y)] 27 | 28 | self.temp_tile = None 29 | self.examine_tile = None 30 | 31 | def update(self, camera): 32 | 33 | mouse_pos = pg.mouse.get_pos() 34 | mouse_action = pg.mouse.get_pressed() 35 | 36 | if mouse_action[2]: 37 | self.examine_tile = None 38 | self.hud.examined_tile = None 39 | 40 | self.temp_tile = None 41 | if self.hud.selected_tile is not None: 42 | 43 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 44 | 45 | if self.can_place_tile(grid_pos): 46 | img = self.hud.selected_tile["image"].copy() 47 | img.set_alpha(100) 48 | 49 | render_pos = self.world[grid_pos[0]][grid_pos[1]]["render_pos"] 50 | iso_poly = self.world[grid_pos[0]][grid_pos[1]]["iso_poly"] 51 | collision = self.world[grid_pos[0]][grid_pos[1]]["collision"] 52 | 53 | self.temp_tile = { 54 | "image": img, 55 | "render_pos": render_pos, 56 | "iso_poly": iso_poly, 57 | "collision": collision 58 | } 59 | 60 | if mouse_action[0] and not collision: 61 | if self.hud.selected_tile["name"] == "lumbermill": 62 | ent = Lumbermill(render_pos) 63 | self.entities.append(ent) 64 | self.buildings[grid_pos[0]][grid_pos[1]] = ent 65 | elif self.hud.selected_tile["name"] == "stonemasonry": 66 | ent = Stonemasonry(render_pos) 67 | self.entities.append(ent) 68 | self.buildings[grid_pos[0]][grid_pos[1]] = ent 69 | self.world[grid_pos[0]][grid_pos[1]]["collision"] = True 70 | self.hud.selected_tile = None 71 | 72 | else: 73 | 74 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 75 | 76 | if self.can_place_tile(grid_pos): 77 | building = self.buildings[grid_pos[0]][grid_pos[1]] 78 | if mouse_action[0] and (building is not None): 79 | self.examine_tile = grid_pos 80 | self.hud.examined_tile = building 81 | 82 | 83 | def draw(self, screen, camera): 84 | 85 | screen.blit(self.grass_tiles, (camera.scroll.x, camera.scroll.y)) 86 | 87 | for x in range(self.grid_length_x): 88 | for y in range(self.grid_length_y): 89 | render_pos = self.world[x][y]["render_pos"] 90 | # draw world tiles 91 | tile = self.world[x][y]["tile"] 92 | if tile != "": 93 | screen.blit(self.tiles[tile], 94 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 95 | render_pos[1] - (self.tiles[tile].get_height() - TILE_SIZE) + camera.scroll.y)) 96 | 97 | # draw buildings 98 | building = self.buildings[x][y] 99 | if building is not None: 100 | screen.blit(building.image, 101 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 102 | render_pos[1] - (building.image.get_height() - TILE_SIZE) + camera.scroll.y)) 103 | if self.examine_tile is not None: 104 | if (x == self.examine_tile[0]) and (y == self.examine_tile[1]): 105 | mask = pg.mask.from_surface(building.image).outline() 106 | mask = [(x + render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, y + render_pos[1] - (building.image.get_height() - TILE_SIZE) + camera.scroll.y) for x, y in mask] 107 | pg.draw.polygon(screen, (255, 255, 255), mask, 3) 108 | 109 | if self.temp_tile is not None: 110 | iso_poly = self.temp_tile["iso_poly"] 111 | iso_poly = [(x + self.grass_tiles.get_width()/2 + camera.scroll.x, y + camera.scroll.y) for x, y in iso_poly] 112 | if self.temp_tile["collision"]: 113 | pg.draw.polygon(screen, (255, 0, 0), iso_poly, 3) 114 | else: 115 | pg.draw.polygon(screen, (255, 255, 255), iso_poly, 3) 116 | render_pos = self.temp_tile["render_pos"] 117 | screen.blit( 118 | self.temp_tile["image"], 119 | ( 120 | render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 121 | render_pos[1] - (self.temp_tile["image"].get_height() - TILE_SIZE) + camera.scroll.y 122 | ) 123 | ) 124 | 125 | def create_world(self): 126 | 127 | world = [] 128 | 129 | for grid_x in range(self.grid_length_x): 130 | world.append([]) 131 | for grid_y in range(self.grid_length_y): 132 | world_tile = self.grid_to_world(grid_x, grid_y) 133 | world[grid_x].append(world_tile) 134 | 135 | render_pos = world_tile["render_pos"] 136 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 137 | 138 | 139 | return world 140 | 141 | def grid_to_world(self, grid_x, grid_y): 142 | 143 | rect = [ 144 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 145 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 146 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 147 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 148 | ] 149 | 150 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 151 | 152 | minx = min([x for x, y in iso_poly]) 153 | miny = min([y for x, y in iso_poly]) 154 | 155 | r = random.randint(1, 100) 156 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 157 | 158 | if (perlin >= 15) or (perlin <= -35): 159 | tile = "tree" 160 | else: 161 | if r == 1: 162 | tile = "tree" 163 | elif r == 2: 164 | tile = "rock" 165 | else: 166 | tile = "" 167 | 168 | out = { 169 | "grid": [grid_x, grid_y], 170 | "cart_rect": rect, 171 | "iso_poly": iso_poly, 172 | "render_pos": [minx, miny], 173 | "tile": tile, 174 | "collision": False if tile == "" else True 175 | } 176 | 177 | return out 178 | 179 | def cart_to_iso(self, x, y): 180 | iso_x = x - y 181 | iso_y = (x + y)/2 182 | return iso_x, iso_y 183 | 184 | def mouse_to_grid(self, x, y, scroll): 185 | # transform to world position (removing camera scroll and offset) 186 | world_x = x - scroll.x - self.grass_tiles.get_width()/2 187 | world_y = y - scroll.y 188 | # transform to cart (inverse of cart_to_iso) 189 | cart_y = (2*world_y - world_x)/2 190 | cart_x = cart_y + world_x 191 | # transform to grid coordinates 192 | grid_x = int(cart_x // TILE_SIZE) 193 | grid_y = int(cart_y // TILE_SIZE) 194 | return grid_x, grid_y 195 | 196 | def load_images(self): 197 | 198 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 199 | # read images 200 | building1 = pg.image.load("assets/graphics/building01.png").convert_alpha() 201 | building2 = pg.image.load("assets/graphics/building02.png").convert_alpha() 202 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 203 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 204 | 205 | images = { 206 | "building1": building1, 207 | "building2": building2, 208 | "tree": tree, 209 | "rock": rock, 210 | "block": block 211 | } 212 | 213 | return images 214 | 215 | def can_place_tile(self, grid_pos): 216 | mouse_on_panel = False 217 | for rect in [self.hud.resources_rect, self.hud.build_rect, self.hud.select_rect]: 218 | if rect.collidepoint(pg.mouse.get_pos()): 219 | mouse_on_panel = True 220 | world_bounds = (0 <= grid_pos[0] <= self.grid_length_x) and (0 <= grid_pos[1] <= self.grid_length_x) 221 | 222 | if world_bounds and not mouse_on_panel: 223 | return True 224 | else: 225 | return False 226 | 227 | -------------------------------------------------------------------------------- /part7/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part8/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/assets/graphics/block.png -------------------------------------------------------------------------------- /part8/assets/graphics/building01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/assets/graphics/building01.png -------------------------------------------------------------------------------- /part8/assets/graphics/building02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/assets/graphics/building02.png -------------------------------------------------------------------------------- /part8/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/assets/graphics/rock.png -------------------------------------------------------------------------------- /part8/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/assets/graphics/tree.png -------------------------------------------------------------------------------- /part8/game/__pycache__/buildings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/buildings.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/hud.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/hud.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/resource_manager.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/resource_manager.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part8/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part8/game/buildings.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | 6 | class Lumbermill: 7 | 8 | def __init__(self, pos, resource_manager): 9 | image = pg.image.load("assets/graphics/building01.png") 10 | self.image = image 11 | self.name = "lumbermill" 12 | self.rect = self.image.get_rect(topleft=pos) 13 | self.resource_manager = resource_manager 14 | self.resource_manager.apply_cost_to_resource(self.name) 15 | self.resource_cooldown = pg.time.get_ticks() 16 | 17 | def update(self): 18 | now = pg.time.get_ticks() 19 | if now - self.resource_cooldown > 2000: 20 | self.resource_manager.resources["wood"] += 1 21 | self.resource_cooldown = now 22 | 23 | 24 | 25 | class Stonemasonry: 26 | 27 | def __init__(self, pos, resource_manager): 28 | image = pg.image.load("assets/graphics/building02.png") 29 | self.image = image 30 | self.name = "stonemasonry" 31 | self.rect = self.image.get_rect(topleft=pos) 32 | self.resource_manager = resource_manager 33 | self.resource_manager.apply_cost_to_resource(self.name) 34 | self.resource_cooldown = pg.time.get_ticks() 35 | 36 | def update(self): 37 | now = pg.time.get_ticks() 38 | if now - self.resource_cooldown > 2000: 39 | self.resource_manager.resources["stone"] += 1 40 | self.resource_cooldown = now 41 | 42 | 43 | -------------------------------------------------------------------------------- /part8/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part8/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | from .hud import Hud 8 | from .resource_manager import ResourceManager 9 | 10 | 11 | class Game: 12 | 13 | def __init__(self, screen, clock): 14 | self.screen = screen 15 | self.clock = clock 16 | self.width, self.height = self.screen.get_size() 17 | 18 | # entities 19 | self.entities = [] 20 | 21 | # resource manager 22 | self.resource_manager = ResourceManager() 23 | 24 | # hud 25 | self.hud = Hud(self.resource_manager, self.width, self.height) 26 | 27 | # world 28 | self.world = World(self.resource_manager, self.entities, self.hud, 50, 50, self.width, self.height) 29 | 30 | # camera 31 | self.camera = Camera(self.width, self.height) 32 | 33 | def run(self): 34 | self.playing = True 35 | while self.playing: 36 | self.clock.tick(60) 37 | self.events() 38 | self.update() 39 | self.draw() 40 | 41 | def events(self): 42 | for event in pg.event.get(): 43 | if event.type == pg.QUIT: 44 | pg.quit() 45 | sys.exit() 46 | if event.type == pg.KEYDOWN: 47 | if event.key == pg.K_ESCAPE: 48 | pg.quit() 49 | sys.exit() 50 | 51 | def update(self): 52 | self.camera.update() 53 | for e in self.entities: e.update() 54 | self.hud.update() 55 | self.world.update(self.camera) 56 | 57 | def draw(self): 58 | self.screen.fill((0, 0, 0)) 59 | self.world.draw(self.screen, self.camera) 60 | self.hud.draw(self.screen) 61 | 62 | draw_text( 63 | self.screen, 64 | 'fps={}'.format(round(self.clock.get_fps())), 65 | 25, 66 | (255, 255, 255), 67 | (10, 10) 68 | ) 69 | 70 | pg.display.flip() 71 | 72 | -------------------------------------------------------------------------------- /part8/game/hud.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .utils import draw_text 4 | 5 | 6 | class Hud: 7 | 8 | def __init__(self, resource_manager, width, height): 9 | 10 | self.resource_manager = resource_manager 11 | self.width = width 12 | self.height = height 13 | 14 | self.hud_colour = (198, 155, 93, 175) 15 | 16 | # resouces hud 17 | self.resouces_surface = pg.Surface((width, height * 0.02), pg.SRCALPHA) 18 | self.resources_rect = self.resouces_surface.get_rect(topleft=(0, 0)) 19 | self.resouces_surface.fill(self.hud_colour) 20 | 21 | # building hud 22 | self.build_surface = pg.Surface((width * 0.15, height * 0.25), pg.SRCALPHA) 23 | self.build_rect = self.build_surface.get_rect(topleft=(self.width * 0.84, self.height * 0.74)) 24 | self.build_surface.fill(self.hud_colour) 25 | 26 | # select hud 27 | self.select_surface = pg.Surface((width * 0.3, height * 0.2), pg.SRCALPHA) 28 | self.select_rect = self.select_surface.get_rect(topleft=(self.width * 0.35, self.height * 0.79)) 29 | self.select_surface.fill(self.hud_colour) 30 | 31 | self.images = self.load_images() 32 | self.tiles = self.create_build_hud() 33 | 34 | self.selected_tile = None 35 | self.examined_tile = None 36 | 37 | def create_build_hud(self): 38 | 39 | render_pos = [self.width * 0.84 + 10, self.height * 0.74 + 10] 40 | object_width = self.build_surface.get_width() // 5 41 | 42 | tiles = [] 43 | 44 | for image_name, image in self.images.items(): 45 | 46 | pos = render_pos.copy() 47 | image_tmp = image.copy() 48 | image_scale = self.scale_image(image_tmp, w=object_width) 49 | rect = image_scale.get_rect(topleft=pos) 50 | 51 | tiles.append( 52 | { 53 | "name": image_name, 54 | "icon": image_scale, 55 | "image": self.images[image_name], 56 | "rect": rect, 57 | "affordable": True 58 | } 59 | ) 60 | 61 | render_pos[0] += image_scale.get_width() + 10 62 | 63 | return tiles 64 | 65 | def update(self): 66 | 67 | mouse_pos = pg.mouse.get_pos() 68 | mouse_action = pg.mouse.get_pressed() 69 | 70 | if mouse_action[2]: 71 | self.selected_tile = None 72 | 73 | for tile in self.tiles: 74 | if self.resource_manager.is_affordable(tile["name"]): 75 | tile["affordable"] = True 76 | else: 77 | tile["affordable"] = False 78 | if tile["rect"].collidepoint(mouse_pos) and tile["affordable"]: 79 | if mouse_action[0]: 80 | self.selected_tile = tile 81 | 82 | def draw(self, screen): 83 | 84 | # resouce hud 85 | screen.blit(self.resouces_surface, (0, 0)) 86 | # build hud 87 | screen.blit(self.build_surface, (self.width * 0.84, self.height * 0.74)) 88 | # select hud 89 | if self.examined_tile is not None: 90 | w, h = self.select_rect.width, self.select_rect.height 91 | screen.blit(self.select_surface, (self.width * 0.35, self.height * 0.79)) 92 | img = self.examined_tile.image.copy() 93 | img_scale = self.scale_image(img, h=h*0.7) 94 | screen.blit(img_scale, (self.width * 0.35 + 10, self.height * 0.79 + 40)) 95 | draw_text(screen, self.examined_tile.name, 40, (255, 255, 255), self.select_rect.topleft) 96 | 97 | for tile in self.tiles: 98 | icon = tile["icon"].copy() 99 | if not tile["affordable"]: 100 | icon.set_alpha(100) 101 | screen.blit(icon, tile["rect"].topleft) 102 | 103 | # resources 104 | pos = self.width - 400 105 | for resource, resource_value in self.resource_manager.resources.items(): 106 | txt = resource + ": " + str(resource_value) 107 | draw_text(screen, txt, 30, (255, 255, 255), (pos, 0)) 108 | pos += 100 109 | 110 | def load_images(self): 111 | 112 | # read images 113 | lumbermill = pg.image.load("assets/graphics/building01.png") 114 | stonemasonry = pg.image.load("assets/graphics/building02.png") 115 | 116 | images = { 117 | "lumbermill": lumbermill, 118 | "stonemasonry": stonemasonry 119 | } 120 | 121 | return images 122 | 123 | def scale_image(self, image, w=None, h=None): 124 | 125 | if (w == None) and (h == None): 126 | pass 127 | elif h == None: 128 | scale = w / image.get_width() 129 | h = scale * image.get_height() 130 | image = pg.transform.scale(image, (int(w), int(h))) 131 | elif w == None: 132 | scale = h / image.get_height() 133 | w = scale * image.get_width() 134 | image = pg.transform.scale(image, (int(w), int(h))) 135 | else: 136 | image = pg.transform.scale(image, (int(w), int(h))) 137 | 138 | return image 139 | 140 | -------------------------------------------------------------------------------- /part8/game/resource_manager.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | 6 | class ResourceManager: 7 | 8 | 9 | def __init__(self): 10 | 11 | # resources 12 | self.resources = { 13 | "wood": 10, 14 | "stone": 10 15 | } 16 | 17 | #costs 18 | self.costs = { 19 | "lumbermill": {"wood": 7, "stone": 3}, 20 | "stonemasonry": {"wood": 3, "stone": 5} 21 | } 22 | 23 | def apply_cost_to_resource(self, building): 24 | for resource, cost in self.costs[building].items(): 25 | self.resources[resource] -= cost 26 | 27 | def is_affordable(self, building): 28 | affordable = True 29 | for resource, cost in self.costs[building].items(): 30 | if cost > self.resources[resource]: 31 | affordable = False 32 | return affordable 33 | 34 | -------------------------------------------------------------------------------- /part8/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part8/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part8/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | from .buildings import Lumbermill, Stonemasonry 7 | 8 | 9 | 10 | class World: 11 | 12 | def __init__(self, resource_manager, entities, hud, grid_length_x, grid_length_y, width, height): 13 | self.resource_manager = resource_manager 14 | self.entities = entities 15 | self.hud = hud 16 | self.grid_length_x = grid_length_x 17 | self.grid_length_y = grid_length_y 18 | self.width = width 19 | self.height = height 20 | 21 | self.perlin_scale = grid_length_x/2 22 | 23 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 24 | self.tiles = self.load_images() 25 | self.world = self.create_world() 26 | 27 | self.buildings = [[None for x in range(self.grid_length_x)] for y in range(self.grid_length_y)] 28 | 29 | self.temp_tile = None 30 | self.examine_tile = None 31 | 32 | def update(self, camera): 33 | 34 | mouse_pos = pg.mouse.get_pos() 35 | mouse_action = pg.mouse.get_pressed() 36 | 37 | if mouse_action[2]: 38 | self.examine_tile = None 39 | self.hud.examined_tile = None 40 | 41 | self.temp_tile = None 42 | if self.hud.selected_tile is not None: 43 | 44 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 45 | 46 | if self.can_place_tile(grid_pos): 47 | img = self.hud.selected_tile["image"].copy() 48 | img.set_alpha(100) 49 | 50 | render_pos = self.world[grid_pos[0]][grid_pos[1]]["render_pos"] 51 | iso_poly = self.world[grid_pos[0]][grid_pos[1]]["iso_poly"] 52 | collision = self.world[grid_pos[0]][grid_pos[1]]["collision"] 53 | 54 | self.temp_tile = { 55 | "image": img, 56 | "render_pos": render_pos, 57 | "iso_poly": iso_poly, 58 | "collision": collision 59 | } 60 | 61 | if mouse_action[0] and not collision: 62 | if self.hud.selected_tile["name"] == "lumbermill": 63 | ent = Lumbermill(render_pos, self.resource_manager) 64 | self.entities.append(ent) 65 | self.buildings[grid_pos[0]][grid_pos[1]] = ent 66 | elif self.hud.selected_tile["name"] == "stonemasonry": 67 | ent = Stonemasonry(render_pos, self.resource_manager) 68 | self.entities.append(ent) 69 | self.buildings[grid_pos[0]][grid_pos[1]] = ent 70 | self.world[grid_pos[0]][grid_pos[1]]["collision"] = True 71 | self.hud.selected_tile = None 72 | 73 | else: 74 | 75 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 76 | 77 | if self.can_place_tile(grid_pos): 78 | building = self.buildings[grid_pos[0]][grid_pos[1]] 79 | if mouse_action[0] and (building is not None): 80 | self.examine_tile = grid_pos 81 | self.hud.examined_tile = building 82 | 83 | 84 | def draw(self, screen, camera): 85 | 86 | screen.blit(self.grass_tiles, (camera.scroll.x, camera.scroll.y)) 87 | 88 | for x in range(self.grid_length_x): 89 | for y in range(self.grid_length_y): 90 | render_pos = self.world[x][y]["render_pos"] 91 | # draw world tiles 92 | tile = self.world[x][y]["tile"] 93 | if tile != "": 94 | screen.blit(self.tiles[tile], 95 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 96 | render_pos[1] - (self.tiles[tile].get_height() - TILE_SIZE) + camera.scroll.y)) 97 | 98 | # draw buildings 99 | building = self.buildings[x][y] 100 | if building is not None: 101 | screen.blit(building.image, 102 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 103 | render_pos[1] - (building.image.get_height() - TILE_SIZE) + camera.scroll.y)) 104 | if self.examine_tile is not None: 105 | if (x == self.examine_tile[0]) and (y == self.examine_tile[1]): 106 | mask = pg.mask.from_surface(building.image).outline() 107 | mask = [(x + render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, y + render_pos[1] - (building.image.get_height() - TILE_SIZE) + camera.scroll.y) for x, y in mask] 108 | pg.draw.polygon(screen, (255, 255, 255), mask, 3) 109 | 110 | if self.temp_tile is not None: 111 | iso_poly = self.temp_tile["iso_poly"] 112 | iso_poly = [(x + self.grass_tiles.get_width()/2 + camera.scroll.x, y + camera.scroll.y) for x, y in iso_poly] 113 | if self.temp_tile["collision"]: 114 | pg.draw.polygon(screen, (255, 0, 0), iso_poly, 3) 115 | else: 116 | pg.draw.polygon(screen, (255, 255, 255), iso_poly, 3) 117 | render_pos = self.temp_tile["render_pos"] 118 | screen.blit( 119 | self.temp_tile["image"], 120 | ( 121 | render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 122 | render_pos[1] - (self.temp_tile["image"].get_height() - TILE_SIZE) + camera.scroll.y 123 | ) 124 | ) 125 | 126 | def create_world(self): 127 | 128 | world = [] 129 | 130 | for grid_x in range(self.grid_length_x): 131 | world.append([]) 132 | for grid_y in range(self.grid_length_y): 133 | world_tile = self.grid_to_world(grid_x, grid_y) 134 | world[grid_x].append(world_tile) 135 | 136 | render_pos = world_tile["render_pos"] 137 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 138 | 139 | 140 | return world 141 | 142 | def grid_to_world(self, grid_x, grid_y): 143 | 144 | rect = [ 145 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 146 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 147 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 148 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 149 | ] 150 | 151 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 152 | 153 | minx = min([x for x, y in iso_poly]) 154 | miny = min([y for x, y in iso_poly]) 155 | 156 | r = random.randint(1, 100) 157 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 158 | 159 | if (perlin >= 15) or (perlin <= -35): 160 | tile = "tree" 161 | else: 162 | if r == 1: 163 | tile = "tree" 164 | elif r == 2: 165 | tile = "rock" 166 | else: 167 | tile = "" 168 | 169 | out = { 170 | "grid": [grid_x, grid_y], 171 | "cart_rect": rect, 172 | "iso_poly": iso_poly, 173 | "render_pos": [minx, miny], 174 | "tile": tile, 175 | "collision": False if tile == "" else True 176 | } 177 | 178 | return out 179 | 180 | def cart_to_iso(self, x, y): 181 | iso_x = x - y 182 | iso_y = (x + y)/2 183 | return iso_x, iso_y 184 | 185 | def mouse_to_grid(self, x, y, scroll): 186 | # transform to world position (removing camera scroll and offset) 187 | world_x = x - scroll.x - self.grass_tiles.get_width()/2 188 | world_y = y - scroll.y 189 | # transform to cart (inverse of cart_to_iso) 190 | cart_y = (2*world_y - world_x)/2 191 | cart_x = cart_y + world_x 192 | # transform to grid coordinates 193 | grid_x = int(cart_x // TILE_SIZE) 194 | grid_y = int(cart_y // TILE_SIZE) 195 | return grid_x, grid_y 196 | 197 | def load_images(self): 198 | 199 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 200 | # read images 201 | building1 = pg.image.load("assets/graphics/building01.png").convert_alpha() 202 | building2 = pg.image.load("assets/graphics/building02.png").convert_alpha() 203 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 204 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 205 | 206 | images = { 207 | "building1": building1, 208 | "building2": building2, 209 | "tree": tree, 210 | "rock": rock, 211 | "block": block 212 | } 213 | 214 | return images 215 | 216 | def can_place_tile(self, grid_pos): 217 | mouse_on_panel = False 218 | for rect in [self.hud.resources_rect, self.hud.build_rect, self.hud.select_rect]: 219 | if rect.collidepoint(pg.mouse.get_pos()): 220 | mouse_on_panel = True 221 | world_bounds = (0 <= grid_pos[0] <= self.grid_length_x) and (0 <= grid_pos[1] <= self.grid_length_x) 222 | 223 | if world_bounds and not mouse_on_panel: 224 | return True 225 | else: 226 | return False 227 | 228 | -------------------------------------------------------------------------------- /part8/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | 5 | 6 | def main(): 7 | 8 | running = True 9 | playing = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | 18 | # implement game 19 | game = Game(screen, clock) 20 | 21 | while running: 22 | 23 | # start menu goes here 24 | 25 | while playing: 26 | # game loop here 27 | game.run() 28 | 29 | if __name__ == "__main__": 30 | main() 31 | 32 | -------------------------------------------------------------------------------- /part9/assets/graphics/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/assets/graphics/block.png -------------------------------------------------------------------------------- /part9/assets/graphics/building01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/assets/graphics/building01.png -------------------------------------------------------------------------------- /part9/assets/graphics/building02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/assets/graphics/building02.png -------------------------------------------------------------------------------- /part9/assets/graphics/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/assets/graphics/rock.png -------------------------------------------------------------------------------- /part9/assets/graphics/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/assets/graphics/tree.png -------------------------------------------------------------------------------- /part9/assets/graphics/worker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/assets/graphics/worker.png -------------------------------------------------------------------------------- /part9/game/__pycache__/buildings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/buildings.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/camera.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/camera.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/game.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/game.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/hud.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/hud.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/menu.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/menu.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/resource_manager.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/resource_manager.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/workers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/workers.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/__pycache__/world.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scartwright91/city_builder_tutorial/398edf5ae88e2aa58d1c0494aa461faffd0cc3c7/part9/game/__pycache__/world.cpython-38.pyc -------------------------------------------------------------------------------- /part9/game/buildings.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | 6 | class Lumbermill: 7 | 8 | def __init__(self, pos, resource_manager): 9 | image = pg.image.load("assets/graphics/building01.png") 10 | self.image = image 11 | self.name = "lumbermill" 12 | self.rect = self.image.get_rect(topleft=pos) 13 | self.resource_manager = resource_manager 14 | self.resource_manager.apply_cost_to_resource(self.name) 15 | self.resource_cooldown = pg.time.get_ticks() 16 | 17 | def update(self): 18 | now = pg.time.get_ticks() 19 | if now - self.resource_cooldown > 2000: 20 | self.resource_manager.resources["wood"] += 1 21 | self.resource_cooldown = now 22 | 23 | 24 | 25 | class Stonemasonry: 26 | 27 | def __init__(self, pos, resource_manager): 28 | image = pg.image.load("assets/graphics/building02.png") 29 | self.image = image 30 | self.name = "stonemasonry" 31 | self.rect = self.image.get_rect(topleft=pos) 32 | self.resource_manager = resource_manager 33 | self.resource_manager.apply_cost_to_resource(self.name) 34 | self.resource_cooldown = pg.time.get_ticks() 35 | 36 | def update(self): 37 | now = pg.time.get_ticks() 38 | if now - self.resource_cooldown > 2000: 39 | self.resource_manager.resources["stone"] += 1 40 | self.resource_cooldown = now 41 | 42 | 43 | -------------------------------------------------------------------------------- /part9/game/camera.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | class Camera: 6 | 7 | def __init__(self, width, height): 8 | 9 | self.width = width 10 | self.height = height 11 | 12 | self.scroll = pg.Vector2(0, 0) 13 | self.dx = 0 14 | self.dy = 0 15 | self.speed = 25 16 | 17 | def update(self): 18 | 19 | mouse_pos = pg.mouse.get_pos() 20 | 21 | # x movement 22 | if mouse_pos[0] > self.width * 0.97: 23 | self.dx = -self.speed 24 | elif mouse_pos[0] < self.width * 0.03: 25 | self.dx = self.speed 26 | else: 27 | self.dx = 0 28 | 29 | # y movement 30 | if mouse_pos[1] > self.height * 0.97: 31 | self.dy = -self.speed 32 | elif mouse_pos[1] < self.height * 0.03: 33 | self.dy = self.speed 34 | else: 35 | self.dy = 0 36 | 37 | # update camera scroll 38 | self.scroll.x += self.dx 39 | self.scroll.y += self.dy 40 | -------------------------------------------------------------------------------- /part9/game/game.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | import sys 3 | from .world import World 4 | from .settings import TILE_SIZE 5 | from .utils import draw_text 6 | from .camera import Camera 7 | from .hud import Hud 8 | from .resource_manager import ResourceManager 9 | from .workers import Worker 10 | 11 | 12 | class Game: 13 | 14 | def __init__(self, screen, clock): 15 | self.screen = screen 16 | self.clock = clock 17 | self.width, self.height = self.screen.get_size() 18 | 19 | # entities 20 | self.entities = [] 21 | 22 | # resource manager 23 | self.resource_manager = ResourceManager() 24 | 25 | # hud 26 | self.hud = Hud(self.resource_manager, self.width, self.height) 27 | 28 | # world 29 | self.world = World(self.resource_manager, self.entities, self.hud, 50, 50, self.width, self.height) 30 | for _ in range(10): Worker(self.world.world[25][25], self.world) 31 | 32 | # camera 33 | self.camera = Camera(self.width, self.height) 34 | 35 | def run(self): 36 | self.playing = True 37 | while self.playing: 38 | self.clock.tick(60) 39 | self.events() 40 | self.update() 41 | self.draw() 42 | 43 | def events(self): 44 | for event in pg.event.get(): 45 | if event.type == pg.QUIT: 46 | pg.quit() 47 | sys.exit() 48 | if event.type == pg.KEYDOWN: 49 | if event.key == pg.K_ESCAPE: 50 | self.playing = False 51 | 52 | def update(self): 53 | self.camera.update() 54 | for e in self.entities: e.update() 55 | self.hud.update() 56 | self.world.update(self.camera) 57 | 58 | def draw(self): 59 | self.screen.fill((0, 0, 0)) 60 | self.world.draw(self.screen, self.camera) 61 | self.hud.draw(self.screen) 62 | 63 | draw_text( 64 | self.screen, 65 | 'fps={}'.format(round(self.clock.get_fps())), 66 | 25, 67 | (255, 255, 255), 68 | (10, 10) 69 | ) 70 | 71 | pg.display.flip() 72 | 73 | -------------------------------------------------------------------------------- /part9/game/hud.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from .utils import draw_text 4 | 5 | 6 | class Hud: 7 | 8 | def __init__(self, resource_manager, width, height): 9 | 10 | self.resource_manager = resource_manager 11 | self.width = width 12 | self.height = height 13 | 14 | self.hud_colour = (198, 155, 93, 175) 15 | 16 | # resouces hud 17 | self.resouces_surface = pg.Surface((width, height * 0.02), pg.SRCALPHA) 18 | self.resources_rect = self.resouces_surface.get_rect(topleft=(0, 0)) 19 | self.resouces_surface.fill(self.hud_colour) 20 | 21 | # building hud 22 | self.build_surface = pg.Surface((width * 0.15, height * 0.25), pg.SRCALPHA) 23 | self.build_rect = self.build_surface.get_rect(topleft=(self.width * 0.84, self.height * 0.74)) 24 | self.build_surface.fill(self.hud_colour) 25 | 26 | # select hud 27 | self.select_surface = pg.Surface((width * 0.3, height * 0.2), pg.SRCALPHA) 28 | self.select_rect = self.select_surface.get_rect(topleft=(self.width * 0.35, self.height * 0.79)) 29 | self.select_surface.fill(self.hud_colour) 30 | 31 | self.images = self.load_images() 32 | self.tiles = self.create_build_hud() 33 | 34 | self.selected_tile = None 35 | self.examined_tile = None 36 | 37 | def create_build_hud(self): 38 | 39 | render_pos = [self.width * 0.84 + 10, self.height * 0.74 + 10] 40 | object_width = self.build_surface.get_width() // 5 41 | 42 | tiles = [] 43 | 44 | for image_name, image in self.images.items(): 45 | 46 | pos = render_pos.copy() 47 | image_tmp = image.copy() 48 | image_scale = self.scale_image(image_tmp, w=object_width) 49 | rect = image_scale.get_rect(topleft=pos) 50 | 51 | tiles.append( 52 | { 53 | "name": image_name, 54 | "icon": image_scale, 55 | "image": self.images[image_name], 56 | "rect": rect, 57 | "affordable": True 58 | } 59 | ) 60 | 61 | render_pos[0] += image_scale.get_width() + 10 62 | 63 | return tiles 64 | 65 | def update(self): 66 | 67 | mouse_pos = pg.mouse.get_pos() 68 | mouse_action = pg.mouse.get_pressed() 69 | 70 | if mouse_action[2]: 71 | self.selected_tile = None 72 | 73 | for tile in self.tiles: 74 | if self.resource_manager.is_affordable(tile["name"]): 75 | tile["affordable"] = True 76 | else: 77 | tile["affordable"] = False 78 | if tile["rect"].collidepoint(mouse_pos) and tile["affordable"]: 79 | if mouse_action[0]: 80 | self.selected_tile = tile 81 | 82 | def draw(self, screen): 83 | 84 | # resouce hud 85 | screen.blit(self.resouces_surface, (0, 0)) 86 | # build hud 87 | screen.blit(self.build_surface, (self.width * 0.84, self.height * 0.74)) 88 | # select hud 89 | if self.examined_tile is not None: 90 | w, h = self.select_rect.width, self.select_rect.height 91 | screen.blit(self.select_surface, (self.width * 0.35, self.height * 0.79)) 92 | img = self.examined_tile.image.copy() 93 | img_scale = self.scale_image(img, h=h*0.7) 94 | screen.blit(img_scale, (self.width * 0.35 + 10, self.height * 0.79 + 40)) 95 | draw_text(screen, self.examined_tile.name, 40, (255, 255, 255), self.select_rect.topleft) 96 | 97 | for tile in self.tiles: 98 | icon = tile["icon"].copy() 99 | if not tile["affordable"]: 100 | icon.set_alpha(100) 101 | screen.blit(icon, tile["rect"].topleft) 102 | 103 | # resources 104 | pos = self.width - 400 105 | for resource, resource_value in self.resource_manager.resources.items(): 106 | txt = resource + ": " + str(resource_value) 107 | draw_text(screen, txt, 30, (255, 255, 255), (pos, 0)) 108 | pos += 100 109 | 110 | def load_images(self): 111 | 112 | # read images 113 | lumbermill = pg.image.load("assets/graphics/building01.png") 114 | stonemasonry = pg.image.load("assets/graphics/building02.png") 115 | 116 | images = { 117 | "lumbermill": lumbermill, 118 | "stonemasonry": stonemasonry 119 | } 120 | 121 | return images 122 | 123 | def scale_image(self, image, w=None, h=None): 124 | 125 | if (w == None) and (h == None): 126 | pass 127 | elif h == None: 128 | scale = w / image.get_width() 129 | h = scale * image.get_height() 130 | image = pg.transform.scale(image, (int(w), int(h))) 131 | elif w == None: 132 | scale = h / image.get_height() 133 | w = scale * image.get_width() 134 | image = pg.transform.scale(image, (int(w), int(h))) 135 | else: 136 | image = pg.transform.scale(image, (int(w), int(h))) 137 | 138 | return image 139 | 140 | -------------------------------------------------------------------------------- /part9/game/menu.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import sys 4 | from .utils import * 5 | 6 | 7 | 8 | class StartMenu: 9 | 10 | def __init__(self, screen, clock): 11 | self.screen = screen 12 | self.clock = clock 13 | self.screen_size = self.screen.get_size() 14 | 15 | def run(self): 16 | self.menu_running = True 17 | while self.menu_running: 18 | self.clock.tick(60) 19 | self.update() 20 | self.draw() 21 | return True 22 | 23 | def update(self): 24 | 25 | for event in pg.event.get(): 26 | if event.type == pg.QUIT: 27 | pg.quit() 28 | sys.exit() 29 | if event.type == pg.KEYDOWN: 30 | if event.key == pg.K_RETURN: 31 | self.menu_running = False 32 | if event.key == pg.K_ESCAPE: 33 | pg.quit() 34 | sys.exit() 35 | 36 | def draw(self): 37 | 38 | self.screen.fill((0, 0, 0)) 39 | 40 | draw_text(self.screen, 41 | 'City building tutorial', 42 | 100, 43 | (255, 255, 255), 44 | (0, self.screen_size[1]*0.3) 45 | ) 46 | draw_text(self.screen, 47 | 'ENTER to play', 48 | 60, 49 | (255, 255, 255), 50 | (0, self.screen_size[1]*0.5) 51 | ) 52 | 53 | pg.display.flip() 54 | 55 | 56 | 57 | class GameMenu: 58 | def __init__(self, screen, clock): 59 | self.screen = screen 60 | self.clock = clock 61 | self.screen_size = self.screen.get_size() 62 | self.playing = True 63 | 64 | def run(self): 65 | self.menu_running = True 66 | while self.menu_running: 67 | self.clock.tick(60) 68 | self.update() 69 | self.draw() 70 | return self.playing 71 | 72 | def update(self): 73 | for event in pg.event.get(): 74 | if event.type == pg.QUIT: 75 | pg.quit() 76 | sys.exit() 77 | if event.type == pg.KEYDOWN: 78 | if event.key == pg.K_RETURN: 79 | self.menu_running = False 80 | if event.key == pg.K_ESCAPE: 81 | self.playing = False 82 | self.menu_running = False 83 | 84 | def draw(self): 85 | 86 | self.screen.fill((0, 0, 0)) 87 | 88 | draw_text(self.screen, 89 | 'PAUSE', 90 | 100, 91 | (255, 255, 255), 92 | (0, self.screen_size[1]*0.3) 93 | ) 94 | draw_text(self.screen, 95 | 'ESC to quit, ENTER to play', 96 | 60, 97 | (255, 255, 255), 98 | (0, self.screen_size[1]*0.5) 99 | ) 100 | 101 | pg.display.flip() 102 | 103 | -------------------------------------------------------------------------------- /part9/game/resource_manager.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | 6 | class ResourceManager: 7 | 8 | 9 | def __init__(self): 10 | 11 | # resources 12 | self.resources = { 13 | "wood": 10, 14 | "stone": 10 15 | } 16 | 17 | #costs 18 | self.costs = { 19 | "lumbermill": {"wood": 7, "stone": 3}, 20 | "stonemasonry": {"wood": 3, "stone": 5} 21 | } 22 | 23 | def apply_cost_to_resource(self, building): 24 | for resource, cost in self.costs[building].items(): 25 | self.resources[resource] -= cost 26 | 27 | def is_affordable(self, building): 28 | affordable = True 29 | for resource, cost in self.costs[building].items(): 30 | if cost > self.resources[resource]: 31 | affordable = False 32 | return affordable 33 | 34 | -------------------------------------------------------------------------------- /part9/game/settings.py: -------------------------------------------------------------------------------- 1 | TILE_SIZE = 64 -------------------------------------------------------------------------------- /part9/game/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | 4 | 5 | def draw_text(screen, text, size, colour, pos): 6 | 7 | font = pg.font.SysFont(None, size) 8 | text_surface = font.render(text, True, colour) 9 | text_rect = text_surface.get_rect(topleft=pos) 10 | 11 | screen.blit(text_surface, text_rect) 12 | -------------------------------------------------------------------------------- /part9/game/workers.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | from pathfinding.core.diagonal_movement import DiagonalMovement 5 | from pathfinding.core.grid import Grid 6 | from pathfinding.finder.a_star import AStarFinder 7 | 8 | 9 | 10 | class Worker: 11 | 12 | def __init__(self, tile, world): 13 | self.world = world 14 | self.world.entities.append(self) 15 | image = pg.image.load("assets/graphics/worker.png").convert_alpha() 16 | self.name = "worker" 17 | self.image = pg.transform.scale(image, (image.get_width()*2, image.get_height()*2)) 18 | self.tile = tile 19 | 20 | # pathfinding 21 | self.world.workers[tile["grid"][0]][tile["grid"][1]] = self 22 | self.move_timer = pg.time.get_ticks() 23 | 24 | self.create_path() 25 | 26 | def create_path(self): 27 | searching_for_path = True 28 | while searching_for_path: 29 | x = random.randint(0, self.world.grid_length_x - 1) 30 | y = random.randint(0, self.world.grid_length_y - 1) 31 | dest_tile = self.world.world[x][y] 32 | if not dest_tile["collision"]: 33 | self.grid = Grid(matrix=self.world.collision_matrix) 34 | self.start = self.grid.node(self.tile["grid"][0], self.tile["grid"][1]) 35 | self.end = self.grid.node(x, y) 36 | finder = AStarFinder(diagonal_movement=DiagonalMovement.never) 37 | self.path_index = 0 38 | self.path, runs = finder.find_path(self.start, self.end, self.grid) 39 | searching_for_path = False 40 | 41 | def change_tile(self, new_tile): 42 | self.world.workers[self.tile["grid"][0]][self.tile["grid"][1]] = None 43 | self.world.workers[new_tile[0]][new_tile[1]] = self 44 | self.tile = self.world.world[new_tile[0]][new_tile[1]] 45 | 46 | def update(self): 47 | now = pg.time.get_ticks() 48 | if now - self.move_timer > 1000: 49 | new_pos = self.path[self.path_index] 50 | # update position in the world 51 | self.change_tile(new_pos) 52 | self.path_index += 1 53 | self.move_timer = now 54 | if self.path_index == len(self.path) - 1: 55 | self.create_path() 56 | 57 | 58 | -------------------------------------------------------------------------------- /part9/game/world.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | import random 4 | import noise 5 | from .settings import TILE_SIZE 6 | from .buildings import Lumbermill, Stonemasonry 7 | 8 | 9 | 10 | class World: 11 | 12 | def __init__(self, resource_manager, entities, hud, grid_length_x, grid_length_y, width, height): 13 | self.resource_manager = resource_manager 14 | self.entities = entities 15 | self.hud = hud 16 | self.grid_length_x = grid_length_x 17 | self.grid_length_y = grid_length_y 18 | self.width = width 19 | self.height = height 20 | 21 | self.perlin_scale = grid_length_x/2 22 | 23 | self.grass_tiles = pg.Surface((grid_length_x * TILE_SIZE * 2, grid_length_y * TILE_SIZE + 2 * TILE_SIZE)).convert_alpha() 24 | self.tiles = self.load_images() 25 | self.world = self.create_world() 26 | self.collision_matrix = self.create_collision_matrix() 27 | 28 | self.buildings = [[None for x in range(self.grid_length_x)] for y in range(self.grid_length_y)] 29 | self.workers = [[None for x in range(self.grid_length_x)] for y in range(self.grid_length_y)] 30 | 31 | self.temp_tile = None 32 | self.examine_tile = None 33 | 34 | def update(self, camera): 35 | 36 | mouse_pos = pg.mouse.get_pos() 37 | mouse_action = pg.mouse.get_pressed() 38 | 39 | if mouse_action[2]: 40 | self.examine_tile = None 41 | self.hud.examined_tile = None 42 | 43 | self.temp_tile = None 44 | if self.hud.selected_tile is not None: 45 | 46 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 47 | 48 | if self.can_place_tile(grid_pos): 49 | img = self.hud.selected_tile["image"].copy() 50 | img.set_alpha(100) 51 | 52 | render_pos = self.world[grid_pos[0]][grid_pos[1]]["render_pos"] 53 | iso_poly = self.world[grid_pos[0]][grid_pos[1]]["iso_poly"] 54 | collision = self.world[grid_pos[0]][grid_pos[1]]["collision"] 55 | 56 | self.temp_tile = { 57 | "image": img, 58 | "render_pos": render_pos, 59 | "iso_poly": iso_poly, 60 | "collision": collision 61 | } 62 | 63 | if mouse_action[0] and not collision: 64 | if self.hud.selected_tile["name"] == "lumbermill": 65 | ent = Lumbermill(render_pos, self.resource_manager) 66 | self.entities.append(ent) 67 | self.buildings[grid_pos[0]][grid_pos[1]] = ent 68 | elif self.hud.selected_tile["name"] == "stonemasonry": 69 | ent = Stonemasonry(render_pos, self.resource_manager) 70 | self.entities.append(ent) 71 | self.buildings[grid_pos[0]][grid_pos[1]] = ent 72 | self.world[grid_pos[0]][grid_pos[1]]["collision"] = True 73 | self.collision_matrix[grid_pos[1]][grid_pos[0]] = 0 74 | self.hud.selected_tile = None 75 | 76 | else: 77 | 78 | grid_pos = self.mouse_to_grid(mouse_pos[0], mouse_pos[1], camera.scroll) 79 | 80 | if self.can_place_tile(grid_pos): 81 | building = self.buildings[grid_pos[0]][grid_pos[1]] 82 | if mouse_action[0] and (building is not None): 83 | self.examine_tile = grid_pos 84 | self.hud.examined_tile = building 85 | 86 | 87 | def draw(self, screen, camera): 88 | 89 | screen.blit(self.grass_tiles, (camera.scroll.x, camera.scroll.y)) 90 | 91 | for x in range(self.grid_length_x): 92 | for y in range(self.grid_length_y): 93 | render_pos = self.world[x][y]["render_pos"] 94 | # draw world tiles 95 | tile = self.world[x][y]["tile"] 96 | if tile != "": 97 | screen.blit(self.tiles[tile], 98 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 99 | render_pos[1] - (self.tiles[tile].get_height() - TILE_SIZE) + camera.scroll.y)) 100 | 101 | # draw buildings 102 | building = self.buildings[x][y] 103 | if building is not None: 104 | screen.blit(building.image, 105 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 106 | render_pos[1] - (building.image.get_height() - TILE_SIZE) + camera.scroll.y)) 107 | if self.examine_tile is not None: 108 | if (x == self.examine_tile[0]) and (y == self.examine_tile[1]): 109 | mask = pg.mask.from_surface(building.image).outline() 110 | mask = [(x + render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, y + render_pos[1] - (building.image.get_height() - TILE_SIZE) + camera.scroll.y) for x, y in mask] 111 | pg.draw.polygon(screen, (255, 255, 255), mask, 3) 112 | 113 | # draw workers 114 | worker = self.workers[x][y] 115 | if worker is not None: 116 | screen.blit(worker.image, 117 | (render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 118 | render_pos[1] - (worker.image.get_height() - TILE_SIZE) + camera.scroll.y)) 119 | 120 | if self.temp_tile is not None: 121 | iso_poly = self.temp_tile["iso_poly"] 122 | iso_poly = [(x + self.grass_tiles.get_width()/2 + camera.scroll.x, y + camera.scroll.y) for x, y in iso_poly] 123 | if self.temp_tile["collision"]: 124 | pg.draw.polygon(screen, (255, 0, 0), iso_poly, 3) 125 | else: 126 | pg.draw.polygon(screen, (255, 255, 255), iso_poly, 3) 127 | render_pos = self.temp_tile["render_pos"] 128 | screen.blit( 129 | self.temp_tile["image"], 130 | ( 131 | render_pos[0] + self.grass_tiles.get_width()/2 + camera.scroll.x, 132 | render_pos[1] - (self.temp_tile["image"].get_height() - TILE_SIZE) + camera.scroll.y 133 | ) 134 | ) 135 | 136 | def create_world(self): 137 | 138 | world = [] 139 | 140 | for grid_x in range(self.grid_length_x): 141 | world.append([]) 142 | for grid_y in range(self.grid_length_y): 143 | world_tile = self.grid_to_world(grid_x, grid_y) 144 | world[grid_x].append(world_tile) 145 | 146 | render_pos = world_tile["render_pos"] 147 | self.grass_tiles.blit(self.tiles["block"], (render_pos[0] + self.grass_tiles.get_width()/2, render_pos[1])) 148 | 149 | 150 | return world 151 | 152 | def grid_to_world(self, grid_x, grid_y): 153 | 154 | rect = [ 155 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE), 156 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE), 157 | (grid_x * TILE_SIZE + TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE), 158 | (grid_x * TILE_SIZE, grid_y * TILE_SIZE + TILE_SIZE) 159 | ] 160 | 161 | iso_poly = [self.cart_to_iso(x, y) for x, y in rect] 162 | 163 | minx = min([x for x, y in iso_poly]) 164 | miny = min([y for x, y in iso_poly]) 165 | 166 | r = random.randint(1, 100) 167 | perlin = 100 * noise.pnoise2(grid_x/self.perlin_scale, grid_y/self.perlin_scale) 168 | 169 | if (perlin >= 15) or (perlin <= -35): 170 | tile = "tree" 171 | else: 172 | if r == 1: 173 | tile = "tree" 174 | elif r == 2: 175 | tile = "rock" 176 | else: 177 | tile = "" 178 | 179 | out = { 180 | "grid": [grid_x, grid_y], 181 | "cart_rect": rect, 182 | "iso_poly": iso_poly, 183 | "render_pos": [minx, miny], 184 | "tile": tile, 185 | "collision": False if tile == "" else True 186 | } 187 | 188 | return out 189 | 190 | def create_collision_matrix(self): 191 | collision_matrix = [[1 for x in range(self.grid_length_x)] for y in range(self.grid_length_y)] 192 | for x in range(self.grid_length_x): 193 | for y in range(self.grid_length_y): 194 | if self.world[x][y]["collision"]: 195 | collision_matrix[y][x] = 0 196 | return collision_matrix 197 | 198 | def cart_to_iso(self, x, y): 199 | iso_x = x - y 200 | iso_y = (x + y)/2 201 | return iso_x, iso_y 202 | 203 | def mouse_to_grid(self, x, y, scroll): 204 | # transform to world position (removing camera scroll and offset) 205 | world_x = x - scroll.x - self.grass_tiles.get_width()/2 206 | world_y = y - scroll.y 207 | # transform to cart (inverse of cart_to_iso) 208 | cart_y = (2*world_y - world_x)/2 209 | cart_x = cart_y + world_x 210 | # transform to grid coordinates 211 | grid_x = int(cart_x // TILE_SIZE) 212 | grid_y = int(cart_y // TILE_SIZE) 213 | return grid_x, grid_y 214 | 215 | def load_images(self): 216 | 217 | block = pg.image.load("assets/graphics/block.png").convert_alpha() 218 | # read images 219 | building1 = pg.image.load("assets/graphics/building01.png").convert_alpha() 220 | building2 = pg.image.load("assets/graphics/building02.png").convert_alpha() 221 | tree = pg.image.load("assets/graphics/tree.png").convert_alpha() 222 | rock = pg.image.load("assets/graphics/rock.png").convert_alpha() 223 | 224 | images = { 225 | "building1": building1, 226 | "building2": building2, 227 | "tree": tree, 228 | "rock": rock, 229 | "block": block 230 | } 231 | 232 | return images 233 | 234 | def can_place_tile(self, grid_pos): 235 | mouse_on_panel = False 236 | for rect in [self.hud.resources_rect, self.hud.build_rect, self.hud.select_rect]: 237 | if rect.collidepoint(pg.mouse.get_pos()): 238 | mouse_on_panel = True 239 | world_bounds = (0 <= grid_pos[0] < self.grid_length_x) and (0 <= grid_pos[1] < self.grid_length_x) 240 | 241 | if world_bounds and not mouse_on_panel: 242 | return True 243 | else: 244 | return False 245 | 246 | -------------------------------------------------------------------------------- /part9/main.py: -------------------------------------------------------------------------------- 1 | 2 | import pygame as pg 3 | from game.game import Game 4 | from game.menu import StartMenu, GameMenu 5 | 6 | 7 | def main(): 8 | 9 | running = True 10 | 11 | pg.init() 12 | pg.mixer.init() 13 | screen = pg.display.set_mode((0, 0), pg.FULLSCREEN) 14 | clock = pg.time.Clock() 15 | 16 | # implement menus 17 | start_menu = StartMenu(screen, clock) 18 | game_menu = GameMenu(screen, clock) 19 | 20 | # implement game 21 | game = Game(screen, clock) 22 | 23 | while running: 24 | 25 | # start menu goes here 26 | playing = start_menu.run() 27 | 28 | while playing: 29 | # game loop here 30 | game.run() 31 | # pause loop here 32 | playing = game_menu.run() 33 | 34 | if __name__ == "__main__": 35 | main() 36 | 37 | --------------------------------------------------------------------------------