diff --git a/CHANGELOG.md b/CHANGELOG.md index e265663e8..8d12569a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ +### v3000.1.15 +- fixed `loadRoot()` not working sometimes +- fixed audio being resumed when the tab is switched on but `debug.paused` is true + ### v3000.1.12 -- fix `color()` and `rgb()` not working +- fixed `color()` and `rgb()` not working ### v3000.1.11 - added option `kaboom({ focus: false })` to disable focus on start @@ -8,11 +12,11 @@ - added `Color#toHSL()` ### v3000.1.10 -- fix test code accidentally getting shipped (where a screenshot will be downloaded every time you press space) +- fixed test code accidentally getting shipped (where a screenshot will be downloaded every time you press space) ### v3000.1.9 - added `fill` option to `rect()`, `circle()` and `sprite()` -- fix view getting cut off in letterbox mode +- fixed view getting cut off in letterbox mode ### v3000.1.8 - fixed `scale` option acting weird when width and height are defined (by @hirnsalat) diff --git a/package.json b/package.json index 7129043c5..483567b1a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kaboom", "description": "kaboom.js is a JavaScript library that helps you make games fast and fun!", - "version": "3000.1.13", + "version": "3000.1.15", "license": "MIT", "homepage": "https://kaboomjs.com/", "repository": "github:replit/kaboom", diff --git a/src/assets.ts b/src/assets.ts index da211c5f4..473496ceb 100644 --- a/src/assets.ts +++ b/src/assets.ts @@ -1,6 +1,5 @@ import { Event, - isDataURL, } from "./utils" export class Asset { @@ -100,61 +99,33 @@ export class AssetBucket { } } -export default () => { - - const state = { - urlPrefix: "", - loaded: false, - } - - function setURLPrefix(prefix: string) { - state.urlPrefix = prefix - } - - function getURLPrefix() { - return state.urlPrefix - } - - function fetchURL(path: string) { - const url = state.urlPrefix + path - return fetch(url) - .then((res) => { - if (!res.ok) throw new Error(`Failed to fetch "${url}"`) - return res - }) - } - - function fetchJSON(path: string) { - return fetchURL(path).then((res) => res.json()) - } - - function fetchText(path: string) { - return fetchURL(path).then((res) => res.text()) - } +export function fetchURL(url: string) { + return fetch(url) + .then((res) => { + if (!res.ok) throw new Error(`Failed to fetch "${url}"`) + return res + }) +} - function fetchArrayBuffer(path: string) { - return fetchURL(path).then((res) => res.arrayBuffer()) - } +export function fetchJSON(path: string) { + return fetchURL(path).then((res) => res.json()) +} - // wrapper around image loader to get a Promise - function loadImg(src: string): Promise { - const img = new Image() - img.crossOrigin = "anonymous" - img.src = isDataURL(src) ? src : state.urlPrefix + src - return new Promise((resolve, reject) => { - img.onload = () => resolve(img) - img.onerror = () => reject(new Error(`Failed to load image from "${src}"`)) - }) - } +export function fetchText(path: string) { + return fetchURL(path).then((res) => res.text()) +} - return { - setURLPrefix, - getURLPrefix, - loadImg, - fetchURL, - fetchJSON, - fetchText, - fetchArrayBuffer, - } +export function fetchArrayBuffer(path: string) { + return fetchURL(path).then((res) => res.arrayBuffer()) +} +// wrapper around image loader to get a Promise +export function loadImg(src: string): Promise { + const img = new Image() + img.crossOrigin = "anonymous" + img.src = src + return new Promise((resolve, reject) => { + img.onload = () => resolve(img) + img.onerror = () => reject(new Error(`Failed to load image from "${src}"`)) + }) } diff --git a/src/kaboom.ts b/src/kaboom.ts index c6e0be83b..ab9004bd3 100644 --- a/src/kaboom.ts +++ b/src/kaboom.ts @@ -1,4 +1,4 @@ -const VERSION = "3000.1.13" +const VERSION = "3000.1.15" import initApp from "./app" import initGfx, { @@ -8,9 +8,13 @@ import initGfx, { BatchRenderer, } from "./gfx" -import initAssets, { +import { Asset, AssetBucket, + fetchArrayBuffer, + fetchJSON, + fetchText, + loadImg, } from "./assets" import { @@ -63,7 +67,7 @@ import { downloadBlob, uid, isDataURL, - getExt, + getFileName, overload2, dataURLToArrayBuffer, EventController, @@ -96,6 +100,7 @@ import type { Vertex, BitmapFontData, ShaderData, + AsepriteData, LoadSpriteSrc, LoadSpriteOpt, SpriteAtlasData, @@ -431,8 +436,6 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { texFilter: gopt.texFilter, }) - const ass = initAssets() - const gfx = (() => { const defShader = makeShader(DEF_VERT, DEF_FRAG) @@ -452,13 +455,13 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { let bgAlpha = 1 if (gopt.background) { - bgColor = Color.fromArray(gopt.background) - bgAlpha = gopt.background[3] ?? 1 + bgColor = rgb(gopt.background) + bgAlpha = Array.isArray(gopt.background) ? gopt.background[3] : 1 gl.clearColor( bgColor.r / 255, bgColor.g / 255, bgColor.b / 255, - bgAlpha, + bgAlpha ?? 1, ) } @@ -567,7 +570,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { } static fromURL(url: string, opt: LoadSpriteOpt = {}): Promise { - return ass.loadImg(url).then((img) => SpriteData.fromImage(img, opt)) + return loadImg(url).then((img) => SpriteData.fromImage(img, opt)) } } @@ -590,7 +593,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { if (isDataURL(url)) { return SoundData.fromArrayBuffer(dataURLToArrayBuffer(url)) } else { - return ass.fetchArrayBuffer(url).then((buf) => SoundData.fromArrayBuffer(buf)) + return fetchArrayBuffer(url).then((buf) => SoundData.fromArrayBuffer(buf)) } } @@ -624,6 +627,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { })() const assets = { + urlPrefix: "", // asset holders sprites: new AssetBucket(), fonts: new AssetBucket(), @@ -636,6 +640,11 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { loaded: false, } + function fixURL(url: D): D { + if (typeof url !== "string" || isDataURL(url)) return url + return assets.urlPrefix + url as D + } + const game = { // general events @@ -717,22 +726,27 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { // global load path prefix function loadRoot(path?: string): string { if (path !== undefined) { - ass.setURLPrefix(path) + assets.urlPrefix = path } - return ass.getURLPrefix() + return assets.urlPrefix } function loadJSON(name, url) { - return assets.custom.add(name, ass.fetchJSON(url)) + return assets.custom.add(name, fetchJSON(url)) } class FontData { fontface: FontFace filter: TexFilter = DEF_FONT_FILTER outline: Outline | null = null + size: number = DEF_TEXT_CACHE_SIZE constructor(face: FontFace, opt: LoadFontOpt = {}) { this.fontface = face this.filter = opt.filter ?? DEF_FONT_FILTER + this.size = opt.size ?? DEF_TEXT_CACHE_SIZE + if (this.size > MAX_TEXT_CACHE_SIZE) { + throw new Error(`Max font size: ${MAX_TEXT_CACHE_SIZE}`) + } if (opt.outline) { this.outline = { width: 1, @@ -770,7 +784,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { gh: number, opt: LoadBitmapFontOpt = {}, ): Asset { - return assets.bitmapFonts.add(name, ass.loadImg(src) + return assets.bitmapFonts.add(name, loadImg(src) .then((img) => { return makeFont( Texture.fromImage(ggl, img, opt), @@ -805,9 +819,10 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { src: LoadSpriteSrc, data: SpriteAtlasData | string, ): Asset> { + src = fixURL(src) if (typeof data === "string") { return load(new Promise((res, rej) => { - ass.fetchJSON(data).then((json) => { + fetchJSON(data).then((json) => { loadSpriteAtlas(src, json).then(res).catch(rej) }) })) @@ -875,12 +890,13 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { anims: {}, }, ): Asset { + src = fixURL(src) if (Array.isArray(src)) { if (src.some((s) => typeof s === "string")) { return assets.sprites.add( name, Promise.all(src.map((s) => { - return typeof s === "string" ? ass.loadImg(s) : Promise.resolve(s) + return typeof s === "string" ? loadImg(s) : Promise.resolve(s) })).then((images) => createSpriteSheet(images, opt)), ) } else { @@ -897,11 +913,13 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { function loadPedit(name: string | null, src: string | PeditFile): Asset { + src = fixURL(src) + // eslint-disable-next-line return assets.sprites.add(name, new Promise(async (resolve) => { - const data = typeof src === "string" ? await ass.fetchJSON(src) : src - const images = await Promise.all(data.frames.map(ass.loadImg)) + const data = typeof src === "string" ? await fetchJSON(src) : src + const images = await Promise.all(data.frames.map(loadImg)) const canvas = document.createElement("canvas") canvas.width = data.width canvas.height = data.height * data.frames.length @@ -926,15 +944,21 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { function loadAseprite( name: string | null, imgSrc: LoadSpriteSrc, - jsonSrc: string, + jsonSrc: string | AsepriteData, ): Asset { + + imgSrc = fixURL(imgSrc) + jsonSrc = fixURL(jsonSrc) + if (typeof imgSrc === "string" && !jsonSrc) { - jsonSrc = imgSrc.replace(new RegExp(`${getExt(imgSrc)}$`), "json") + jsonSrc = getFileName(imgSrc) + ".json" } + const resolveJSON = typeof jsonSrc === "string" - ? ass.fetchJSON(jsonSrc) + ? fetchJSON(jsonSrc) : Promise.resolve(jsonSrc) - return assets.sprites.add(name, resolveJSON.then((data) => { + + return assets.sprites.add(name, resolveJSON.then((data: AsepriteData) => { const size = data.meta.size const frames = data.frames.map((f: any) => { return new Quad( @@ -963,6 +987,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { anims: anims, }) })) + } function loadShader( @@ -978,9 +1003,11 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { vert?: string, frag?: string, ): Asset { + vert = fixURL(vert) + frag = fixURL(frag) const resolveUrl = (url?: string) => url - ? ass.fetchText(url) + ? fetchText(url) : Promise.resolve(null) const load = Promise.all([resolveUrl(vert), resolveUrl(frag)]) .then(([vcode, fcode]: [string | null, string | null]) => { @@ -995,6 +1022,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { name: string | null, src: string | ArrayBuffer, ): Asset { + src = fixURL(src) return assets.sounds.add( name, typeof src === "string" @@ -6183,7 +6211,7 @@ export default (gopt: KaboomOpt = {}): KaboomCtx => { }) app.onShow(() => { - if (!gopt.backgroundAudio) { + if (!gopt.backgroundAudio && !debug.paused) { audio.ctx.resume() } }) diff --git a/src/types.ts b/src/types.ts index 4f829ddcd..2542dea5e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1156,7 +1156,7 @@ export interface KaboomCtx { loadAseprite( name: string | null, imgSrc: LoadSpriteSrc, - jsonSrc: string + jsonSrc: string | AsepriteData, ): Asset, loadPedit(name: string | null, src: string): Asset, /** @@ -1757,7 +1757,7 @@ export interface KaboomCtx { vec2(xy: number): Vec2, vec2(): Vec2, /** - * RGB color (0 - 255). + * Create a color from RGB values (0 - 255). * * @example * ```js @@ -1766,6 +1766,16 @@ export interface KaboomCtx { * ``` */ rgb(r: number, g: number, b: number): Color, + /** + * Create a color from hex string. + * + * @since v3000.2 + * + * @example + * ```js + * sky.color = rgb("#ef6360") + */ + rgb(hex: string): Color, /** * Same as rgb(255, 255, 255). */ @@ -2511,7 +2521,7 @@ export interface KaboomOpt = any> { /** * Background color. E.g. [ 0, 0, 255 ] for solid blue background, or [ 0, 0, 0, 0 ] for transparent background. */ - background?: number[], + background?: number[] | string, /** * Default texture filter. */ @@ -2971,6 +2981,26 @@ export declare class Asset { export type LoadSpriteSrc = string | ImageSource +export type AsepriteData = { + frames: Array<{ + frame: { + x: number, + y: number, + w: number, + h: number, + }, + }>, + meta: { + size: { w: number, h: number }, + frameTags: Array<{ + name: string, + from: number, + to: number, + direction: "forward" | "reverse" | "pingpong", + }>, + }, +} + export declare class SpriteData { tex: Texture frames: Quad[] @@ -3000,6 +3030,12 @@ export declare class SoundData { export interface LoadFontOpt { filter?: TexFilter, outline?: number | Outline, + /** + * The size to load the font in (default 64). + * + * @since v3000.2 + */ + size?: number, } export interface LoadBitmapFontOpt { diff --git a/src/utils.ts b/src/utils.ts index b8c8366be..7694194dc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -176,7 +176,8 @@ export function downloadBlob(filename: string, blob: Blob) { } export const isDataURL = (str: string) => str.match(/^data:\w+\/\w+;base64,.+/) -export const getExt = (p: string) => p.split(".").pop() +export const getFileExt = (p: string) => p.split(".").pop() +export const getFileName = (p: string) => p.split(".").slice(0, -1).join(".") type Func = (...args: any[]) => any