UNPKG

littlejsengine

Version:

LittleJS - Tiny and Fast HTML5 Game Engine

175 lines (151 loc) 5.78 kB
/** * LittleJS Medal System * - Tracks and displays medals * - Saves medals to local storage * - Newgrounds integration * @namespace Medals */ 'use strict'; /** List of all medals * @type {Object} * @memberof Medals */ const medals = {}; // Engine internal variables not exposed to documentation let medalsDisplayQueue = [], medalsSaveName, medalsDisplayTimeLast; /////////////////////////////////////////////////////////////////////////////// /** Initialize medals with a save name used for storage * - Call this after creating all medals * - Checks if medals are unlocked * @param {String} saveName * @memberof Medals */ function medalsInit(saveName) { // check if medals are unlocked medalsSaveName = saveName; if (!debugMedals) medalsForEach(medal=> medal.unlocked = !!localStorage[medal.storageKey()]); // engine automatically renders medals engineAddPlugin(undefined, medalsRender); function medalsRender() { if (!medalsDisplayQueue.length) return; // update first medal in queue const medal = medalsDisplayQueue[0]; const time = timeReal - medalsDisplayTimeLast; if (!medalsDisplayTimeLast) medalsDisplayTimeLast = timeReal; else if (time > medalDisplayTime) { medalsDisplayTimeLast = 0; medalsDisplayQueue.shift(); } else { // slide on/off medals const slideOffTime = medalDisplayTime - medalDisplaySlideTime; const hidePercent = time < medalDisplaySlideTime ? 1 - time / medalDisplaySlideTime : time > slideOffTime ? (time - slideOffTime) / medalDisplaySlideTime : 0; medal.render(hidePercent); } } } /** Calls a function for each medal * @param {Function} callback * @memberof Medals */ function medalsForEach(callback) { Object.values(medals).forEach(medal=>callback(medal)); } /////////////////////////////////////////////////////////////////////////////// /** * Medal - Tracks an unlockable medal * @example * // create a medal * const medal_example = new Medal(0, 'Example Medal', 'More info about the medal goes here.', '🎖️'); * * // initialize medals * medalsInit('Example Game'); * * // unlock the medal * medal_example.unlock(); */ class Medal { /** Create a medal object and adds it to the list of medals * @param {Number} id - The unique identifier of the medal * @param {String} name - Name of the medal * @param {String} [description] - Description of the medal * @param {String} [icon] - Icon for the medal * @param {String} [src] - Image location for the medal */ constructor(id, name, description='', icon='🏆', src) { ASSERT(id >= 0 && !medals[id]); /** @property {Number} - The unique identifier of the medal */ this.id = id; /** @property {String} - Name of the medal */ this.name = name; /** @property {String} - Description of the medal */ this.description = description; /** @property {String} - Icon for the medal */ this.icon = icon; /** @property {boolean} - Is the medal unlocked? */ this.unlocked = false; // load the source image if provided if (src) (this.image = new Image).src = src; // add this to list of medals medals[id] = this; } /** Unlocks a medal if not already unlocked */ unlock() { if (medalsPreventUnlock || this.unlocked) return; // save the medal ASSERT(medalsSaveName, 'save name must be set'); localStorage[this.storageKey()] = this.unlocked = true; medalsDisplayQueue.push(this); } /** Render a medal * @param {Number} [hidePercent] - How much to slide the medal off screen */ render(hidePercent=0) { const context = overlayContext; const width = min(medalDisplaySize.x, mainCanvas.width); const x = overlayCanvas.width - width; const y = -medalDisplaySize.y*hidePercent; // draw containing rect and clip to that region context.save(); context.beginPath(); context.fillStyle = new Color(.9,.9,.9).toString(); context.strokeStyle = new Color(0,0,0).toString(); context.lineWidth = 3; context.rect(x, y, width, medalDisplaySize.y); context.fill(); context.stroke(); context.clip(); // draw the icon and text this.renderIcon(vec2(x+15+medalDisplayIconSize/2, y+medalDisplaySize.y/2)); const pos = vec2(x+medalDisplayIconSize+30, y+28); drawTextScreen(this.name, pos, 38, new Color(0,0,0), 0, undefined, 'left'); pos.y += 32; drawTextScreen(this.description, pos, 24, new Color(0,0,0), 0, undefined, 'left'); context.restore(); } /** Render the icon for a medal * @param {Vector2} pos - Screen space position * @param {Number} [size=medalDisplayIconSize] - Screen space size */ renderIcon(pos, size=medalDisplayIconSize) { // draw the image or icon if (this.image) overlayContext.drawImage(this.image, pos.x-size/2, pos.y-size/2, size, size); else drawTextScreen(this.icon, pos, size*.7, new Color(0,0,0)); } // Get local storage key used by the medal storageKey() { return medalsSaveName + '_' + this.id; } }