From 8ac11eda05e7159a7f762d3396cf7bb8ee51534b Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 15 Sep 2024 17:38:02 -0700 Subject: [PATCH] render the spritesheet spec in the render system --- kennel/engine/components/sprite_sheet.py | 17 ++++- kennel/engine/entities/cat.py | 35 ++++++++-- static/src/engine/component.ts | 2 +- static/src/engine/debounce_publisher.ts | 2 +- static/src/engine/render.ts | 84 ++++++++++++++++++++++-- static/src/engine/trailing_position.ts | 20 +++--- 6 files changed, 136 insertions(+), 24 deletions(-) diff --git a/kennel/engine/components/sprite_sheet.py b/kennel/engine/components/sprite_sheet.py index 5730085..3a01f5b 100644 --- a/kennel/engine/components/sprite_sheet.py +++ b/kennel/engine/components/sprite_sheet.py @@ -18,6 +18,19 @@ class SpriteSpec: self.end_x = end_x 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): def __init__( @@ -44,5 +57,7 @@ class SpriteSheet(Component): "current_frame": self.current_frame, "last_update": self.last_update, "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() + }, } diff --git a/kennel/engine/entities/cat.py b/kennel/engine/entities/cat.py index b5a8c6c..c0acfd8 100644 --- a/kennel/engine/entities/cat.py +++ b/kennel/engine/entities/cat.py @@ -4,7 +4,7 @@ from enum import Enum from .entity import Entity, EntityType -class CatState(Enum): +class CatState(str, Enum): IDLE = "IDLE" FROLICKING = "FROLICKING" EEPY = "EEPY" @@ -13,20 +13,47 @@ class CatState(Enum): CHASING_CAT = "CHASING_CAT" SCRATCHING = "SCRATCHING" ITCHY = "ITCHY" + MAKING_BISCUITS = "MAKING_BISCUITS" + + +class CatSpriteState(str, Enum): + ALERT = "ALERT" + MAKING_BISCUITS = "MAKING_BISCUITS" class Cat(Entity): def __init__(self, id: str, spritesheet_source: str): state_to_spritespec = self.get_state_to_spritespec() components = [ - Position(0, 0), - SpriteSheet(spritesheet_source, state_to_spritespec, CatState.ALERT), + Position(50, 50), + SpriteSheet( + spritesheet_source, state_to_spritespec, CatSpriteState.MAKING_BISCUITS + ), ] super().__init__(EntityType.CAT, id, components) 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, + ), + } # diff --git a/static/src/engine/component.ts b/static/src/engine/component.ts index a16215b..eb6fdd6 100644 --- a/static/src/engine/component.ts +++ b/static/src/engine/component.ts @@ -36,7 +36,7 @@ export interface SpriteSpec { export interface SpriteSheetComponent extends Component { name: ComponentType.SPRITESHEET; source: string; - sheet: HTMLImageElement; + sheet?: HTMLImageElement; current_frame: number; last_update: number; diff --git a/static/src/engine/debounce_publisher.ts b/static/src/engine/debounce_publisher.ts index bc5e95f..8ee4bb0 100644 --- a/static/src/engine/debounce_publisher.ts +++ b/static/src/engine/debounce_publisher.ts @@ -5,7 +5,7 @@ export class DebouncePublisher { constructor( private readonly publisher: (data: T) => void | Promise, - private readonly debounce_ms = 150, + private readonly debounce_ms = 100, ) {} public start() { diff --git a/static/src/engine/render.ts b/static/src/engine/render.ts index 8f0343a..4ff1ec0 100644 --- a/static/src/engine/render.ts +++ b/static/src/engine/render.ts @@ -2,6 +2,8 @@ import { ComponentType, PositionComponent, TrailingPositionComponent, + SpriteSheetComponent, + SpriteSpec, } from "./component"; import { Game } from "./game"; import { System, SystemType } from "./system"; @@ -17,7 +19,7 @@ export class RenderSystem extends System { this.canvas.height = height; } - public update(_dt: number, game: Game) { + public update(dt: number, game: Game) { const ctx = this.canvas.getContext("2d"); if (ctx === null) return; ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); @@ -32,15 +34,87 @@ export class RenderSystem extends System { return; } - if (ComponentType.POSITION in entity.components) { + if ( + ComponentType.POSITION in entity.components && + ComponentType.SPRITESHEET in entity.components + ) { const position = entity.components[ ComponentType.POSITION ] as PositionComponent; - ctx.beginPath(); - ctx.arc(position.x, position.y, 50, 0, 2 * Math.PI); - ctx.stroke(); + const spritesheet = entity.components[ + ComponentType.SPRITESHEET + ] 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; } }); } + + 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, + ]; + } } diff --git a/static/src/engine/trailing_position.ts b/static/src/engine/trailing_position.ts index 1ea22d3..5b41682 100644 --- a/static/src/engine/trailing_position.ts +++ b/static/src/engine/trailing_position.ts @@ -2,19 +2,15 @@ import { ComponentType, TrailingPositionComponent } from "./component"; import { Game } from "./game"; import { System, SystemType } from "./system"; +interface Point { + x: number; + y: number; + time: number; +} + export class TrailingPositionSystem extends System { constructor( - private readonly point_filter: ( - trail_point: { - x: number; - y: number; - time: number; - }[], - ) => { - x: number; - y: number; - time: number; - }[], + private readonly point_filter: (trail_point: Array) => Array ) { super(SystemType.TRAILING_POSITION); } @@ -27,7 +23,7 @@ export class TrailingPositionSystem extends System { ComponentType.TRAILING_POSITION ] as TrailingPositionComponent; trailing_position.trails = this.point_filter(trailing_position.trails); - }, + } ); } }