WIP: ECS / Network System #1

Draft
simponic wants to merge 13 commits from websockets into main
9 changed files with 103 additions and 24 deletions
Showing only changes of commit f7e7121fce - Show all commits

View File

@ -6,6 +6,7 @@ class ComponentType(str, Enum):
POSITION = "POSITION" POSITION = "POSITION"
CONTROLLABLE = "CONTROLLABLE" CONTROLLABLE = "CONTROLLABLE"
MARKOV = "MARKOV" MARKOV = "MARKOV"
SPRITESHEET = "SPRITESHEET"
class Component: class Component:

View File

@ -0,0 +1,48 @@
from .component import Component, ComponentType
class SpriteSpec:
def __init__(
self,
ms_per_frame: int,
top_x: int,
top_y: int,
end_x: int,
end_y: int,
frames: int,
):
self.ms_per_frame = ms_per_frame
self.frames = frames
self.top_x = top_x
self.top_y = top_y
self.end_x = end_x
self.end_y = end_y
class SpriteSheet(Component):
def __init__(
self,
source: str,
state_to_spritespec: dict[str, SpriteSpec],
initial_state: str,
):
super().__init__(ComponentType.SPRITESHEET)
self.source = source
self.state_to_spritespec = state_to_spritespec
# these are only really used for client initialization
self.initial_state = initial_state
self.current_frame = 0
self.last_update = 0
def __repr__(self) -> str:
return f"SpriteSheet(source={self.source}, state_to_spritespec={self.state_to_spritespec}, initial_state={self.initial_state}, current_frame={self.current_frame}, last_update={self.last_update})"
def to_dict(self) -> dict:
return {
"source": self.source,
"current_frame": self.current_frame,
"last_update": self.last_update,
"current_state": self.initial_state,
"state_to_spritespec": self.state_to_spritespec,
}

View File

@ -1,14 +1,33 @@
from kennel.engine.components.position import Position from kennel.engine.components.position import Position
from kennel.engine.components.sprite_sheet import SpriteSheet, SpriteSpec
from enum import Enum
from .entity import Entity, EntityType from .entity import Entity, EntityType
class CatState(Enum):
IDLE = "IDLE"
FROLICKING = "FROLICKING"
EEPY = "EEPY"
ALERT = "ALERT"
CHASING_CURSOR = "CHASING_CURSOR"
CHASING_CAT = "CHASING_CAT"
SCRATCHING = "SCRATCHING"
ITCHY = "ITCHY"
class Cat(Entity): class Cat(Entity):
def __init__(self, id: str): def __init__(self, id: str, spritesheet_source: str):
components = [Position(0, 0)] state_to_spritespec = self.get_state_to_spritespec()
components = [
Position(0, 0),
SpriteSheet(spritesheet_source, state_to_spritespec, CatState.ALERT),
]
super().__init__(EntityType.CAT, id, components) super().__init__(EntityType.CAT, id, components)
def get_state_to_spritespec(self):
return {CatState.ALERT: SpriteSpec(100, 0, 0, 200, 40, 5)}
# #
# # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY # # IDLE, FROLICKING, EEPY, ALERT, CHASING_CURSOR, CHASING_CAT, SCRATCHING, ITCHY
@ -32,14 +51,6 @@ class Cat(Entity):
# ] # ]
# #
# #
# class CatState(Enum):
# IDLE = 0
# FROLICKING = 1
# EEPY = 2
# ALERT = 3
# CHASING_CURSOR = 4
# CHASING_CAT = 5
# SCRATCHING = 6
# ITCHY = 7
# #
# #

View File

@ -104,8 +104,7 @@ class KennelCatsManager:
removed_cats = [cats_table[id] for id in self.last_seen.difference(cat_ids)] removed_cats = [cats_table[id] for id in self.last_seen.difference(cat_ids)]
added_cats = [cats_table[id] for id in cat_ids.difference(self.last_seen)] added_cats = [cats_table[id] for id in cat_ids.difference(self.last_seen)]
logger.info(f"removing {removed_cats}") logger.info(f"removing {removed_cats}, adding {added_cats}")
logger.info(f"adding {added_cats}")
for removed in removed_cats: for removed in removed_cats:
self.entity_manager.remove_entity(removed) self.entity_manager.remove_entity(removed)
@ -113,7 +112,7 @@ class KennelCatsManager:
self.network_system.server_global_event(entity_death) self.network_system.server_global_event(entity_death)
for added in added_cats: for added in added_cats:
new_cat = Cat(added.id) new_cat = Cat(added.id, added.spritesheet)
self.entity_manager.add_entity(new_cat) self.entity_manager.add_entity(new_cat)
entity_born = EntityBornEvent(new_cat) entity_born = EntityBornEvent(new_cat)
self.network_system.server_global_event(entity_born) self.network_system.server_global_event(entity_born)

View File

@ -43,4 +43,7 @@ class KennelCatService:
def get_kennel(self) -> List[KennelCat]: def get_kennel(self) -> List[KennelCat]:
response = requests.get(f"{self.hc_endpoint}/kennel") response = requests.get(f"{self.hc_endpoint}/kennel")
response.raise_for_status() response.raise_for_status()
return [KennelCat.from_dict(cat) for cat in response.json()] cats = [KennelCat.from_dict(cat) for cat in response.json()]
for cat in cats:
cat.spritesheet = self.hc_endpoint + cat.spritesheet
return cats

View File

@ -2,6 +2,7 @@ export enum ComponentType {
POSITION = "POSITION", POSITION = "POSITION",
RENDERABLE = "RENDERABLE", RENDERABLE = "RENDERABLE",
TRAILING_POSITION = "TRAILING_POSITION", TRAILING_POSITION = "TRAILING_POSITION",
SPRITESHEET = "SPRITESHEET",
} }
export interface Component { export interface Component {
@ -22,3 +23,24 @@ export interface TrailingPositionComponent extends Component {
export interface RenderableComponent extends Component { export interface RenderableComponent extends Component {
name: ComponentType.RENDERABLE; name: ComponentType.RENDERABLE;
} }
export interface SpriteSpec {
ms_per_frame: number;
frames: number;
top_x: number;
top_y: number;
end_x: number;
end_y: number;
}
export interface SpriteSheetComponent extends Component {
name: ComponentType.SPRITESHEET;
source: string;
sheet: HTMLImageElement;
current_frame: number;
last_update: number;
current_state: string;
state_to_spritespec: Record<string, SpriteSpec>;
}

View File

@ -5,7 +5,7 @@ export class DebouncePublisher<T> {
constructor( constructor(
private readonly publisher: (data: T) => void | Promise<void>, private readonly publisher: (data: T) => void | Promise<void>,
private readonly debounce_ms = 100, private readonly debounce_ms = 150,
) {} ) {}
public start() { public start() {

View File

@ -1,7 +1,6 @@
import { import {
Component, Component,
ComponentType, ComponentType,
PositionComponent,
RenderableComponent, RenderableComponent,
TrailingPositionComponent, TrailingPositionComponent,
} from "./component"; } from "./component";
@ -36,10 +35,5 @@ export const create_cat = (base: Entity) => {
name: ComponentType.RENDERABLE, name: ComponentType.RENDERABLE,
}; };
base.components[ComponentType.RENDERABLE] = renderable; base.components[ComponentType.RENDERABLE] = renderable;
base.components[ComponentType.POSITION] = {
component: ComponentType.POSITION,
x: Math.random() * 1_000,
y: Math.random() * 1_000,
} as unknown as PositionComponent; // TODO: hack
return base; return base;
}; };

View File

@ -10,7 +10,7 @@ import { NetworkSystem } from "./engine/network";
import { RenderSystem } from "./engine/render"; import { RenderSystem } from "./engine/render";
import { InputSystem } from "./engine/input"; import { InputSystem } from "./engine/input";
import { TrailingPositionSystem } from "./engine/trailing_position"; import { TrailingPositionSystem } from "./engine/trailing_position";
import { drainPoints, setDelay } from "laser-pen"; import { drainPoints, setDelay, setRoundCap } from "laser-pen";
$(async () => { $(async () => {
const client_id = await fetch("/assign", { const client_id = await fetch("/assign", {
@ -32,6 +32,7 @@ $(async () => {
const input_system = new InputSystem(publisher, gamecanvas); const input_system = new InputSystem(publisher, gamecanvas);
setDelay(500); setDelay(500);
setRoundCap(true);
const render_system = new RenderSystem(gamecanvas); const render_system = new RenderSystem(gamecanvas);
const trailing_position = new TrailingPositionSystem(drainPoints); const trailing_position = new TrailingPositionSystem(drainPoints);