├── .DS_Store ├── .gitignore ├── README.md ├── dist ├── Bitmap.d.ts ├── Font.d.ts ├── Game.d.ts ├── GameContext.d.ts ├── Graphics.d.ts ├── Gute.d.ts ├── Keys.d.ts ├── Log.d.ts ├── Resource.d.ts ├── Sound.d.ts ├── SoundScape.d.ts ├── Tileset.d.ts ├── font.ttf ├── impl │ ├── BitmapImpl.d.ts │ ├── FontImpl.d.ts │ ├── GraphicsImpl.d.ts │ ├── Palette.d.ts │ ├── SoundImpl.d.ts │ └── TilesetImpl.d.ts ├── index-min.js ├── index-min.js.LICENSE.txt ├── index.d.ts ├── index.js ├── ldtk │ ├── LDTKEntity.d.ts │ ├── LDTKLayer.d.ts │ ├── LDTKLevel.d.ts │ └── LDTKWorld.d.ts ├── opengl │ ├── OpenGLBitmap.1.d.ts │ ├── OpenGLBitmap.d.ts │ ├── OpenGLGraphicsImpl.d.ts │ ├── OpenGLOffscreen.d.ts │ ├── OpenGLTilesetImpl.d.ts │ ├── RenderingState.d.ts │ └── State.d.ts ├── path │ ├── AStarPathFinder.d.ts │ ├── MapNode.d.ts │ ├── Path.d.ts │ ├── PathFinderMap.d.ts │ ├── PathMover.d.ts │ └── Step.d.ts └── tilemaps │ ├── LDTKWorld.d.ts │ ├── MapEntity.d.ts │ ├── MapLayer.d.ts │ ├── MapLevel.d.ts │ └── MapWorld.d.ts ├── docs ├── assets │ ├── css │ │ └── main.css │ ├── images │ │ ├── icons.png │ │ ├── icons@2x.png │ │ ├── widgets.png │ │ └── widgets@2x.png │ └── js │ │ ├── main.js │ │ └── search.js ├── classes │ ├── astarpathfinder.html │ ├── keys.html │ ├── ldtkworld.html │ ├── path.html │ └── step.html ├── index.html ├── interfaces │ ├── bitmap.html │ ├── font.html │ ├── game.html │ ├── gamecontext.html │ ├── graphics.html │ ├── pathfindermap.html │ ├── pathmover.html │ ├── sound.html │ └── tileset.html ├── modules.html └── test │ ├── assets │ ├── Gray.png │ ├── coin.mp3 │ ├── idle.png │ ├── jump.mp3 │ ├── jump.png │ ├── music.mp3 │ ├── run.png │ └── terrain.png │ ├── font.ttf │ ├── game.js │ ├── gute-1.0.14.js │ ├── index.html │ └── logo.png ├── package-lock.json ├── package.json ├── src ├── .DS_Store ├── Bitmap.ts ├── Font.ts ├── Game.ts ├── GameContext.ts ├── Graphics.ts ├── Gute.ts ├── Keys.ts ├── Log.ts ├── Resource.ts ├── Sound.ts ├── SoundScape.ts ├── Tileset.ts ├── impl │ ├── BitmapImpl.ts │ ├── FontImpl.ts │ ├── GraphicsImpl.ts │ ├── Palette.ts │ ├── SoundImpl.ts │ └── TilesetImpl.ts ├── index.ts ├── opengl │ ├── OpenGLBitmap.1.ts │ ├── OpenGLBitmap.ts │ ├── OpenGLGraphicsImpl.ts │ ├── OpenGLOffscreen.ts │ ├── OpenGLTilesetImpl.ts │ └── RenderingState.ts ├── path │ ├── AStarPathFinder.ts │ ├── MapNode.ts │ ├── Path.ts │ ├── PathFinderMap.ts │ ├── PathMover.ts │ └── Step.ts ├── tilemaps │ ├── LDTKWorld.ts │ ├── MapEntity.ts │ ├── MapLayer.ts │ ├── MapLevel.ts │ └── MapWorld.ts └── types.d.ts ├── tsconfig.json ├── webpack.config.js └── webpack.config.prod.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /lib 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://kevglass.github.io/gute/test/logo.png) 2 | 3 | # gute 4 | 5 | Gute is a simple web games library. It gives you a game loop, input, sounds, music and tilesets. I'll probably add some bitmap font support. 6 | 7 | Linked to Discord 8 | 9 | # Installation 10 | 11 | ```npm install gute``` 12 | 13 | or if you're using pure JS just grab https://github.com/kevglass/gute/blob/master/dist/index.js and include it. See the example below. 14 | 15 | # Example 16 | 17 | https://kevglass.github.io/gute/test/ 18 | 19 | Pure JS Source code: https://kevglass.github.io/gute/test/game.js 20 | 21 | TS/Webpack Example: https://github.com/kevglass/gute-test 22 | 23 | # Guide 24 | 25 | https://github.com/kevglass/gute/wiki 26 | 27 | # Docs 28 | 29 | https://kevglass.github.io/gute/ 30 | 31 | # Next Up 32 | 33 | * [LDTk](https://ldtk.io/) Map Loading 34 | * A-Star Pathfinder 35 | * Bitmap Font support if I can find a common standard 36 | 37 | 38 | -------------------------------------------------------------------------------- /dist/Bitmap.d.ts: -------------------------------------------------------------------------------- 1 | import { Graphics } from "./Graphics"; 2 | import { Resource } from "./Resource"; 3 | export interface Bitmap extends Resource { 4 | width: number; 5 | height: number; 6 | draw(graphics: Graphics, x: number, y: number): void; 7 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 8 | } 9 | -------------------------------------------------------------------------------- /dist/Font.d.ts: -------------------------------------------------------------------------------- 1 | export interface Font { 2 | apply(ctx: CanvasRenderingContext2D, size: number): void; 3 | } 4 | -------------------------------------------------------------------------------- /dist/Game.d.ts: -------------------------------------------------------------------------------- 1 | import { GameContext } from "./GameContext"; 2 | import { Graphics } from "./Graphics"; 3 | export interface Game { 4 | init(context: GameContext): void; 5 | onMouseDown(context: GameContext, x: number, y: number, id: number): void; 6 | onMouseWheel(context: GameContext, delta: number): void; 7 | onMouseUp(context: GameContext, x: number, y: number, id: number): void; 8 | onMouseMove(context: GameContext, x: number, y: number): void; 9 | onKeyDown(context: GameContext, key: string): void; 10 | onKeyUp(context: GameContext, key: string): void; 11 | update(context: GameContext, delta: number): void; 12 | render(context: GameContext, g: Graphics): void; 13 | rendererError(message: string): void; 14 | } 15 | -------------------------------------------------------------------------------- /dist/GameContext.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "./Bitmap"; 2 | import { Font } from "./Font"; 3 | import { Graphics } from "./Graphics"; 4 | import { MapWorld } from "./tilemaps/MapWorld"; 5 | import { Sound } from "./Sound"; 6 | import { Tileset } from "./Tileset"; 7 | export interface GameContext { 8 | isShiftPressed(): boolean; 9 | isCommandPressed(): boolean; 10 | isAltPressed(): boolean; 11 | isControlPressed(): boolean; 12 | resourcesRemaining(): number; 13 | resourcesTotal(): number; 14 | currentResource(): string; 15 | dumpResourceIssues(): void; 16 | allResourcesLoaded(): boolean; 17 | applyPalette(hexFile: string): Promise; 18 | loadZip(url: string): Promise; 19 | loadLDTK(name: string, locator: (name: string) => string): Promise; 20 | loadMusic(url: string): Sound; 21 | loadSound(url: string): Sound; 22 | loadScaledTileset(url: string, tileWidth: number, tileHeight: number, scale: number): Tileset; 23 | loadTileset(url: string, tileWidth: number, tileHeight: number): Tileset; 24 | loadBitmap(url: string): Bitmap; 25 | getZipProgress(): number; 26 | loadFont(url: string, name: string): Font; 27 | getGraphics(): Graphics; 28 | loadJson(url: string, transform?: (text: string) => string): Promise; 29 | isRunningStandalone(): boolean; 30 | isTablet(): boolean; 31 | isMobile(): boolean; 32 | isAndroid(): boolean; 33 | isIOS(): boolean; 34 | isPhone(): boolean; 35 | setSoundVolume(v: number): void; 36 | getSoundVolume(): number; 37 | setMusicVolume(v: number): void; 38 | getMusicVolume(): number; 39 | } 40 | -------------------------------------------------------------------------------- /dist/Graphics.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "./Bitmap"; 2 | import { Font } from "./Font"; 3 | export declare const WHITE: string; 4 | export declare const BLACK: string; 5 | export declare const RED: string; 6 | export declare const GREEN: string; 7 | export declare const BLUE: string; 8 | export interface Offscreen { 9 | getWidth(): number; 10 | getHeight(): number; 11 | setDimension(width: number, height: number): void; 12 | needsRefresh(): boolean; 13 | release(): void; 14 | } 15 | export interface Graphics { 16 | initResourceOnLoaded(): void; 17 | newResourceLoaded(): void; 18 | get canvas(): HTMLCanvasElement; 19 | getError(): string | undefined; 20 | applyFont(): void; 21 | smooth(): void; 22 | copy(): Bitmap; 23 | getOffscreen(): Offscreen | null; 24 | clip(x: number, y: number, width: number, height: number): void; 25 | createOffscreen(): Offscreen; 26 | drawToOffscreen(screen: Offscreen | null): void; 27 | drawOffscreen(screen: Offscreen): void; 28 | drawScaledOffscreen(screen: Offscreen, x: number, y: number, width: number, height: number): void; 29 | drawScaledOffscreenSegment(screen: Offscreen, sx: number, sy: number, swidth: number, sheight: number, x: number, y: number, width: number, height: number): void; 30 | fillRect(x: number, y: number, width: number, height: number, col: string): void; 31 | fillCircle(x: number, y: number, radius: number, col: string): void; 32 | drawCircle(x: number, y: number, radius: number, col: string, width: number): void; 33 | setLineDash(segments: number[]): void; 34 | drawLine(x1: number, y1: number, x2: number, y2: number, col: string, width?: number): void; 35 | drawBitmap(x: number, y: number, bitmap: Bitmap): void; 36 | drawScaledBitmap(x: number, y: number, width: number, height: number, bitmap: Bitmap): void; 37 | clearRect(x: number, y: number, width: number, height: number): void; 38 | clear(): void; 39 | setFont(font: Font): void; 40 | setComposite(name: string): void; 41 | setFontSize(size: number): void; 42 | drawString(x: number, y: number, text: string, col: string): void; 43 | translate(x: number, y: number): void; 44 | scale(x: number, y: number): void; 45 | push(): void; 46 | pop(): void; 47 | getWidth(): number; 48 | getHeight(): number; 49 | fitScreen(widthInVirtualPixels: number): void; 50 | getStringWidth(text: string): number; 51 | setAlpha(alpha: number): void; 52 | getTransform(): DOMMatrix; 53 | renderStart(): void; 54 | renderEnd(): void; 55 | } 56 | -------------------------------------------------------------------------------- /dist/Gute.d.ts: -------------------------------------------------------------------------------- 1 | import { Game } from "./Game"; 2 | export declare function isSoundOn(): boolean; 3 | export declare function isMusicOn(): boolean; 4 | export declare function shouldUseXbr(): boolean; 5 | export declare function setUseXbr(on: boolean): void; 6 | export declare function shouldPrescaleTilesets(): boolean; 7 | export declare function setPrescaleTilesets(on: boolean): void; 8 | export declare function setSoundOn(on: boolean): void; 9 | export declare function setMusicOn(on: boolean): void; 10 | export declare function startGame(game: Game, renderer?: Renderer): void; 11 | export declare enum Renderer { 12 | CANVAS = "Canvas", 13 | OPENGL = "OpenGL" 14 | } 15 | -------------------------------------------------------------------------------- /dist/Keys.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Keys { 2 | static ESCAPE_KEY: string; 3 | static ENTER_KEY: string; 4 | static LEFT_KEY: string; 5 | static RIGHT_KEY: string; 6 | static UP_KEY: string; 7 | static DOWN_KEY: string; 8 | static SPACE_KEY: string; 9 | static S_KEY: string; 10 | static M_KEY: string; 11 | static A_KEY: string; 12 | static W_KEY: string; 13 | static D_KEY: string; 14 | static CONTROL_KEY: string; 15 | static META_KEY: string; 16 | static ALT_KEY: string; 17 | static TAB_KEY: string; 18 | } 19 | -------------------------------------------------------------------------------- /dist/Log.d.ts: -------------------------------------------------------------------------------- 1 | export declare class GuteLog { 2 | static INFO: boolean; 3 | static ERROR: boolean; 4 | static WARN: boolean; 5 | static TRACE: boolean; 6 | static log(...args: any): void; 7 | static info(...args: any): void; 8 | static error(...args: any): void; 9 | static warm(...args: any): void; 10 | static trace(...args: any): void; 11 | } 12 | -------------------------------------------------------------------------------- /dist/Resource.d.ts: -------------------------------------------------------------------------------- 1 | export interface Resource { 2 | loaded: boolean; 3 | name: string; 4 | initOnFirstClick(): void; 5 | } 6 | -------------------------------------------------------------------------------- /dist/Sound.d.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "./Resource"; 2 | export interface Sound extends Resource { 3 | play(volume: number, loop?: boolean): void; 4 | stop(): void; 5 | } 6 | -------------------------------------------------------------------------------- /dist/SoundScape.d.ts: -------------------------------------------------------------------------------- 1 | import { Sound } from "./Sound"; 2 | export declare enum SoundEasing { 3 | Linear = 0, 4 | Quadratic = 1, 5 | Cubic = 2 6 | } 7 | export declare class SoundScape { 8 | private _soundVolume; 9 | private points; 10 | private listenerX; 11 | private listenerY; 12 | private categories; 13 | category(name: string, volume: number, maxDistance: number, scale: number, easing: SoundEasing): SoundScape; 14 | get soundVolume(): number; 15 | set soundVolume(value: number); 16 | moveTo(x: number, y: number): void; 17 | clear(): void; 18 | play(sound: Sound, volume: number, category: string, x?: number, y?: number): void; 19 | private updateVolumes; 20 | private calculateVolume; 21 | } 22 | -------------------------------------------------------------------------------- /dist/Tileset.d.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "./Resource"; 2 | import { Bitmap } from "./Bitmap"; 3 | export interface Tileset extends Resource { 4 | onLoaded: () => void; 5 | getTile(tile: number): Bitmap; 6 | getBlockColorTile(tile: number, tintName: string, col: number[]): Bitmap; 7 | getShadedTile(tile: number, tintName: string, shade: number): Bitmap; 8 | getTintedTile(tile: number, tintName: string, tint: number[]): Bitmap; 9 | getTileCount(): number; 10 | getTileWidth(): number; 11 | getTileHeight(): number; 12 | getTilesAcross(): number; 13 | modify(modification: (imageData: ImageData) => void): Tileset; 14 | } 15 | -------------------------------------------------------------------------------- /dist/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/dist/font.ttf -------------------------------------------------------------------------------- /dist/impl/BitmapImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Graphics } from "../Graphics"; 3 | import { Palette } from "./Palette"; 4 | export declare class BitmapImpl implements Bitmap { 5 | width: number; 6 | height: number; 7 | loaded: boolean; 8 | image: HTMLImageElement; 9 | name: string; 10 | constructor(url: string, dataUrlLoader: Promise | undefined, pal?: Palette | undefined); 11 | draw(graphics: Graphics, x: number, y: number): void; 12 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 13 | initOnFirstClick(): void; 14 | } 15 | -------------------------------------------------------------------------------- /dist/impl/FontImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Font } from "../Font"; 2 | export declare class FontImpl implements Font { 3 | name: string; 4 | constructor(url: string, name: string); 5 | apply(ctx: CanvasRenderingContext2D, size: number): void; 6 | } 7 | -------------------------------------------------------------------------------- /dist/impl/GraphicsImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap, Graphics } from ".."; 2 | import { Font } from "../Font"; 3 | import { Offscreen } from "../Graphics"; 4 | export declare class GraphicsImpl implements Graphics { 5 | canvas: HTMLCanvasElement; 6 | ctx: CanvasRenderingContext2D; 7 | mainCtx: CanvasRenderingContext2D; 8 | font: Font; 9 | fontSize: number; 10 | offscreen: Offscreen | null; 11 | constructor(); 12 | getError(): string | undefined; 13 | renderStart(): void; 14 | renderEnd(): void; 15 | newResourceLoaded(): void; 16 | initResourceOnLoaded(): void; 17 | smooth(): void; 18 | getTransform(): DOMMatrix; 19 | clear(): void; 20 | clip(x: number, y: number, width: number, height: number): void; 21 | createOffscreen(): Offscreen; 22 | getOffscreen(): Offscreen | null; 23 | drawToOffscreen(screen: Offscreen | null): void; 24 | drawOffscreen(screen: Offscreen): void; 25 | drawScaledOffscreen(screen: Offscreen, x: number, y: number, width: number, height: number): void; 26 | drawScaledOffscreenSegment(screen: Offscreen, sx: number, sy: number, swidth: number, sheight: number, x: number, y: number, width: number, height: number): void; 27 | clearRect(x: number, y: number, width: number, height: number): void; 28 | fitScreen(pixelScale: number): void; 29 | setAlpha(alpha: number): void; 30 | copy(): Bitmap; 31 | getWidth(): number; 32 | getHeight(): number; 33 | push(): void; 34 | pop(): void; 35 | translate(x: number, y: number): void; 36 | scale(x: number, y: number): void; 37 | applyFont(): void; 38 | setFont(font: Font): void; 39 | setFontSize(size: number): void; 40 | getStringWidth(text: string): number; 41 | drawString(x: number, y: number, text: string, col: string): void; 42 | setComposite(name: string): void; 43 | drawCircle(x: number, y: number, radius: number, col: string, width: number): void; 44 | fillCircle(x: number, y: number, radius: number, col: string): void; 45 | fillRect(x: number, y: number, width: number, height: number, col: string): void; 46 | setLineDash(segments: number[]): void; 47 | drawLine(x1: number, y1: number, x2: number, y2: number, col: string, width?: number): void; 48 | drawBitmap(x: number, y: number, bitmap: Bitmap): void; 49 | drawScaledBitmap(x: number, y: number, width: number, height: number, bitmap: Bitmap): void; 50 | } 51 | -------------------------------------------------------------------------------- /dist/impl/Palette.d.ts: -------------------------------------------------------------------------------- 1 | interface Col { 2 | r: number; 3 | g: number; 4 | b: number; 5 | } 6 | export declare class Palette { 7 | cols: Col[]; 8 | mapping: Record; 9 | constructor(hexValues: string); 10 | findBestMatch(r: number, g: number, b: number): Col; 11 | adjustImage(image: HTMLImageElement): Promise; 12 | } 13 | export {}; 14 | -------------------------------------------------------------------------------- /dist/impl/SoundImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Sound } from "../Sound"; 2 | export declare let AUDIO_CONTEXT: AudioContext; 3 | export declare class SoundImpl implements Sound { 4 | static CURRENT_MUSIC: SoundImpl | null; 5 | static CURRENT_LOOPS: SoundImpl[]; 6 | static soundVolume: number; 7 | static musicVolume: number; 8 | static setSoundVolume(v: number): void; 9 | static getSoundVolume(): number; 10 | static setMusicVolume(v: number): void; 11 | static getMusicVolume(): number; 12 | loaded: boolean; 13 | data: ArrayBuffer; 14 | volume: number; 15 | buffer: AudioBuffer; 16 | music: boolean; 17 | source: AudioBufferSourceNode | null; 18 | gain: GainNode; 19 | url: string; 20 | looped: boolean; 21 | name: string; 22 | constructor(url: string, music: boolean, arrayBuffer: Promise | undefined); 23 | private tryLoad; 24 | confirmAudioContext(): void; 25 | initOnFirstClick(): void; 26 | play(volume: number, loop?: boolean): void; 27 | stop(remove?: boolean): void; 28 | } 29 | -------------------------------------------------------------------------------- /dist/impl/TilesetImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap, Graphics } from ".."; 2 | import { Tileset } from "../Tileset"; 3 | import { Palette } from "./Palette"; 4 | declare class Tile implements Bitmap { 5 | image: HTMLImageElement; 6 | width: number; 7 | height: number; 8 | loaded: boolean; 9 | x: number; 10 | y: number; 11 | scale: number; 12 | name: string; 13 | cached: Record; 14 | constructor(canvas: HTMLImageElement, x: number, y: number, width: number, height: number, scale: number); 15 | draw(graphics: Graphics, x: number, y: number): void; 16 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 17 | initOnFirstClick(): void; 18 | } 19 | export declare class TilesetImpl implements Tileset { 20 | loaded: boolean; 21 | tileWidth: number; 22 | tileHeight: number; 23 | originalTileWidth: number; 24 | originalTileHeight: number; 25 | image: any | null; 26 | bitmaps: Tile[]; 27 | scanline: number; 28 | tileCount: number; 29 | tints: Record; 30 | tintTiles: Record; 31 | scale: number; 32 | onLoaded: () => void; 33 | name: string; 34 | constructor(url: string, dataUrlLoader: Promise | undefined, tileWidth: number, tileHeight: number, scale?: number, pal?: Palette | undefined); 35 | getTilesAcross(): number; 36 | getTileWidth(): number; 37 | getTileHeight(): number; 38 | getTileCount(): number; 39 | initOnFirstClick(): void; 40 | getTile(tile: number): Bitmap; 41 | getShadedTile(tile: number, tintName: string, shade: number): Bitmap; 42 | getTintedTile(tile: number, tintName: string, tint: number[]): Bitmap; 43 | modify(modification: (imageData: ImageData) => void): Tileset; 44 | getBlockColorTile(tile: number, tintName: string, col: number[]): Bitmap; 45 | } 46 | export {}; 47 | -------------------------------------------------------------------------------- /dist/index-min.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | JSZip v3.7.1 - A JavaScript class for generating and reading zip files 4 | 5 | 6 | (c) 2009-2016 Stuart Knightley 7 | Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/master/LICENSE.markdown. 8 | 9 | JSZip uses the library pako released under the MIT license : 10 | https://github.com/nodeca/pako/blob/master/LICENSE 11 | */ 12 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export { startGame, isMusicOn, isSoundOn, setMusicOn, setSoundOn, setPrescaleTilesets, Renderer } from "./Gute"; 2 | export { GameContext } from "./GameContext"; 3 | export { Graphics, WHITE, BLACK, GREEN, RED, BLUE, Offscreen } from "./Graphics"; 4 | export { getMaxTextureSize } from "./opengl/OpenGLGraphicsImpl"; 5 | export { Game } from "./Game"; 6 | export { GuteLog } from "./Log"; 7 | export { Bitmap } from "./Bitmap"; 8 | export { Font } from "./Font"; 9 | export { Sound } from "./Sound"; 10 | export { Tileset } from "./Tileset"; 11 | export { Keys } from "./Keys"; 12 | export { AStarPathFinder } from "./path/AStarPathFinder"; 13 | export { PathFinderMap } from "./path/PathFinderMap"; 14 | export { Path } from "./path/Path"; 15 | export { PathMover } from "./path/PathMover"; 16 | export { Step } from "./path/Step"; 17 | export { LDTKWorld, LDTKLayerCompression } from "./tilemaps/LDTKWorld"; 18 | export { MapWorld } from "./tilemaps/MapWorld"; 19 | export { MapLevel } from "./tilemaps/MapLevel"; 20 | export { MapLayer } from "./tilemaps/MapLayer"; 21 | export { MapEntity } from "./tilemaps/MapEntity"; 22 | export { SoundScape, SoundEasing } from "./SoundScape"; 23 | -------------------------------------------------------------------------------- /dist/ldtk/LDTKEntity.d.ts: -------------------------------------------------------------------------------- 1 | import { LDTKLevel } from "./LDTKLevel"; 2 | export declare class LDTKEntity { 3 | type: string; 4 | x: number; 5 | y: number; 6 | level: LDTKLevel; 7 | fields: any; 8 | constructor(level: LDTKLevel, layerData: any, data: any); 9 | } 10 | -------------------------------------------------------------------------------- /dist/ldtk/LDTKLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { LDTKLevel } from "./LDTKLevel"; 2 | export declare class LDTKLayer { 3 | name: string; 4 | level: LDTKLevel; 5 | width: number; 6 | height: number; 7 | tiles: number[]; 8 | constructor(level: LDTKLevel, data: any); 9 | get(x: number, y: number): number; 10 | } 11 | -------------------------------------------------------------------------------- /dist/ldtk/LDTKLevel.d.ts: -------------------------------------------------------------------------------- 1 | import { LDTKEntity } from "./LDTKEntity"; 2 | import { LDTKLayer } from "./LDTKLayer"; 3 | import { LDTKWorld } from "./LDTKWorld"; 4 | export declare class LDTKLevel { 5 | id: string; 6 | layers: LDTKLayer[]; 7 | layerByName: Record; 8 | width: number; 9 | height: number; 10 | world: LDTKWorld; 11 | entities: LDTKEntity[]; 12 | constructor(world: LDTKWorld, data: any); 13 | getFirstEntityOfType(type: string): LDTKEntity | null; 14 | } 15 | -------------------------------------------------------------------------------- /dist/ldtk/LDTKWorld.d.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "../Resource"; 2 | import { LDTKLevel } from "./LDTKLevel"; 3 | export declare class LDTKWorld implements Resource { 4 | levels: Record; 5 | gridSize: number; 6 | tilesetScanline: number; 7 | tilesetSize: number; 8 | loaded: boolean; 9 | constructor(); 10 | initOnFirstClick(): void; 11 | load(json: any): LDTKWorld; 12 | } 13 | -------------------------------------------------------------------------------- /dist/opengl/OpenGLBitmap.1.d.ts: -------------------------------------------------------------------------------- 1 | import { Graphics } from "../Graphics"; 2 | import { Palette } from "../impl/Palette"; 3 | import { OpenGLGraphicsImpl } from "./OpenGLGraphicsImpl"; 4 | import { IOpenGLBitmap } from "./OpenGLBitmap"; 5 | export declare class OpenGLBitmap implements IOpenGLBitmap { 6 | width: number; 7 | height: number; 8 | loaded: boolean; 9 | name: string; 10 | image: HTMLImageElement; 11 | texX: number; 12 | texY: number; 13 | texIndex: number; 14 | constructor(graphics: OpenGLGraphicsImpl, url: string, dataUrlLoader: Promise | undefined, pal?: Palette | undefined); 15 | draw(graphics: Graphics, x: number, y: number): void; 16 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 17 | initOnFirstClick(): void; 18 | } 19 | -------------------------------------------------------------------------------- /dist/opengl/OpenGLBitmap.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Graphics } from "../Graphics"; 3 | export interface IOpenGLBitmap { 4 | texX: number; 5 | texY: number; 6 | texIndex: number; 7 | width: number; 8 | height: number; 9 | image?: HTMLImageElement; 10 | } 11 | export declare class NullBitmap implements Bitmap { 12 | width: number; 13 | height: number; 14 | loaded: boolean; 15 | name: string; 16 | draw(graphics: Graphics, x: number, y: number): void; 17 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 18 | initOnFirstClick(): void; 19 | } 20 | -------------------------------------------------------------------------------- /dist/opengl/OpenGLGraphicsImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Font } from "../Font"; 3 | import { Graphics, Offscreen } from "../Graphics"; 4 | import { IOpenGLBitmap, NullBitmap } from "./OpenGLBitmap"; 5 | import { OpenGlOffscreen } from "./OpenGLOffscreen"; 6 | import { RenderingState } from "./RenderingState"; 7 | export declare function colToNumber(input: string): number; 8 | export declare function getMaxTextureSize(): number; 9 | export declare class OpenGLGraphicsImpl implements Graphics, RenderingState { 10 | static NULL_COPY: NullBitmap; 11 | canvas: HTMLCanvasElement; 12 | offscreen: Offscreen | null; 13 | gl: WebGLRenderingContext; 14 | extension?: ANGLE_instanced_arrays; 15 | images: IOpenGLBitmap[]; 16 | atlasTextures: WebGLTexture[] | null; 17 | currentTexture: WebGLTexture | null; 18 | texWidth: number; 19 | texHeight: number; 20 | offscreenId: number; 21 | offscreens: OpenGlOffscreen[]; 22 | loaded: boolean; 23 | arrayBuffer?: ArrayBuffer; 24 | shaderProgram?: WebGLProgram; 25 | glBuffer?: WebGLBuffer | null; 26 | maxDraws: number; 27 | positions?: Int16Array; 28 | rgbas?: Uint32Array; 29 | draws: number; 30 | clipX: number; 31 | clipY: number; 32 | clipX2: number; 33 | clipY2: number; 34 | alpha: number; 35 | currentContextState: RenderingState; 36 | transformCanvas: HTMLCanvasElement; 37 | transformCtx: CanvasRenderingContext2D; 38 | uniforms: Record; 39 | saves: number; 40 | constructor(); 41 | private lostContext; 42 | private recoverContext; 43 | private initGlResources; 44 | registerImage(bitmap: IOpenGLBitmap): void; 45 | newResourceLoaded(): void; 46 | initResourceOnLoaded(): void; 47 | resetState(): void; 48 | getUniformLoc(name: string): WebGLUniformLocation; 49 | resize(): void; 50 | getError(): string | undefined; 51 | _drawBitmap(img: IOpenGLBitmap, x: number, y: number, width: number, height: number, col?: number): void; 52 | _drawImage(texIndex: number, texX: number, texY: number, texWidth: number, texHeight: number, drawX: number, drawY: number, width: number, height: number, rgba: number, alpha: number): void; 53 | glStartContext(): void; 54 | glCommitContext(): void; 55 | renderStart(): void; 56 | renderEnd(): void; 57 | applyFont(): void; 58 | smooth(): void; 59 | copy(): Bitmap; 60 | getOffscreen(): Offscreen | null; 61 | clip(x: number, y: number, width: number, height: number): void; 62 | createOffscreen(): Offscreen; 63 | drawToOffscreen(screen: Offscreen | null): void; 64 | drawOffscreen(screen: Offscreen): void; 65 | drawScaledOffscreen(screen: Offscreen, x: number, y: number, width: number, height: number): void; 66 | drawScaledOffscreenSegment(screen: Offscreen, sx: number, sy: number, swidth: number, sheight: number, x: number, y: number, width: number, height: number): void; 67 | fillRect(x: number, y: number, width: number, height: number, col: string): void; 68 | fillCircle(x: number, y: number, radius: number, col: string): void; 69 | drawCircle(x: number, y: number, radius: number, col: string, width: number): void; 70 | setLineDash(segments: number[]): void; 71 | drawLine(x1: number, y1: number, x2: number, y2: number, col: string, width?: number | undefined): void; 72 | drawBitmap(x: number, y: number, bitmap: Bitmap): void; 73 | drawScaledBitmap(x: number, y: number, width: number, height: number, bitmap: Bitmap): void; 74 | clearRect(x: number, y: number, width: number, height: number): void; 75 | clear(): void; 76 | setFont(font: Font): void; 77 | setComposite(name: string): void; 78 | setFontSize(size: number): void; 79 | drawString(x: number, y: number, text: string, col: string): void; 80 | translate(x: number, y: number): void; 81 | scale(x: number, y: number): void; 82 | push(): void; 83 | pop(): void; 84 | getWidth(): number; 85 | getHeight(): number; 86 | fitScreen(pixelScale: number): void; 87 | getStringWidth(text: string): number; 88 | setAlpha(alpha: number): void; 89 | getTransform(): DOMMatrix; 90 | } 91 | -------------------------------------------------------------------------------- /dist/opengl/OpenGLOffscreen.d.ts: -------------------------------------------------------------------------------- 1 | import { Offscreen } from "../Graphics"; 2 | import { OpenGLGraphicsImpl } from "./OpenGLGraphicsImpl"; 3 | import { RenderingState } from "./RenderingState"; 4 | export declare class OpenGlOffscreen implements Offscreen, RenderingState { 5 | width: number; 6 | height: number; 7 | texture: WebGLTexture | null; 8 | gl: WebGLRenderingContext; 9 | fb: WebGLFramebuffer | null; 10 | graphics: OpenGLGraphicsImpl; 11 | id: number; 12 | inuse: boolean; 13 | clipX: number; 14 | clipY: number; 15 | clipX2: number; 16 | clipY2: number; 17 | alpha: number; 18 | refreshRequired: boolean; 19 | constructor(gl: WebGLRenderingContext, graphics: OpenGLGraphicsImpl, id: number); 20 | getWidth(): number; 21 | getHeight(): number; 22 | recover(): void; 23 | use(): void; 24 | unuse(): void; 25 | needsRefresh(): boolean; 26 | release(): void; 27 | setDimension(width: number, height: number): void; 28 | } 29 | -------------------------------------------------------------------------------- /dist/opengl/OpenGLTilesetImpl.d.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Tileset } from "../Tileset"; 3 | import { Palette } from "../impl/Palette"; 4 | import { OpenGLGraphicsImpl } from "./OpenGLGraphicsImpl"; 5 | import { IOpenGLBitmap } from "./OpenGLBitmap"; 6 | import { Graphics } from "../Graphics"; 7 | declare class OpenGLTile implements Bitmap, IOpenGLBitmap { 8 | parent: OpenGLTilesetImpl; 9 | width: number; 10 | height: number; 11 | loaded: boolean; 12 | x: number; 13 | y: number; 14 | scale: number; 15 | name: string; 16 | texX: number; 17 | texY: number; 18 | texIndex: number; 19 | image: HTMLImageElement; 20 | col: number; 21 | constructor(parent: OpenGLTilesetImpl, image: HTMLImageElement, x: number, y: number, width: number, height: number, scale: number); 22 | copyWithCol(rgba: number): OpenGLTile; 23 | draw(graphics: Graphics, x: number, y: number): void; 24 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 25 | initOnFirstClick(): void; 26 | } 27 | export declare class OpenGLTilesetImpl implements Tileset, IOpenGLBitmap { 28 | loaded: boolean; 29 | tileWidth: number; 30 | tileHeight: number; 31 | originalTileWidth: number; 32 | originalTileHeight: number; 33 | image: any | null; 34 | bitmaps: OpenGLTile[]; 35 | scanline: number; 36 | tileCount: number; 37 | scale: number; 38 | onLoaded: () => void; 39 | name: string; 40 | texX: number; 41 | texY: number; 42 | texIndex: number; 43 | tintTiles: Record; 44 | constructor(graphics: OpenGLGraphicsImpl, url: string, dataUrlLoader: Promise | undefined, tileWidth: number, tileHeight: number, scale?: number, pal?: Palette | undefined); 45 | get height(): number; 46 | get width(): number; 47 | draw(graphics: Graphics, x: number, y: number): void; 48 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 49 | getBlockColorTile(tile: number, tintName: string, rgba: number[]): Bitmap; 50 | getShadedTile(tile: number, tintName: string, shade: number): Bitmap; 51 | getTintedTile(tile: number, tintName: string, source: number[]): Bitmap; 52 | modify(modification: (imageData: ImageData) => void): Tileset; 53 | getTilesAcross(): number; 54 | getTileWidth(): number; 55 | getTileHeight(): number; 56 | getTileCount(): number; 57 | initOnFirstClick(): void; 58 | getTile(tile: number): OpenGLTile; 59 | } 60 | export {}; 61 | -------------------------------------------------------------------------------- /dist/opengl/RenderingState.d.ts: -------------------------------------------------------------------------------- 1 | export interface RenderingState { 2 | clipX: number; 3 | clipY: number; 4 | clipX2: number; 5 | clipY2: number; 6 | alpha: number; 7 | } 8 | -------------------------------------------------------------------------------- /dist/opengl/State.d.ts: -------------------------------------------------------------------------------- 1 | export declare class State { 2 | alphas: number[]; 3 | transforms: any[]; 4 | states: any[]; 5 | positions: Int16Array; 6 | rotations: Float32Array; 7 | rgbas: Uint32Array; 8 | draws: number; 9 | brightness: number; 10 | translateX: number; 11 | translateY: number; 12 | scaleX: number; 13 | scaleY: number; 14 | rotation: number; 15 | clipX: number; 16 | clipY: number; 17 | clipX2: number; 18 | clipY2: number; 19 | maxDraws: number; 20 | alpha: number; 21 | gl: WebGLRenderingContext; 22 | name: string; 23 | constructor(gl: WebGLRenderingContext, name: string); 24 | reset(): void; 25 | } 26 | -------------------------------------------------------------------------------- /dist/path/AStarPathFinder.d.ts: -------------------------------------------------------------------------------- 1 | import { Path } from "./Path"; 2 | import { PathFinderMap } from "./PathFinderMap"; 3 | import { PathMover } from "./PathMover"; 4 | export declare class AStarPathFinder { 5 | static NORTH_TO_SOUTH: number; 6 | static EAST_TO_WEST: number; 7 | static SOUTH_TO_NORTH: number; 8 | static WEST_TO_EAST: number; 9 | static NONE: number; 10 | private objectPool; 11 | private openList; 12 | private parentList; 13 | private open; 14 | private closed; 15 | private map; 16 | private height; 17 | private width; 18 | private pathFindCounter; 19 | private mover; 20 | private tx; 21 | private ty; 22 | private cx; 23 | private cy; 24 | private max; 25 | constructor(map: PathFinderMap); 26 | setMap(map: PathFinderMap): void; 27 | clear(): void; 28 | private generatePath; 29 | private blocked; 30 | private atTarget; 31 | findPath(mover: PathMover, tx: number, ty: number, width: number, height: number, max: number): Path | null; 32 | private addLocation; 33 | private static binarySearch; 34 | private createMapNode; 35 | } 36 | -------------------------------------------------------------------------------- /dist/path/MapNode.d.ts: -------------------------------------------------------------------------------- 1 | export declare class MapNode { 2 | x: number; 3 | y: number; 4 | parent: MapNode | null; 5 | h: number; 6 | depth: number; 7 | } 8 | -------------------------------------------------------------------------------- /dist/path/Path.d.ts: -------------------------------------------------------------------------------- 1 | import { Step } from "./Step"; 2 | export declare class Path { 3 | steps: Array; 4 | add(x: number, y: number): void; 5 | getLastStep(): Step; 6 | pop(): Step; 7 | copy(): Path; 8 | } 9 | -------------------------------------------------------------------------------- /dist/path/PathFinderMap.d.ts: -------------------------------------------------------------------------------- 1 | import { PathMover } from "./PathMover"; 2 | export interface PathFinderMap { 3 | getMapWidth(): number; 4 | getMapHeight(): number; 5 | locationDiscovered(x: number, y: number): boolean; 6 | blocked(mover: PathMover, object: PathMover | null, sx: number, sy: number, x: number, y: number, lastStep: boolean): boolean; 7 | getMoverAt(tx: number, ty: number): PathMover | null; 8 | validLocation(x: number, y: number): boolean; 9 | } 10 | -------------------------------------------------------------------------------- /dist/path/PathMover.d.ts: -------------------------------------------------------------------------------- 1 | export interface PathMover { 2 | getTilesWidth(): number; 3 | getTilesHeight(): number; 4 | getTarget(): PathMover | null; 5 | getTileMapY(): number; 6 | getTileMapX(): number; 7 | } 8 | -------------------------------------------------------------------------------- /dist/path/Step.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Step { 2 | x: number; 3 | y: number; 4 | constructor(x: number, y: number); 5 | } 6 | -------------------------------------------------------------------------------- /dist/tilemaps/LDTKWorld.d.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "../Resource"; 2 | import { MapWorld } from "./MapWorld"; 3 | export interface LDTKLayerCompression { 4 | from: string; 5 | to: string; 6 | offset: number; 7 | } 8 | export declare class LDTKWorld extends MapWorld implements Resource { 9 | static LAYER_COMPRESSIONS: LDTKLayerCompression[]; 10 | name: string; 11 | tilesets: any[]; 12 | initOnFirstClick(): void; 13 | load(file: string, loader: (file: string) => Promise): Promise; 14 | private static loadLayers; 15 | } 16 | -------------------------------------------------------------------------------- /dist/tilemaps/MapEntity.d.ts: -------------------------------------------------------------------------------- 1 | import { MapLevel } from "./MapLevel"; 2 | export declare class MapEntity { 3 | id?: string; 4 | type: string; 5 | x: number; 6 | y: number; 7 | width: number; 8 | height: number; 9 | level: MapLevel; 10 | fields: any; 11 | constructor(level: MapLevel, x: number, y: number, width: number, height: number, type: string); 12 | copy(level: MapLevel): MapEntity; 13 | } 14 | -------------------------------------------------------------------------------- /dist/tilemaps/MapLayer.d.ts: -------------------------------------------------------------------------------- 1 | import { MapLevel } from "./MapLevel"; 2 | export declare class MapLayer { 3 | static FLIP_X: number; 4 | static FLIP_Y: number; 5 | name: string; 6 | level: MapLevel; 7 | width: number; 8 | height: number; 9 | tiles: number[]; 10 | flips: number[]; 11 | constructor(level: MapLevel, name: string, width: number, height: number); 12 | getFlipFlags(x: number, y: number): number; 13 | set(x: number, y: number, value: number): void; 14 | get(x: number, y: number): number; 15 | copy(level: MapLevel): MapLayer; 16 | } 17 | -------------------------------------------------------------------------------- /dist/tilemaps/MapLevel.d.ts: -------------------------------------------------------------------------------- 1 | import { MapEntity } from "./MapEntity"; 2 | import { MapLayer } from "./MapLayer"; 3 | import { MapWorld } from "./MapWorld"; 4 | export declare class MapLevel { 5 | id: string; 6 | layers: MapLayer[]; 7 | layerByName: Record; 8 | width: number; 9 | height: number; 10 | world: MapWorld; 11 | entities: MapEntity[]; 12 | fields: any; 13 | worldX: number; 14 | worldY: number; 15 | worldDepth: number; 16 | constructor(world: MapWorld, id: string); 17 | entitiesOfType(type: string): MapEntity[]; 18 | firstEntityOfType(type: string): MapEntity | undefined; 19 | copy(id: string): MapLevel; 20 | } 21 | -------------------------------------------------------------------------------- /dist/tilemaps/MapWorld.d.ts: -------------------------------------------------------------------------------- 1 | import { MapLevel } from "./MapLevel"; 2 | export declare class MapWorld { 3 | levels: Record; 4 | gridSize: number; 5 | tilesetScanline: number; 6 | tilesetSize: number; 7 | loaded: boolean; 8 | constructor(); 9 | } 10 | -------------------------------------------------------------------------------- /docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /docs/classes/step.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Step | gute 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |
    54 |
  • 55 | gute 56 |
  • 57 |
  • 58 | Step 59 |
  • 60 |
61 |

Class Step

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | Step 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Constructors

82 | 85 |
86 |
87 |

Properties

88 |
    89 |
  • x
  • 90 |
  • y
  • 91 |
92 |
93 |
94 |
95 |
96 |
97 |

Constructors

98 |
99 | 100 |

constructor

101 |
    102 |
  • new Step(x: number, y: number): Step
  • 103 |
104 |
    105 |
  • 106 | 111 |

    Parameters

    112 |
      113 |
    • 114 |
      x: number
      115 |
    • 116 |
    • 117 |
      y: number
      118 |
    • 119 |
    120 |

    Returns Step

    121 |
  • 122 |
123 |
124 |
125 |
126 |

Properties

127 |
128 | 129 |

x

130 |
x: number
131 | 136 |
137 |
138 | 139 |

y

140 |
y: number
141 | 146 |
147 |
148 |
149 | 237 |
238 |
239 |
240 |
241 |

Legend

242 |
243 |
    244 |
  • Constructor
  • 245 |
  • Property
  • 246 |
  • Method
  • 247 |
248 |
    249 |
  • Property
  • 250 |
  • Method
  • 251 |
252 |
    253 |
  • Private property
  • 254 |
  • Private method
  • 255 |
256 |
    257 |
  • Static property
  • 258 |
259 |
260 |
261 |
262 |
263 |

Generated using TypeDoc

264 |
265 |
266 | 267 | 268 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | gute 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |

gute

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |

logo

62 | 63 |

gute

64 |
65 |

Gute is a simple web games library. It gives you a game loop, input, sounds, music and tilesets. I'll probably add some bitmap font support.

66 | 67 |

Installation

68 |
69 |

npm install gute

70 |

or if you're using pure JS just grab https://kevglass.github.io/gute/dist/index.js and include it. See the example below.

71 | 72 |

Example

73 |
74 |

https://kevglass.github.io/gute/test/

75 |

Pure JS Source code: https://kevglass.github.io/gute/test/game.js

76 |

TS/Webpack Example: https://github.com/kevglass/gute-test

77 | 78 |

Guide

79 |
80 |

https://github.com/kevglass/gute/wiki

81 | 82 |

Docs

83 |
84 |

https://kevglass.github.io/gute/

85 | 86 |

Next Up

87 |
88 |
    89 |
  • LDTk Map Loading
  • 90 |
  • A-Star Pathfinder
  • 91 |
  • Bitmap Font support if I can find a common standard
  • 92 |
93 |
94 |
95 | 168 |
169 |
170 |
171 |
172 |

Legend

173 |
174 |
    175 |
  • Constructor
  • 176 |
  • Property
  • 177 |
  • Method
  • 178 |
179 |
    180 |
  • Property
  • 181 |
  • Method
  • 182 |
183 |
    184 |
  • Private property
  • 185 |
  • Private method
  • 186 |
187 |
    188 |
  • Static property
  • 189 |
190 |
191 |
192 |
193 |
194 |

Generated using TypeDoc

195 |
196 |
197 | 198 | 199 | -------------------------------------------------------------------------------- /docs/interfaces/font.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Font | gute 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |
    54 |
  • 55 | gute 56 |
  • 57 |
  • 58 | Font 59 |
  • 60 |
61 |

Interface Font

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | Font 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Methods

82 | 85 |
86 |
87 |
88 |
89 |
90 |

Methods

91 |
92 | 93 |

apply

94 |
    95 |
  • apply(ctx: CanvasRenderingContext2D, size: number): void
  • 96 |
97 |
    98 |
  • 99 | 104 |

    Parameters

    105 |
      106 |
    • 107 |
      ctx: CanvasRenderingContext2D
      108 |
    • 109 |
    • 110 |
      size: number
      111 |
    • 112 |
    113 |

    Returns void

    114 |
  • 115 |
116 |
117 |
118 |
119 | 201 |
202 |
203 |
204 |
205 |

Legend

206 |
207 |
    208 |
  • Constructor
  • 209 |
  • Property
  • 210 |
  • Method
  • 211 |
212 |
    213 |
  • Property
  • 214 |
  • Method
  • 215 |
216 |
    217 |
  • Private property
  • 218 |
  • Private method
  • 219 |
220 |
    221 |
  • Static property
  • 222 |
223 |
224 |
225 |
226 |
227 |

Generated using TypeDoc

228 |
229 |
230 | 231 | 232 | -------------------------------------------------------------------------------- /docs/interfaces/pathmover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PathMover | gute 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Interface PathMover

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | PathMover 73 |
  • 74 |
75 |
76 |
77 |

Index

78 |
79 |
80 |
81 |

Methods

82 | 89 |
90 |
91 |
92 |
93 |
94 |

Methods

95 |
96 | 97 |

getTarget

98 | 101 | 111 |
112 |
113 | 114 |

getTileMapX

115 |
    116 |
  • getTileMapX(): number
  • 117 |
118 |
    119 |
  • 120 | 125 |

    Returns number

    126 |
  • 127 |
128 |
129 |
130 | 131 |

getTileMapY

132 |
    133 |
  • getTileMapY(): number
  • 134 |
135 |
    136 |
  • 137 | 142 |

    Returns number

    143 |
  • 144 |
145 |
146 |
147 | 148 |

getTilesHeight

149 |
    150 |
  • getTilesHeight(): number
  • 151 |
152 |
    153 |
  • 154 | 159 |

    Returns number

    160 |
  • 161 |
162 |
163 |
164 | 165 |

getTilesWidth

166 |
    167 |
  • getTilesWidth(): number
  • 168 |
169 |
    170 |
  • 171 | 176 |

    Returns number

    177 |
  • 178 |
179 |
180 |
181 |
182 | 276 |
277 |
278 |
279 |
280 |

Legend

281 |
282 |
    283 |
  • Constructor
  • 284 |
  • Property
  • 285 |
  • Method
  • 286 |
287 |
    288 |
  • Property
  • 289 |
  • Method
  • 290 |
291 |
    292 |
  • Private property
  • 293 |
  • Private method
  • 294 |
295 |
    296 |
  • Static property
  • 297 |
298 |
299 |
300 |
301 |
302 |

Generated using TypeDoc

303 |
304 |
305 | 306 | 307 | -------------------------------------------------------------------------------- /docs/interfaces/sound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Sound | gute 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |
    54 |
  • 55 | gute 56 |
  • 57 |
  • 58 | Sound 59 |
  • 60 |
61 |

Interface Sound

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Hierarchy

70 |
    71 |
  • 72 | Resource 73 |
      74 |
    • 75 | Sound 76 |
    • 77 |
    78 |
  • 79 |
80 |
81 |
82 |

Index

83 |
84 |
85 |
86 |

Properties

87 | 90 |
91 |
92 |

Methods

93 | 98 |
99 |
100 |
101 |
102 |
103 |

Properties

104 |
105 | 106 |

loaded

107 |
loaded: boolean
108 | 114 |
115 |
116 |
117 |

Methods

118 |
119 | 120 |

initOnFirstClick

121 |
    122 |
  • initOnFirstClick(): void
  • 123 |
124 |
    125 |
  • 126 | 132 |

    Returns void

    133 |
  • 134 |
135 |
136 |
137 | 138 |

play

139 |
    140 |
  • play(volume: number): void
  • 141 |
142 |
    143 |
  • 144 | 149 |

    Parameters

    150 |
      151 |
    • 152 |
      volume: number
      153 |
    • 154 |
    155 |

    Returns void

    156 |
  • 157 |
158 |
159 |
160 | 161 |

stop

162 |
    163 |
  • stop(): void
  • 164 |
165 |
    166 |
  • 167 | 172 |

    Returns void

    173 |
  • 174 |
175 |
176 |
177 |
178 | 269 |
270 |
271 |
272 |
273 |

Legend

274 |
275 |
    276 |
  • Constructor
  • 277 |
  • Property
  • 278 |
  • Method
  • 279 |
280 |
    281 |
  • Property
  • 282 |
  • Method
  • 283 |
284 |
    285 |
  • Private property
  • 286 |
  • Private method
  • 287 |
288 |
    289 |
  • Static property
  • 290 |
291 |
292 |
293 |
294 |
295 |

Generated using TypeDoc

296 |
297 |
298 | 299 | 300 | -------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | gute 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |

gute

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |

Index

62 |
63 |
64 |
65 |

Classes

66 | 73 |
74 |
75 |

Interfaces

76 | 87 |
88 |
89 |

Variables

90 | 97 |
98 |
99 |

Functions

100 | 103 |
104 |
105 |
106 |
107 |
108 |

Variables

109 |
110 | 111 |

Const BLACK

112 |
BLACK: string = "black"
113 | 118 |
119 |
120 | 121 |

Const BLUE

122 |
BLUE: string = "blue"
123 | 128 |
129 |
130 | 131 |

Const GREEN

132 |
GREEN: string = "green"
133 | 138 |
139 |
140 | 141 |

Const RED

142 |
RED: string = "red"
143 | 148 |
149 |
150 | 151 |

Const WHITE

152 |
WHITE: string = "white"
153 | 158 |
159 |
160 |
161 |

Functions

162 |
163 | 164 |

startGame

165 |
    166 |
  • startGame(game: Game): void
  • 167 |
168 |
    169 |
  • 170 | 175 |

    Parameters

    176 |
      177 |
    • 178 |
      game: Game
      179 |
    • 180 |
    181 |

    Returns void

    182 |
  • 183 |
184 |
185 |
186 |
187 | 260 |
261 |
262 |
263 |
264 |

Legend

265 |
266 |
    267 |
  • Constructor
  • 268 |
  • Property
  • 269 |
  • Method
  • 270 |
271 |
    272 |
  • Property
  • 273 |
  • Method
  • 274 |
275 |
    276 |
  • Private property
  • 277 |
  • Private method
  • 278 |
279 |
    280 |
  • Static property
  • 281 |
282 |
283 |
284 |
285 |
286 |

Generated using TypeDoc

287 |
288 |
289 | 290 | 291 | -------------------------------------------------------------------------------- /docs/test/assets/Gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/Gray.png -------------------------------------------------------------------------------- /docs/test/assets/coin.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/coin.mp3 -------------------------------------------------------------------------------- /docs/test/assets/idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/idle.png -------------------------------------------------------------------------------- /docs/test/assets/jump.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/jump.mp3 -------------------------------------------------------------------------------- /docs/test/assets/jump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/jump.png -------------------------------------------------------------------------------- /docs/test/assets/music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/music.mp3 -------------------------------------------------------------------------------- /docs/test/assets/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/run.png -------------------------------------------------------------------------------- /docs/test/assets/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/assets/terrain.png -------------------------------------------------------------------------------- /docs/test/font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/font.ttf -------------------------------------------------------------------------------- /docs/test/game.js: -------------------------------------------------------------------------------- 1 | const JUMP = 7; 2 | const GRAVITY = 0.5; 3 | 4 | class TestGame { 5 | keySound; 6 | mouseSound; 7 | music; 8 | musicStarted = false; 9 | font; 10 | 11 | run; 12 | idle; 13 | jump; 14 | terrain; 15 | bg; 16 | 17 | anim = 0; 18 | left = false; 19 | right = false; 20 | flipped = false; 21 | 22 | vx = 0; 23 | vy = 0; 24 | x = 100; 25 | y = 100; 26 | onground = true; 27 | 28 | init(context) { 29 | this.run = context.loadTileset("assets/run.png", 32, 32); 30 | this.idle = context.loadTileset("assets/idle.png", 32, 32); 31 | this.jump = context.loadTileset("assets/jump.png", 32, 32); 32 | this.terrain = context.loadTileset("assets/terrain.png", 16, 16); 33 | this.bg = context.loadBitmap("assets/Gray.png"); 34 | this.keySound = context.loadSound("assets/coin.mp3"); 35 | this.mouseSound = context.loadSound("assets/jump.mp3"); 36 | this.music = context.loadMusic("assets/music.mp3"); 37 | this.music.play(1.0); 38 | } 39 | 40 | onMouseDown(context, x, y) { 41 | this.mouseSound.play(1.0); 42 | this.keySound.play(1.0); 43 | } 44 | 45 | onMouseUp(context, x, y) { 46 | } 47 | 48 | onKeyDown(context, key) { 49 | if (key === gute.Keys.LEFT_KEY) { 50 | this.left = true; 51 | } 52 | if (key === gute.Keys.RIGHT_KEY) { 53 | this.right = true; 54 | } 55 | if (key === gute.Keys.UP_KEY) { 56 | if (this.onground) { 57 | this.onground = false; 58 | this.vy = -JUMP; 59 | } 60 | } 61 | } 62 | 63 | onKeyUp(context, key) { 64 | if (key === gute.Keys.LEFT_KEY) { 65 | this.left = false; 66 | } 67 | if (key === gute.Keys.RIGHT_KEY) { 68 | this.right = false; 69 | } 70 | } 71 | 72 | update(context, delta) { 73 | this.vx = (this.left ? -1 : 0) + (this.right ? 1 : 0); 74 | 75 | if (!this.onground) { 76 | this.vy += GRAVITY; 77 | this.y += this.vy; 78 | if (this.y > 100) { 79 | this.y = 100; 80 | this.onground = true; 81 | } 82 | } 83 | 84 | this.x += this.vx; 85 | if (this.vx < 0) { 86 | this.flipped = true; 87 | } 88 | if (this.vx > 0) { 89 | this.flipped = false; 90 | } 91 | } 92 | 93 | render(context, g) { 94 | this.anim++; 95 | 96 | if (context.allResourcesLoaded()) { 97 | // draw a tiled background 98 | for (let x = 0; x <= Math.floor(g.getWidth() / this.bg.width); x++) { 99 | for (let y = 0; y <= Math.floor(g.getHeight() / this.bg.height); y++) { 100 | g.drawBitmap(x * this.bg.width, y * this.bg.height, this.bg); 101 | } 102 | } 103 | // draw some floor 104 | for (let x = 0; x <= Math.floor(g.getWidth() / this.terrain.getTileWidth()); x++) { 105 | g.drawBitmap(x * this.terrain.getTileWidth(), 132, this.terrain.getTile(7)); 106 | for (let y = 0; y < 10; y++) { 107 | g.drawBitmap(x * this.terrain.getTileWidth(), 148 + (y * 16), this.terrain.getTile(29)); 108 | } 109 | } 110 | 111 | let animation = this.idle; 112 | if (this.vx !== 0) { 113 | animation = this.run; 114 | } 115 | if (!this.onground) { 116 | animation = this.jump; 117 | } 118 | 119 | g.push(); 120 | g.translate(this.x, this.y); 121 | if (this.flipped) { 122 | g.scale(-1, 1); 123 | g.translate(-32, 0); 124 | } 125 | g.drawBitmap(0, 0, animation.getTile(Math.floor(this.anim / 3) % animation.getTileCount())); 126 | 127 | g.pop(); 128 | 129 | g.drawString(81, 13, "Gute Test Game", "black"); 130 | g.drawString(80, 12, "Gute Test Game", "white"); 131 | } else { 132 | g.fillRect(0, 0, 320, 200, "black"); 133 | } 134 | } 135 | 136 | } 137 | 138 | gute.startGame(new TestGame()); -------------------------------------------------------------------------------- /docs/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/test/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/docs/test/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gute", 3 | "version": "1.0.47", 4 | "description": "Simple Web Game Library", 5 | "main": "dist/bundle.js", 6 | "types": "dist/bundle.d.ts", 7 | "scripts": { 8 | "doc": "mv docs/test .; typedoc src/index.ts --out docs; mv test docs", 9 | "version": "git add -A src", 10 | "postversion": "git push && git push --tags", 11 | "build": "webpack; webpack --config webpack.config.prod.js", 12 | "watch": "webpack --watch" 13 | }, 14 | "files": [ 15 | "dist/**/*" 16 | ], 17 | "author": "Kevin Glass", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "ts-loader": "^9.2.3", 21 | "typedoc": "^0.20.37", 22 | "typescript": "^4.3.4", 23 | "webpack": "^5.40.0", 24 | "webpack-cli": "^4.7.2" 25 | }, 26 | "dependencies": { 27 | "@types/jszip": "^3.4.1", 28 | "jszip": "^3.7.1", 29 | "potpack": "^2.0.0", 30 | "xbr-js": "^2.0.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevglass/gute/5a5b361cfb718942eb549fbe529fa706ace5d456/src/.DS_Store -------------------------------------------------------------------------------- /src/Bitmap.ts: -------------------------------------------------------------------------------- 1 | import { Graphics } from "./Graphics"; 2 | import { Resource } from "./Resource"; 3 | 4 | export interface Bitmap extends Resource { 5 | width: number; 6 | height: number; 7 | 8 | draw(graphics: Graphics, x: number, y: number): void; 9 | 10 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void; 11 | } -------------------------------------------------------------------------------- /src/Font.ts: -------------------------------------------------------------------------------- 1 | export interface Font { 2 | apply(ctx: CanvasRenderingContext2D, size: number): void; 3 | } -------------------------------------------------------------------------------- /src/Game.ts: -------------------------------------------------------------------------------- 1 | import { GameContext } from "./GameContext"; 2 | import { Graphics } from "./Graphics"; 3 | 4 | export interface Game { 5 | init(context: GameContext): void; 6 | 7 | onMouseDown(context: GameContext, x: number, y: number, id: number): void; 8 | 9 | onMouseWheel(context: GameContext, delta: number): void; 10 | 11 | onMouseUp(context: GameContext, x: number, y: number, id: number): void; 12 | 13 | onMouseMove(context: GameContext, x: number, y: number): void; 14 | 15 | onKeyDown(context: GameContext, key: string): void; 16 | 17 | onKeyUp(context: GameContext, key: string): void; 18 | 19 | update(context: GameContext, delta: number): void; 20 | 21 | render(context: GameContext, g: Graphics): void; 22 | 23 | rendererError(message: string): void; 24 | } -------------------------------------------------------------------------------- /src/GameContext.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "./Bitmap"; 2 | import { Font } from "./Font"; 3 | import { Graphics } from "./Graphics"; 4 | import { MapWorld } from "./tilemaps/MapWorld"; 5 | import { Sound } from "./Sound"; 6 | import { Tileset } from "./Tileset"; 7 | 8 | export interface GameContext { 9 | isShiftPressed(): boolean; 10 | 11 | isCommandPressed(): boolean; 12 | 13 | isAltPressed(): boolean; 14 | 15 | isControlPressed(): boolean; 16 | 17 | resourcesRemaining(): number; 18 | 19 | resourcesTotal(): number; 20 | 21 | currentResource(): string; 22 | 23 | dumpResourceIssues(): void; 24 | 25 | allResourcesLoaded(): boolean; 26 | 27 | applyPalette(hexFile: string): Promise; 28 | 29 | loadZip(url: string): Promise; 30 | 31 | loadLDTK(name: string, locator: (name: string) => string): Promise; 32 | 33 | loadMusic(url: string): Sound; 34 | 35 | loadSound(url: string): Sound; 36 | 37 | loadScaledTileset(url: string, tileWidth: number, tileHeight: number, scale: number): Tileset; 38 | 39 | loadTileset(url: string, tileWidth: number, tileHeight: number): Tileset; 40 | 41 | loadBitmap(url: string): Bitmap; 42 | 43 | getZipProgress(): number; 44 | 45 | loadFont(url: string, name: string): Font; 46 | 47 | getGraphics(): Graphics; 48 | 49 | loadJson(url: string, transform?: (text: string) => string): Promise; 50 | 51 | isRunningStandalone(): boolean; 52 | 53 | isTablet(): boolean; 54 | 55 | isMobile(): boolean; 56 | 57 | isAndroid(): boolean; 58 | 59 | isIOS(): boolean; 60 | 61 | isPhone(): boolean; 62 | 63 | setSoundVolume(v: number) : void; 64 | 65 | getSoundVolume(): number; 66 | 67 | setMusicVolume(v: number): void; 68 | 69 | getMusicVolume(): number; 70 | } 71 | -------------------------------------------------------------------------------- /src/Graphics.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "./Bitmap"; 2 | import { Font } from "./Font"; 3 | 4 | export const WHITE: string = "white"; 5 | export const BLACK: string = "black"; 6 | export const RED: string = "red"; 7 | export const GREEN: string = "green"; 8 | export const BLUE: string = "blue"; 9 | 10 | export interface Offscreen { 11 | getWidth(): number; 12 | 13 | getHeight(): number; 14 | 15 | setDimension(width: number, height: number): void; 16 | 17 | needsRefresh(): boolean; 18 | 19 | release(): void; 20 | } 21 | 22 | export interface Graphics { 23 | initResourceOnLoaded(): void; 24 | 25 | newResourceLoaded(): void; 26 | 27 | get canvas(): HTMLCanvasElement; 28 | 29 | getError(): string | undefined; 30 | 31 | applyFont(): void; 32 | 33 | smooth(): void; 34 | 35 | copy(): Bitmap; 36 | 37 | getOffscreen(): Offscreen | null; 38 | 39 | clip(x: number, y: number, width: number, height: number): void; 40 | 41 | createOffscreen(): Offscreen; 42 | 43 | drawToOffscreen(screen: Offscreen | null): void; 44 | 45 | drawOffscreen(screen: Offscreen): void; 46 | 47 | drawScaledOffscreen(screen: Offscreen, x: number, y: number, width: number, height: number): void; 48 | 49 | drawScaledOffscreenSegment(screen: Offscreen, sx: number, sy: number, swidth: number, sheight: number, x: number, y: number, width: number, height: number): void; 50 | 51 | fillRect(x: number, y: number, width: number, height: number, col: string): void; 52 | 53 | fillCircle(x: number, y: number, radius: number, col: string): void; 54 | 55 | drawCircle(x: number, y: number, radius: number, col: string, width: number): void; 56 | 57 | setLineDash(segments: number[]): void; 58 | 59 | drawLine(x1: number, y1: number, x2: number, y2: number, col: string, width?: number): void; 60 | 61 | drawBitmap(x: number, y: number, bitmap: Bitmap): void; 62 | 63 | drawScaledBitmap(x: number, y: number, width: number, height: number, bitmap: Bitmap): void; 64 | 65 | clearRect(x: number, y: number, width: number, height: number): void; 66 | 67 | clear(): void; 68 | 69 | setFont(font: Font): void; 70 | 71 | setComposite(name: string): void; 72 | 73 | setFontSize(size: number): void; 74 | 75 | drawString(x: number, y: number, text: string, col: string): void; 76 | 77 | translate(x: number, y: number): void; 78 | 79 | scale(x: number, y: number): void; 80 | 81 | push(): void; 82 | 83 | pop(): void; 84 | 85 | getWidth(): number; 86 | 87 | getHeight(): number; 88 | 89 | fitScreen(widthInVirtualPixels: number): void; 90 | 91 | getStringWidth(text: string): number; 92 | 93 | setAlpha(alpha: number): void; 94 | 95 | getTransform(): DOMMatrix; 96 | 97 | renderStart(): void; 98 | 99 | renderEnd(): void; 100 | } -------------------------------------------------------------------------------- /src/Keys.ts: -------------------------------------------------------------------------------- 1 | export class Keys { 2 | static ESCAPE_KEY: string = "Escape"; 3 | static ENTER_KEY: string = "Enter"; 4 | static LEFT_KEY: string = "ArrowLeft"; 5 | static RIGHT_KEY: string = "ArrowRight"; 6 | static UP_KEY: string = "ArrowUp"; 7 | static DOWN_KEY: string = "ArrowDown"; 8 | static SPACE_KEY: string = " "; 9 | static S_KEY: string = "s"; 10 | static M_KEY: string = "m"; 11 | static A_KEY: string = "a"; 12 | static W_KEY: string = "w"; 13 | static D_KEY: string = "d"; 14 | static CONTROL_KEY: string = "Control"; 15 | static META_KEY: string = "Meta"; 16 | static ALT_KEY: string = "Alt"; 17 | static TAB_KEY: string = "Tab"; 18 | } 19 | -------------------------------------------------------------------------------- /src/Log.ts: -------------------------------------------------------------------------------- 1 | export class GuteLog { 2 | static INFO: boolean = true; 3 | static ERROR: boolean = true; 4 | static WARN: boolean = true; 5 | static TRACE: boolean = false; 6 | 7 | static log(...args: any) { 8 | if (GuteLog.INFO) { 9 | console.log(...args); 10 | } 11 | } 12 | 13 | static info(...args: any) { 14 | if (GuteLog.INFO) { 15 | console.info(...args); 16 | } 17 | } 18 | 19 | static error(...args: any) { 20 | if (GuteLog.ERROR) { 21 | console.error(...args); 22 | } 23 | } 24 | 25 | static warm(...args: any) { 26 | if (GuteLog.WARN) { 27 | console.warn(...args); 28 | } 29 | } 30 | 31 | static trace(...args: any) { 32 | if (GuteLog.TRACE) { 33 | console.trace(...args); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Resource.ts: -------------------------------------------------------------------------------- 1 | export interface Resource { 2 | loaded: boolean; 3 | 4 | name: string; 5 | 6 | initOnFirstClick(): void; 7 | } -------------------------------------------------------------------------------- /src/Sound.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "./Resource"; 2 | 3 | export interface Sound extends Resource { 4 | name: string; 5 | 6 | play(volume: number, loop?: boolean): void; 7 | 8 | stop(): void; 9 | } -------------------------------------------------------------------------------- /src/SoundScape.ts: -------------------------------------------------------------------------------- 1 | import { AUDIO_CONTEXT, SoundImpl } from "./impl/SoundImpl"; 2 | import { Sound } from "./Sound"; 3 | 4 | interface SoundPoint { 5 | x?: number 6 | y?: number 7 | volume: number 8 | source: AudioScheduledSourceNode 9 | gain: GainNode 10 | category: string 11 | } 12 | 13 | export enum SoundEasing { 14 | Linear, 15 | Quadratic, 16 | Cubic 17 | } 18 | 19 | interface SoundCategory { 20 | name: string 21 | volume: number 22 | maxDistance2: number 23 | scale2: number 24 | easing: SoundEasing 25 | } 26 | 27 | export class SoundScape { 28 | private _soundVolume: number = 1; 29 | 30 | private points: SoundPoint[] = [] 31 | private listenerX: number = 0 32 | private listenerY: number = 0 33 | private categories: Record = {} 34 | 35 | category(name: string, volume: number, maxDistance: number, scale: number, easing: SoundEasing): SoundScape { 36 | this.categories[name] = { 37 | name, volume, maxDistance2: maxDistance * maxDistance, scale2: scale * scale, easing 38 | } 39 | return this 40 | } 41 | 42 | get soundVolume(): number { 43 | return this._soundVolume; 44 | } 45 | 46 | set soundVolume(value: number) { 47 | this._soundVolume = value; 48 | } 49 | 50 | moveTo(x: number, y: number) { 51 | this.listenerX = x 52 | this.listenerY = y 53 | this.updateVolumes() 54 | } 55 | 56 | clear() { 57 | for (const point of this.points) { 58 | point.source.stop() 59 | } 60 | this.points = [] 61 | } 62 | 63 | play(sound: Sound, volume: number, category: string, x?: number, y?: number) { 64 | // protect against playing sounds too early or without auto working 65 | if (!AUDIO_CONTEXT) { 66 | return; 67 | } 68 | 69 | const impl = sound 70 | const source = AUDIO_CONTEXT.createBufferSource(); 71 | source.buffer = impl.buffer; 72 | const gain = AUDIO_CONTEXT.createGain(); 73 | source.connect(gain); 74 | gain.connect(AUDIO_CONTEXT.destination); 75 | const point: SoundPoint = { 76 | x, y, volume, 77 | source, gain, category 78 | } 79 | gain.gain.value = this.calculateVolume(point) 80 | this.points.push(point) 81 | source.addEventListener("ended", ev => { 82 | const index = this.points.indexOf(point) 83 | this.points.splice(index, 1) 84 | // GuteLog.log(`Sound ended: ${sound.name}, total: ${this.points.length}`) 85 | }) 86 | source.start() 87 | // GuteLog.log(`Sound started: ${sound.name}, total: ${this.points.length}`) 88 | } 89 | 90 | private updateVolumes() { 91 | for (const point of this.points) { 92 | let value = this.calculateVolume(point); 93 | point.gain.gain.linearRampToValueAtTime(value, AUDIO_CONTEXT.currentTime) 94 | } 95 | } 96 | 97 | private calculateVolume(point: SoundPoint): number { 98 | const category = this.categories[point.category] 99 | if (category === undefined) { 100 | return point.volume * this._soundVolume 101 | } 102 | 103 | if (point.x === undefined || point.y === undefined) { 104 | return point.volume * category.volume * this._soundVolume 105 | } 106 | const dx: number = point.x - this.listenerX 107 | const dy: number = point.y - this.listenerY 108 | const distance = (dx * dx + dy * dy) / category.scale2; 109 | // * (los ? 1 : 0.3) TODO: callback 110 | const reduction = Math.max(1 - distance / category.maxDistance2, 0); 111 | switch (category.easing) { 112 | case SoundEasing.Linear: 113 | return this._soundVolume * point.volume * category.volume * reduction 114 | case SoundEasing.Quadratic: 115 | return this._soundVolume * point.volume * category.volume * reduction * reduction 116 | case SoundEasing.Cubic: 117 | return this._soundVolume * point.volume * category.volume * reduction * reduction * reduction 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Tileset.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "./Resource"; 2 | import { Bitmap } from "./Bitmap"; 3 | 4 | export interface Tileset extends Resource { 5 | onLoaded: () => void; 6 | 7 | getTile(tile: number): Bitmap; 8 | 9 | getBlockColorTile(tile: number, tintName: string, col: number[]): Bitmap; 10 | 11 | getShadedTile(tile: number, tintName: string, shade: number): Bitmap; 12 | 13 | getTintedTile(tile: number, tintName: string, tint: number[]): Bitmap; 14 | 15 | getTileCount(): number; 16 | 17 | getTileWidth(): number; 18 | 19 | getTileHeight(): number; 20 | 21 | getTilesAcross(): number; 22 | 23 | modify(modification: (imageData: ImageData) => void): Tileset; 24 | } -------------------------------------------------------------------------------- /src/impl/BitmapImpl.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Graphics } from "../Graphics"; 3 | import { GuteLog } from "../Log"; 4 | import { GraphicsImpl } from "./GraphicsImpl"; 5 | import { Palette } from "./Palette"; 6 | 7 | export class BitmapImpl implements Bitmap { 8 | width: number = 0; 9 | height: number = 0; 10 | loaded: boolean = false; 11 | image: HTMLImageElement; 12 | name: string; 13 | 14 | constructor(url: string, dataUrlLoader: Promise | undefined, pal: Palette | undefined = undefined) { 15 | this.name = url; 16 | this.image = new Image(); 17 | this.image.onerror = () => { 18 | GuteLog.log("Error loading: " + url); 19 | } 20 | this.image.onload = () => { 21 | this.width = this.image.width; 22 | this.height = this.image.height; 23 | 24 | if (pal) { 25 | pal.adjustImage(this.image).then((image: HTMLImageElement) => { 26 | this.image = image; 27 | this.loaded = true; 28 | }); 29 | } else { 30 | this.loaded = true; 31 | } 32 | }; 33 | 34 | if (dataUrlLoader) { 35 | dataUrlLoader.then((base64: string) => { 36 | this.image.src = "data:"+url.substring(url.length-3)+";base64,"+base64; 37 | }) 38 | } else { 39 | this.image.src = url; 40 | } 41 | } 42 | 43 | draw(graphics: Graphics, x: number, y: number): void { 44 | const ctx = (graphics as GraphicsImpl).ctx; 45 | ctx.drawImage(this.image, x, y); 46 | } 47 | 48 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 49 | const ctx = (graphics as GraphicsImpl).ctx; 50 | ctx.drawImage(this.image, x, y, width, height); 51 | } 52 | 53 | initOnFirstClick(): void { 54 | } 55 | } -------------------------------------------------------------------------------- /src/impl/FontImpl.ts: -------------------------------------------------------------------------------- 1 | import { Font } from "../Font"; 2 | 3 | declare let FontFace: any; 4 | 5 | export class FontImpl implements Font { 6 | name: string; 7 | 8 | constructor(url: string, name: string) { 9 | this.name = name; 10 | 11 | const style = document.createElement("style"); 12 | style.innerHTML = "@font-face { font-family: "+name+"; src: url('"+url+"'); } body { font-family: "+name+"; }"; 13 | document.head.appendChild(style); 14 | } 15 | 16 | apply(ctx: CanvasRenderingContext2D, size: number): void { 17 | ctx.font = size+"px " + this.name; 18 | } 19 | } -------------------------------------------------------------------------------- /src/impl/GraphicsImpl.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap, Graphics } from ".."; 2 | import { Font } from "../Font"; 3 | import { Offscreen } from "../Graphics"; 4 | import { FontImpl } from "./FontImpl"; 5 | import { SoundImpl } from "./SoundImpl"; 6 | 7 | declare let InstallTrigger: any; 8 | 9 | var isFirefox = typeof InstallTrigger !== 'undefined'; 10 | 11 | class OffscreenImpl implements Offscreen { 12 | ctx: CanvasRenderingContext2D; 13 | canvas: HTMLCanvasElement; 14 | 15 | constructor(canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) { 16 | this.canvas = canvas; 17 | this.ctx = ctx; 18 | } 19 | 20 | release(): void { 21 | } 22 | 23 | needsRefresh(): boolean { 24 | return false; 25 | } 26 | 27 | getWidth(): number { 28 | return this.canvas.width; 29 | } 30 | 31 | getHeight(): number { 32 | return this.canvas.height; 33 | } 34 | 35 | setDimension(width: number, height: number): void { 36 | this.canvas.width = width; 37 | this.canvas.height = height; 38 | } 39 | } 40 | 41 | class CopyBitmap implements Bitmap { 42 | width: number; 43 | height: number; 44 | canvas: HTMLCanvasElement; 45 | loaded: boolean; 46 | name: string = "copy"; 47 | 48 | constructor(canvas: HTMLCanvasElement) { 49 | this.canvas = canvas; 50 | this.width = canvas.width; 51 | this.height = canvas.height; 52 | this.loaded = true; 53 | } 54 | 55 | draw(graphics: Graphics, x: number, y: number): void { 56 | const ctx = (graphics as GraphicsImpl).ctx; 57 | ctx.drawImage(this.canvas, x, y); 58 | } 59 | 60 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 61 | const ctx = (graphics as GraphicsImpl).ctx; 62 | ctx.drawImage(this.canvas, x, y, width, height); 63 | } 64 | 65 | getDrawable(): CanvasImageSource { 66 | return this.canvas; 67 | } 68 | 69 | initOnFirstClick(): void { 70 | } 71 | } 72 | 73 | export class GraphicsImpl implements Graphics { 74 | canvas: HTMLCanvasElement; 75 | ctx: CanvasRenderingContext2D; 76 | mainCtx: CanvasRenderingContext2D; 77 | font: Font; 78 | fontSize: number = 20; 79 | offscreen: Offscreen | null = null; 80 | 81 | constructor() { 82 | this.canvas = document.getElementById("gamecanvas"); 83 | this.ctx = this.canvas.getContext("2d", { alpha: false }); 84 | this.mainCtx = this.ctx; 85 | 86 | ( this.ctx).webkitImageSmoothingEnabled = false; 87 | this.ctx.imageSmoothingEnabled = false; 88 | ( this.canvas).style.fontSmooth = "never"; 89 | ( this.canvas).style.webkitFontSmoothing = "none"; 90 | 91 | if (isFirefox) { 92 | this.canvas.style.imageRendering = "crisp-edges"; 93 | } else { 94 | this.canvas.style.imageRendering = "pixelated"; 95 | } 96 | 97 | this.font = new FontImpl("font.ttf", "GuteDefault"); 98 | if (this.font) { 99 | this.applyFont(); 100 | } 101 | } 102 | 103 | getError(): string | undefined { 104 | return undefined; 105 | } 106 | 107 | renderStart(): void { 108 | 109 | } 110 | 111 | renderEnd(): void { 112 | 113 | } 114 | 115 | newResourceLoaded(): void { 116 | } 117 | 118 | initResourceOnLoaded(): void { 119 | } 120 | 121 | smooth(): void { 122 | ( this.ctx).webkitImageSmoothingEnabled = true; 123 | this.ctx.imageSmoothingEnabled = true; 124 | ( this.canvas).style.fontSmooth = "initial"; 125 | ( this.canvas).style.webkitFontSmoothing = "initial"; 126 | 127 | this.canvas.style.imageRendering = "initial"; 128 | } 129 | 130 | getTransform(): DOMMatrix { 131 | return this.ctx.getTransform(); 132 | } 133 | 134 | clear(): void { 135 | this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); 136 | } 137 | 138 | clip(x: number, y: number, width: number, height: number): void { 139 | let squarePath = new Path2D(); 140 | squarePath.rect(x, y, width, height); 141 | this.ctx.clip(squarePath); 142 | } 143 | 144 | createOffscreen(): Offscreen { 145 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 146 | canvas.width = this.getWidth(); 147 | canvas.height = this.getHeight(); 148 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); 149 | if (ctx) { 150 | ( ctx).webkitImageSmoothingEnabled = false; 151 | ctx.imageSmoothingEnabled = false; 152 | ( canvas).style.fontSmooth = "never"; 153 | ( canvas).style.webkitFontSmoothing = "none"; 154 | 155 | return new OffscreenImpl(canvas, ctx); 156 | } else { 157 | throw new Error("Unable to create offscreen canvas"); 158 | } 159 | } 160 | 161 | getOffscreen(): Offscreen | null { 162 | return this.offscreen; 163 | } 164 | 165 | drawToOffscreen(screen: Offscreen | null): void { 166 | if (screen) { 167 | this.ctx = (screen as OffscreenImpl).ctx; 168 | this.ctx.resetTransform(); 169 | } else { 170 | this.ctx = this.mainCtx; 171 | } 172 | this.offscreen = screen; 173 | } 174 | 175 | drawOffscreen(screen: Offscreen): void { 176 | ( this.ctx).webkitImageSmoothingEnabled = false; 177 | this.ctx.imageSmoothingEnabled = false; 178 | this.ctx.drawImage((screen as OffscreenImpl).canvas, 0, 0); 179 | } 180 | 181 | drawScaledOffscreen(screen: Offscreen, x: number, y: number, width: number, height: number): void { 182 | ( this.ctx).webkitImageSmoothingEnabled = false; 183 | this.ctx.imageSmoothingEnabled = false; 184 | this.ctx.drawImage((screen as OffscreenImpl).canvas, x, y, width, height); 185 | } 186 | 187 | drawScaledOffscreenSegment(screen: Offscreen, sx: number, sy: number, swidth: number, sheight: number, x: number, y: number, width: number, height: number): void { 188 | ( this.ctx).webkitImageSmoothingEnabled = false; 189 | this.ctx.imageSmoothingEnabled = false; 190 | this.ctx.drawImage((screen as OffscreenImpl).canvas, sx, sy, swidth, sheight, x, y, width, height); 191 | } 192 | 193 | clearRect(x: number, y: number, width: number, height: number): void { 194 | this.ctx.clearRect(x, y, width, height); 195 | } 196 | 197 | fitScreen(pixelScale: number): void { 198 | const width: number = Math.max(document.documentElement.clientWidth, window.innerWidth || 0); 199 | const height: number = Math.max(document.documentElement.clientHeight, window.innerHeight || 0) 200 | const realWidth: number = Math.floor(width / pixelScale) * pixelScale; 201 | const realHeight: number = Math.floor(height / pixelScale) * pixelScale; 202 | const virtualWidth: number = realWidth / pixelScale; 203 | const virtualHeight: number = realHeight / pixelScale; 204 | this.canvas.style.position = "absolute"; 205 | this.canvas.style.top = "0px"; 206 | this.canvas.style.left = "0px"; 207 | this.canvas.width = virtualWidth; 208 | this.canvas.height = virtualHeight; 209 | this.canvas.style.width = realWidth + "px"; 210 | this.canvas.style.height = realHeight + "px"; 211 | } 212 | 213 | setAlpha(alpha: number): void { 214 | this.ctx.globalAlpha = alpha; 215 | } 216 | 217 | copy(): Bitmap { 218 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 219 | canvas.width = this.getWidth(); 220 | canvas.height = this.getHeight(); 221 | 222 | canvas.getContext("2d")?.drawImage(this.canvas, 0, 0); 223 | return new CopyBitmap(canvas); 224 | } 225 | 226 | getWidth(): number { 227 | return this.ctx.canvas.width; 228 | } 229 | 230 | getHeight(): number { 231 | return this.ctx.canvas.height; 232 | } 233 | 234 | push(): void { 235 | this.ctx.save(); 236 | } 237 | 238 | pop(): void { 239 | this.ctx.restore(); 240 | } 241 | 242 | translate(x: number, y: number): void { 243 | this.ctx.translate(x,y); 244 | } 245 | 246 | scale(x: number, y: number): void { 247 | this.ctx.scale(x,y); 248 | } 249 | 250 | applyFont(): void { 251 | this.font.apply(this.ctx, this.fontSize); 252 | } 253 | 254 | setFont(font: Font): void { 255 | this.font = font; 256 | this.applyFont(); 257 | } 258 | 259 | setFontSize(size: number): void { 260 | this.fontSize = size; 261 | this.applyFont(); 262 | } 263 | 264 | getStringWidth(text: string): number { 265 | return this.ctx.measureText(text).width; 266 | } 267 | 268 | drawString(x: number, y: number, text: string, col: string): void { 269 | this.ctx.fillStyle = col; 270 | this.ctx.fillText(text, x, y); 271 | } 272 | 273 | setComposite(name: string): void { 274 | ( this.ctx.globalCompositeOperation) = name; 275 | } 276 | 277 | drawCircle(x: number, y: number, radius: number, col: string, width: number): void { 278 | this.ctx.strokeStyle = col; 279 | this.ctx.lineWidth = width; 280 | this.ctx.beginPath(); 281 | this.ctx.arc(x, y, radius, 0, Math.PI * 2); 282 | this.ctx.stroke(); 283 | } 284 | 285 | fillCircle(x: number, y: number, radius: number, col: string): void { 286 | this.ctx.fillStyle = col; 287 | this.ctx.beginPath(); 288 | this.ctx.arc(x, y, radius, 0, Math.PI * 2); 289 | this.ctx.fill(); 290 | } 291 | 292 | fillRect(x: number, y: number, width: number, height: number, col: string): void { 293 | this.ctx.fillStyle = col; 294 | this.ctx.fillRect(x,y,width,height); 295 | } 296 | 297 | setLineDash(segments: number[]): void { 298 | this.ctx.setLineDash(segments); 299 | } 300 | 301 | drawLine(x1: number, y1: number, x2: number, y2: number, col: string, width: number = 1): void { 302 | this.ctx.strokeStyle = col; 303 | this.ctx.lineWidth = width; 304 | this.ctx.moveTo(x1, y1); 305 | this.ctx.lineTo(x2, y2); 306 | this.ctx.stroke(); 307 | } 308 | 309 | drawBitmap(x: number, y: number, bitmap: Bitmap): void { 310 | if (!bitmap) { 311 | return; 312 | } 313 | 314 | this.ctx.imageSmoothingEnabled = false; 315 | ( this.ctx).webkitImageSmoothingEnabled = false; 316 | bitmap.draw(this, x, y); 317 | } 318 | 319 | drawScaledBitmap(x: number, y: number, width: number, height: number, bitmap: Bitmap): void { 320 | if (!bitmap) { 321 | return; 322 | } 323 | 324 | this.ctx.imageSmoothingEnabled = false; 325 | ( this.ctx).webkitImageSmoothingEnabled = false; 326 | bitmap.drawScaled(this, x, y, width, height); 327 | } 328 | } -------------------------------------------------------------------------------- /src/impl/Palette.ts: -------------------------------------------------------------------------------- 1 | import { MapNode } from "../path/MapNode"; 2 | 3 | interface Col { 4 | r: number; 5 | g: number; 6 | b: number; 7 | } 8 | 9 | function hexToRgb(hex: string): Col { 10 | var bigint = parseInt(hex, 16); 11 | var r = (bigint >> 16) & 255; 12 | var g = (bigint >> 8) & 255; 13 | var b = bigint & 255; 14 | 15 | return {r, g, b} 16 | } 17 | 18 | function deltaE(rgbA: Col, rgbB: Col) { 19 | let labA = rgb2lab(rgbA); 20 | let labB = rgb2lab(rgbB); 21 | let deltaL = labA[0] - labB[0]; 22 | let deltaA = labA[1] - labB[1]; 23 | let deltaB = labA[2] - labB[2]; 24 | let c1 = Math.sqrt(labA[1] * labA[1] + labA[2] * labA[2]); 25 | let c2 = Math.sqrt(labB[1] * labB[1] + labB[2] * labB[2]); 26 | let deltaC = c1 - c2; 27 | let deltaH = deltaA * deltaA + deltaB * deltaB - deltaC * deltaC; 28 | deltaH = deltaH < 0 ? 0 : Math.sqrt(deltaH); 29 | let sc = 1.0 + 0.045 * c1; 30 | let sh = 1.0 + 0.015 * c1; 31 | let deltaLKlsl = deltaL / (1.0); 32 | let deltaCkcsc = deltaC / (sc); 33 | let deltaHkhsh = deltaH / (sh); 34 | let i = deltaLKlsl * deltaLKlsl + deltaCkcsc * deltaCkcsc + deltaHkhsh * deltaHkhsh; 35 | return i < 0 ? 0 : Math.sqrt(i); 36 | } 37 | 38 | function rgb2lab(rgb: Col){ 39 | let r = rgb.r / 255, g = rgb.g / 255, b = rgb.b / 255, x, y, z; 40 | r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; 41 | g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; 42 | b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; 43 | x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047; 44 | y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000; 45 | z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883; 46 | x = (x > 0.008856) ? Math.pow(x, 1/3) : (7.787 * x) + 16/116; 47 | y = (y > 0.008856) ? Math.pow(y, 1/3) : (7.787 * y) + 16/116; 48 | z = (z > 0.008856) ? Math.pow(z, 1/3) : (7.787 * z) + 16/116; 49 | return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)] 50 | } 51 | 52 | export class Palette { 53 | cols: Col[] = []; 54 | mapping: Record = {}; 55 | 56 | constructor(hexValues: string) { 57 | for (const hex of hexValues.split("\n")) { 58 | this.cols.push(hexToRgb(hex)); 59 | } 60 | } 61 | 62 | findBestMatch(r: number, g: number, b: number): Col { 63 | const toMatchCol: Col = { r, g, b }; 64 | let bestMatch: Col = this.cols[0]; 65 | let smallestDelta = 1000; 66 | 67 | for (const palCol of this.cols) { 68 | const dif = deltaE(palCol, toMatchCol); 69 | if (dif < smallestDelta) { 70 | smallestDelta = dif; 71 | bestMatch = palCol; 72 | } 73 | } 74 | 75 | return bestMatch; 76 | } 77 | 78 | adjustImage(image: HTMLImageElement): Promise { 79 | return new Promise((resolve, reject) => { 80 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 81 | canvas.width = image.width; 82 | canvas.height = image.height; 83 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d") as CanvasRenderingContext2D; 84 | if (ctx) { 85 | ctx.drawImage(image, 0, 0); 86 | const id: ImageData = ctx.getImageData(0,0,image.width,image.height); 87 | for (let i=0;i { 107 | resolve(result); 108 | } 109 | result.src = canvas.toDataURL(); 110 | } else { 111 | reject("Failure to create context"); 112 | } 113 | }); 114 | } 115 | } -------------------------------------------------------------------------------- /src/impl/SoundImpl.ts: -------------------------------------------------------------------------------- 1 | import { isMusicOn, isSoundOn } from "../Gute"; 2 | import { GuteLog } from "../Log"; 3 | import { Sound } from "../Sound"; 4 | 5 | let AudioContext: any; 6 | 7 | if (typeof window !== "undefined") { 8 | AudioContext = window.AudioContext || (window).webkitAudioContext; 9 | } 10 | export let AUDIO_CONTEXT: AudioContext; 11 | 12 | function handleVisibilityChange() { 13 | if (isMusicOn()) { 14 | if (SoundImpl.CURRENT_MUSIC) { 15 | if (document.hidden) { 16 | SoundImpl.CURRENT_MUSIC.stop(); 17 | try { 18 | AUDIO_CONTEXT.suspend().catch((e) => { 19 | GuteLog.trace("Suspend audio context failed"); 20 | GuteLog.trace(e); 21 | }); 22 | } catch (e) { 23 | GuteLog.trace("Suspend audio context failed"); 24 | GuteLog.trace(e); 25 | } 26 | } else { 27 | try { 28 | AUDIO_CONTEXT.resume().catch((e) => { 29 | GuteLog.trace("Resume audio context failed"); 30 | GuteLog.trace(e); 31 | }); 32 | } catch (e) { 33 | GuteLog.trace("Resume audio context failed"); 34 | GuteLog.trace(e); 35 | } 36 | setTimeout(() => { 37 | SoundImpl.CURRENT_MUSIC!.play(SoundImpl.CURRENT_MUSIC!.volume); 38 | }, 500); 39 | } 40 | } 41 | } 42 | 43 | if (isSoundOn()) { 44 | for (const loop of SoundImpl.CURRENT_LOOPS) { 45 | if (document.hidden) { 46 | loop.stop(false); 47 | } else { 48 | loop.play(loop.volume); 49 | } 50 | } 51 | } 52 | } 53 | 54 | if (typeof document !== "undefined") { 55 | document.addEventListener("visibilitychange", handleVisibilityChange); 56 | } 57 | 58 | export class SoundImpl implements Sound { 59 | static CURRENT_MUSIC: SoundImpl | null; 60 | static CURRENT_LOOPS: SoundImpl[] = []; 61 | 62 | static soundVolume: number = 1; 63 | static musicVolume: number = 1; 64 | 65 | static setSoundVolume(v: number): void { 66 | this.soundVolume = v; 67 | 68 | for (const loop of this.CURRENT_LOOPS) { 69 | loop.gain.gain.linearRampToValueAtTime(loop.volume * SoundImpl.soundVolume, AUDIO_CONTEXT.currentTime + 0.25); 70 | } 71 | } 72 | 73 | static getSoundVolume(): number { 74 | return this.soundVolume; 75 | } 76 | 77 | static setMusicVolume(v: number): void { 78 | this.musicVolume = v; 79 | 80 | if (SoundImpl.CURRENT_MUSIC) { 81 | if (SoundImpl.CURRENT_MUSIC.gain) { 82 | SoundImpl.CURRENT_MUSIC.gain.gain.linearRampToValueAtTime(SoundImpl.CURRENT_MUSIC.volume * SoundImpl.musicVolume, AUDIO_CONTEXT.currentTime + 0.25); 83 | } 84 | } 85 | } 86 | 87 | static getMusicVolume(): number { 88 | return this.musicVolume; 89 | } 90 | 91 | loaded: boolean = false; 92 | data!: ArrayBuffer; 93 | volume: number = 1; 94 | buffer!: AudioBuffer; 95 | music: boolean = false; 96 | source!: AudioBufferSourceNode | null; 97 | gain!: GainNode; 98 | url: string; 99 | looped: boolean = false; 100 | name: string; 101 | 102 | constructor(url: string, music: boolean, arrayBuffer: Promise | undefined) { 103 | this.name = url; 104 | this.url = url; 105 | this.music = music; 106 | 107 | if (arrayBuffer) { 108 | this.loaded = true; 109 | arrayBuffer.then((arrayBuffer: ArrayBuffer) => { 110 | this.data = arrayBuffer; 111 | this.tryLoad(); 112 | }); 113 | } else { 114 | var req = new XMLHttpRequest(); 115 | req.open("GET", url, true); 116 | req.responseType = "arraybuffer"; 117 | 118 | req.onload = (event) => { 119 | var arrayBuffer = req.response; 120 | if (arrayBuffer) { 121 | this.data = arrayBuffer; 122 | this.tryLoad(); 123 | } 124 | }; 125 | 126 | req.send(); 127 | } 128 | 129 | this.loaded = true; 130 | } 131 | 132 | private tryLoad(): void { 133 | if (AUDIO_CONTEXT && this.data) { 134 | try { 135 | const promise = AUDIO_CONTEXT.decodeAudioData(this.data, (buffer: AudioBuffer) => { 136 | this.buffer = buffer; 137 | if (SoundImpl.CURRENT_MUSIC === this) { 138 | SoundImpl.CURRENT_MUSIC = null; 139 | this.play(this.volume); 140 | } 141 | }, (e) => { 142 | GuteLog.error(e); 143 | GuteLog.log("Sound decode failed for this: " + this.name); 144 | GuteLog.log("Failed to load: " + this.url) 145 | }); 146 | if (promise) { 147 | promise.then(() => { }).catch((e) => { }); 148 | } 149 | } catch (e) { 150 | GuteLog.log("decodeAudioData exception on loading " + this.url); 151 | } 152 | } 153 | } 154 | 155 | confirmAudioContext() { 156 | try { 157 | AUDIO_CONTEXT.resume().catch((e) => { 158 | GuteLog.trace("Resume audio context failed"); 159 | GuteLog.trace(e); 160 | }); 161 | } catch (e) { 162 | GuteLog.trace("Resume audio context failed"); 163 | } 164 | } 165 | 166 | initOnFirstClick(): void { 167 | if (!AUDIO_CONTEXT) { 168 | try { 169 | AUDIO_CONTEXT = new AudioContext(); 170 | AUDIO_CONTEXT.resume().catch((e) => { 171 | GuteLog.trace("Resume audio context failed"); 172 | GuteLog.trace(e); 173 | }); 174 | } catch (e) { 175 | GuteLog.trace("Resume audio context failed"); 176 | } 177 | } 178 | 179 | this.tryLoad(); 180 | } 181 | 182 | play(volume: number, loop: boolean = false): void { 183 | this.confirmAudioContext(); 184 | this.volume = volume; 185 | 186 | if (!this.buffer) { 187 | if (this.music) { 188 | if (SoundImpl.CURRENT_MUSIC) { 189 | SoundImpl.CURRENT_MUSIC.stop(); 190 | } 191 | SoundImpl.CURRENT_MUSIC = this; 192 | } 193 | return; 194 | } 195 | 196 | if (this.music) { 197 | if (SoundImpl.CURRENT_MUSIC !== this) { 198 | if (SoundImpl.CURRENT_MUSIC) { 199 | SoundImpl.CURRENT_MUSIC.stop(); 200 | } 201 | 202 | SoundImpl.CURRENT_MUSIC = this; 203 | } 204 | 205 | if (this.source) { 206 | return; 207 | } 208 | } else { 209 | // don't play sound effects in the background or they all 210 | // get stacked up 211 | if (document.hidden) { 212 | return; 213 | } 214 | } 215 | 216 | if (this.music && !isMusicOn()) { 217 | return; 218 | } else if (!this.music && !isSoundOn()) { 219 | return; 220 | } 221 | 222 | this.source = AUDIO_CONTEXT.createBufferSource(); 223 | this.source.buffer = this.buffer; 224 | this.gain = AUDIO_CONTEXT.createGain(); 225 | this.source.connect(this.gain); 226 | this.gain.connect(AUDIO_CONTEXT.destination); 227 | 228 | this.looped = false; 229 | if (this.music || loop) { 230 | this.gain.gain.value = 0; 231 | this.source.loop = true; 232 | this.looped = true; 233 | } 234 | 235 | this.source.start(0); 236 | 237 | if (this.music || loop) { 238 | this.gain.gain.linearRampToValueAtTime(volume * (loop ? SoundImpl.soundVolume : SoundImpl.musicVolume), AUDIO_CONTEXT.currentTime + 2); 239 | } else { 240 | this.gain.gain.value = volume * SoundImpl.soundVolume; 241 | } 242 | 243 | if (loop) { 244 | SoundImpl.CURRENT_LOOPS.push(this); 245 | } 246 | } 247 | 248 | stop(remove: boolean = true): void { 249 | if (this.source) { 250 | if (this.looped) { 251 | this.gain.gain.linearRampToValueAtTime(0, AUDIO_CONTEXT.currentTime + 3); 252 | const tempSource: AudioBufferSourceNode = this.source; 253 | setTimeout(() => { 254 | tempSource.stop(); 255 | }, 4000); 256 | } else { 257 | this.source.stop(); 258 | } 259 | 260 | this.source = null; 261 | } 262 | 263 | if (remove) { 264 | const index: number = SoundImpl.CURRENT_LOOPS.findIndex(a => a === this); 265 | if (index >= 0) { 266 | SoundImpl.CURRENT_LOOPS.splice(index, 1); 267 | } 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/impl/TilesetImpl.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap, Graphics, GuteLog } from ".."; 2 | import { shouldPrescaleTilesets, shouldUseXbr } from "../Gute"; 3 | import { Tileset } from "../Tileset"; 4 | import { GraphicsImpl } from "./GraphicsImpl"; 5 | import { Palette } from "./Palette"; 6 | 7 | import { xbr2x, xbr3x, xbr4x } from 'xbr-js'; 8 | 9 | class Tile implements Bitmap { 10 | image: HTMLImageElement; 11 | width: number; 12 | height: number; 13 | loaded: boolean; 14 | x: number; 15 | y: number; 16 | scale: number; 17 | name: string = "tile"; 18 | cached: Record = {}; 19 | 20 | constructor(canvas: HTMLImageElement, x: number, y: number, width: number, height: number, scale: number) { 21 | this.image = canvas; 22 | this.width = width; 23 | this.height = height; 24 | this.x = x; 25 | this.y = y; 26 | this.scale = scale; 27 | this.loaded = true; 28 | } 29 | 30 | draw(graphics: Graphics, x: number, y: number): void { 31 | const ctx = (graphics as GraphicsImpl).ctx; 32 | 33 | if (!shouldPrescaleTilesets() && shouldUseXbr() && (this.scale === 2 || this.scale === 3)) { 34 | if (!this.cached[this.scale]) { 35 | this.cached[this.scale] = document.createElement("canvas"); 36 | this.cached[this.scale].width = this.width; 37 | this.cached[this.scale].height = this.height; 38 | const ctx = this.cached[this.scale].getContext("2d"); 39 | ctx!.drawImage(this.image!, this.x, this.y, this.width, this.height, 0, 0, this.width, this.height); 40 | 41 | const originalImageData = ctx!.getImageData(0, 0, this.width, this.height); 42 | const originalPixelView = new Uint32Array(originalImageData.data.buffer); 43 | const scaledPixelView = this.scale === 2 ? xbr2x(originalPixelView, this.width, this.height) : xbr3x(originalPixelView, this.width, this.height); 44 | 45 | const destWidth = this.width * this.scale; 46 | const destHeight = this.height * this.scale; 47 | this.cached[this.scale].width = destWidth; 48 | this.cached[this.scale].height = destHeight; 49 | const scaledImageData = new ImageData(new Uint8ClampedArray(scaledPixelView.buffer), this.cached[this.scale].width, this.cached[this.scale].height); 50 | 51 | ctx!.putImageData(scaledImageData, 0, 0); 52 | } 53 | ctx.drawImage(this.cached[this.scale], x, y); 54 | } else { 55 | ctx.drawImage(this.image, this.x, this.y, this.width - 0.1, this.height - 0.1, x, y, this.width * this.scale, this.height * this.scale); 56 | } 57 | } 58 | 59 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 60 | const ctx = (graphics as GraphicsImpl).ctx; 61 | const scale = Math.min(Math.floor(width / this.width), Math.floor(height / this.height)); 62 | 63 | if (!shouldPrescaleTilesets() && shouldUseXbr() && (scale === 2 || scale === 3)) { 64 | if (!this.cached[scale]) { 65 | this.cached[scale] = document.createElement("canvas"); 66 | this.cached[scale].width = this.width; 67 | this.cached[scale].height = this.height; 68 | const ctx = this.cached[scale].getContext("2d"); 69 | ctx!.drawImage(this.image!, this.x, this.y, this.width, this.height, 0, 0, this.width, this.height); 70 | 71 | const originalImageData = ctx!.getImageData(0, 0, this.width, this.height); 72 | const originalPixelView = new Uint32Array(originalImageData.data.buffer); 73 | const scaledPixelView = scale === 2 ? xbr2x(originalPixelView, this.width, this.height) : xbr3x(originalPixelView, this.width, this.height); 74 | 75 | const destWidth = this.width * scale; 76 | const destHeight = this.height * scale; 77 | this.cached[scale].width = destWidth; 78 | this.cached[scale].height = destHeight; 79 | const scaledImageData = new ImageData(new Uint8ClampedArray(scaledPixelView.buffer), this.cached[scale].width, this.cached[scale].height); 80 | 81 | ctx!.putImageData(scaledImageData, 0, 0); 82 | } 83 | ctx.drawImage(this.cached[scale], x, y, width, height); 84 | } else { 85 | ctx.drawImage(this.image, this.x, this.y, this.width, this.height, x, y, width, height); 86 | } 87 | } 88 | 89 | initOnFirstClick(): void { 90 | } 91 | } 92 | 93 | export class TilesetImpl implements Tileset { 94 | loaded: boolean = false; 95 | tileWidth: number; 96 | tileHeight: number; 97 | originalTileWidth: number; 98 | originalTileHeight: number; 99 | image: any | null; 100 | bitmaps: Tile[] = []; 101 | scanline: number = 0; 102 | tileCount: number = 0; 103 | tints: Record = {}; 104 | tintTiles: Record = {}; 105 | scale: number; 106 | onLoaded: () => void = () => { }; 107 | name: string; 108 | 109 | constructor(url: string, dataUrlLoader: Promise | undefined, tileWidth: number, tileHeight: number, scale: number = 1, pal: Palette | undefined = undefined) { 110 | this.tileWidth = this.originalTileWidth = tileWidth; 111 | this.tileHeight = this.originalTileHeight = tileHeight; 112 | this.scale = scale; 113 | this.name = url; 114 | this.image = new Image(); 115 | 116 | this.image.onerror = () => { 117 | GuteLog.log("Error loading: " + url); 118 | } 119 | this.image.onload = () => { 120 | if (shouldPrescaleTilesets() && scale !== 1) { 121 | const scaledImage = document.createElement("canvas"); 122 | 123 | if (shouldUseXbr()) { 124 | const ctx = scaledImage.getContext("2d"); 125 | ctx!.drawImage(this.image!, 0, 0); 126 | 127 | const originalImageData = ctx!.getImageData(0, 0, this.image!.width, this.image!.height); 128 | const originalPixelView = new Uint32Array(originalImageData.data.buffer); 129 | const scaledPixelView = scale === 2 ? xbr2x(originalPixelView, this.image!.width, this.image!.height) : xbr3x(originalPixelView, this.image!.width, this.image!.height); 130 | 131 | scaledImage.width = this.image!.width * scale; 132 | scaledImage.height = this.image!.height * scale; 133 | const scaledImageData = new ImageData(new Uint8ClampedArray(scaledPixelView.buffer), scaledImage.width, scaledImage.height); 134 | 135 | ctx!.putImageData(scaledImageData, 0, 0); 136 | } else { 137 | scaledImage.width = this.image!.width * scale; 138 | scaledImage.height = this.image!.height * scale; 139 | const ctx = scaledImage.getContext("2d"); 140 | ctx!.imageSmoothingEnabled = false; 141 | (ctx!).webkitImageSmoothingEnabled = false; 142 | ctx?.drawImage(this.image!, 0, 0, scaledImage.width, scaledImage.height); 143 | } 144 | 145 | 146 | this.image = scaledImage; 147 | this.tileWidth *= scale; 148 | this.tileHeight *= scale; 149 | this.originalTileWidth *= scale; 150 | this.originalTileHeight *= scale; 151 | this.scale = 1; 152 | scale = 1; 153 | } 154 | 155 | this.scanline = Math.floor(this.image!.width / this.tileWidth); 156 | const depth: number = Math.floor(this.image!.height / this.tileHeight); 157 | this.tileCount = depth * this.scanline; 158 | 159 | if (pal) { 160 | pal.adjustImage(this.image!).then((image) => { 161 | this.image = image; 162 | 163 | // cut the image into pieces 164 | for (let y = 0; y < depth; y++) { 165 | for (let x = 0; x < this.scanline; x++) { 166 | this.bitmaps.push(new Tile(this.image!, x * this.tileWidth, y * this.tileHeight, this.tileWidth, this.tileHeight, scale)); 167 | } 168 | } 169 | this.tileWidth *= scale; 170 | this.tileHeight *= scale; 171 | 172 | this.onLoaded(); 173 | this.loaded = true; 174 | }) 175 | } else { 176 | // cut the image into pieces 177 | for (let y = 0; y < depth; y++) { 178 | for (let x = 0; x < this.scanline; x++) { 179 | this.bitmaps.push(new Tile(this.image!, x * this.tileWidth, y * this.tileHeight, this.tileWidth, this.tileHeight, scale)); 180 | } 181 | } 182 | this.tileWidth *= scale; 183 | this.tileHeight *= scale; 184 | 185 | this.onLoaded(); 186 | this.loaded = true; 187 | } 188 | }; 189 | 190 | if (dataUrlLoader) { 191 | dataUrlLoader.then((blob: Blob) => { 192 | var urlCreator = window.URL || window.webkitURL; 193 | this.image!.src = urlCreator.createObjectURL(blob); 194 | }) 195 | } else { 196 | this.image.src = url; 197 | } 198 | } 199 | 200 | getTilesAcross(): number { 201 | return this.scanline; 202 | } 203 | 204 | getTileWidth(): number { 205 | return this.tileWidth; 206 | } 207 | 208 | getTileHeight(): number { 209 | return this.tileHeight; 210 | } 211 | 212 | getTileCount(): number { 213 | return this.tileCount; 214 | } 215 | 216 | initOnFirstClick(): void { 217 | } 218 | 219 | getTile(tile: number): Bitmap { 220 | return this.bitmaps[tile]; 221 | } 222 | 223 | getShadedTile(tile: number, tintName: string, shade: number): Bitmap { 224 | let tiles = this.tintTiles[tintName]; 225 | if (!tiles) { 226 | tiles = this.tintTiles[tintName] = []; 227 | } 228 | 229 | let tileRecord = tiles[tile]; 230 | if (!tileRecord) { 231 | const x: number = tile % this.scanline; 232 | const y: number = Math.floor(tile / this.scanline); 233 | let image: HTMLImageElement = this.tints[tintName]; 234 | if (!image) { 235 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 236 | canvas.width = this.image!.width; 237 | canvas.height = this.image!.height; 238 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); 239 | if (ctx) { 240 | ctx.drawImage(this.image!, 0, 0); 241 | const id: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 242 | for (let i = 0; i < id.data.length; i += 4) { 243 | id.data[i] *= shade; 244 | id.data[i + 1] *= shade; 245 | id.data[i + 2] *= shade; 246 | } 247 | ctx.putImageData(id, 0, 0); 248 | } 249 | image = new Image(); 250 | image.src = canvas.toDataURL(); 251 | this.tints[tintName] = image; 252 | } 253 | 254 | tileRecord = tiles[tile] = new Tile(image, x * this.originalTileWidth, y * this.originalTileHeight, this.originalTileWidth, this.originalTileHeight, this.scale) 255 | } 256 | return tileRecord; 257 | } 258 | 259 | getTintedTile(tile: number, tintName: string, tint: number[]): Bitmap { 260 | let tiles = this.tintTiles[tintName]; 261 | if (!tiles) { 262 | tiles = this.tintTiles[tintName] = []; 263 | } 264 | 265 | let tileRecord = tiles[tile]; 266 | if (!tileRecord) { 267 | const x: number = tile % this.scanline; 268 | const y: number = Math.floor(tile / this.scanline); 269 | let image: HTMLImageElement = this.tints[tintName]; 270 | if (!image) { 271 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 272 | canvas.width = this.image!.width; 273 | canvas.height = this.image!.height; 274 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); 275 | if (ctx) { 276 | ctx.drawImage(this.image!, 0, 0); 277 | const id: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 278 | for (let i = 0; i < id.data.length; i += 4) { 279 | // leave black alone 280 | const avg: number = (id.data[i] + id.data[i + 1] + id.data[i + 2]) / 3; 281 | id.data[i] = Math.floor(avg * tint[0]); 282 | id.data[i + 1] = Math.floor(avg * tint[1]); 283 | id.data[i + 2] = Math.floor(avg * tint[2]); 284 | } 285 | ctx.putImageData(id, 0, 0); 286 | } 287 | image = new Image(); 288 | image.src = canvas.toDataURL(); 289 | this.tints[tintName] = image; 290 | } 291 | 292 | tileRecord = tiles[tile] = new Tile(image, x * this.originalTileWidth, y * this.originalTileHeight, this.originalTileWidth, this.originalTileHeight, this.scale) 293 | } 294 | return tileRecord; 295 | } 296 | 297 | modify(modification: (imageData: ImageData) => void): Tileset { 298 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 299 | canvas.width = this.image!.width; 300 | canvas.height = this.image!.height; 301 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); 302 | if (ctx) { 303 | ctx.drawImage(this.image!, 0, 0); 304 | const id: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 305 | modification(id); 306 | ctx.putImageData(id, 0, 0); 307 | } 308 | this.image = new Image(); 309 | this.image.src = canvas.toDataURL(); 310 | for (const tile of this.bitmaps) { 311 | tile.image = this.image; 312 | } 313 | 314 | return this; 315 | } 316 | 317 | getBlockColorTile(tile: number, tintName: string, col: number[]): Bitmap { 318 | let tiles = this.tintTiles[tintName]; 319 | if (!tiles) { 320 | tiles = this.tintTiles[tintName] = []; 321 | } 322 | 323 | let tileRecord = tiles[tile]; 324 | if (!tileRecord) { 325 | const x: number = tile % this.scanline; 326 | const y: number = Math.floor(tile / this.scanline); 327 | let image: HTMLImageElement = this.tints[tintName]; 328 | if (!image) { 329 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 330 | canvas.width = this.image!.width; 331 | canvas.height = this.image!.height; 332 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); 333 | if (ctx) { 334 | ctx.drawImage(this.image!, 0, 0); 335 | const id: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 336 | for (let i = 0; i < id.data.length; i += 4) { 337 | id.data[i] = Math.floor(255 * col[0]); 338 | id.data[i + 1] = Math.floor(255 * col[1]); 339 | id.data[i + 2] = Math.floor(255 * col[2]); 340 | id.data[i + 3] = Math.floor(id.data[i + 3] * col[3]); 341 | } 342 | ctx.putImageData(id, 0, 0); 343 | } 344 | image = new Image(); 345 | image.src = canvas.toDataURL(); 346 | this.tints[tintName] = image; 347 | } 348 | 349 | tileRecord = tiles[tile] = new Tile(image, x * this.originalTileWidth, y * this.originalTileHeight, this.originalTileWidth, this.originalTileHeight, this.scale) 350 | } 351 | return tileRecord; 352 | } 353 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {startGame, isMusicOn, isSoundOn, setMusicOn, setSoundOn, setPrescaleTilesets, Renderer } from "./Gute"; 2 | export { GameContext } from "./GameContext"; 3 | export { Graphics, WHITE, BLACK, GREEN, RED, BLUE, Offscreen } from "./Graphics"; 4 | export { getMaxTextureSize } from "./opengl/OpenGLGraphicsImpl"; 5 | export { Game } from "./Game"; 6 | export { GuteLog } from "./Log"; 7 | export { Bitmap } from "./Bitmap"; 8 | export { Font } from "./Font"; 9 | export { Sound } from "./Sound"; 10 | export { Tileset } from "./Tileset"; 11 | export { Keys } from "./Keys"; 12 | export { AStarPathFinder } from "./path/AStarPathFinder"; 13 | export { PathFinderMap } from "./path/PathFinderMap"; 14 | export { Path } from "./path/Path"; 15 | export { PathMover } from "./path/PathMover"; 16 | export { Step } from "./path/Step"; 17 | export { LDTKWorld, LDTKLayerCompression } from "./tilemaps/LDTKWorld"; 18 | export { MapWorld } from "./tilemaps/MapWorld"; 19 | export { MapLevel } from "./tilemaps/MapLevel"; 20 | export { MapLayer } from "./tilemaps/MapLayer"; 21 | export { MapEntity } from "./tilemaps/MapEntity"; 22 | export { SoundScape, SoundEasing } from "./SoundScape" 23 | -------------------------------------------------------------------------------- /src/opengl/OpenGLBitmap.1.ts: -------------------------------------------------------------------------------- 1 | import { Graphics } from "../Graphics"; 2 | import { Palette } from "../impl/Palette"; 3 | import { OpenGLGraphicsImpl } from "./OpenGLGraphicsImpl"; 4 | import { IOpenGLBitmap } from "./OpenGLBitmap"; 5 | import { GuteLog } from "../Log"; 6 | 7 | 8 | export class OpenGLBitmap implements IOpenGLBitmap { 9 | width: number = 0; 10 | height: number = 0; 11 | loaded: boolean = false; 12 | name: string; 13 | image: HTMLImageElement; 14 | texX: number = 0; 15 | texY: number = 0; 16 | texIndex: number = 0; 17 | 18 | constructor(graphics: OpenGLGraphicsImpl, url: string, dataUrlLoader: Promise | undefined, pal: Palette | undefined = undefined) { 19 | graphics.registerImage(this); 20 | 21 | this.name = url; 22 | this.image = new Image(); 23 | this.image.onerror = () => { 24 | GuteLog.log("Error loading: " + url); 25 | }; 26 | this.image.onload = () => { 27 | this.width = this.image.width; 28 | this.height = this.image.height; 29 | 30 | if (pal) { 31 | pal.adjustImage(this.image).then((image: HTMLImageElement) => { 32 | this.image = image; 33 | this.loaded = true; 34 | graphics.newResourceLoaded(); 35 | }); 36 | } else { 37 | this.loaded = true; 38 | graphics.newResourceLoaded(); 39 | } 40 | }; 41 | 42 | if (dataUrlLoader) { 43 | dataUrlLoader.then((base64: string) => { 44 | this.image.src = "data:" + url.substring(url.length - 3) + ";base64," + base64; 45 | }); 46 | } else { 47 | this.image.src = url; 48 | } 49 | } 50 | 51 | draw(graphics: Graphics, x: number, y: number): void { 52 | const g = (graphics as OpenGLGraphicsImpl); 53 | g._drawBitmap(this, x, y, this.width, this.height); 54 | } 55 | 56 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 57 | const g = (graphics as OpenGLGraphicsImpl); 58 | g._drawBitmap(this, x, y, width, height); 59 | } 60 | 61 | initOnFirstClick(): void { 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/opengl/OpenGLBitmap.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Graphics } from "../Graphics"; 3 | 4 | export interface IOpenGLBitmap { 5 | texX: number; 6 | texY: number; 7 | texIndex: number; 8 | width: number; 9 | height: number; 10 | image?: HTMLImageElement; 11 | } 12 | 13 | export class NullBitmap implements Bitmap { 14 | width: number = 0; 15 | height: number = 0; 16 | loaded: boolean = true; 17 | name: string = "null"; 18 | 19 | draw(graphics: Graphics, x: number, y: number): void { 20 | } 21 | 22 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 23 | } 24 | 25 | initOnFirstClick(): void { 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/opengl/OpenGLOffscreen.ts: -------------------------------------------------------------------------------- 1 | import { Offscreen } from "../Graphics"; 2 | import { OpenGLGraphicsImpl } from "./OpenGLGraphicsImpl"; 3 | import { RenderingState } from "./RenderingState"; 4 | 5 | export class OpenGlOffscreen implements Offscreen, RenderingState { 6 | width: number; 7 | height: number; 8 | texture: WebGLTexture | null = null; 9 | gl: WebGLRenderingContext; 10 | fb: WebGLFramebuffer | null = null; 11 | graphics: OpenGLGraphicsImpl; 12 | id: number = 0; 13 | inuse: boolean = false; 14 | 15 | clipX: number = 0; 16 | clipY: number = 0; 17 | clipX2: number = 0; 18 | clipY2: number = 0; 19 | alpha: number = 255; 20 | refreshRequired: boolean = false; 21 | 22 | constructor(gl: WebGLRenderingContext, graphics: OpenGLGraphicsImpl, id: number) { 23 | this.gl = gl; 24 | this.width = 0; 25 | this.height = 0; 26 | this.graphics = graphics; 27 | this.id = id; 28 | } 29 | 30 | getWidth(): number { 31 | return this.width; 32 | } 33 | 34 | getHeight(): number { 35 | return this.height; 36 | } 37 | 38 | recover(): void { 39 | this.fb = null; 40 | this.texture = null; 41 | this.refreshRequired = true; 42 | this.setDimension(this.width, this.height); 43 | } 44 | 45 | use(): void { 46 | if (!this.graphics.shaderProgram) { 47 | return; 48 | } 49 | 50 | if (this.inuse) { 51 | return; 52 | } 53 | 54 | this.refreshRequired = false; 55 | this.graphics.glCommitContext(); 56 | 57 | this.inuse = true; 58 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fb); 59 | this.gl.uniform2f(this.graphics.getUniformLoc("uCanvasSize"), Math.floor(this.width / 2), Math.floor(this.height / 2)); 60 | 61 | this.gl.viewport(0, 0, this.width, this.height); 62 | 63 | this.graphics.currentContextState = this; 64 | this.graphics.push(); 65 | this.graphics.transformCtx.resetTransform(); 66 | this.graphics.resetState(); 67 | this.graphics.glStartContext(); 68 | } 69 | 70 | unuse(): void { 71 | if (!this.graphics.shaderProgram) { 72 | return; 73 | } 74 | 75 | if (!this.inuse) { 76 | return; 77 | } 78 | 79 | this.inuse = false; 80 | this.graphics.glCommitContext(); 81 | this.graphics.currentContextState = this.graphics; 82 | 83 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); 84 | this.gl.viewport(0, 0, this.graphics.canvas.width, this.graphics.canvas.height); 85 | this.gl.uniform2f(this.graphics.getUniformLoc("uCanvasSize"), this.graphics.canvas.width / 2, this.graphics.canvas.height / 2); 86 | this.graphics.pop(); 87 | this.graphics.glStartContext(); 88 | } 89 | 90 | needsRefresh(): boolean { 91 | return this.refreshRequired; 92 | } 93 | 94 | release(): void { 95 | if (this.texture) { 96 | this.gl.deleteTexture(this.texture); 97 | } 98 | if (this.fb) { 99 | this.gl.deleteFramebuffer(this.fb); 100 | this.fb = 0; 101 | } 102 | 103 | this.width = 0; 104 | this.height = 0; 105 | } 106 | 107 | setDimension(width: number, height: number): void { 108 | if (this.width !== width || this.height !== height || !this.fb) { 109 | this.release(); 110 | 111 | this.width = width; 112 | this.height = height; 113 | 114 | this.texture = this.gl.createTexture(); 115 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); 116 | 117 | const level = 0; 118 | const internalFormat = this.gl.RGBA; 119 | const border = 0; 120 | const format = this.gl.RGBA; 121 | const type = this.gl.UNSIGNED_BYTE; 122 | const data = null; 123 | this.gl.texImage2D(this.gl.TEXTURE_2D, level, internalFormat, 124 | width, height, border, 125 | format, type, data); 126 | 127 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST); 128 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST); 129 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); 130 | this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); 131 | 132 | this.fb = this.gl.createFramebuffer(); 133 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fb); 134 | this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.texture, level); 135 | 136 | this.gl.clearColor(0,0,0,1); 137 | this.gl.clear(this.gl.COLOR_BUFFER_BIT); 138 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); 139 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.graphics.currentTexture); 140 | } 141 | } 142 | 143 | } -------------------------------------------------------------------------------- /src/opengl/OpenGLTilesetImpl.ts: -------------------------------------------------------------------------------- 1 | import { Bitmap } from "../Bitmap"; 2 | import { Tileset } from "../Tileset"; 3 | import { Palette } from "../impl/Palette"; 4 | import { OpenGLGraphicsImpl } from "./OpenGLGraphicsImpl"; 5 | import { IOpenGLBitmap } from "./OpenGLBitmap"; 6 | import { Graphics } from "../Graphics"; 7 | import { GuteLog } from "../Log"; 8 | 9 | class OpenGLTile implements Bitmap, IOpenGLBitmap { 10 | parent: OpenGLTilesetImpl; 11 | width: number; 12 | height: number; 13 | loaded: boolean; 14 | x: number; 15 | y: number; 16 | scale: number; 17 | name: string = "tile"; 18 | texX: number = 0; 19 | texY: number = 0; 20 | texIndex: number = 0; 21 | image: HTMLImageElement; 22 | col: number = 0xFFFFFF00; 23 | 24 | constructor(parent: OpenGLTilesetImpl, image: HTMLImageElement, x: number, y: number, width: number, height: number, scale: number) { 25 | this.parent = parent; 26 | this.width = width; 27 | this.height = height; 28 | this.x = x; 29 | this.y = y; 30 | this.scale = scale; 31 | this.loaded = true; 32 | this.image = image; 33 | } 34 | 35 | copyWithCol(rgba: number): OpenGLTile { 36 | const copy = new OpenGLTile(this.parent, this.image, this.x, this.y, this.width, this.height, this.scale); 37 | copy.loaded = true; 38 | copy.col = rgba; 39 | copy.texX = this.texX; 40 | copy.texY = this.texY; 41 | 42 | return copy; 43 | } 44 | 45 | draw(graphics: Graphics, x: number, y: number): void { 46 | const g = (graphics as OpenGLGraphicsImpl); 47 | this.texX = this.parent.texX + this.x; 48 | this.texY = this.parent.texY + this.y; 49 | this.texIndex = this.parent.texIndex; 50 | 51 | g._drawBitmap(this, x, y, Math.floor(this.width * this.scale), Math.floor(this.height * this.scale), this.col); 52 | } 53 | 54 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 55 | const g = (graphics as OpenGLGraphicsImpl); 56 | this.texX = this.parent.texX + this.x; 57 | this.texY = this.parent.texY + this.y; 58 | this.texIndex = this.parent.texIndex; 59 | 60 | g._drawBitmap(this, x, y, width, height, this.col); 61 | } 62 | 63 | initOnFirstClick(): void { 64 | } 65 | } 66 | 67 | export class OpenGLTilesetImpl implements Tileset, IOpenGLBitmap { 68 | loaded: boolean = false; 69 | tileWidth: number; 70 | tileHeight: number; 71 | originalTileWidth: number; 72 | originalTileHeight: number; 73 | image: any | null; 74 | bitmaps: OpenGLTile[] = []; 75 | scanline: number = 0; 76 | tileCount: number = 0; 77 | scale: number; 78 | onLoaded: () => void = () => { }; 79 | name: string; 80 | texX: number = 0; 81 | texY: number = 0; 82 | texIndex: number = 0; 83 | tintTiles: Record = {}; 84 | 85 | constructor(graphics: OpenGLGraphicsImpl, url: string, dataUrlLoader: Promise | undefined, tileWidth: number, tileHeight: number, scale: number = 1, pal: Palette | undefined = undefined) { 86 | this.tileWidth = this.originalTileWidth = tileWidth; 87 | this.tileHeight = this.originalTileHeight = tileHeight; 88 | this.scale = scale; 89 | this.name = url; 90 | this.image = new Image(); 91 | 92 | this.image.onerror = () => { 93 | GuteLog.log("Error loading: " + url); 94 | } 95 | this.image.onload = () => { 96 | this.scanline = Math.floor(this.image!.width / this.tileWidth); 97 | const depth: number = Math.floor(this.image!.height / this.tileHeight); 98 | this.tileCount = depth * this.scanline; 99 | 100 | if (pal) { 101 | pal.adjustImage(this.image!).then((image) => { 102 | this.image = image; 103 | 104 | // cut the image into pieces 105 | for (let y = 0; y < depth; y++) { 106 | for (let x = 0; x < this.scanline; x++) { 107 | this.bitmaps.push(new OpenGLTile(this, this.image!, x * this.tileWidth, y * this.tileHeight, this.tileWidth, this.tileHeight, scale)); 108 | } 109 | } 110 | this.tileWidth *= scale; 111 | this.tileHeight *= scale; 112 | 113 | this.onLoaded(); 114 | graphics.registerImage(this); 115 | this.loaded = true; 116 | }) 117 | } else { 118 | // cut the image into pieces 119 | for (let y = 0; y < depth; y++) { 120 | for (let x = 0; x < this.scanline; x++) { 121 | this.bitmaps.push(new OpenGLTile(this, this.image!, x * this.tileWidth, y * this.tileHeight, this.tileWidth, this.tileHeight, scale)); 122 | } 123 | } 124 | this.tileWidth *= scale; 125 | this.tileHeight *= scale; 126 | 127 | this.onLoaded(); 128 | graphics.registerImage(this); 129 | this.loaded = true; 130 | } 131 | }; 132 | 133 | if (dataUrlLoader) { 134 | dataUrlLoader.then((blob: Blob) => { 135 | var urlCreator = window.URL || window.webkitURL; 136 | this.image!.src = urlCreator.createObjectURL(blob); 137 | }) 138 | } else { 139 | this.image.src = url; 140 | } 141 | } 142 | 143 | get height(): number { 144 | return this.image.height; 145 | } 146 | 147 | get width(): number { 148 | return this.image.width; 149 | } 150 | 151 | draw(graphics: Graphics, x: number, y: number): void { 152 | } 153 | 154 | drawScaled(graphics: Graphics, x: number, y: number, width: number, height: number): void { 155 | } 156 | 157 | getBlockColorTile(tile: number, tintName: string, rgba: number[]): Bitmap { 158 | let tiles = this.tintTiles[tintName]; 159 | if (!tiles) { 160 | tiles = this.tintTiles[tintName] = []; 161 | } 162 | 163 | let tileRecord = tiles[tile]; 164 | if (!tileRecord) { 165 | rgba[0] *= 255; 166 | rgba[1] *= 255; 167 | rgba[2] *= 255; 168 | 169 | const value = (rgba[0] * (256 * 256 * 256)) + (rgba[1] * (256 * 256)) + (rgba[2] * 256) + Math.floor(rgba[3] * 255); 170 | tiles[tile] = tileRecord = this.getTile(tile).copyWithCol(value) 171 | } 172 | return tileRecord; 173 | } 174 | 175 | getShadedTile(tile: number, tintName: string, shade: number): Bitmap { 176 | let tiles = this.tintTiles[tintName]; 177 | if (!tiles) { 178 | tiles = this.tintTiles[tintName] = []; 179 | } 180 | 181 | let tileRecord = tiles[tile]; 182 | if (!tileRecord) { 183 | const value = (255 * (256 * 256 * 256)) + (255 * (256 * 256)) + (255 * 256) + Math.floor(shade * 255); 184 | tiles[tile] = tileRecord = this.getTile(tile).copyWithCol(value) 185 | } 186 | return tileRecord; 187 | } 188 | 189 | getTintedTile(tile: number, tintName: string, source: number[]): Bitmap { 190 | let tiles = this.tintTiles[tintName]; 191 | if (!tiles) { 192 | tiles = this.tintTiles[tintName] = []; 193 | } 194 | 195 | let tileRecord = tiles[tile]; 196 | if (!tileRecord) { 197 | const rgba = [...source]; 198 | rgba[0] *= 255; 199 | rgba[1] *= 255; 200 | rgba[2] *= 255; 201 | if (!rgba[3]) { 202 | rgba[3] = 1; 203 | } 204 | 205 | const value = (rgba[0] * (256 * 256 * 256)) + (rgba[1] * (256 * 256)) + (rgba[2] * 256) + Math.floor(rgba[3] * 255); 206 | 207 | tiles[tile] = tileRecord = this.getTile(tile).copyWithCol(value) 208 | } 209 | return tileRecord; 210 | } 211 | 212 | modify(modification: (imageData: ImageData) => void): Tileset { 213 | const canvas: HTMLCanvasElement = document.createElement("canvas"); 214 | canvas.width = this.image!.width; 215 | canvas.height = this.image!.height; 216 | const ctx: CanvasRenderingContext2D | null = canvas.getContext("2d"); 217 | if (ctx) { 218 | ctx.drawImage(this.image!, 0, 0); 219 | const id: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 220 | modification(id); 221 | ctx.putImageData(id, 0, 0); 222 | } 223 | this.image = new Image(); 224 | this.image.src = canvas.toDataURL(); 225 | for (const tile of this.bitmaps) { 226 | tile.image = this.image; 227 | } 228 | 229 | return this; 230 | } 231 | 232 | getTilesAcross(): number { 233 | return this.scanline; 234 | } 235 | 236 | getTileWidth(): number { 237 | return this.tileWidth; 238 | } 239 | 240 | getTileHeight(): number { 241 | return this.tileHeight; 242 | } 243 | 244 | getTileCount(): number { 245 | return this.tileCount; 246 | } 247 | 248 | initOnFirstClick(): void { 249 | } 250 | 251 | getTile(tile: number): OpenGLTile { 252 | return this.bitmaps[tile]; 253 | } 254 | 255 | } -------------------------------------------------------------------------------- /src/opengl/RenderingState.ts: -------------------------------------------------------------------------------- 1 | export interface RenderingState { 2 | clipX: number; 3 | clipY: number; 4 | clipX2: number; 5 | clipY2: number; 6 | alpha: number; 7 | } -------------------------------------------------------------------------------- /src/path/AStarPathFinder.ts: -------------------------------------------------------------------------------- 1 | import { MapNode } from "./MapNode"; 2 | import { Path } from "./Path"; 3 | import { PathFinderMap } from "./PathFinderMap"; 4 | import { PathMover } from "./PathMover"; 5 | 6 | export class AStarPathFinder { 7 | public static NORTH_TO_SOUTH = 0; 8 | public static EAST_TO_WEST = 1; 9 | public static SOUTH_TO_NORTH = 2; 10 | public static WEST_TO_EAST = 3; 11 | public static NONE = 4; 12 | 13 | private objectPool: Array = []; 14 | private openList: Array = []; 15 | private parentList: Array = []; 16 | private open: Array> = []; 17 | private closed: Array> = []; 18 | 19 | private map!: PathFinderMap; 20 | private height!: number; 21 | private width!: number; 22 | 23 | private pathFindCounter: number = 1; 24 | private mover!: PathMover; 25 | private tx!: number[]; 26 | private ty!: number[]; 27 | private cx!: number; 28 | private cy!: number; 29 | private max!: number; 30 | 31 | public constructor(map: PathFinderMap) { 32 | this.setMap(map); 33 | } 34 | 35 | public setMap(map: PathFinderMap) { 36 | this.width = map.getMapWidth(); 37 | this.height = map.getMapHeight(); 38 | this.map = map; 39 | 40 | if (!this.open) { 41 | this.open = new Array>(); 42 | } 43 | if (!this.closed) { 44 | this.closed = new Array>(); 45 | } 46 | 47 | for (var i = 0; i < this.width * this.height; i++) { 48 | let o = this.open[i] 49 | let c = this.closed[i]; 50 | 51 | if (!o) { 52 | this.open[i] = o = new Array(); 53 | for (var j = 0; j < 5; j++) { 54 | o.push(0); 55 | } 56 | } 57 | if (!c) { 58 | this.closed[i] = c = new Array(); 59 | for (var j = 0; j < 5; j++) { 60 | c.push(0); 61 | } 62 | } 63 | 64 | for (var j = 0; j < 5; j++) { 65 | o[j] = 0; 66 | c[j] = 0; 67 | } 68 | } 69 | } 70 | 71 | public clear(): void { 72 | for (let node of this.openList) { 73 | this.objectPool.push(node); 74 | } 75 | for (let node of this.parentList) { 76 | this.objectPool.push(node); 77 | } 78 | this.parentList = [] 79 | this.openList = [] 80 | this.pathFindCounter++; 81 | } 82 | 83 | private generatePath(node: MapNode): Path { 84 | var current: MapNode | null = node; 85 | var path: Path = new Path(); 86 | 87 | while (current != null) { 88 | path.add(current.x, current.y); 89 | current = current.parent; 90 | } 91 | 92 | return path; 93 | } 94 | 95 | private blocked(sx: number, sy: number, x: number, y: number): boolean { 96 | if (!this.map.locationDiscovered(x, y)) { 97 | return true; 98 | } 99 | 100 | return this.map.blocked(this.mover, null, sx, sy, x, y, this.atTarget(x, y)); 101 | } 102 | 103 | private atTarget(x: number, y: number): boolean { 104 | for (let i = 0; i < this.tx.length; i++) { 105 | const tx = this.tx[i] 106 | const ty = this.ty[i] 107 | if (tx >= x && tx < x + this.mover.getTilesWidth() 108 | && ty >= y && ty < y + this.mover.getTilesHeight()) 109 | return true 110 | } 111 | return false 112 | } 113 | 114 | public findPath(mover: PathMover, tx: number, ty: number, width: number, height: number, max: number): Path | null { 115 | 116 | tx = Math.floor(tx); 117 | ty = Math.floor(ty); 118 | 119 | 120 | this.max = max; 121 | this.mover = mover; 122 | this.tx = []; 123 | this.ty = []; 124 | // central point for heuristic ordering 125 | this.cx = tx + width / 2 126 | this.cy = ty + height / 2 127 | 128 | for (let i = 0; i < width; i++) { 129 | this.tx.push(tx + i) 130 | this.ty.push(ty) 131 | if (height > 1) { 132 | this.tx.push(tx + i) 133 | this.ty.push(ty + height - 1) 134 | } 135 | } 136 | 137 | if (height > 2) { 138 | for (let i = 1; i < height - 1; i++) { 139 | this.tx.push(tx) 140 | this.ty.push(ty + i) 141 | if (width > 1) { 142 | this.tx.push(tx + width - 1) 143 | this.ty.push(ty + i) 144 | } 145 | } 146 | } 147 | 148 | if (this.tx.length === 0) { 149 | return null; 150 | } 151 | 152 | this.clear(); 153 | 154 | this.addLocation(null, Math.floor(mover.getTileMapX()), Math.floor(mover.getTileMapY())); 155 | while (this.openList.length > 0) { 156 | const best: MapNode = this.openList[0]; 157 | this.openList.splice(0, 1); 158 | 159 | // if best is the target then we've found it! 160 | if (this.atTarget(best.x, best.y)) { 161 | return this.generatePath(best); 162 | } 163 | 164 | this.addLocation(best, best.x + 1, best.y); 165 | this.addLocation(best, best.x - 1, best.y); 166 | this.addLocation(best, best.x, best.y + 1); 167 | this.addLocation(best, best.x, best.y - 1); 168 | 169 | this.parentList.push(best) 170 | } 171 | 172 | return null; 173 | } 174 | 175 | private addLocation(parent: MapNode | null, x: number, y: number): void { 176 | x = Math.floor(x); 177 | y = Math.floor(y); 178 | 179 | var sx = x; 180 | var sy = y; 181 | var dir = AStarPathFinder.NONE; 182 | 183 | if (parent != null) { 184 | sx = parent.x; 185 | sy = parent.y; 186 | 187 | if (sy + 1 == y) { 188 | dir = AStarPathFinder.NORTH_TO_SOUTH; 189 | } 190 | if (sy - 1 == y) { 191 | dir = AStarPathFinder.SOUTH_TO_NORTH; 192 | } 193 | if (sx + 1 == x) { 194 | dir = AStarPathFinder.WEST_TO_EAST; 195 | } 196 | if (sx - 1 == x) { 197 | dir = AStarPathFinder.EAST_TO_WEST; 198 | } 199 | } 200 | 201 | 202 | if (!this.map.validLocation(x, y)) { 203 | return; 204 | } 205 | 206 | // if it's in the open list ignore 207 | if (this.open[x + (y * this.width)][dir] == this.pathFindCounter) { 208 | return; 209 | } 210 | if (this.closed[x + (y * this.width)][dir] == this.pathFindCounter) { 211 | return; 212 | } 213 | 214 | // if it's blocked for any reason, add it to the closed 215 | if (parent != null) { 216 | if (parent.depth > this.max) { 217 | this.closed[x + (y * this.width)][dir] = this.pathFindCounter; 218 | return; 219 | } 220 | } 221 | if (this.blocked(sx, sy, x, y)) { 222 | this.closed[x + (y * this.width)][dir] = this.pathFindCounter; 223 | return; 224 | } 225 | 226 | // otherwise it's a possible step add it to the open 227 | this.open[x + (y * this.width)][dir] = this.pathFindCounter; 228 | 229 | const dx: number = Math.abs(this.cx - x); 230 | const dy: number = Math.abs(this.cy - y); 231 | 232 | const node: MapNode = this.createMapNode(x, y, parent, dx + dy); 233 | const index = AStarPathFinder.binarySearch(this.openList, node.h) 234 | this.openList.splice(index, 0, node); 235 | } 236 | 237 | private static binarySearch(array: MapNode[], h: number) { 238 | let lo = -1, hi = array.length; 239 | while (1 + lo < hi) { 240 | const mi = lo + ((hi - lo) >> 1); 241 | if (array[mi].h > h) { 242 | hi = mi; 243 | } else { 244 | lo = mi; 245 | } 246 | } 247 | return hi; 248 | } 249 | 250 | // object pool accessor - free is done in bulk 251 | private createMapNode(x: number, y: number, parent: MapNode | null, h: number): MapNode { 252 | if (this.objectPool.length == 0) { 253 | var n: MapNode = new MapNode(); 254 | this.objectPool.push(n); 255 | } 256 | 257 | var node: MapNode = this.objectPool[0]; 258 | this.objectPool.splice(0, 1); 259 | node.x = x; 260 | node.y = y; 261 | node.parent = parent; 262 | if (parent != null) { 263 | node.depth = parent.depth + 1; 264 | } else { 265 | node.depth = 0; 266 | } 267 | node.h = h + node.depth; 268 | return node; 269 | } 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/path/MapNode.ts: -------------------------------------------------------------------------------- 1 | export class MapNode { 2 | x!: number; 3 | y!: number; 4 | parent!: MapNode | null; 5 | h!: number; 6 | depth!: number; 7 | } -------------------------------------------------------------------------------- /src/path/Path.ts: -------------------------------------------------------------------------------- 1 | import { GuteLog } from "../Log"; 2 | import { Step } from "./Step"; 3 | 4 | export class Path { 5 | steps: Array = new Array(); 6 | 7 | add(x: number, y: number): void { 8 | this.steps.splice(0, 0, new Step(x, y)); 9 | } 10 | 11 | getLastStep(): Step { 12 | return this.steps[this.steps.length - 1]; 13 | } 14 | 15 | pop(): Step { 16 | const result: Step = this.steps[0]; 17 | this.steps.splice(0, 1); 18 | return result; 19 | } 20 | 21 | copy(): Path { 22 | const copy = new Path(); 23 | for (const step of this.steps) { 24 | copy.steps.push(new Step(step.x, step.y)); 25 | } 26 | if (copy.steps.length === 0) { 27 | GuteLog.log("Created copy of path with zero steps: "); 28 | } 29 | 30 | return copy; 31 | } 32 | } -------------------------------------------------------------------------------- /src/path/PathFinderMap.ts: -------------------------------------------------------------------------------- 1 | import { PathMover } from "./PathMover"; 2 | 3 | export interface PathFinderMap { 4 | 5 | getMapWidth(): number; 6 | 7 | getMapHeight(): number; 8 | 9 | locationDiscovered(x: number, y: number): boolean; 10 | 11 | blocked(mover: PathMover, object: PathMover | null, sx: number, sy: number, x: number, y: number, lastStep: boolean): boolean; 12 | 13 | getMoverAt(tx: number, ty: number): PathMover | null; 14 | 15 | validLocation(x: number, y: number): boolean; 16 | } 17 | -------------------------------------------------------------------------------- /src/path/PathMover.ts: -------------------------------------------------------------------------------- 1 | export interface PathMover { 2 | 3 | getTilesWidth(): number; 4 | 5 | getTilesHeight(): number; 6 | 7 | getTarget(): PathMover | null; 8 | 9 | getTileMapY(): number; 10 | 11 | getTileMapX(): number; 12 | } -------------------------------------------------------------------------------- /src/path/Step.ts: -------------------------------------------------------------------------------- 1 | export class Step { 2 | x: number; 3 | y: number; 4 | 5 | constructor(x: number, y: number) { 6 | this.x = x; 7 | this.y = y; 8 | } 9 | } -------------------------------------------------------------------------------- /src/tilemaps/LDTKWorld.ts: -------------------------------------------------------------------------------- 1 | import { GuteLog } from "../Log"; 2 | import { Resource } from "../Resource"; 3 | import { MapEntity } from "./MapEntity"; 4 | import { MapLayer } from "./MapLayer"; 5 | import { MapLevel } from "./MapLevel"; 6 | import { MapWorld } from "./MapWorld"; 7 | 8 | interface EntityRef { 9 | value: string|string[] 10 | entity: MapEntity 11 | field: string 12 | } 13 | 14 | export interface LDTKLayerCompression { 15 | from: string; 16 | to: string; 17 | offset: number; 18 | } 19 | 20 | export class LDTKWorld extends MapWorld implements Resource { 21 | static LAYER_COMPRESSIONS: LDTKLayerCompression[] = []; 22 | 23 | name: string = "world"; 24 | tilesets: any[] = []; 25 | 26 | initOnFirstClick(): void { 27 | } 28 | 29 | load(file: string, loader: (file: string) => Promise) : Promise { 30 | this.name = file; 31 | 32 | return loader(file).then(json => { 33 | const entityRefs : EntityRef[] = [] 34 | const entityMap: Record = {} 35 | 36 | this.gridSize = json.defaultGridSize; 37 | const tileset: any = json.defs.tilesets[0]; 38 | this.tilesets = json.defs.tilesets; 39 | this.tilesetScanline = tileset.pxWid / tileset.tileGridSize; 40 | this.tilesetSize = tileset.tileGridSize; 41 | 42 | let levels = json.levels; 43 | if (json.worlds && json.worlds.length > 0) { 44 | levels = []; 45 | for (const world of json.worlds) { 46 | levels = levels.concat(world.levels); 47 | } 48 | } 49 | 50 | const asyncLevels : Promise[] = [] 51 | for (const levelData of json.levels) { 52 | const level: MapLevel = new MapLevel(this, levelData.identifier); 53 | 54 | level.worldX = levelData.worldX; 55 | level.worldY = levelData.worldY; 56 | level.worldDepth = levelData.worldDepth; 57 | 58 | for (const fieldInstance of levelData.fieldInstances) { 59 | level.fields[fieldInstance.__identifier] = fieldInstance.__value; 60 | } 61 | 62 | let layers : Promise 63 | if (levelData.layerInstances) // embedded layers 64 | layers = Promise.resolve(levelData) 65 | else if (levelData.externalRelPath) { 66 | layers = loader(levelData.externalRelPath) 67 | } else { 68 | throw new Error("Unknown LDTK file format") 69 | } 70 | 71 | asyncLevels.push(layers.then(data => { 72 | LDTKWorld.loadLayers(level, data.layerInstances, entityRefs, entityMap); 73 | 74 | if (level.layers.length > 0) { 75 | level.width = level.layers[0].width; 76 | level.height = level.layers[0].height; 77 | } else { 78 | level.width = levelData.pxWid / this.gridSize; 79 | level.height = levelData.pxHei / this.gridSize; 80 | } 81 | 82 | this.levels[level.id] = level; 83 | return level 84 | })) 85 | } 86 | 87 | return Promise.all(asyncLevels).then(value => { 88 | // resolve all entity ids now that we have all the data 89 | for (const ref of entityRefs) { 90 | if (ref.value instanceof Array) { 91 | const value : MapEntity[] = [] 92 | for (const item of ref.value) { 93 | const entity = entityMap[item] 94 | if (entity) { 95 | value.push(entity) 96 | } 97 | } 98 | ref.entity.fields[ref.field] = value 99 | } else { 100 | const entity = entityMap[ref.value] 101 | if (entity) { 102 | ref.entity.fields[ref.field] = entity 103 | } 104 | } 105 | } 106 | 107 | this.loaded = true; 108 | return this 109 | }) 110 | }).catch((e) => { 111 | GuteLog.error(e); 112 | throw e; 113 | }); 114 | } 115 | 116 | private static loadLayers(level: MapLevel, layerInstances: any, entityRefs: EntityRef[], entityMap: Record) { 117 | for (const layerData of layerInstances) { 118 | if (layerData.__type === "Entities") { 119 | for (const entityData of layerData.entityInstances) { 120 | const entity: MapEntity = new MapEntity(level, 121 | entityData.px[0] / layerData.__gridSize, 122 | entityData.px[1] / layerData.__gridSize, 123 | entityData.width / layerData.__gridSize, 124 | entityData.height / layerData.__gridSize, 125 | entityData.__identifier) 126 | 127 | entity.id = entityData.iid; 128 | entityMap[entityData.iid] = entity 129 | for (const fieldInstance of entityData.fieldInstances) { 130 | switch (fieldInstance.__type) { 131 | case "EntityRef": // save information to resolve refs to entities later when all information will be loaded 132 | entityRefs.push({ 133 | value: fieldInstance.__value.entityIid, 134 | entity: entity, 135 | field: fieldInstance.__identifier 136 | }) 137 | break; 138 | case "Array": 139 | entityRefs.push({ 140 | value: (fieldInstance.__value as Array).map(it => it.entityIid), 141 | entity: entity, 142 | field: fieldInstance.__identifier 143 | }); 144 | break 145 | default: 146 | entity.fields[fieldInstance.__identifier] = fieldInstance.__value; 147 | break; 148 | } 149 | } 150 | level.entities.push(entity); 151 | } 152 | } else { 153 | const compression = LDTKWorld.LAYER_COMPRESSIONS.find(c => c.from === layerData.__identifier); 154 | let layer: MapLayer | undefined; 155 | let offset = 0; 156 | if (compression) { 157 | const targetLayer = level.layerByName[compression.to]; 158 | if (!targetLayer) { 159 | throw "Unable to find compression layer: " + compression.to; 160 | } 161 | 162 | layer = targetLayer; 163 | offset = compression.offset; 164 | } else { 165 | layer = new MapLayer(level, layerData.__identifier, layerData.__cWid, layerData.__cHei); 166 | } 167 | 168 | const tileset = (level.world as LDTKWorld).tilesets.find(t => t.uid === layerData.__tilesetDefUid); 169 | 170 | const scanline: number =tileset.pxWid / tileset.tileGridSize; 171 | const tileSize: number =tileset.tileGridSize; 172 | 173 | for (const tile of layerData.gridTiles) { 174 | const x: number = Math.floor(tile.px[0] / layerData.__gridSize); 175 | const y: number = Math.floor(tile.px[1] / layerData.__gridSize); 176 | const posIndex: number = x + (y * layer.width); 177 | 178 | const tx: number = Math.floor(tile.src[0] / tileSize); 179 | const ty: number = Math.floor(tile.src[1] / tileSize); 180 | 181 | const tileIndex: number = (ty * scanline) + tx; 182 | layer.tiles[posIndex] = tileIndex + 1 + offset; 183 | layer.flips[posIndex] = tile.f; 184 | } 185 | 186 | if (!compression) { 187 | level.layers.splice(0, 0, layer); 188 | level.layerByName[layer.name] = layer; 189 | } 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/tilemaps/MapEntity.ts: -------------------------------------------------------------------------------- 1 | import { MapLevel } from "./MapLevel"; 2 | 3 | export class MapEntity { 4 | id?: string; 5 | type: string; 6 | x: number; 7 | y: number; 8 | width: number; 9 | height: number; 10 | level: MapLevel; 11 | fields: any = {}; 12 | 13 | constructor(level: MapLevel, x: number, y: number, width: number, height: number, type: string) { 14 | this.level = level; 15 | this.x = x; 16 | this.y = y; 17 | this.width = width; 18 | this.height = height; 19 | this.type = type; 20 | } 21 | 22 | copy(level: MapLevel): MapEntity { 23 | const result: MapEntity = new MapEntity(level, this.x, this.y, this.width, this.height, this.type); 24 | result.fields = {...this.fields}; 25 | result.id = this.id; 26 | 27 | return result; 28 | } 29 | } -------------------------------------------------------------------------------- /src/tilemaps/MapLayer.ts: -------------------------------------------------------------------------------- 1 | import { MapLevel } from "./MapLevel"; 2 | 3 | export class MapLayer { 4 | static FLIP_X: number = 1; 5 | static FLIP_Y: number = 2; 6 | 7 | name: string; 8 | level: MapLevel; 9 | width: number; 10 | height: number; 11 | tiles: number[]; 12 | flips: number[]; 13 | 14 | constructor(level: MapLevel, name: string, width: number, height: number) { 15 | this.name = name; 16 | this.level = level; 17 | this.width = width; 18 | this.height = height; 19 | 20 | this.tiles = []; 21 | this.flips = []; 22 | for (let i=0;i= this.width) || (y >= this.height)) { 30 | return 0; 31 | } 32 | const posIndex: number = x + (y * this.width); 33 | 34 | return this.flips[posIndex]; 35 | } 36 | 37 | set(x: number, y: number, value: number): void { 38 | if ((x < 0) || (y < 0) || (x >= this.width) || (y >= this.height)) { 39 | return; 40 | } 41 | const posIndex: number = x + (y * this.width); 42 | this.tiles[posIndex] = value; 43 | } 44 | 45 | get(x: number, y: number): number { 46 | if ((x < 0) || (y < 0) || (x >= this.width) || (y >= this.height)) { 47 | return 0; 48 | } 49 | const posIndex: number = x + (y * this.width); 50 | 51 | return this.tiles[posIndex]; 52 | } 53 | 54 | copy(level: MapLevel): MapLayer { 55 | const result: MapLayer = new MapLayer(level, this.name, this.width, this.height); 56 | for (let i=0;i = {}; 9 | width!: number; 10 | height!: number; 11 | world: MapWorld; 12 | entities: MapEntity[] = []; 13 | fields: any = {}; 14 | worldX: number = 0; 15 | worldY: number = 0; 16 | worldDepth: number = 0; 17 | 18 | constructor(world: MapWorld, id: string) { 19 | this.world = world; 20 | this.id = id; 21 | } 22 | 23 | entitiesOfType(type: string): MapEntity[] { 24 | return this.entities.filter(entity => entity.type === type); 25 | } 26 | 27 | firstEntityOfType(type: string): MapEntity | undefined { 28 | return this.entities.find(entity => entity.type === type); 29 | } 30 | 31 | copy(id: string): MapLevel { 32 | const worldCopy: MapWorld = new MapWorld(); 33 | worldCopy.gridSize = this.world.gridSize; 34 | worldCopy.loaded = this.world.loaded; 35 | worldCopy.tilesetScanline = this.world.tilesetScanline; 36 | worldCopy.tilesetSize = this.world.tilesetSize; 37 | 38 | const levelCopy: MapLevel = new MapLevel(worldCopy, id); 39 | levelCopy.width = this.width; 40 | levelCopy.height = this.height; 41 | levelCopy.worldX = this.worldX; 42 | levelCopy.worldY = this.worldY; 43 | levelCopy.worldDepth = this.worldDepth; 44 | levelCopy.fields = {...this.fields}; 45 | 46 | for (const layer of this.layers) { 47 | const copy: MapLayer = layer.copy(levelCopy); 48 | levelCopy.layers.push(copy); 49 | levelCopy.layerByName[copy.name] = copy; 50 | } 51 | for (const entity of this.entities) { 52 | const copy: MapEntity = entity.copy(levelCopy); 53 | levelCopy.entities.push(copy); 54 | } 55 | return levelCopy; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/tilemaps/MapWorld.ts: -------------------------------------------------------------------------------- 1 | import { Resource } from "../Resource"; 2 | import { MapLevel } from "./MapLevel"; 3 | 4 | export class MapWorld { 5 | levels: Record = {}; 6 | gridSize: number = 0; 7 | tilesetScanline: number = 0; 8 | tilesetSize: number = 0; 9 | loaded: boolean = false; 10 | 11 | constructor() { 12 | } 13 | } -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'xbr-js'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "target": "es5", 5 | "lib": ["es2015", "dom"], 6 | "module": "commonjs", 7 | "declaration": true, 8 | "outDir": "./dist", 9 | "strict": true 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", "**/__tests__/*"] 13 | } 14 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'development', 5 | entry: './src/index.ts', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.ts?$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/, 12 | }, 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'style-loader', 17 | 'css-loader' 18 | ], 19 | }, 20 | { 21 | test: /\.(png|svg|jpg|gif)$/, 22 | use: [ 23 | 'file-loader', 24 | ], 25 | }, 26 | ], 27 | }, 28 | resolve: { 29 | extensions: ['.tsx', '.ts', '.js'], 30 | }, 31 | devtool: 'inline-source-map', 32 | devServer: { 33 | contentBase: './lib', 34 | }, 35 | output: { 36 | globalObject: 'this', 37 | filename: 'index.js', 38 | path: path.resolve(__dirname, 'dist'), 39 | library: "gute", 40 | libraryTarget: 'umd' 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/index.ts', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.ts?$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/, 12 | }, 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'style-loader', 17 | 'css-loader' 18 | ], 19 | }, 20 | { 21 | test: /\.(png|svg|jpg|gif)$/, 22 | use: [ 23 | 'file-loader', 24 | ], 25 | }, 26 | ], 27 | }, 28 | resolve: { 29 | extensions: ['.tsx', '.ts', '.js'], 30 | }, 31 | devtool: 'inline-source-map', 32 | devServer: { 33 | contentBase: './lib', 34 | }, 35 | output: { 36 | filename: 'index-min.js', 37 | path: path.resolve(__dirname, 'dist'), 38 | library: "gute", 39 | libraryTarget: 'umd' 40 | } 41 | }; 42 | --------------------------------------------------------------------------------