WIP: ECS / Network System #1
|
@ -18,6 +18,19 @@ class SpriteSpec:
|
||||||
self.end_x = end_x
|
self.end_x = end_x
|
||||||
self.end_y = end_y
|
self.end_y = end_y
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {
|
||||||
|
"ms_per_frame": self.ms_per_frame,
|
||||||
|
"top_x": self.top_x,
|
||||||
|
"top_y": self.top_y,
|
||||||
|
"end_x": self.end_x,
|
||||||
|
"end_y": self.end_y,
|
||||||
|
"frames": self.frames,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"SpriteSpec(ms_per_frame={self.ms_per_frame}, top_x={self.top_x}, top_y={self.top_y}, end_x={self.end_x}, end_y={self.end_y}, frames={self.frames})"
|
||||||
|
|
||||||
|
|
||||||
class SpriteSheet(Component):
|
class SpriteSheet(Component):
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -44,5 +57,7 @@ class SpriteSheet(Component):
|
||||||
"current_frame": self.current_frame,
|
"current_frame": self.current_frame,
|
||||||
"last_update": self.last_update,
|
"last_update": self.last_update,
|
||||||
"current_state": self.initial_state,
|
"current_state": self.initial_state,
|
||||||
"state_to_spritespec": self.state_to_spritespec,
|
"state_to_spritespec": {
|
||||||
|
k: v.to_dict() for k, v in self.state_to_spritespec.items()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ from enum import Enum
|
||||||
from .entity import Entity, EntityType
|
from .entity import Entity, EntityType
|
||||||
|
|
||||||
|
|
||||||
class CatState(Enum):
|
class CatState(str, Enum):
|
||||||
IDLE = "IDLE"
|
IDLE = "IDLE"
|
||||||
FROLICKING = "FROLICKING"
|
FROLICKING = "FROLICKING"
|
||||||
EEPY = "EEPY"
|
EEPY = "EEPY"
|
||||||
|
@ -13,20 +13,47 @@ class CatState(Enum):
|
||||||
CHASING_CAT = "CHASING_CAT"
|
CHASING_CAT = "CHASING_CAT"
|
||||||
SCRATCHING = "SCRATCHING"
|
SCRATCHING = "SCRATCHING"
|
||||||
ITCHY = "ITCHY"
|
ITCHY = "ITCHY"
|
||||||
|
MAKING_BISCUITS = "MAKING_BISCUITS"
|
||||||
|
|
||||||
|
|
||||||
|
class CatSpriteState(str, Enum):
|
||||||
|
ALERT = "ALERT"
|
||||||
|
MAKING_BISCUITS = "MAKING_BISCUITS"
|
||||||
|
|
||||||
|
|
||||||
class Cat(Entity):
|
class Cat(Entity):
|
||||||
def __init__(self, id: str, spritesheet_source: str):
|
def __init__(self, id: str, spritesheet_source: str):
|
||||||
state_to_spritespec = self.get_state_to_spritespec()
|
state_to_spritespec = self.get_state_to_spritespec()
|
||||||
components = [
|
components = [
|
||||||
Position(0, 0),
|
Position(50, 50),
|
||||||
SpriteSheet(spritesheet_source, state_to_spritespec, CatState.ALERT),
|
SpriteSheet(
|
||||||
|
spritesheet_source, state_to_spritespec, CatSpriteState.MAKING_BISCUITS
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
super().__init__(EntityType.CAT, id, components)
|
super().__init__(EntityType.CAT, id, components)
|
||||||
|
|
||||||
def get_state_to_spritespec(self):
|
def get_state_to_spritespec(self):
|
||||||
return {CatState.ALERT: SpriteSpec(100, 0, 0, 200, 40, 5)}
|
creature_width = 32
|
||||||
|
creature_height = 32
|
||||||
|
return {
|
||||||
|
CatSpriteState.ALERT: SpriteSpec(
|
||||||
|
100,
|
||||||
|
creature_width * 7,
|
||||||
|
creature_height * 3,
|
||||||
|
creature_width * 8,
|
||||||
|
creature_height * 4,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
CatSpriteState.MAKING_BISCUITS: SpriteSpec(
|
||||||
|
300,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
creature_width,
|
||||||
|
creature_height * 2,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -36,7 +36,7 @@ export interface SpriteSpec {
|
||||||
export interface SpriteSheetComponent extends Component {
|
export interface SpriteSheetComponent extends Component {
|
||||||
name: ComponentType.SPRITESHEET;
|
name: ComponentType.SPRITESHEET;
|
||||||
source: string;
|
source: string;
|
||||||
sheet: HTMLImageElement;
|
sheet?: HTMLImageElement;
|
||||||
|
|
||||||
current_frame: number;
|
current_frame: number;
|
||||||
last_update: number;
|
last_update: number;
|
||||||
|
|
|
@ -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 = 150,
|
private readonly debounce_ms = 100,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import {
|
||||||
ComponentType,
|
ComponentType,
|
||||||
PositionComponent,
|
PositionComponent,
|
||||||
TrailingPositionComponent,
|
TrailingPositionComponent,
|
||||||
|
SpriteSheetComponent,
|
||||||
|
SpriteSpec,
|
||||||
} from "./component";
|
} from "./component";
|
||||||
import { Game } from "./game";
|
import { Game } from "./game";
|
||||||
import { System, SystemType } from "./system";
|
import { System, SystemType } from "./system";
|
||||||
|
@ -17,7 +19,7 @@ export class RenderSystem extends System {
|
||||||
this.canvas.height = height;
|
this.canvas.height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(_dt: number, game: Game) {
|
public update(dt: number, game: Game) {
|
||||||
const ctx = this.canvas.getContext("2d");
|
const ctx = this.canvas.getContext("2d");
|
||||||
if (ctx === null) return;
|
if (ctx === null) return;
|
||||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
@ -32,15 +34,87 @@ export class RenderSystem extends System {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ComponentType.POSITION in entity.components) {
|
if (
|
||||||
|
ComponentType.POSITION in entity.components &&
|
||||||
|
ComponentType.SPRITESHEET in entity.components
|
||||||
|
) {
|
||||||
const position = entity.components[
|
const position = entity.components[
|
||||||
ComponentType.POSITION
|
ComponentType.POSITION
|
||||||
] as PositionComponent;
|
] as PositionComponent;
|
||||||
ctx.beginPath();
|
const spritesheet = entity.components[
|
||||||
ctx.arc(position.x, position.y, 50, 0, 2 * Math.PI);
|
ComponentType.SPRITESHEET
|
||||||
ctx.stroke();
|
] as SpriteSheetComponent;
|
||||||
|
|
||||||
|
if (typeof spritesheet.sheet === "undefined") {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = spritesheet.source;
|
||||||
|
spritesheet.sheet = img;
|
||||||
|
}
|
||||||
|
const spritespec =
|
||||||
|
spritesheet.state_to_spritespec[spritesheet.current_state];
|
||||||
|
|
||||||
|
this.blit_sprite(ctx, spritesheet, position);
|
||||||
|
|
||||||
|
spritesheet.last_update += dt;
|
||||||
|
if (spritesheet.last_update > spritespec.ms_per_frame) {
|
||||||
|
spritesheet.current_frame++;
|
||||||
|
spritesheet.current_frame %= spritespec.frames;
|
||||||
|
spritesheet.last_update = 0;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private blit_sprite(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
spritesheet: SpriteSheetComponent,
|
||||||
|
position: { x: number; y: number },
|
||||||
|
) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(position.x, position.y);
|
||||||
|
ctx.translate(-position.x, -position.y);
|
||||||
|
|
||||||
|
if (typeof spritesheet.sheet === "undefined") return;
|
||||||
|
|
||||||
|
const spritespec =
|
||||||
|
spritesheet.state_to_spritespec[spritesheet.current_state];
|
||||||
|
|
||||||
|
ctx.drawImage(
|
||||||
|
spritesheet.sheet,
|
||||||
|
...this.get_sprite_args(spritesheet.current_frame, spritespec),
|
||||||
|
...this.get_draw_args(spritespec, position),
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get_sprite_args(
|
||||||
|
current_frame: number,
|
||||||
|
sprite_spec: SpriteSpec,
|
||||||
|
): [sx: number, sy: number, sw: number, sh: number] {
|
||||||
|
const [width, height] = this.get_dimensions(sprite_spec);
|
||||||
|
return [
|
||||||
|
sprite_spec.top_x,
|
||||||
|
sprite_spec.top_y + current_frame * height,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get_draw_args(
|
||||||
|
sprite_spec: SpriteSpec,
|
||||||
|
position: { x: number; y: number },
|
||||||
|
): [dx: number, dy: number, dw: number, dh: number] {
|
||||||
|
const [width, height] = this.get_dimensions(sprite_spec);
|
||||||
|
return [position.x - width / 2, position.y - height / 2, width, height];
|
||||||
|
}
|
||||||
|
|
||||||
|
private get_dimensions(sprite_spec: SpriteSpec): [number, number] {
|
||||||
|
return [
|
||||||
|
sprite_spec.end_x - sprite_spec.top_x,
|
||||||
|
(sprite_spec.end_y - sprite_spec.top_y) / sprite_spec.frames,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,15 @@ import { ComponentType, TrailingPositionComponent } from "./component";
|
||||||
import { Game } from "./game";
|
import { Game } from "./game";
|
||||||
import { System, SystemType } from "./system";
|
import { System, SystemType } from "./system";
|
||||||
|
|
||||||
|
interface Point {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class TrailingPositionSystem extends System {
|
export class TrailingPositionSystem extends System {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly point_filter: (
|
private readonly point_filter: (trail_point: Array<Point>) => Array<Point>
|
||||||
trail_point: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
time: number;
|
|
||||||
}[],
|
|
||||||
) => {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
time: number;
|
|
||||||
}[],
|
|
||||||
) {
|
) {
|
||||||
super(SystemType.TRAILING_POSITION);
|
super(SystemType.TRAILING_POSITION);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +23,7 @@ export class TrailingPositionSystem extends System {
|
||||||
ComponentType.TRAILING_POSITION
|
ComponentType.TRAILING_POSITION
|
||||||
] as TrailingPositionComponent;
|
] as TrailingPositionComponent;
|
||||||
trailing_position.trails = this.point_filter(trailing_position.trails);
|
trailing_position.trails = this.point_filter(trailing_position.trails);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue