add sprite code
This commit is contained in:
parent
3930220c3e
commit
f93b50e870
1 changed files with 153 additions and 0 deletions
153
src/sprite.ts
Normal file
153
src/sprite.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import { Schema as JSONSchema, validate as validateJSON } from "./json";
|
||||
|
||||
/**
|
||||
* JSON schema for a single sprite.
|
||||
*/
|
||||
interface ISpriteData {
|
||||
/** URL to this tile image, appended to `IPaletteData.baseurl`. */
|
||||
url: string,
|
||||
/**
|
||||
* All edge sections and their material IDs.
|
||||
* Edges are divided into `IPaletteData.sections` sections,
|
||||
* and their individual materials are stored here in clockwise order.
|
||||
*
|
||||
* The illustration below shows the indices of material IDs for
|
||||
* 3-section edges.
|
||||
*
|
||||
* ```
|
||||
* 0 1 2
|
||||
* + - - - +
|
||||
* 11 | | 3
|
||||
* 10 | tile | 4
|
||||
* 9 | | 5
|
||||
* + - - - +
|
||||
* 8 7 6
|
||||
* ```
|
||||
*/
|
||||
edges: number[],
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON schema for `/dist/sprites.json`
|
||||
*/
|
||||
export interface IPaletteData {
|
||||
/**
|
||||
* Absolute base URL prepended to all tile URLs.
|
||||
* Must not end with a trailing slash.
|
||||
*/
|
||||
baseurl: string,
|
||||
/**
|
||||
* Number of sections per edge.
|
||||
* The `ISprite.edges` array must be exactly 4 times as long as this value
|
||||
* (as in the four edges of a quadratic tile).
|
||||
*/
|
||||
sections: number,
|
||||
/** Array of all tile prototypes. */
|
||||
sprites: ISpriteData[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores and renders a single type of tile.
|
||||
* Actual `Tile`s refer to instances of this class for their data.
|
||||
*/
|
||||
export class Sprite {
|
||||
private readonly sections: number;
|
||||
private readonly edges: number[];
|
||||
private bitmap?: ImageBitmap;
|
||||
|
||||
public constructor(sections: number, baseurl: string, proto: ISpriteData) {
|
||||
this.sections = sections;
|
||||
if (proto.edges.length !== sections * 4) {
|
||||
throw new TypeError("Invalid edge count");
|
||||
}
|
||||
this.edges = proto.edges;
|
||||
this.fetchBitmapAsync(baseurl + proto.url)
|
||||
.then(i => this.bitmap = i)
|
||||
.catch(e => console.error(e));
|
||||
}
|
||||
|
||||
public matches(ownDir: Direction, other: Sprite): boolean {
|
||||
if (other.sections !== this.sections) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ownStart = ownDir * this.sections;
|
||||
|
||||
const otherDir: Direction = (ownDir + 2) % 4;
|
||||
const otherStart = otherDir * other.sections;
|
||||
|
||||
let ownEdge = this.edges.slice(ownStart, ownStart + this.sections);
|
||||
let otherEdge = other.edges.slice(otherStart, otherStart + other.sections);
|
||||
return !ownEdge.some((v, i) => otherEdge[other.sections - i - 1] !== v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw this sprite to the screen at the specified position and rotation.
|
||||
*
|
||||
* @param context The context to draw the sprite in
|
||||
* @param dx horizontal offset in pixels
|
||||
* @param dy vertical offset in pixels
|
||||
* @param orientation If specified, rotate the sprite such that the north
|
||||
* facing edge points to the specified direction.
|
||||
*/
|
||||
public draw(context: CanvasRenderingContext2D, dx: number, dy: number, orientation?: Direction) {
|
||||
if (this.bitmap !== undefined) {
|
||||
if (orientation === undefined || orientation === Direction.NORTH) {
|
||||
context.drawImage(this.bitmap, dx, dy);
|
||||
} else {
|
||||
context.save();
|
||||
const halfWidth = this.bitmap.width / 2;
|
||||
const halfHeight = this.bitmap.height / 2;
|
||||
context.translate(dx + halfWidth, dy + halfHeight);
|
||||
context.rotate(Math.PI * (orientation / 2));
|
||||
context.drawImage(this.bitmap, -halfWidth, -halfHeight);
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchBitmapAsync(url: string): Promise<ImageBitmap> {
|
||||
console.debug(`fetching bitmap ${url}`);
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const image = await createImageBitmap(blob);
|
||||
console.debug(`fetched and parsed bitmap ${url}`);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
export const enum Direction {
|
||||
NORTH = 0,
|
||||
EAST = 1,
|
||||
SOUTH = 2,
|
||||
WEST = 3,
|
||||
}
|
||||
|
||||
export class Palette {
|
||||
private readonly sections: number;
|
||||
public readonly sprites: Sprite[];
|
||||
|
||||
public constructor(proto: IPaletteData) {
|
||||
this.sections = proto.sections;
|
||||
const tiles = [];
|
||||
for (let tile of proto.sprites) {
|
||||
tiles.push(new Sprite(proto.sections, proto.baseurl, tile));
|
||||
}
|
||||
this.sprites = tiles;
|
||||
}
|
||||
}
|
||||
|
||||
const PALETTE_SCHEMA: JSONSchema = {
|
||||
baseurl: "string",
|
||||
sections: "number",
|
||||
sprites: [{
|
||||
url: "string",
|
||||
edges: ["number"],
|
||||
}],
|
||||
};
|
||||
|
||||
export async function fetchPalette(rootURL: string): Promise<Palette> {
|
||||
const response = await fetch(rootURL + "/palette.json");
|
||||
const json = await response.json();
|
||||
return new Palette(validateJSON(json, PALETTE_SCHEMA));
|
||||
}
|
Loading…
Reference in a new issue