Created
December 16, 2022 19:23
-
-
Save nitori/768bbbe56edcbd1d95fdd7f8079f42d4 to your computer and use it in GitHub Desktop.
small survival game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from __future__ import annotations | |
| import pygame | |
| import random | |
| import pathlib | |
| HERE = pathlib.Path(__file__).absolute().parent | |
| class BaseSprite(pygame.sprite.Sprite): | |
| _image_res: tuple[int] | |
| _origin: tuple[int] | |
| _color: tuple[int, int, int] | |
| def __init__(self, x, y, speed, *groups: pygame.sprite.Group) -> None: | |
| super().__init__(*groups) | |
| self.image = pygame.Surface(self._image_res) | |
| self.image.fill(self._color) | |
| self.rect = self.image.get_rect() | |
| self.speed = speed | |
| self.origin = pygame.math.Vector2(*self._origin) | |
| self._pos = pygame.math.Vector2(x, y) - self.origin | |
| self._move_direction = pygame.math.Vector2(x, y) | |
| self.rect.x = self._pos.x | |
| self.rect.y = self._pos.y | |
| @property | |
| def pos(self) -> pygame.math.Vector2: | |
| return self._pos + self.origin | |
| def move_to(self, x: float, y: float) -> None: | |
| self._move_direction = pygame.math.Vector2(x, y) - self.pos | |
| if self._move_direction.length_squared() > 0: | |
| self._move_direction.normalize_ip() | |
| def direction(self) -> pygame.math.Vector2: | |
| return self._move_direction | |
| def update(self, delta) -> None: | |
| move_vector = self.direction() | |
| if move_vector.length_squared() > 0: | |
| move_vector.normalize_ip() | |
| move_vector *= self.speed * delta | |
| self._pos += move_vector | |
| self.rect.x = self._pos.x | |
| self.rect.y = self._pos.y | |
| class Player(BaseSprite): | |
| _image_res = (32, 32) | |
| _origin = (15, 32) | |
| _color = (0, 255, 0) | |
| _move_to: pygame.math.Vector2 | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self._move_to = self.pos | |
| self.dead = False | |
| def move_to(self, x: float, y: float): | |
| super().move_to(x, y) | |
| self._move_to = pygame.math.Vector2(x, y) | |
| def direction(self) -> pygame.math.Vector2: | |
| return self._move_to - self.pos | |
| def target_reached(self): | |
| return self._move_to.distance_to(self.pos) < 3 | |
| def update(self, delta, enemy_group: pygame.sprite.Group): | |
| if not self.target_reached(): | |
| super().update(delta) | |
| # test if we hit an enemy, and die | |
| for enemy in enemy_group: | |
| if self.rect.colliderect(enemy.rect): | |
| self.dead = True | |
| return | |
| def spawn_bullet(self, to_x, to_y): | |
| bullet = Bullet(self.pos.x, self.pos.y - self._image_res[1] // 2, 500) | |
| bullet.move_to(to_x, to_y) | |
| return bullet | |
| class Bullet(BaseSprite): | |
| _image_res = (4, 4) | |
| _origin = (2, 2) | |
| _color = (255, 255, 0) | |
| def update(self, delta, enemy_group: pygame.sprite.Group): | |
| super().update(delta) | |
| for enemy in enemy_group: | |
| if self.rect.colliderect(enemy.rect): | |
| enemy.kill() | |
| self.kill() | |
| return | |
| class Enemy(BaseSprite): | |
| _image_res = (16, 16) | |
| _origin = (8, 8) | |
| _color = (255, 0, 0) | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| self.target_entity = None | |
| def move_towards(self, entity: BaseSprite): | |
| self.target_entity = entity | |
| def direction(self) -> pygame.math.Vector2: | |
| if self.target_entity: | |
| return self.target_entity.pos - self.pos | |
| return pygame.math.Vector2(0, 0) | |
| class Highlight(pygame.sprite.Sprite): | |
| def __init__(self, x, y, color, *groups: pygame.sprite.Group) -> None: | |
| super().__init__(*groups) | |
| self.image = pygame.Surface((16, 16), pygame.SRCALPHA, 32) | |
| self.image.fill(color[:3] + (0,)) | |
| self.rect = self.image.get_rect() | |
| self.rect.x = x - 8 | |
| self.rect.y = y - 8 | |
| self.color = color | |
| self.start_time = pygame.time.get_ticks() / 1000 | |
| self.progress = 0 | |
| def update(self, delta) -> None: | |
| self.progress = (pygame.time.get_ticks() / 1000 - self.start_time) * 4 | |
| r = int(self.progress * 16) // 2 | |
| pygame.draw.circle(self.image, self.color[:3], (8, 8), r, 0) | |
| def show_kills(screen, kills, font): | |
| kill_hud = font.render(f'Kills: {kills}', True, (255, 255, 255)) | |
| screen.blit(kill_hud, (10, 10)) | |
| def main(): | |
| pygame.init() | |
| # https://fonts.google.com/specimen/Press+Start+2P | |
| kill_font = pygame.font.Font(str(HERE / 'PressStart2P-Regular.ttf'), 16) | |
| game_over_font = pygame.font.Font(str(HERE / 'PressStart2P-Regular.ttf'), 72) | |
| img = game_over_font.render('GAME OVER', True, (255, 0, 0)) | |
| width, height = 800, 800 | |
| screen = pygame.display.set_mode((width, height)) | |
| pygame.display.set_caption("My Game") | |
| clock = pygame.time.Clock() | |
| player_group = pygame.sprite.Group() | |
| bullet_group = pygame.sprite.Group() | |
| enemy_group = pygame.sprite.Group() | |
| highlight_group = pygame.sprite.Group() | |
| player = Player(width // 2, height // 2, 250, player_group) | |
| kills = 0 | |
| while True: | |
| delta = clock.tick(60) / 1000 | |
| for event in pygame.event.get(): | |
| if event.type == pygame.QUIT: | |
| pygame.quit() | |
| return | |
| if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: | |
| pygame.quit() | |
| return | |
| if player.dead: | |
| continue | |
| # left click get mouse position | |
| if event.type == pygame.MOUSEBUTTONDOWN: | |
| if event.button == 3: | |
| mx, my = event.pos | |
| Highlight(mx, my, (255, 0, 0), highlight_group) | |
| player.move_to(*event.pos) | |
| if event.button == 1: | |
| mx, my = event.pos | |
| Highlight(mx, my, (0, 0, 255), highlight_group) | |
| bullet = player.spawn_bullet(*event.pos) | |
| bullet.add(bullet_group) | |
| if player.dead: | |
| screen.fill((0, 0, 0)) | |
| screen.blit(img, (width // 2 - img.get_width() // 2, height // 2 - img.get_height() // 2)) | |
| show_kills(screen, kills, kill_font) | |
| pygame.display.flip() | |
| continue | |
| highlight_group.update(delta) | |
| enemy_group.update(delta) | |
| before = len(enemy_group) | |
| bullet_group.update(delta, enemy_group) | |
| kills += before - len(enemy_group) | |
| player_group.update(delta, enemy_group) | |
| if player.dead: | |
| continue | |
| while len(enemy_group) < 10: | |
| # spawn outside visible screen area | |
| side = random.randint(0, 3) | |
| if side == 0: | |
| x = random.randint(0, width) | |
| y = -16 | |
| elif side == 1: | |
| x = random.randint(0, width) | |
| y = height + 16 | |
| elif side == 2: | |
| x = -16 | |
| y = random.randint(0, height) | |
| else: | |
| x = width + 16 | |
| y = random.randint(0, height) | |
| enemy = Enemy(x, y, 50, enemy_group) | |
| enemy.move_towards(player) | |
| for hl in highlight_group.sprites(): | |
| hl: Highlight | |
| if hl.progress >= 1: | |
| hl.kill() | |
| for bullet in bullet_group.sprites(): | |
| if bullet.rect.x < -50 or bullet.rect.x > width + 50 or bullet.rect.y < -50 or bullet.rect.y > height + 50: | |
| bullet.kill() | |
| screen.fill((0, 0, 0)) | |
| kill_hud = kill_font.render(f'Kills: {kills}', True, (255, 255, 255)) | |
| screen.blit(kill_hud, (10, 10)) | |
| highlight_group.draw(screen) | |
| bullet_group.draw(screen) | |
| enemy_group.draw(screen) | |
| show_kills(screen, kills, kill_font) | |
| player_group.draw(screen) | |
| pygame.display.flip() | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment