add sprite code
parent
3930220c3e
commit
f93b50e870
@ -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 New Issue