From b83b596b0f79fa1d5b95c462d3fa7171ff221a19 Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sun, 11 Nov 2018 00:56:22 +0100 Subject: Add support for simple periodic sprite animations --- src/controller/entitycontext.ts | 33 ++++++++++++++++++++++++++++----- src/controller/gamecontext.ts | 7 +++++-- src/model/data/entity.ts | 10 ++++++++++ src/view/entity.ts | 33 ++++++++++++++++++++------------- src/view/util/image.ts | 6 ++++-- 5 files changed, 67 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/controller/entitycontext.ts b/src/controller/entitycontext.ts index c1eb1fc..6932227 100644 --- a/src/controller/entitycontext.ts +++ b/src/controller/entitycontext.ts @@ -1,4 +1,4 @@ -import { EntityData } from '../model/data/entity'; +import { EntityAnimation, EntityData } from '../model/data/entity'; import { loadEntity } from '../view/entity'; import { Renderer } from '../view/renderer/renderer'; @@ -20,21 +20,30 @@ export class EntityContext implements CollidableGroup { name, await loadEntity(renderer, entity), mkCollision(entity.collision), + entity.animation, ); } public readonly pos: vec2 = vec2.create(); + private readonly totalTime: number; + private constructor( private readonly renderer: Renderer, private readonly name: string, - private readonly view: SpriteView, + private readonly sprites: SpriteView[], private readonly collision: Collidable[], - ) {} + private readonly animation?: EntityAnimation, + ) { + if (animation) + this.totalTime = animation.sequence.reduce((a, s) => a + s[0], 0); + else + this.totalTime = 0; + } - public render() { + public render(time: number) { this.renderer.setTranslation(this.pos); - this.view.render(); + this.getSprite(time).render(); } public getTranslation(): vec2 { @@ -48,4 +57,18 @@ export class EntityContext implements CollidableGroup { public interact() { alert(`You've interacted with ${this.name}!`); } + + private getSprite(time: number): SpriteView { + time %= this.totalTime; + + if (this.animation) { + for (const [len, sprite] of this.animation.sequence) { + time -= len; + if (time < 0) + return this.sprites[sprite]; + } + } + + return this.sprites[0]; + } } diff --git a/src/controller/gamecontext.ts b/src/controller/gamecontext.ts index 9c25c6b..771affa 100644 --- a/src/controller/gamecontext.ts +++ b/src/controller/gamecontext.ts @@ -19,19 +19,22 @@ export class GameContext implements CollidableGroup { const map = this.loadMap(renderer, 'test'); const loadPlayer = EntityContext.load(renderer, 'green_circle'); const loadEntity = EntityContext.load(renderer, 'red_circle'); + const loadEntity2 = EntityContext.load(renderer, 'square'); const [mapView, mapCollision] = await map; const player = await loadPlayer; const entity = await loadEntity; + const entity2 = await loadEntity2; vec2.set(player.pos, 6, 6); vec2.set(entity.pos, 3, 3); + vec2.set(entity2.pos, 3, 8); return new GameContext( renderer, mapView, player, - [entity], + [entity, entity2], mapCollision, ); } @@ -168,7 +171,7 @@ export class GameContext implements CollidableGroup { this.mapView.render(); for (const r of [...this.entities, this.player]) - r.render(); + r.render(time); window.requestAnimationFrame(this.render); } diff --git a/src/model/data/entity.ts b/src/model/data/entity.ts index 3474a38..f52c130 100644 --- a/src/model/data/entity.ts +++ b/src/model/data/entity.ts @@ -1,19 +1,29 @@ import { Collision } from './collision'; +export interface EntityAnimation { + readonly sequence: ReadonlyArray<[number, number]>; +} + export interface EntityDataInput { readonly sprite: string; readonly anchor?: [number, number]; readonly collision?: Collision[]; + readonly frames?: number; + readonly animation?: EntityAnimation; } export class EntityData { public readonly sprite: string; public readonly anchor: [number, number]; public readonly collision: Collision[]; + public readonly frames: number; + public readonly animation?: EntityAnimation; constructor(input: EntityDataInput) { this.sprite = input.sprite; this.anchor = input.anchor || [0.5, 0.5]; this.collision = input.collision || []; + this.frames = input.frames || 1; + this.animation = input.animation; } } diff --git a/src/view/entity.ts b/src/view/entity.ts index b59bf92..7600826 100644 --- a/src/view/entity.ts +++ b/src/view/entity.ts @@ -8,21 +8,28 @@ import { vec2 } from 'gl-matrix'; export async function loadEntity( r: Renderer, data: EntityData, -): Promise { +): Promise { const tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`); - const [texture, size, coords] = mkTexture(r, tile); - const offset = vec2.mul(vec2.create(), data.anchor, size); - r.snapToGrid(offset, offset); + const sprites: SpriteView[] = []; - const anchorCoords: SpriteCoords = [ - coords[0] - offset[0], - coords[1] - offset[1], - coords[2] - offset[0], - coords[3] - offset[1], - ]; + for (let frame = 0; frame < data.frames; frame++) { + const [texture, size, coords] = mkTexture(r, tile, frame, data.frames); - const builder = new SpriteViewBuilder(r, texture); - builder.addSprite(anchorCoords, [0, 0, 1, 1]); - return builder.build(); + const offset = vec2.mul(vec2.create(), data.anchor, size); + r.snapToGrid(offset, offset); + + const anchorCoords: SpriteCoords = [ + coords[0] - offset[0], + coords[1] - offset[1], + coords[2] - offset[0], + coords[3] - offset[1], + ]; + + const builder = new SpriteViewBuilder(r, texture); + builder.addSprite(anchorCoords, [0, 0, 1, 1]); + sprites.push(builder.build()); + } + + return sprites; } diff --git a/src/view/util/image.ts b/src/view/util/image.ts index e01246c..7baeccc 100644 --- a/src/view/util/image.ts +++ b/src/view/util/image.ts @@ -14,13 +14,15 @@ export function loadImage(url: string): Promise { export function mkTexture( r: Renderer, src: HTMLCanvasElement|HTMLImageElement, + frame: number = 0, + total: number = 1, ): [WebGLTexture, [number, number], SpriteCoords] { const gl = r.getContext(); const texture = gl.createTexture(); if (!texture) throw new Error('unable to create texture'); - const w = src.width, h = src.height; + const w = src.width, h = src.height / total; const w2 = nextPowerOf2(w), h2 = nextPowerOf2(h); const canvas = document.createElement('canvas'); @@ -28,7 +30,7 @@ export function mkTexture( canvas.height = h2; const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; - ctx.drawImage(src, 0, 0); + ctx.drawImage(src, 0, frame * h, w, h, 0, 0, w, h); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); -- cgit v1.2.3