mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-26 12:17:23 +00:00
Fix race condition
This commit is contained in:
545
birb.js
545
birb.js
@@ -338,14 +338,77 @@ const bluebirdColors = {
|
|||||||
[HEART_SHINE]: "#ff6b6b",
|
[HEART_SHINE]: "#ff6b6b",
|
||||||
};
|
};
|
||||||
|
|
||||||
const SPRITE_WIDTH = 32;
|
const Directions = {
|
||||||
const SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAgCAYAAACy9KU0AAAAAXNSR0IArs4c6QAABBdJREFUeJztnL9LHEEUx79zWkgk3RFwTRvsDKS5KpWpbDSVVQgkjSCYKkfIHyDBIhAhIAiBkOoqsUmVVDbaBFKkkLTxhHBgEQM28aU4Z53d25lddXfm1vt+4Ni5X/vm9t77znfm9hYghBBCCCGEEOIJFboDhOQhImJ7TinFHK4x/PJILiEFQMd+PD0NANg+PEy0ffSBEBIIOWcximQxigbaLnEqK/5iFMne7KzIyspAu+r4pFrGQ3eAFCOr0HyN/I+np/Gq2UTr4cOBtnYhPtjf3cWrZjNuk/rTCN0Bkk96GqK3vkf/UAKwfXiIN71e4rE3vZ5X8SNkZDGnIebWhwCZU7C92dn45msKltUHn7FJtdABFUQyCNEP7UB8oad5pgsx3YePaWC6D1x8JiOHiPQXP42tz9ihHYDZh1ACHFr8SflwBCmIiMj+/fvx/db3715HYHMdKJQDMAuf7oMQj+iRV7sfOgBCrk+hUcyW8KM2CtIBEFIuuUWkiy5db7oWfRQiBZDUCeZrcZwnIpri83wreUyVUhAR8zWVHFyXAIqI8Eslw0BadGwDNkliFSCX+ADA8y2JD3JVYjAMAkhIUcwUXFrvxu1OOwrRncqYGlNy9E9KqTenA2pMRlC3pvDhRf8APnvXxeflBuY3zwBcHOQqxGAYBJCQgohSKiE6N5WpMSXzm2f4vNwoRYSsO8gTAAA4OT6K21rlXVbzMgIhItKYTI4caQHU8TvtKBGXQkQ8Ii7hMeuCeTmI1QEppZTr515TfICkG8riKi7l7G93QAC1+KRjp91Q1v6YAKRknOJjwNSzUOjf8O/vPMLK7y9xGwCeHn/KfG36C/n56w+Ai2laUYZBAAmxsbTezU3mKtd+bPGNmLXIc2cnzwsWpztzA89NLHzFg9UD3Lt7O/O9Wni+bczoffUDXnIapuMPCOBBtgC6+kEbTMoiT4A67QhL61102lFV+WZ1X+ciVIs8v/L1gE535jCxMAOsHmQ+nxYe4Ho+VAuPptOOriSAhJTBx5kn1kHQg/gA6Oe4Lf/rQq4DAvrTmrQLOvlxAgBovt63vfciyBWFx+XAgAsXlkXZAkhImt5aS1bGtwEkp1s+xAeAuAbgurigQmdCp0VAi08Wzdf7pZ0lHVoACcmjt9aKE80QI1/5ZhUh85fhYc7/S/0Vo7fWcr62TPEx44cSQEJqgOhZQFqItAgNcx3krgHpX6POP4hVhKosfB0bgFMEKT5kBFHfNmb6SW8RohuBcRkI2X77UgDEN32/qstEpC5D4YzPS1WQEUbQd0TyYPWg0pr0SlbxZ92vWoBCxSekLui60LcbURPpYrdtqxSgkPEJqQuSQeg+uSh8HpBeh3FtqyR0fELqQN3WPi91VnLuzir88KHjE0LK5z8tFuzphqiPOAAAAABJRU5ErkJggg==";
|
LEFT: -1,
|
||||||
const SPRITE_SHEET = dataUriTo2DArray(SPRITE_SHEET_URI);
|
RIGHT: 1,
|
||||||
const DECORATIONS_SPRITE_WIDTH = 48;
|
};
|
||||||
const DECORATIONS_SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAPNJREFUaIHtmTESgzAMBHWZDC+gp0vP/x9Bn44+L6BRmrhJA4csM05uGzfY1s1JxggzIYQQQgghxEnATnB3zwikAICKiXq4BE/uwaxvn/UPb3BnNwFg27Ky0w6vzRp8S4mkIbQD3wzzFJofdTMkYJgn89czFADGKSSiSgphfFBjTaoIKC4cHWvSxIFMmjiQSYoDLUlxoCVywOwHHWjpROop1IL/vsxty2oYO77M1QggSvcpJAFXE66BPfa+2C4v4j2yi7z7FJKAq6FrwN3TO3MMlAAAKO3F2sVZTiu2N9p9CnUv4FR7PbMG2BQ69SJL/kVA8QauAnHUj36BVwAAAABJRU5ErkJggg==";
|
|
||||||
const DECORATIONS_SPRITE_SHEET = dataUriTo2DArray(DECORATIONS_SPRITE_SHEET_URI, false);
|
|
||||||
|
|
||||||
const layers = {
|
const SPRITE_WIDTH = 32;
|
||||||
|
const DECORATIONS_SPRITE_WIDTH = 48;
|
||||||
|
const SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAgCAYAAACy9KU0AAAAAXNSR0IArs4c6QAABBdJREFUeJztnL9LHEEUx79zWkgk3RFwTRvsDKS5KpWpbDSVVQgkjSCYKkfIHyDBIhAhIAiBkOoqsUmVVDbaBFKkkLTxhHBgEQM28aU4Z53d25lddXfm1vt+4Ni5X/vm9t77znfm9hYghBBCCCGEEOIJFboDhOQhImJ7TinFHK4x/PJILiEFQMd+PD0NANg+PEy0ffSBEBIIOWcximQxigbaLnEqK/5iFMne7KzIyspAu+r4pFrGQ3eAFCOr0HyN/I+np/Gq2UTr4cOBtnYhPtjf3cWrZjNuk/rTCN0Bkk96GqK3vkf/UAKwfXiIN71e4rE3vZ5X8SNkZDGnIebWhwCZU7C92dn45msKltUHn7FJtdABFUQyCNEP7UB8oad5pgsx3YePaWC6D1x8JiOHiPQXP42tz9ihHYDZh1ACHFr8SflwBCmIiMj+/fvx/db3715HYHMdKJQDMAuf7oMQj+iRV7sfOgBCrk+hUcyW8KM2CtIBEFIuuUWkiy5db7oWfRQiBZDUCeZrcZwnIpri83wreUyVUhAR8zWVHFyXAIqI8Eslw0BadGwDNkliFSCX+ADA8y2JD3JVYjAMAkhIUcwUXFrvxu1OOwrRncqYGlNy9E9KqTenA2pMRlC3pvDhRf8APnvXxeflBuY3zwBcHOQqxGAYBJCQgohSKiE6N5WpMSXzm2f4vNwoRYSsO8gTAAA4OT6K21rlXVbzMgIhItKYTI4caQHU8TvtKBGXQkQ8Ii7hMeuCeTmI1QEppZTr515TfICkG8riKi7l7G93QAC1+KRjp91Q1v6YAKRknOJjwNSzUOjf8O/vPMLK7y9xGwCeHn/KfG36C/n56w+Ai2laUYZBAAmxsbTezU3mKtd+bPGNmLXIc2cnzwsWpztzA89NLHzFg9UD3Lt7O/O9Wni+bczoffUDXnIapuMPCOBBtgC6+kEbTMoiT4A67QhL61102lFV+WZ1X+ciVIs8v/L1gE535jCxMAOsHmQ+nxYe4Ho+VAuPptOOriSAhJTBx5kn1kHQg/gA6Oe4Lf/rQq4DAvrTmrQLOvlxAgBovt63vfciyBWFx+XAgAsXlkXZAkhImt5aS1bGtwEkp1s+xAeAuAbgurigQmdCp0VAi08Wzdf7pZ0lHVoACcmjt9aKE80QI1/5ZhUh85fhYc7/S/0Vo7fWcr62TPEx44cSQEJqgOhZQFqItAgNcx3krgHpX6POP4hVhKosfB0bgFMEKT5kBFHfNmb6SW8RohuBcRkI2X77UgDEN32/qstEpC5D4YzPS1WQEUbQd0TyYPWg0pr0SlbxZ92vWoBCxSekLui60LcbURPpYrdtqxSgkPEJqQuSQeg+uSh8HpBeh3FtqyR0fELqQN3WPi91VnLuzir88KHjE0LK5z8tFuzphqiPOAAAAABJRU5ErkJggg==";
|
||||||
|
const DECORATIONS_SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAPNJREFUaIHtmTESgzAMBHWZDC+gp0vP/x9Bn44+L6BRmrhJA4csM05uGzfY1s1JxggzIYQQQgghxEnATnB3zwikAICKiXq4BE/uwaxvn/UPb3BnNwFg27Ky0w6vzRp8S4mkIbQD3wzzFJofdTMkYJgn89czFADGKSSiSgphfFBjTaoIKC4cHWvSxIFMmjiQSYoDLUlxoCVywOwHHWjpROop1IL/vsxty2oYO77M1QggSvcpJAFXE66BPfa+2C4v4j2yi7z7FJKAq6FrwN3TO3MMlAAAKO3F2sVZTiu2N9p9CnUv4FR7PbMG2BQ69SJL/kVA8QauAnHUj36BVwAAAABJRU5ErkJggg==";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the spritesheet and return the pixelmap template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {Promise<string[][]>}
|
||||||
|
*/
|
||||||
|
function loadSpritesheetPixels(dataUri, templateColors = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Failed to get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITESHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
console.error(`Unknown color: ${hex}`);
|
||||||
|
row.push(TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITESHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
resolve(hexArray);
|
||||||
|
};
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all([loadSpritesheetPixels(SPRITE_SHEET_URI), loadSpritesheetPixels(DECORATIONS_SPRITE_SHEET_URI, false)]).then(([birbPixels, decorationPixels]) => {
|
||||||
|
const SPRITE_SHEET = birbPixels;
|
||||||
|
const DECORATIONS_SPRITE_SHEET = decorationPixels;
|
||||||
|
|
||||||
|
const layers = {
|
||||||
base: new Layer(getLayer(SPRITE_SHEET, 0)),
|
base: new Layer(getLayer(SPRITE_SHEET, 0)),
|
||||||
down: new Layer(getLayer(SPRITE_SHEET, 1)),
|
down: new Layer(getLayer(SPRITE_SHEET, 1)),
|
||||||
heartOne: new Layer(getLayer(SPRITE_SHEET, 2)),
|
heartOne: new Layer(getLayer(SPRITE_SHEET, 2)),
|
||||||
@@ -355,13 +418,13 @@ const layers = {
|
|||||||
wingsUp: new Layer(getLayer(SPRITE_SHEET, 6)),
|
wingsUp: new Layer(getLayer(SPRITE_SHEET, 6)),
|
||||||
wingsDown: new Layer(getLayer(SPRITE_SHEET, 7)),
|
wingsDown: new Layer(getLayer(SPRITE_SHEET, 7)),
|
||||||
happyEye: new Layer(getLayer(SPRITE_SHEET, 8)),
|
happyEye: new Layer(getLayer(SPRITE_SHEET, 8)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const decorationLayers = {
|
const decorationLayers = {
|
||||||
mac: new Layer(getLayer(DECORATIONS_SPRITE_SHEET, 0, DECORATIONS_SPRITE_WIDTH)),
|
mac: new Layer(getLayer(DECORATIONS_SPRITE_SHEET, 0, DECORATIONS_SPRITE_WIDTH)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const birbFrames = {
|
const birbFrames = {
|
||||||
base: new Frame([layers.base]),
|
base: new Frame([layers.base]),
|
||||||
headDown: new Frame([layers.down]),
|
headDown: new Frame([layers.down]),
|
||||||
wingsDown: new Frame([layers.base, layers.wingsDown]),
|
wingsDown: new Frame([layers.base, layers.wingsDown]),
|
||||||
@@ -370,13 +433,13 @@ const birbFrames = {
|
|||||||
heartTwo: new Frame([layers.base, layers.happyEye, layers.heartTwo]),
|
heartTwo: new Frame([layers.base, layers.happyEye, layers.heartTwo]),
|
||||||
heartThree: new Frame([layers.base, layers.happyEye, layers.heartThree]),
|
heartThree: new Frame([layers.base, layers.happyEye, layers.heartThree]),
|
||||||
heartFour: new Frame([layers.base, layers.happyEye, layers.heartFour]),
|
heartFour: new Frame([layers.base, layers.happyEye, layers.heartFour]),
|
||||||
};
|
};
|
||||||
|
|
||||||
const decorationFrames = {
|
const decorationFrames = {
|
||||||
mac: new Frame([decorationLayers.mac]),
|
mac: new Frame([decorationLayers.mac]),
|
||||||
};
|
};
|
||||||
|
|
||||||
const Animations = {
|
const Animations = {
|
||||||
STILL: new Anim([birbFrames.base], [1000]),
|
STILL: new Anim([birbFrames.base], [1000]),
|
||||||
BOB: new Anim([
|
BOB: new Anim([
|
||||||
birbFrames.base,
|
birbFrames.base,
|
||||||
@@ -415,57 +478,52 @@ const Animations = {
|
|||||||
250,
|
250,
|
||||||
250,
|
250,
|
||||||
], false),
|
], false),
|
||||||
};
|
};
|
||||||
|
|
||||||
const DECORATION_ANIMATIONS = {
|
const DECORATION_ANIMATIONS = {
|
||||||
mac: new Anim([
|
mac: new Anim([
|
||||||
decorationFrames.mac,
|
decorationFrames.mac,
|
||||||
], [
|
], [
|
||||||
1000,
|
1000,
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
|
|
||||||
/** @type {CanvasRenderingContext2D} */
|
/** @type {CanvasRenderingContext2D} */
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
const Directions = {
|
const States = {
|
||||||
LEFT: -1,
|
|
||||||
RIGHT: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const States = {
|
|
||||||
IDLE: "idle",
|
IDLE: "idle",
|
||||||
HOP: "hop",
|
HOP: "hop",
|
||||||
FLYING: "flying",
|
FLYING: "flying",
|
||||||
};
|
};
|
||||||
|
|
||||||
let stateStart = Date.now();
|
let stateStart = Date.now();
|
||||||
let currentState = States.IDLE;
|
let currentState = States.IDLE;
|
||||||
let animStart = Date.now();
|
let animStart = Date.now();
|
||||||
let currentAnimation = Animations.BOB;
|
let currentAnimation = Animations.BOB;
|
||||||
let direction = Directions.RIGHT;
|
let direction = Directions.RIGHT;
|
||||||
let ticks = 0;
|
let ticks = 0;
|
||||||
// Bird's current position
|
// Bird's current position
|
||||||
let birdY = 0;
|
let birdY = 0;
|
||||||
let birdX = 40;
|
let birdX = 40;
|
||||||
// Bird's starting position (when flying)
|
// Bird's starting position (when flying)
|
||||||
let startX = 0;
|
let startX = 0;
|
||||||
let startY = 0;
|
let startY = 0;
|
||||||
// Bird's target position (when flying)
|
// Bird's target position (when flying)
|
||||||
let targetX = 0;
|
let targetX = 0;
|
||||||
let targetY = 0;
|
let targetY = 0;
|
||||||
/** @type {HTMLElement|null} */
|
/** @type {HTMLElement|null} */
|
||||||
let focusedElement = null;
|
let focusedElement = null;
|
||||||
// Time of the user's last action on the page
|
// Time of the user's last action on the page
|
||||||
let timeOfLastAction = Date.now();
|
let timeOfLastAction = Date.now();
|
||||||
// Stack of timestamps for each mouseover, max length of 10
|
// Stack of timestamps for each mouseover, max length of 10
|
||||||
let petStack = [];
|
let petStack = [];
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
if (window !== window.top) {
|
if (window !== window.top) {
|
||||||
// Skip installation if within an iframe
|
// Skip installation if within an iframe
|
||||||
return;
|
return;
|
||||||
@@ -516,9 +574,9 @@ function init() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setInterval(update, 1000 / 60);
|
setInterval(update, 1000 / 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
ticks++;
|
ticks++;
|
||||||
if (currentState === States.IDLE) {
|
if (currentState === States.IDLE) {
|
||||||
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isStartMenuOpen()) {
|
if (Math.random() < 1 / (60 * 3) && currentAnimation !== Animations.HEART && !isStartMenuOpen()) {
|
||||||
@@ -529,9 +587,9 @@ function update() {
|
|||||||
setState(States.IDLE);
|
setState(States.IDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
requestAnimationFrame(draw);
|
requestAnimationFrame(draw);
|
||||||
|
|
||||||
// Update the bird's position
|
// Update the bird's position
|
||||||
@@ -568,19 +626,19 @@ function draw() {
|
|||||||
// Update HTML element position
|
// Update HTML element position
|
||||||
setX(birdX);
|
setX(birdX);
|
||||||
setY(birdY);
|
setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
draw();
|
draw();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HTML element with the specified parameters
|
* Create an HTML element with the specified parameters
|
||||||
* @param {string} className
|
* @param {string} className
|
||||||
* @param {string} [textContent]
|
* @param {string} [textContent]
|
||||||
* @param {string} [id]
|
* @param {string} [id]
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
function makeElement(className, textContent, id) {
|
function makeElement(className, textContent, id) {
|
||||||
const element = document.createElement("div");
|
const element = document.createElement("div");
|
||||||
element.classList.add(className);
|
element.classList.add(className);
|
||||||
if (textContent) {
|
if (textContent) {
|
||||||
@@ -590,9 +648,9 @@ function makeElement(className, textContent, id) {
|
|||||||
element.id = id;
|
element.id = id;
|
||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertDecoration() {
|
function insertDecoration() {
|
||||||
// Create a canvas element for the decoration
|
// Create a canvas element for the decoration
|
||||||
const decorationCanvas = document.createElement("canvas");
|
const decorationCanvas = document.createElement("canvas");
|
||||||
decorationCanvas.classList.add("birb-decoration");
|
decorationCanvas.classList.add("birb-decoration");
|
||||||
@@ -607,14 +665,14 @@ function insertDecoration() {
|
|||||||
// Add the decoration to the page
|
// Add the decoration to the page
|
||||||
document.body.appendChild(decorationCanvas);
|
document.body.appendChild(decorationCanvas);
|
||||||
makeDraggable(decorationCanvas, false);
|
makeDraggable(decorationCanvas, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertDecoration();
|
// insertDecoration();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the start menu to the page if it doesn't already exist
|
* Add the start menu to the page if it doesn't already exist
|
||||||
*/
|
*/
|
||||||
function insertStartMenu() {
|
function insertStartMenu() {
|
||||||
if (document.querySelector("#" + START_MENU_ID)) {
|
if (document.querySelector("#" + START_MENU_ID)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -663,29 +721,29 @@ function insertStartMenu() {
|
|||||||
}
|
}
|
||||||
startMenu.style.left = `${x}px`;
|
startMenu.style.left = `${x}px`;
|
||||||
startMenu.style.top = `${y}px`;
|
startMenu.style.top = `${y}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the start menu from the page
|
* Remove the start menu from the page
|
||||||
*/
|
*/
|
||||||
function removeStartMenu() {
|
function removeStartMenu() {
|
||||||
const startMenu = document.querySelector("#" + START_MENU_ID);
|
const startMenu = document.querySelector("#" + START_MENU_ID);
|
||||||
if (startMenu) {
|
if (startMenu) {
|
||||||
startMenu.remove();
|
startMenu.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether the start menu element is on the page
|
* @returns {boolean} Whether the start menu element is on the page
|
||||||
*/
|
*/
|
||||||
function isStartMenuOpen() {
|
function isStartMenuOpen() {
|
||||||
return document.querySelector("#" + START_MENU_ID) !== null;
|
return document.querySelector("#" + START_MENU_ID) !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {HTMLElement|null} element
|
* @param {HTMLElement|null} element
|
||||||
*/
|
*/
|
||||||
function makeDraggable(element, parent = true) {
|
function makeDraggable(element, parent = true) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -719,87 +777,30 @@ function makeDraggable(element, parent = true) {
|
|||||||
element.style.top = `${e.clientY - offsetY}px`;
|
element.style.top = `${e.clientY - offsetY}px`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} dataUri
|
|
||||||
* @param {boolean} [templateColors]
|
|
||||||
* @returns {string[][]}
|
|
||||||
*/
|
|
||||||
function dataUriTo2DArray(dataUri, templateColors = true) {
|
|
||||||
const img = new Image();
|
|
||||||
img.src = dataUri;
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = img.width;
|
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const pixels = imageData.data;
|
|
||||||
const hexArray = [];
|
|
||||||
for (let y = 0; y < img.height; y++) {
|
|
||||||
const row = [];
|
|
||||||
for (let x = 0; x < img.width; x++) {
|
|
||||||
const index = (y * img.width + x) * 4;
|
|
||||||
const r = pixels[index];
|
|
||||||
const g = pixels[index + 1];
|
|
||||||
const b = pixels[index + 2];
|
|
||||||
const a = pixels[index + 3];
|
|
||||||
if (a === 0) {
|
|
||||||
row.push(TRANSPARENT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
||||||
if (!templateColors) {
|
|
||||||
row.push(hex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (SPRITESHEET_COLOR_MAP[hex] === undefined) {
|
|
||||||
console.error(`Unknown color: ${hex}`);
|
|
||||||
row.push(TRANSPARENT);
|
|
||||||
}
|
|
||||||
row.push(SPRITESHEET_COLOR_MAP[hex]);
|
|
||||||
}
|
|
||||||
hexArray.push(row);
|
|
||||||
}
|
|
||||||
return hexArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string[][]} array
|
* @param {string[][]} array
|
||||||
* @param {number} sprite
|
* @param {number} sprite
|
||||||
* @param {number} [width]
|
* @param {number} [width]
|
||||||
* @returns {string[][]}
|
* @returns {string[][]}
|
||||||
*/
|
*/
|
||||||
function getLayer(array, sprite, width = SPRITE_WIDTH) {
|
function getLayer(array, sprite, width = SPRITE_WIDTH) {
|
||||||
// From an array of a horizontal sprite sheet, get the layer for a specific sprite
|
// From an array of a horizontal sprite sheet, get the layer for a specific sprite
|
||||||
const layer = [];
|
const layer = [];
|
||||||
for (let y = 0; y < width; y++) {
|
for (let y = 0; y < width; y++) {
|
||||||
layer.push(array[y].slice(sprite * width, (sprite + 1) * width));
|
layer.push(array[y].slice(sprite * width, (sprite + 1) * width));
|
||||||
}
|
}
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} start
|
|
||||||
* @param {number} end
|
|
||||||
* @param {number} amount
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
function linearLerp(start, end, amount) {
|
|
||||||
return start + (end - start) * amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the birds location from the start to the target location on a parabolic path
|
* Update the birds location from the start to the target location on a parabolic path
|
||||||
* @param {number} speed The speed of the bird along the path
|
* @param {number} speed The speed of the bird along the path
|
||||||
* @param {number} [intensity] The intensity of the parabolic path
|
* @param {number} [intensity] The intensity of the parabolic path
|
||||||
* @returns {boolean} Whether the bird has reached the target location
|
* @returns {boolean} Whether the bird has reached the target location
|
||||||
*/
|
*/
|
||||||
function updateParabolicPath(speed, intensity = 2.5) {
|
function updateParabolicPath(speed, intensity = 2.5) {
|
||||||
const dx = targetX - startX;
|
const dx = targetX - startX;
|
||||||
const dy = targetY - startY;
|
const dy = targetY - startY;
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
@@ -819,6 +820,144 @@ function updateParabolicPath(speed, intensity = 2.5) {
|
|||||||
direction = targetX > birdX ? Directions.RIGHT : Directions.LEFT;
|
direction = targetX > birdX ? Directions.RIGHT : Directions.LEFT;
|
||||||
}
|
}
|
||||||
return complete;
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFocusedElementRandomX() {
|
||||||
|
if (focusedElement === null) {
|
||||||
|
return Math.random() * window.innerWidth;
|
||||||
|
}
|
||||||
|
const rect = focusedElement.getBoundingClientRect();
|
||||||
|
return Math.random() * (rect.right - rect.left) + rect.left;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFocusedElementY() {
|
||||||
|
if (focusedElement === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const rect = focusedElement.getBoundingClientRect();
|
||||||
|
return window.innerHeight - rect.top;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusOnGround() {
|
||||||
|
if (focusedElement === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
focusedElement = null;
|
||||||
|
flyTo(Math.random() * window.innerWidth, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusOnElement() {
|
||||||
|
const images = document.querySelectorAll("img");
|
||||||
|
const inWindow = Array.from(images).filter((img) => {
|
||||||
|
const rect = img.getBoundingClientRect();
|
||||||
|
return rect.left >= 0 && rect.top >= 0 + 100 && rect.right <= window.innerWidth && rect.top <= window.innerHeight;
|
||||||
|
});
|
||||||
|
const MIN_SIZE = 100;
|
||||||
|
const largeImages = Array.from(inWindow).filter((img) => img !== focusedElement && img.width >= MIN_SIZE && img.height >= MIN_SIZE);
|
||||||
|
if (largeImages.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const randomImage = largeImages[Math.floor(Math.random() * largeImages.length)];
|
||||||
|
focusedElement = randomImage;
|
||||||
|
flyTo(getFocusedElementRandomX(), getFocusedElementY());
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCanvasWidth() {
|
||||||
|
return canvas.width * CSS_SCALE
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCanvasHeight() {
|
||||||
|
return canvas.height * CSS_SCALE
|
||||||
|
}
|
||||||
|
|
||||||
|
function hop() {
|
||||||
|
if (currentState === States.IDLE) {
|
||||||
|
// Determine bounds for hopping
|
||||||
|
let minX = 0;
|
||||||
|
let maxX = window.innerWidth;
|
||||||
|
let y = 0;
|
||||||
|
if (focusedElement !== null) {
|
||||||
|
// Hop on the element
|
||||||
|
const rect = focusedElement.getBoundingClientRect();
|
||||||
|
minX = rect.left;
|
||||||
|
maxX = rect.right;
|
||||||
|
y = window.innerHeight - rect.top;
|
||||||
|
}
|
||||||
|
setState(States.HOP);
|
||||||
|
setAnimation(Animations.FLYING);
|
||||||
|
if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > minX) || birdX + HOP_DISTANCE > maxX) {
|
||||||
|
targetX = birdX - HOP_DISTANCE;
|
||||||
|
} else {
|
||||||
|
targetX = birdX + HOP_DISTANCE;
|
||||||
|
}
|
||||||
|
targetY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pet() {
|
||||||
|
if (currentState === States.IDLE) {
|
||||||
|
setAnimation(Animations.HEART);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
function flyTo(x, y) {
|
||||||
|
targetX = x;
|
||||||
|
targetY = y;
|
||||||
|
setState(States.FLYING);
|
||||||
|
setAnimation(Animations.FLYING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current animation and reset the animation timer
|
||||||
|
* @param {Anim} animation
|
||||||
|
*/
|
||||||
|
function setAnimation(animation) {
|
||||||
|
currentAnimation = animation;
|
||||||
|
animStart = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current state and reset the state timer
|
||||||
|
* @param {string} state
|
||||||
|
*/
|
||||||
|
function setState(state) {
|
||||||
|
stateStart = Date.now();
|
||||||
|
startX = birdX;
|
||||||
|
startY = birdY;
|
||||||
|
currentState = state;
|
||||||
|
if (state === States.IDLE) {
|
||||||
|
setAnimation(Animations.BOB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} x
|
||||||
|
*/
|
||||||
|
function setX(x) {
|
||||||
|
let mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
|
||||||
|
canvas.style.left = `${x + mod}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} y
|
||||||
|
*/
|
||||||
|
function setY(y) {
|
||||||
|
canvas.style.bottom = `${y}px`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} start
|
||||||
|
* @param {number} end
|
||||||
|
* @param {number} amount
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function linearLerp(start, end, amount) {
|
||||||
|
return start + (end - start) * amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -843,118 +982,6 @@ function parabolicLerp(startX, startY, endX, endY, amount, intensity = 1.2) {
|
|||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFocusedElementRandomX() {
|
|
||||||
if (focusedElement === null) {
|
|
||||||
return Math.random() * window.innerWidth;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return Math.random() * (rect.right - rect.left) + rect.left;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFocusedElementY() {
|
|
||||||
if (focusedElement === null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
return window.innerHeight - rect.top;
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnGround() {
|
|
||||||
if (focusedElement === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
focusedElement = null;
|
|
||||||
flyTo(Math.random() * window.innerWidth, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOnElement() {
|
|
||||||
const images = document.querySelectorAll("img");
|
|
||||||
const inWindow = Array.from(images).filter((img) => {
|
|
||||||
const rect = img.getBoundingClientRect();
|
|
||||||
return rect.left >= 0 && rect.top >= 0 + 100 && rect.right <= window.innerWidth && rect.top <= window.innerHeight;
|
|
||||||
});
|
|
||||||
const MIN_SIZE = 100;
|
|
||||||
const largeImages = Array.from(inWindow).filter((img) => img !== focusedElement && img.width >= MIN_SIZE && img.height >= MIN_SIZE);
|
|
||||||
if (largeImages.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const randomImage = largeImages[Math.floor(Math.random() * largeImages.length)];
|
|
||||||
focusedElement = randomImage;
|
|
||||||
flyTo(getFocusedElementRandomX(), getFocusedElementY());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCanvasWidth() {
|
|
||||||
return canvas.width * CSS_SCALE
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCanvasHeight() {
|
|
||||||
return canvas.height * CSS_SCALE
|
|
||||||
}
|
|
||||||
|
|
||||||
function hop() {
|
|
||||||
if (currentState === States.IDLE) {
|
|
||||||
// Determine bounds for hopping
|
|
||||||
let minX = 0;
|
|
||||||
let maxX = window.innerWidth;
|
|
||||||
let y = 0;
|
|
||||||
if (focusedElement !== null) {
|
|
||||||
// Hop on the element
|
|
||||||
const rect = focusedElement.getBoundingClientRect();
|
|
||||||
minX = rect.left;
|
|
||||||
maxX = rect.right;
|
|
||||||
y = window.innerHeight - rect.top;
|
|
||||||
}
|
|
||||||
setState(States.HOP);
|
|
||||||
setAnimation(Animations.FLYING);
|
|
||||||
if ((Math.random() < 0.5 && birdX - HOP_DISTANCE > minX) || birdX + HOP_DISTANCE > maxX) {
|
|
||||||
targetX = birdX - HOP_DISTANCE;
|
|
||||||
} else {
|
|
||||||
targetX = birdX + HOP_DISTANCE;
|
|
||||||
}
|
|
||||||
targetY = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pet() {
|
|
||||||
if (currentState === States.IDLE) {
|
|
||||||
setAnimation(Animations.HEART);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} x
|
|
||||||
* @param {number} y
|
|
||||||
*/
|
|
||||||
function flyTo(x, y) {
|
|
||||||
targetX = x;
|
|
||||||
targetY = y;
|
|
||||||
setState(States.FLYING);
|
|
||||||
setAnimation(Animations.FLYING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the current animation and reset the animation timer
|
|
||||||
* @param {Anim} animation
|
|
||||||
*/
|
|
||||||
function setAnimation(animation) {
|
|
||||||
currentAnimation = animation;
|
|
||||||
animStart = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the current state and reset the state timer
|
|
||||||
* @param {string} state
|
|
||||||
*/
|
|
||||||
function setState(state) {
|
|
||||||
stateStart = Date.now();
|
|
||||||
startX = birdX;
|
|
||||||
startY = birdY;
|
|
||||||
currentState = state;
|
|
||||||
if (state === States.IDLE) {
|
|
||||||
setAnimation(Animations.BOB);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} value
|
* @param {number} value
|
||||||
*/
|
*/
|
||||||
@@ -962,22 +989,6 @@ function roundToPixel(value) {
|
|||||||
return Math.round(value / WINDOW_PIXEL_SIZE) * WINDOW_PIXEL_SIZE;
|
return Math.round(value / WINDOW_PIXEL_SIZE) * WINDOW_PIXEL_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} x
|
|
||||||
*/
|
|
||||||
function setX(x) {
|
|
||||||
let mod = getCanvasWidth() / -2 - (WINDOW_PIXEL_SIZE * (direction === Directions.RIGHT ? 2 : -2));
|
|
||||||
canvas.style.left = `${x + mod}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} y
|
|
||||||
*/
|
|
||||||
function setY(y) {
|
|
||||||
canvas.style.bottom = `${y}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether the user is on a mobile device
|
* @returns {boolean} Whether the user is on a mobile device
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<img src="./images/bird-3.jpg" alt="Bird 3" style="width: 300px; height: auto; margin: 10px;">
|
<img src="./images/bird-3.jpg" alt="Bird 3" style="width: 300px; height: auto; margin: 10px;">
|
||||||
</div>
|
</div>
|
||||||
<div id="spacer"></div>
|
<div id="spacer"></div>
|
||||||
|
<!-- <script type="module" src="spritesheet-compiler.js"></script> -->
|
||||||
<script src="birb.js"></script>
|
<script src="birb.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
121
spritesheet-compiler.js
Normal file
121
spritesheet-compiler.js
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const TRANSPARENT = 0;
|
||||||
|
const OUTLINE = 1;
|
||||||
|
const BORDER = 2;
|
||||||
|
const FOOT = 3;
|
||||||
|
const BEAK = 4;
|
||||||
|
const EYE = 5;
|
||||||
|
const FACE = 6;
|
||||||
|
const BELLY = 7;
|
||||||
|
const UNDERBELLY = 8;
|
||||||
|
const WING = 9;
|
||||||
|
const WING_EDGE = 10;
|
||||||
|
const HEART = 11;
|
||||||
|
const HEART_BORDER = 12;
|
||||||
|
const HEART_SHINE = 13;
|
||||||
|
|
||||||
|
const SPRITESHEET_COLOR_MAP = {
|
||||||
|
"transparent": TRANSPARENT,
|
||||||
|
"#ffffff": BORDER,
|
||||||
|
"#000000": OUTLINE,
|
||||||
|
"#010a19": BEAK,
|
||||||
|
"#190301": EYE,
|
||||||
|
"#af8e75": FOOT,
|
||||||
|
"#639bff": FACE,
|
||||||
|
"#f8b143": BELLY,
|
||||||
|
"#ec8637": UNDERBELLY,
|
||||||
|
"#578ae6": WING,
|
||||||
|
"#326ed9": WING_EDGE,
|
||||||
|
"#c82e2e": HEART,
|
||||||
|
"#501a1a": HEART_BORDER,
|
||||||
|
"#ff6b6b": HEART_SHINE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SPRITE_SHEET_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASAAAAAgCAYAAACy9KU0AAAAAXNSR0IArs4c6QAABBdJREFUeJztnL9LHEEUx79zWkgk3RFwTRvsDKS5KpWpbDSVVQgkjSCYKkfIHyDBIhAhIAiBkOoqsUmVVDbaBFKkkLTxhHBgEQM28aU4Z53d25lddXfm1vt+4Ni5X/vm9t77znfm9hYghBBCCCGEEOIJFboDhOQhImJ7TinFHK4x/PJILiEFQMd+PD0NANg+PEy0ffSBEBIIOWcximQxigbaLnEqK/5iFMne7KzIyspAu+r4pFrGQ3eAFCOr0HyN/I+np/Gq2UTr4cOBtnYhPtjf3cWrZjNuk/rTCN0Bkk96GqK3vkf/UAKwfXiIN71e4rE3vZ5X8SNkZDGnIebWhwCZU7C92dn45msKltUHn7FJtdABFUQyCNEP7UB8oad5pgsx3YePaWC6D1x8JiOHiPQXP42tz9ihHYDZh1ACHFr8SflwBCmIiMj+/fvx/db3715HYHMdKJQDMAuf7oMQj+iRV7sfOgBCrk+hUcyW8KM2CtIBEFIuuUWkiy5db7oWfRQiBZDUCeZrcZwnIpri83wreUyVUhAR8zWVHFyXAIqI8Eslw0BadGwDNkliFSCX+ADA8y2JD3JVYjAMAkhIUcwUXFrvxu1OOwrRncqYGlNy9E9KqTenA2pMRlC3pvDhRf8APnvXxeflBuY3zwBcHOQqxGAYBJCQgohSKiE6N5WpMSXzm2f4vNwoRYSsO8gTAAA4OT6K21rlXVbzMgIhItKYTI4caQHU8TvtKBGXQkQ8Ii7hMeuCeTmI1QEppZTr515TfICkG8riKi7l7G93QAC1+KRjp91Q1v6YAKRknOJjwNSzUOjf8O/vPMLK7y9xGwCeHn/KfG36C/n56w+Ai2laUYZBAAmxsbTezU3mKtd+bPGNmLXIc2cnzwsWpztzA89NLHzFg9UD3Lt7O/O9Wni+bczoffUDXnIapuMPCOBBtgC6+kEbTMoiT4A67QhL61102lFV+WZ1X+ciVIs8v/L1gE535jCxMAOsHmQ+nxYe4Ho+VAuPptOOriSAhJTBx5kn1kHQg/gA6Oe4Lf/rQq4DAvrTmrQLOvlxAgBovt63vfciyBWFx+XAgAsXlkXZAkhImt5aS1bGtwEkp1s+xAeAuAbgurigQmdCp0VAi08Wzdf7pZ0lHVoACcmjt9aKE80QI1/5ZhUh85fhYc7/S/0Vo7fWcr62TPEx44cSQEJqgOhZQFqItAgNcx3krgHpX6POP4hVhKosfB0bgFMEKT5kBFHfNmb6SW8RohuBcRkI2X77UgDEN32/qstEpC5D4YzPS1WQEUbQd0TyYPWg0pr0SlbxZ92vWoBCxSekLui60LcbURPpYrdtqxSgkPEJqQuSQeg+uSh8HpBeh3FtqyR0fELqQN3WPi91VnLuzir88KHjE0LK5z8tFuzphqiPOAAAAABJRU5ErkJggg==";
|
||||||
|
console.log(stringifyPixels(compress(loadSpritesheetPixels(SPRITE_SHEET_URI))))
|
||||||
|
|
||||||
|
function compress(pixels) {
|
||||||
|
let counts = [];
|
||||||
|
let rowCounts = [];
|
||||||
|
let count = null;
|
||||||
|
for (let row of pixels) {
|
||||||
|
console.log("Row length: " + row.length);
|
||||||
|
for (let pixel of row) {
|
||||||
|
if (count === null) {
|
||||||
|
count = [pixel, 1];
|
||||||
|
} else if (pixel === count[0]) {
|
||||||
|
count[1] = count[1] + 1;
|
||||||
|
} else {
|
||||||
|
rowCounts.push(count);
|
||||||
|
count = [pixel, 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rowCounts.push(count);
|
||||||
|
counts.push([...rowCounts]);
|
||||||
|
rowCounts = [];
|
||||||
|
count = null;
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyPixels(pixels) {
|
||||||
|
// Add newlines between every row
|
||||||
|
let str = "";
|
||||||
|
for (let row of pixels) {
|
||||||
|
str += JSON.stringify(row) + ",\n";
|
||||||
|
}
|
||||||
|
str = str.slice(0, -2);
|
||||||
|
return "[" + str + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the spritesheet and return the pixelmap template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {string[][]}
|
||||||
|
*/
|
||||||
|
function loadSpritesheetPixels(dataUri, templateColors = true) {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITESHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
console.error(`Unknown color: ${hex}`);
|
||||||
|
row.push(TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITESHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
return hexArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
Reference in New Issue
Block a user