Skip to content

Instantly share code, notes, and snippets.

@f0ursqu4r3
Created March 4, 2023 07:04
Show Gist options
  • Save f0ursqu4r3/ea62ff76cfc80254885d47c9f5dc12fe to your computer and use it in GitHub Desktop.
Save f0ursqu4r3/ea62ff76cfc80254885d47c9f5dc12fe to your computer and use it in GitHub Desktop.
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