Add support for simple periodic sprite animations
This commit is contained in:
parent
426b2c37ff
commit
b83b596b0f
7 changed files with 90 additions and 22 deletions
23
dist/resources/entity/square.json
vendored
Normal file
23
dist/resources/entity/square.json
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"sprite": "square",
|
||||
"collision": [
|
||||
{
|
||||
"type": "polygon",
|
||||
"vertices": [
|
||||
[-0.46875, -0.46875],
|
||||
[-0.46875, 0.46875],
|
||||
[0.46875, 0.46875],
|
||||
[0.46875, -0.46875]
|
||||
]
|
||||
}
|
||||
],
|
||||
"frames": 4,
|
||||
"animation": {
|
||||
"sequence": [
|
||||
[500, 0],
|
||||
[500, 1],
|
||||
[500, 2],
|
||||
[500, 3]
|
||||
]
|
||||
}
|
||||
}
|
BIN
dist/resources/sprite/entity/square.png
vendored
BIN
dist/resources/sprite/entity/square.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 528 B |
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,13 @@ import { vec2 } from 'gl-matrix';
|
|||
export async function loadEntity(
|
||||
r: Renderer,
|
||||
data: EntityData,
|
||||
): Promise<SpriteView> {
|
||||
): Promise<SpriteView[]> {
|
||||
const tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`);
|
||||
const [texture, size, coords] = mkTexture(r, tile);
|
||||
|
||||
const sprites: SpriteView[] = [];
|
||||
|
||||
for (let frame = 0; frame < data.frames; frame++) {
|
||||
const [texture, size, coords] = mkTexture(r, tile, frame, data.frames);
|
||||
|
||||
const offset = vec2.mul(vec2.create(), data.anchor, size);
|
||||
r.snapToGrid(offset, offset);
|
||||
|
@ -24,5 +28,8 @@ export async function loadEntity(
|
|||
|
||||
const builder = new SpriteViewBuilder(r, texture);
|
||||
builder.addSprite(anchorCoords, [0, 0, 1, 1]);
|
||||
return builder.build();
|
||||
sprites.push(builder.build());
|
||||
}
|
||||
|
||||
return sprites;
|
||||
}
|
||||
|
|
|
@ -14,13 +14,15 @@ export function loadImage(url: string): Promise<HTMLImageElement> {
|
|||
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);
|
||||
|
|
Reference in a new issue