UNPKG

littlejsengine

Version:

LittleJS - Tiny and Fast HTML5 Game Engine

203 lines (176 loc) 7.38 kB
/** * LittleJS Utility Classes and Functions * - General purpose utilities * - Timer - tracks time automatically * @namespace Utilities */ 'use strict'; /** * Timer object tracks how long has passed since it was set * @memberof Engine * @example * let a = new Timer; // creates a timer that is not set * a.set(3); // sets the timer to 3 seconds * * let b = new Timer(1); // creates a timer with 1 second left * b.unset(); // unset the timer */ class Timer { /** Create a timer object set time passed in * @param {number} [timeLeft] - How much time left before the timer * @param {boolean} [useRealTime] - Should the timer keep running even when the game is paused? (useful for UI) */ constructor(timeLeft, useRealTime=false) { ASSERT(timeLeft === undefined || isNumber(timeLeft), 'Constructed Timer is invalid.', timeLeft); this.useRealTime = useRealTime; const globalTime = this.getGlobalTime(); this.time = timeLeft === undefined ? undefined : globalTime + timeLeft; this.setTime = timeLeft; } /** Set the timer with seconds passed in * @param {number} [timeLeft] - How much time left before the timer is elapsed in seconds */ set(timeLeft=0) { ASSERT(isNumber(timeLeft), 'Timer is invalid.', timeLeft); const globalTime = this.getGlobalTime(); this.time = globalTime + timeLeft; this.setTime = timeLeft; } /** Set if the timer should keep running even when the game is paused * @param {boolean} [useRealTime] */ setUseRealTime(useRealTime=true) { ASSERT(!this.isSet(), 'Cannot change global time setting while timer is set.'); this.useRealTime = useRealTime; } /** Unset the timer */ unset() { this.time = undefined; } /** Returns true if set * @return {boolean} */ isSet() { return this.time !== undefined; } /** Returns true if set and has not elapsed * @return {boolean} */ active() { return this.getGlobalTime() < this.time; } /** Returns true if set and elapsed * @return {boolean} */ elapsed() { return this.getGlobalTime() >= this.time; } /** Get how long since elapsed, returns 0 if not set (returns negative if currently active) * @return {number} */ get() { return this.isSet()? this.getGlobalTime() - this.time : 0; } /** Get percentage elapsed based on time it was set to, returns 0 if not set * @return {number} */ getPercent() { return this.isSet()? 1-percent(this.time - this.getGlobalTime(), 0, this.setTime) : 0; } /** Get the time this timer was set to, returns 0 if not set * @return {number} */ getSetTime() { return this.isSet() ? this.setTime : 0; } /** Get the current global time this timer is based on * @return {number} */ getGlobalTime() { return this.useRealTime ? timeReal : time; } /** Returns this timer expressed as a string * @return {string} */ toString() { return this.isSet() ? abs(this.get()) + ' seconds ' + (this.get()<0 ? 'before' : 'after' ) : 'unset'; } /** Get how long since elapsed, returns 0 if not set (returns negative if currently active) * @return {number} */ valueOf() { return this.get(); } } /////////////////////////////////////////////////////////////////////////////// /** Formats seconds to mm:ss style for display purposes * @param {number} t - time in seconds * @return {string} * @memberof Utilities */ function formatTime(t) { const sign = t < 0 ? '-' : ''; t = abs(t)|0; return sign + (t/60|0) + ':' + (t%60<10?'0':'') + t%60; } /** Fetches a JSON file from a URL and returns the parsed JSON object. Must be used with await! * @param {string} url - URL of JSON file * @return {Promise<object>} * @memberof Utilities */ async function fetchJSON(url) { const response = await fetch(url); if (!response.ok) throw new Error(`Failed to fetch JSON from ${url}: ${response.status} ${response.statusText}`); return response.json(); } /////////////////////////////////////////////////////////////////////////////// /** Save a text file to disk * @param {string} text * @param {string} [filename] * @param {string} [type] * @memberof Utilities */ function saveText(text, filename='text', type='text/plain') { saveDataURL(URL.createObjectURL(new Blob([text], {'type':type})), filename); } /** Save a canvas to disk * @param {HTMLCanvasElement|OffscreenCanvas} canvas * @param {string} [filename] * @param {string} [type] * @memberof Utilities */ function saveCanvas(canvas, filename='screenshot', type='image/png') { if (canvas instanceof OffscreenCanvas) { // copy to temporary canvas and save const saveCanvas = document.createElement('canvas'); saveCanvas.width = canvas.width; saveCanvas.height = canvas.height; saveCanvas.getContext('2d').drawImage(canvas, 0, 0); saveDataURL(saveCanvas.toDataURL(type), filename); } else saveDataURL(canvas.toDataURL(type), filename); } /** Save a data url to disk * @param {string} url * @param {string} [filename] * @param {number} [revokeTime] - how long before revoking the url * @memberof Utilities */ function saveDataURL(url, filename='download', revokeTime) { ASSERT(isString(url), 'saveDataURL requires url string'); ASSERT(isString(filename), 'saveDataURL requires filename string'); // create link for saving screenshots const link = document.createElement('a'); link.download = filename; link.href = url; link.click(); if (revokeTime !== undefined) setTimeout(()=> URL.revokeObjectURL(url), revokeTime); } /** Share content using the native share dialog if available * @param {string} title - title of the share * @param {string} url - url to share * @param {Function} [callback] - Called when share is complete * @memberof Utilities */ function shareURL(title, url, callback) { ASSERT(isString(title), 'shareURL requires title string'); ASSERT(isString(url), 'shareURL requires url string'); navigator.share?.({title, url}).then(()=>callback?.()); } /////////////////////////////////////////////////////////////////////////////// /** Read save data from local storage * @param {string} saveName - unique name for the game/save * @param {Object} [defaultSaveData] - default values for save * @return {Object} * @memberof Utilities */ function readSaveData(saveName, defaultSaveData) { ASSERT(isString(saveName), 'loadData requires saveName string'); // replace undefined values with defaults const data = localStorage[saveName]; const loadedData = data ? JSON.parse(data) : {}; return { ...defaultSaveData, ...loadedData }; } /** Write save data to local storage * @param {string} saveName - unique name for the game/save * @param {Object} saveData - object containing data to be saved * @memberof Utilities */ function writeSaveData(saveName, saveData) { ASSERT(isString(saveName), 'saveData requires saveName string'); localStorage[saveName] = JSON.stringify(saveData); }