Skip to content

Instantly share code, notes, and snippets.

@nitori
Created December 16, 2022 19:23
Show Gist options
  • Select an option

  • Save nitori/768bbbe56edcbd1d95fdd7f8079f42d4 to your computer and use it in GitHub Desktop.

Select an option

Save nitori/768bbbe56edcbd1d95fdd7f8079f42d4 to your computer and use it in GitHub Desktop.
small survival game
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