A browser source overlay for winter vibes for your Live Streams or Videos

we got a plow

public/sprite_sheet.png

This is a binary file and will not be displayed.

+2 -1
src/config.js
···
{ name: "pspd", value: 0.1 }, // plow speed
{ name: "pd", value: "left" }, // plow direction
{ name: "gamax", value: 4 }, // ground accumulator max
-
{ name: "gas", value: 10 }, // ground accumulatior slices
+
{ name: "gas", value: 20 }, // ground accumulatior slices
];
/**
···
if (PARAM_DEFAULTS.some(({ name }) => !urlParams.has(name))) {
setParamsAndRefresh(PARAM_DEFAULTS);
}
+
return {
version: parseInt(urlParams.get("v")),
canvas: {
+21 -10
src/main.js
···
import { createSnowflake, drawSnowflake, moveSnowflake } from "./snowflake";
import {
accumulateSnow,
-
checkCollision,
+
snowAccumulatorCollisionY,
createSnowAccumulator,
drawSnowAccumulators,
+
resetSnowAccumulator,
+
snowAccumulatorCollisionX,
} from "./snow_accumulator";
+
import { createPlow, drawPlow, movePlow, plowDone } from "./plow";
/**
* @type{Array.<Snowflake>}
···
*/
let ctx;
+
/**
+
* @type {Plow}
+
*/
+
let plow;
+
let lastSpawn = 0;
let lastFrame = 0;
···
ctx = canvas.getContext("2d");
canvas.width = Config.canvas.width;
canvas.height = Config.canvas.height;
+
ctx.imageSmoothingEnabled = false;
for (let i = 0; i < Config.groundAccumulator.slices; i++) {
floor.push(
···
moveSnowflake(flake, delta);
const floorIdx = Math.floor(flake.x / sectionSize);
-
if (checkCollision(floor[floorIdx], flake)) {
+
if (snowAccumulatorCollisionY(floor[floorIdx], flake)) {
if (!isPlowing) {
accumulateSnow(floor[floorIdx], flake);
}
···
isPlowing = true;
}
+
drawSnowAccumulators(ctx, floor, plow);
+
if (isPlowing) {
-
// plow the snow and reset the accumulators
-
floor.forEach((sa) => {
-
sa.height = 0;
-
sa.accumulator = 0;
-
});
-
isPlowing = false;
+
plow ??= createPlow();
+
movePlow(plow, delta);
+
drawPlow(plow, ctx);
+
if (plowDone(plow)) {
+
isPlowing = false;
+
plow = null;
+
floor.forEach((sa) => resetSnowAccumulator(sa));
+
}
}
-
-
drawSnowAccumulators(ctx, floor);
lastFrame = time;
window.requestAnimationFrame(gameLoop);
+40 -4
src/plow.js
···
-
export function spawnPlow(direction) {
+
import Config from "./config";
+
import { drawSprite } from "./sprites";
+
+
/**
+
* @returns {Plow}
+
*/
+
export function createPlow() {
return {
-
x: 0,
-
y: 0,
-
speed: 0.1,
+
x: Config.plow.direction === "left" ? Config.canvas.width : -88,
+
y: Config.canvas.height - 56,
+
speed: Config.plow.speed,
+
direction: Config.plow.direction,
+
height: 56,
+
width: 88,
};
}
+
+
/**
+
* @param {Plow} plow
+
* @param {number} delta
+
*/
+
export function movePlow(plow, delta) {
+
if (plow.direction === "left") {
+
plow.x -= plow.speed * delta;
+
} else {
+
plow.x += plow.speed * delta;
+
}
+
}
+
+
/**
+
* @param {Plow} plow
+
* @param {CanvasRenderingContext2D} ctx
+
*/
+
export function drawPlow(plow, ctx) {
+
drawSprite(ctx, 0, 0, 44, 28, plow.x, plow.y, plow.width, plow.height);
+
}
+
+
/** @param {Plow} plow */
+
export function plowDone(plow) {
+
return plow.direction === "left"
+
? plow.x + plow.width < 0
+
: plow.x > Config.canvas.width;
+
}
+38 -5
src/snow_accumulator.js
···
* @param {SnowAccumulator} sa
* @param {Object} item
* @param {number} item.y
+
* @returns {boolean}
*/
-
export function checkCollision(sa, { y }) {
+
export function snowAccumulatorCollisionY(sa, { y }) {
if (y >= sa.y - sa.height) {
return true;
}
···
/**
* @param {SnowAccumulator} sa
* @param {Object} item
+
* @param {number} item.x
+
* @returns {boolean}
+
*/
+
export function snowAccumulatorCollisionX(sa, { x }) {
+
if (x >= sa.x && x <= sa.x + sa.width) {
+
return true;
+
}
+
+
return false;
+
}
+
+
/**
+
* @param {SnowAccumulator} sa
+
* @param {Object} item
* @param {number} item.size
*/
export function accumulateSnow(sa, { size }) {
···
}
/**
+
* @param {SnowAccumulator} sa
+
*/
+
export function resetSnowAccumulator(sa) {
+
sa.accumulator = 0;
+
sa.height = 0;
+
}
+
+
/**
* @param {CanvasRenderingContext2D} ctx
* @param {SnowAccumulator[]} accumulators
+
* @param {Plow} plow
*/
-
export function drawSnowAccumulators(ctx, accumulators) {
+
export function drawSnowAccumulators(ctx, accumulators, plow) {
const points = accumulators.map((sa) => [sa.x, sa.y - sa.height]);
const avgHeight =
accumulators.reduce((acc, sa) => acc + sa.height, 0) / accumulators.length;
-
const noramlized = points.map(([x, y]) => [x, y - avgHeight]);
+
const normalized = points
+
.map(([x, y]) => [x, y - avgHeight])
+
.filter(([x]) => !(plow && plow.direction === "left" && x >= plow.x));
ctx.beginPath();
-
noramlized.forEach(([x, y]) => ctx.lineTo(x, y));
-
ctx.lineTo(Config.canvas.width, noramlized[noramlized.length - 1][1]);
+
normalized.forEach(([x, y]) => {
+
ctx.lineTo(x, y);
+
});
+
plow &&
+
normalized.length > 0 &&
+
ctx.lineTo(plow.x, normalized[normalized.length - 1][1]);
+
plow && ctx.lineTo(plow.x, Config.canvas.height);
+
!plow &&
+
ctx.lineTo(Config.canvas.width, normalized[normalized.length - 1][1]);
ctx.lineTo(Config.canvas.width, Config.canvas.height);
ctx.lineTo(0, Config.canvas.height);
ctx.fillStyle = "#fff";
+12
src/sprites.js
···
+
function loadSpriteSheet() {
+
const image = new Image();
+
image.src = "./sprite_sheet.png";
+
return image;
+
}
+
const sheet = loadSpriteSheet();
+
+
export function drawSprite(ctx, x, y, width, height, dx, dy, dWidth, dHeight) {
+
ctx.drawImage(sheet, x, y, width, height, dx, dy, dWidth, dHeight);
+
}
+
+
export default sheet;
+7 -2
src/types.d.ts
···
};
plow: {
speed: number;
-
direction: "left" | "right";
+
direction: "left" | "right" | "random";
};
};
···
};
type Plow = {
-
left: boolean;
+
speed: number;
+
x: number;
+
y: number;
+
height: number;
+
width: number;
+
direction: "left" | "right";
};
}