Skip to content

Instantly share code, notes, and snippets.

@f0ursqu4r3
Created December 14, 2023 19:21
Show Gist options
  • Save f0ursqu4r3/d99f01ba987ecd73a8bc2644654b8891 to your computer and use it in GitHub Desktop.
Save f0ursqu4r3/d99f01ba987ecd73a8bc2644654b8891 to your computer and use it in GitHub Desktop.
ecs_example.py
from __future__ import annotations
import sys
import random
from dataclasses import dataclass
import pygame as pg
import glm
from glm import vec2
import esper
# ==============================
# GAME
def main(width:int, height:int, include_renderer=True):
pg.init()
esper.add_processor(Input())
esper.add_processor(Time())
esper.add_processor(Grid())
esper.add_processor(Physics())
esper.add_processor(PlayerMovement())
esper.add_processor(Movement())
if include_renderer:
esper.add_processor(Renderer(width, height))
ent = esper.create_entity(
PlayerInput(),
Position(128),
Velocity(128),
)
if renderer := esper.get_processor(Renderer):
res = Resources()
tile_map = TileMap(res.surfs['dirt'], 32)
renderer.tile_map = tile_map
esper.add_component(ent, Sprite(
surface=res.surfs['green_circle']
))
while True:
esper.process()
# ==============================
# EXTRAS
class Resources:
surfs={}
def __init__(self):
red_rect = pg.Surface(vec2(32))
red_rect.fill((200,0,0))
green_circle = pg.Surface(vec2(32), pg.SRCALPHA)
pg.draw.circle(green_circle,(0,200,0),(16,16),16)
dirt = pg.image.load('./96x64dirt.png').convert()
self.surfs['red_rect'] = red_rect
self.surfs['green_circle'] = green_circle
self.surfs['dirt'] = dirt
class TileMap:
tiles = []
tile_size = 0
def __init__(self, tile_surf, tile_size):
self.tile_size = tile_size
self.tiles = []
for _ in range(100):
row = []
for _ in range(100):
x = random.randint(0,2) * tile_size
y = random.randint(0,1) * tile_size
row.append(
tile_surf.subsurface(
(x, y), (tile_size, tile_size)
)
)
self.tiles.append(row)
# ==============================
# COMPONENTS
class Position(vec2):
pass
class Velocity(vec2):
pass
class Accelleration(vec2):
pass
@dataclass
class Sprite:
surface: pg.Surface
@property
def size(self):
return vec2(self.surface.get_size())
class PlayerInput:
pass
# ==============================
# PROCESSORS
class Time:
def __init__(self):
self.clock = pg.time.Clock()
self.dt = 0.0
self.fps = 0.0
def process(self):
self.dt = self.clock.tick(60) * 0.001
self.fps = self.clock.get_fps()
pg.display.set_caption(f'{self.fps:0.2f}')
class Input:
def __init__(self):
self.move_left = False
self.move_right = False
self.move_up = False
self.move_down = False
self.mpos = vec2()
def process(self):
for event in pg.event.get():
if (event.type == pg.QUIT or
(event.type == pg.KEYDOWN and
event.key == pg.K_ESCAPE)):
pg.quit()
sys.exit()
keys = pg.key.get_pressed()
self.move_left = keys[pg.K_a]
self.move_right = keys[pg.K_d]
self.move_up = keys[pg.K_w]
self.move_down = keys[pg.K_s]
self.mpos = vec2(pg.mouse.get_pos())
class Grid:
def __init__(self, cell_size=32):
self.cell_size = cell_size
self.cells = {}
def process(self):
self.cells = {}
for ent, (pos,) in esper.get_components(Position):
key = tuple(map(lambda i: int(i)//self.cell_size, pos))
self.cells.setdefault(key, []).append(ent)
def query(self, pos):
key = tuple(map(lambda i: int(i)//self.cell_size, pos))
return self.cells.get(key, [])
def query_rect(self, pos, size):
entities = set()
for x in range(pos.x//self.cell_size, (pos.x+size.x)//self.cell_size):
for y in range(pos.y//self.cell_size, (pos.y+size.y)//self.cell_size):
entities.update(self.cells.get((x, y), []))
return entities
class Renderer:
def __init__(self, width, height):
self.window = pg.display.set_mode((width, height))
self.win_size = vec2(width, height)
self.tile_map = None
def process(self):
self.window.fill((30,20,20))
if self.tile_map:
tile_size = self.tile_map.tile_size
for y, row in enumerate(self.tile_map.tiles):
for x, tile in enumerate(row):
self.window.blit(
tile, (x * tile_size, y * tile_size)
)
for _, (pos, spr) in esper.get_components(Position, Sprite):
self.window.blit(spr.surface, (pos-(spr.size*0.05)))
pg.display.update()
class PlayerMovement:
def process(self):
inp = esper.get_processor(Input)
for _, (vel, _) in esper.get_components(Velocity, PlayerInput):
direction = vec2()
if inp.move_left:
direction += vec2(-1,0)
if inp.move_right:
direction += vec2(1,0)
if inp.move_up:
direction += vec2(0,-1)
if inp.move_down:
direction += vec2(0,1)
if not direction == vec2():
norm = glm.normalize(direction)
vel += norm * 20
class Movement:
def __init__(self):
self.time = esper.get_processor(Time)
def process(self):
dt = self.time.dt
for _, (pos, vel) in esper.get_components(Position, Velocity):
pos += vel * self.time.dt
vel *= 0.9
class Physics:
def __init__(self):
self.time = esper.get_processor(Time)
def process(self):
dt = self.time.dt
for _, (vel, acc) in esper.get_components(Velocity, Accelleration):
vel += acc * self.time.dt
acc *= 0.0
# ==============================
# FIN
main(1280, 720)
@f0ursqu4r3
Copy link
Author

96x64dirt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment