Created
March 4, 2023 07:04
-
-
Save f0ursqu4r3/ea62ff76cfc80254885d47c9f5dc12fe to your computer and use it in GitHub Desktop.
This file contains 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 glm | |
import random | |
from dataclasses import dataclass, field | |
from glm import vec2, vec4 | |
from typing import Callable | |
class Game: | |
def __init__(self): | |
pygame.init() | |
self.display = pygame.display.set_mode((1440, 900)) | |
self.clock = pygame.time.Clock() | |
self.dt = 0 | |
self.running = False | |
self.font = pygame.font.Font('/Users/kyle/Downloads/homespun/homespun.ttf', 24) | |
self.weapons = [] | |
self.beams = [] | |
self.entities = [] | |
self.particles = [] | |
self.player = Player( | |
speed=120, | |
pos=vec2(self.display.get_size()) * 0.5 | |
) | |
self.score = 0 | |
lasergun = spawn_lasergun() | |
self.player.holding = lasergun | |
self.weapons.append(lasergun) | |
def run(self): | |
self.running = True | |
while self.running: | |
self.dt = self.clock.tick(60) * 0.001 | |
self.events() | |
self.update() | |
self.render() | |
def events(self): | |
for event in pygame.event.get(): | |
if (event.type == pygame.QUIT or | |
(event.type == pygame.KEYDOWN and | |
event.key == pygame.K_ESCAPE)): | |
self.running = False | |
elif (event.type == pygame.KEYDOWN and | |
event.key == pygame.K_SPACE): | |
entity = spawn_level1( | |
pos=vec2(pygame.mouse.get_pos()) | |
) | |
self.entities.append(entity) | |
elif (event.type == pygame.MOUSEBUTTONDOWN and | |
event.button == 1): | |
fire_weapon(self.player, self) | |
def update(self): | |
pygame.display.set_caption( | |
f'playground - {int(self.clock.get_fps())}' | |
f' - score: {self.score}' | |
) | |
pressed = pygame.key.get_pressed() | |
vec = vec2() | |
if pressed[pygame.K_w]: | |
vec += vec2(0, -1) | |
if pressed[pygame.K_s]: | |
vec += vec2(0, 1) | |
if pressed[pygame.K_a]: | |
vec += vec2(-1, 0) | |
if pressed[pygame.K_d]: | |
vec += vec2(1, 0) | |
if glm.length(vec) > 0: | |
self.player.vel = glm.normalize(vec) | |
for weapon in self.weapons: | |
weapon.timer = max(weapon.timer-self.dt, 0) | |
self.player.pos += (self.player.vel * self.player.speed) * self.dt | |
self.player.vel *= 0.98 * self.dt | |
if self.player.holding: | |
self.player.holding.pos = self.player.pos | |
for beam in reversed(self.beams): | |
beam.age += self.dt | |
if beam.age >= beam.lifespan: | |
self.beams.remove(beam) | |
for particle in reversed(self.particles): | |
particle.age += self.dt | |
if particle.age >= particle.lifespan: | |
self.particles.remove(particle) | |
continue | |
particle.pos += particle.vel * self.dt | |
for ent in reversed(self.entities): | |
ent.vel = glm.normalize(self.player.pos - ent.pos) * ent.speed | |
ent.pos += ent.vel * self.dt | |
for beam in self.beams: | |
if beam.age < 0.1 and is_circle_touching_line_segment(ent.pos, ent.radius, beam.start, beam.end): | |
ent.health -= beam.damage | |
if ent.health <= 0: | |
self.score += 1 | |
self.entities.remove(ent) | |
def render(self): | |
self.display.fill((30, 20, 30)) | |
surf = pygame.Surface(self.display.get_size(), pygame.SRCALPHA) | |
for entity in self.entities: | |
draw_entity(surf, entity) | |
for beam in self.beams: | |
draw_beam(surf, beam) | |
for particle in self.particles: | |
draw_particle(surf, particle) | |
draw_player(surf, self.player) | |
draw_weapon_readyness(surf, self) | |
self.display.blit(surf, vec2()) | |
pygame.display.flip() | |
# ============================== | |
# ACTIONS | |
def fire_weapon(entity, game): | |
if not entity.holding: | |
return | |
weapon = entity.holding | |
if weapon.timer: | |
return | |
if weapon.type == 'beam': | |
vec = vec2(pygame.mouse.get_pos()) - weapon.pos | |
length = glm.length(vec) | |
norm = glm.normalize(vec) | |
beam = spawn_beam( | |
start=weapon.pos, | |
end=vec2(pygame.mouse.get_pos()) | |
) | |
particle_count = random.randint(length//20, length//20*2) | |
for i in range(particle_count): | |
speed = random.random() * 25 | |
particle = spawn_particle( | |
color=vec4(200,0,0,255), | |
pos=weapon.pos+(norm * ((length/particle_count) * i)) + (norm * random.random()*5), | |
vel=vec2( | |
(random.random()*2-1) * speed, | |
(random.random()*2-1) * speed | |
), | |
lifespan=random.random()*2.0 | |
) | |
game.particles.append(particle) | |
weapon.timer = weapon.cooldown | |
game.beams.append(beam) | |
# ============================== | |
# SPAWNING | |
def spawn_level1(pos): | |
return Entity( | |
name='level1', | |
speed=100, | |
damage=10, | |
pos=vec2(pos) | |
) | |
def spawn_beam(start, end, color=None): | |
return Beam( | |
color=color or vec4(200,0,0,255), | |
lifespan=0.5, | |
damage=10.0, | |
start=vec2(start), | |
end=vec2(end) | |
) | |
def spawn_lasergun(pos=None): | |
return Weapon( | |
name='lasergun', | |
type='beam', | |
cooldown=1.0, | |
timer=0.0, | |
pos=vec2(pos or 0) | |
) | |
def spawn_particle(color=None, pos=None, vel=None, lifespan=None): | |
return Particle( | |
color=color or vec4(200,0,0,255), | |
lifespan=lifespan or 2.0, | |
pos=vec2(pos or 0), | |
vel=vec2(vel or 0) | |
) | |
# ============================== | |
# DRAWING | |
def draw_beam(surf, beam): | |
a1 = easeIn(flip(beam.age/beam.lifespan)) | |
a2 = easeIn(flip(square(square(beam.age/beam.lifespan)))) | |
vec = glm.normalize(beam.end - beam.start) | |
length = glm.length(beam.end - beam.start) * a2 | |
pygame.draw.line(surf, beam.color*vec4(1,1,1,a1), beam.end - vec * length, beam.end, int(4*a1)) | |
def draw_particle(surf, particle): | |
a = easeIn(flip(square(particle.age/particle.lifespan))) | |
pygame.draw.circle(surf, particle.color*vec4(1,1,1,a), particle.pos, 3*a, 1) | |
def draw_entity(surf, entity): | |
pygame.draw.circle(surf, (200,0,0), entity.pos, entity.radius) | |
def draw_player(surf, player): | |
pygame.draw.circle(surf, (0,200,0), player.pos, player.radius) | |
def draw_weapon_readyness(surf, game): | |
weapon = game.player.holding | |
if not weapon: | |
return | |
text = f'{weapon.timer:0.2f}' if weapon.timer else 'ready' | |
text_surf = game.font.render(text, False, (255,255,255,255)) | |
surf.blit(text_surf, vec2(10)) | |
# ============================== | |
# OBJECTS | |
@dataclass | |
class Player: | |
speed: float | |
holding: Weapon | None = None | |
radius: int = 16 | |
health: int = 100 | |
pos: vec2 = field(default_factory=vec2) | |
vel: vec2 = field(default_factory=vec2) | |
@dataclass | |
class Entity: | |
name: str | |
speed: float | |
damage: float | |
radius: int = 16 | |
health: int = 10 | |
pos: vec2 = field(default_factory=vec2) | |
vel: vec2 = field(default_factory=vec2) | |
@dataclass | |
class Weapon: | |
name: str | |
type: str | |
cooldown: float | |
timer: float | |
pos: vec2 = field(default_factory=vec2) | |
vel: vec2 = field(default_factory=vec2) | |
@dataclass | |
class Projectile: | |
name: str | |
damage: float | |
speed: float | |
pos: vec2 = field(default_factory=vec2) | |
vel: vec2 = field(default_factory=vec2) | |
@dataclass | |
class Beam: | |
color: vec4 | |
damage: float | |
lifespan: float | |
age: float = 0.0 | |
start: vec2 = field(default_factory=vec2) | |
end: vec2 = field(default_factory=vec2) | |
@dataclass | |
class Particle: | |
color: vec4 | |
lifespan: float | |
age: float = 0.0 | |
pos: vec2 = field(default_factory=vec2) | |
vel: vec2 = field(default_factory=vec2) | |
# ============================== | |
# HELPERS | |
def is_circle_touching_line_segment(circle_center, circle_radius, line_start, line_end): | |
line_vec = line_end - line_start | |
circle_vec = circle_center - line_start | |
closest_point = line_start + glm.dot(line_vec, circle_vec) / glm.dot(line_vec, line_vec) * line_vec | |
return glm.length(closest_point - circle_center) <= circle_radius | |
def lerp(start:float, end:float, pct:float): | |
return (start + (end - start) * max(0, min(pct, 1))) | |
def easeIn(t:float): | |
return square(t) | |
def flip(t:float): | |
return 1 - t | |
def square(t:float): | |
return t * t | |
Game().run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment