feat(design): Raeumfahrzeug im Theme Snow
Faehrt gleich nach dem Laden und dann alle vier Minuten abwechselnd von links und rechts ueber den Boden, raeumt die Schneedecke und schiebt den Schnee als Wall mit fliegenden Brocken aus dem Fenster. Nebenbei SnowWeather.tsx wieder im Stil des Projekts formatiert (einfache Anfuehrungszeichen). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
831429d559
commit
575de2e954
@@ -250,6 +250,9 @@ ein `<canvas>` über dem Fenster (`z-[90]`, `pointer-events: none`), bei
|
|||||||
| **Schneedecke** | Eine Höhenkarte in 3-px-Spalten über die Fensterbreite. Eine Flocke landet, sobald sie die Oberfläche **ihrer** Spalte erreicht, und verteilt sich auf fünf Spalten |
|
| **Schneedecke** | Eine Höhenkarte in 3-px-Spalten über die Fensterbreite. Eine Flocke landet, sobald sie die Oberfläche **ihrer** Spalte erreicht, und verteilt sich auf fünf Spalten |
|
||||||
| **Glätten** | Diffusion: jede Spalte gleicht sich an ihre Nachbarn an, Faktor an der Zeit (`SPREAD`), nicht an der Framezahl - gleich auf 60 und 144 Hz. Mengenerhaltend, aus Einzelflocken werden flache Hügel |
|
| **Glätten** | Diffusion: jede Spalte gleicht sich an ihre Nachbarn an, Faktor an der Zeit (`SPREAD`), nicht an der Framezahl - gleich auf 60 und 144 Hz. Mengenerhaltend, aus Einzelflocken werden flache Hügel |
|
||||||
| **Deckel** | `MAX_DEPTH` = 26 px, voll nach rund vier Minuten. Was danach landet, verschwindet |
|
| **Deckel** | `MAX_DEPTH` = 26 px, voll nach rund vier Minuten. Was danach landet, verschwindet |
|
||||||
|
| **Räumfahrzeug** | Startet 2,5 s nach dem Laden, dann alle 4 min (`PLOW_INTERVAL_MS`), Richtung wechselt jedes Mal. Fährt mit 140 px/s von außerhalb des Fensters herein, setzt jede Spalte hinter der Schaufelkante (und unter dem Fahrzeug) auf null und sammelt die Menge in `pushed`. Daraus wächst der Wall vor der Schaufel (Wurzel der Menge, gedeckelt auf `MAX_DEPTH + 8`); dazu fliegen Brocken. Hinter dem Fensterrand verschwindet alles. Gezeichnet im Canvas, nach rechts und für die Rückfahrt per `scale(-1, 1)` gespiegelt |
|
||||||
|
|
||||||
|
Vier Minuten sind abgestimmt: so lange braucht die Decke, bis sie voll ist.
|
||||||
|
|
||||||
Die Decke lebt nur im Speicher; beim Ändern der Fensterbreite wird sie auf die
|
Die Decke lebt nur im Speicher; beim Ändern der Fensterbreite wird sie auf die
|
||||||
neue Spaltenzahl umgerechnet, nach dem Neuladen ist der Boden frei.
|
neue Spaltenzahl umgerechnet, nach dem Neuladen ist der Boden frei.
|
||||||
|
|||||||
@@ -103,8 +103,10 @@ die Scanner-Einstellungen. Alles andere ist für beide Rollen gleich.
|
|||||||
alle paar Sekunden kurz auf.
|
alle paar Sekunden kurz auf.
|
||||||
* Bei **Snow** schneit es, und der Schnee bleibt unten liegen: nach etwa vier
|
* Bei **Snow** schneit es, und der Schnee bleibt unten liegen: nach etwa vier
|
||||||
Minuten ist am unteren Rand eine Schneedecke gewachsen. Sie bleibt ein
|
Minuten ist am unteren Rand eine Schneedecke gewachsen. Sie bleibt ein
|
||||||
schmaler Streifen und wächst nicht weiter. Neu laden, und der Boden ist
|
schmaler Streifen und wächst nicht weiter. Alle vier Minuten kommt ein
|
||||||
wieder frei.
|
kleines **Räumfahrzeug** mit Rundumleuchte vorbei und schiebt den Schnee
|
||||||
|
vom Bildschirm - abwechselnd von links nach rechts und zurück. Die erste
|
||||||
|
Fahrt startet gleich nach dem Laden der Seite.
|
||||||
|
|
||||||
Wer im Betriebssystem **„Bewegung reduzieren"** eingeschaltet hat, sieht bei
|
Wer im Betriebssystem **„Bewegung reduzieren"** eingeschaltet hat, sieht bei
|
||||||
beiden nur Farben und Grafiken - ohne Regen, Blitz und Schnee.
|
beiden nur Farben und Grafiken - ohne Regen, Blitz und Schnee.
|
||||||
|
|||||||
+229
-25
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Das Wetter des Themes "Snow": Schneefall ueber dem Bildschirm, und was
|
* Das Wetter des Themes "Snow": Schneefall ueber dem Bildschirm, und was
|
||||||
@@ -16,8 +16,22 @@ import React, { useEffect, useRef } from "react";
|
|||||||
* mehr als einen schmalen Streifen am Rand verdeckt.
|
* mehr als einen schmalen Streifen am Rand verdeckt.
|
||||||
*
|
*
|
||||||
* Die Decke lebt nur im Speicher: neu laden, und der Boden ist wieder frei.
|
* Die Decke lebt nur im Speicher: neu laden, und der Boden ist wieder frei.
|
||||||
|
*
|
||||||
|
* Alle vier Minuten - und einmal gleich nach dem Laden - faehrt ein kleines
|
||||||
|
* Raeumfahrzeug ueber den Boden, abwechselnd nach rechts und nach links. Es
|
||||||
|
* setzt jede Spalte, ueber die seine Schaufel faehrt, auf null und schiebt den
|
||||||
|
* Schnee als Wall vor sich her, der am Fensterrand verschwindet. Vier Minuten
|
||||||
|
* sind kein Zufall: so lange braucht die Decke, bis sie voll ist.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
interface Chunk {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
vx: number;
|
||||||
|
vy: number;
|
||||||
|
radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Flake {
|
interface Flake {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
@@ -40,27 +54,33 @@ const SPAWN_MIN_MS = 70;
|
|||||||
const SPAWN_MAX_MS = 160;
|
const SPAWN_MAX_MS = 160;
|
||||||
const MAX_FLAKES = 70;
|
const MAX_FLAKES = 70;
|
||||||
|
|
||||||
|
/** Abstand zwischen zwei Raeumfahrten; die erste startet kurz nach dem Laden. */
|
||||||
|
const PLOW_INTERVAL_MS = 4 * 60 * 1000;
|
||||||
|
const PLOW_FIRST_MS = 2500;
|
||||||
|
/** px/s - ueber ein breites Fenster in gut zehn Sekunden. */
|
||||||
|
const PLOW_SPEED = 140;
|
||||||
|
/** Vom Mittelpunkt des Fahrzeugs bis zur Schaufelkante bzw. zum Heck. */
|
||||||
|
const PLOW_FRONT = 38;
|
||||||
|
const PLOW_REAR = 36;
|
||||||
|
|
||||||
function random(min: number, max: number) {
|
function random(min: number, max: number) {
|
||||||
return min + Math.random() * (max - min);
|
return min + Math.random() * (max - min);
|
||||||
}
|
}
|
||||||
|
|
||||||
function prefersReducedMotion() {
|
function prefersReducedMotion() {
|
||||||
return (
|
return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
||||||
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Farben aus index.css, damit hell und dunkel passen. */
|
/** Farben aus index.css, damit hell und dunkel passen. */
|
||||||
function snowColors() {
|
function snowColors() {
|
||||||
const style = getComputedStyle(document.documentElement);
|
const style = getComputedStyle(document.documentElement);
|
||||||
const read = (name: string, fallback: string) =>
|
const read = (name: string, fallback: string) => style.getPropertyValue(name).trim() || fallback;
|
||||||
style.getPropertyValue(name).trim() || fallback;
|
|
||||||
return {
|
return {
|
||||||
flake: read("--snow-flake", "#ffffff"),
|
flake: read('--snow-flake', '#ffffff'),
|
||||||
flakeEdge: read("--snow-flake-edge", "rgb(120 145 170 / 0.35)"),
|
flakeEdge: read('--snow-flake-edge', 'rgb(120 145 170 / 0.35)'),
|
||||||
pile: read("--snow-pile", "#ffffff"),
|
pile: read('--snow-pile', '#ffffff'),
|
||||||
pileShade: read("--snow-pile-shade", "#dbe6f1"),
|
pileShade: read('--snow-pile-shade', '#dbe6f1'),
|
||||||
pileEdge: read("--snow-pile-edge", "rgb(120 145 170 / 0.5)"),
|
pileEdge: read('--snow-pile-edge', 'rgb(120 145 170 / 0.5)'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +89,7 @@ export const SnowWeather: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
const ctx = canvas?.getContext("2d");
|
const ctx = canvas?.getContext('2d');
|
||||||
if (!canvas || !ctx || prefersReducedMotion()) return;
|
if (!canvas || !ctx || prefersReducedMotion()) return;
|
||||||
|
|
||||||
let width = 0;
|
let width = 0;
|
||||||
@@ -89,19 +109,13 @@ export const SnowWeather: React.FC = () => {
|
|||||||
const next = new Float32Array(Math.ceil(width / COLUMN) + 1);
|
const next = new Float32Array(Math.ceil(width / COLUMN) + 1);
|
||||||
if (pile.length > 0) {
|
if (pile.length > 0) {
|
||||||
for (let i = 0; i < next.length; i++) {
|
for (let i = 0; i < next.length; i++) {
|
||||||
next[i] =
|
next[i] = pile[Math.min(pile.length - 1, Math.floor((i / next.length) * pile.length))];
|
||||||
pile[
|
|
||||||
Math.min(
|
|
||||||
pile.length - 1,
|
|
||||||
Math.floor((i / next.length) * pile.length),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pile = next;
|
pile = next;
|
||||||
};
|
};
|
||||||
resize();
|
resize();
|
||||||
window.addEventListener("resize", resize);
|
window.addEventListener('resize', resize);
|
||||||
|
|
||||||
const flakes: Flake[] = [];
|
const flakes: Flake[] = [];
|
||||||
let nextSpawn = 0;
|
let nextSpawn = 0;
|
||||||
@@ -110,6 +124,12 @@ export const SnowWeather: React.FC = () => {
|
|||||||
let colors = snowColors();
|
let colors = snowColors();
|
||||||
let colorsReadAt = 0;
|
let colorsReadAt = 0;
|
||||||
|
|
||||||
|
// Raeumfahrzeug: null = steht in der Garage.
|
||||||
|
let plow: { x: number; dir: 1 | -1; pushed: number } | null = null;
|
||||||
|
let nextPlowDir: 1 | -1 = 1;
|
||||||
|
let nextPlowAt = performance.now() + PLOW_FIRST_MS;
|
||||||
|
const chunks: Chunk[] = [];
|
||||||
|
|
||||||
const spawn = () => {
|
const spawn = () => {
|
||||||
if (flakes.length >= MAX_FLAKES) return;
|
if (flakes.length >= MAX_FLAKES) return;
|
||||||
const radius = random(1, 3);
|
const radius = random(1, 3);
|
||||||
@@ -149,8 +169,7 @@ export const SnowWeather: React.FC = () => {
|
|||||||
*/
|
*/
|
||||||
let smoothed = new Float32Array(0);
|
let smoothed = new Float32Array(0);
|
||||||
const settle = (dt: number) => {
|
const settle = (dt: number) => {
|
||||||
if (smoothed.length !== pile.length)
|
if (smoothed.length !== pile.length) smoothed = new Float32Array(pile.length);
|
||||||
smoothed = new Float32Array(pile.length);
|
|
||||||
const k = Math.min(0.25, SPREAD * dt);
|
const k = Math.min(0.25, SPREAD * dt);
|
||||||
const last = pile.length - 1;
|
const last = pile.length - 1;
|
||||||
for (let i = 0; i <= last; i++) {
|
for (let i = 0; i <= last; i++) {
|
||||||
@@ -177,7 +196,7 @@ export const SnowWeather: React.FC = () => {
|
|||||||
x0,
|
x0,
|
||||||
height - pile[i - 1],
|
height - pile[i - 1],
|
||||||
(x0 + x1) / 2,
|
(x0 + x1) / 2,
|
||||||
height - (pile[i - 1] + pile[i]) / 2,
|
height - (pile[i - 1] + pile[i]) / 2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ctx.lineTo(width, height - pile[pile.length - 1]);
|
ctx.lineTo(width, height - pile[pile.length - 1]);
|
||||||
@@ -200,7 +219,7 @@ export const SnowWeather: React.FC = () => {
|
|||||||
x0,
|
x0,
|
||||||
height - pile[i - 1],
|
height - pile[i - 1],
|
||||||
(x0 + x1) / 2,
|
(x0 + x1) / 2,
|
||||||
height - (pile[i - 1] + pile[i]) / 2,
|
height - (pile[i - 1] + pile[i]) / 2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ctx.strokeStyle = colors.pileEdge;
|
ctx.strokeStyle = colors.pileEdge;
|
||||||
@@ -208,6 +227,185 @@ export const SnowWeather: React.FC = () => {
|
|||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Das Fahrzeug, nach rechts gezeichnet und fuer die Rueckfahrt gespiegelt.
|
||||||
|
* Ursprung ist die Mitte auf dem Boden; alles in px.
|
||||||
|
*/
|
||||||
|
const drawPlow = (x: number, dir: 1 | -1, t: number) => {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, height);
|
||||||
|
ctx.scale(dir, 1);
|
||||||
|
|
||||||
|
// Rundumleuchte: Schein zuerst, damit das Fahrzeug darueber liegt.
|
||||||
|
const beaconOn = Math.sin(t * 9) > 0;
|
||||||
|
if (beaconOn) {
|
||||||
|
const glow = ctx.createRadialGradient(-15, -41, 0, -15, -41, 16);
|
||||||
|
glow.addColorStop(0, 'rgb(255 170 40 / 0.7)');
|
||||||
|
glow.addColorStop(1, 'rgb(255 170 40 / 0)');
|
||||||
|
ctx.fillStyle = glow;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(-15, -41, 16, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.lineJoin = 'round';
|
||||||
|
ctx.strokeStyle = 'rgb(40 45 55 / 0.55)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
|
||||||
|
// Schaufelarm
|
||||||
|
ctx.fillStyle = '#5b6470';
|
||||||
|
ctx.fillRect(18, -15, 14, 4);
|
||||||
|
|
||||||
|
// Fahrgestell und Motorhaube
|
||||||
|
ctx.fillStyle = '#f2a900';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(-PLOW_REAR, -21, 56, 14, 3);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(-4, -28, 24, 10, [5, 3, 0, 0]);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Fuehrerhaus mit Fenster
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(-31, -38, 27, 19, [3, 3, 0, 0]);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.fillStyle = '#bfe3f5';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(-25, -35, 17, 10, 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Warnstreifen
|
||||||
|
ctx.fillStyle = '#2b2f36';
|
||||||
|
for (let i = 0; i < 4; i++) ctx.fillRect(-30 + i * 12, -12, 5, 3);
|
||||||
|
|
||||||
|
// Schnee auf dem Dach
|
||||||
|
ctx.fillStyle = colors.pile;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(-19, -38, 11, 2.5, 0, Math.PI, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Leuchte auf dem Dach
|
||||||
|
ctx.fillStyle = beaconOn ? '#ffb020' : '#b86e00';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.roundRect(-18, -43, 6, 5, [2, 2, 0, 0]);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Raeder
|
||||||
|
for (const wheelX of [-22, 10]) {
|
||||||
|
ctx.fillStyle = '#2b2f36';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(wheelX, -6, 6, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.fillStyle = '#9aa3ad';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(wheelX, -6, 2, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schaufel: schraeg gestellt, unten auf dem Boden
|
||||||
|
ctx.fillStyle = '#c9d1d9';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(28, -24);
|
||||||
|
ctx.lineTo(33, -25);
|
||||||
|
ctx.lineTo(PLOW_FRONT, 0);
|
||||||
|
ctx.lineTo(29, 0);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Der vorgeschobene Schnee: ein Huegel vor der Schaufel, der mit der Menge waechst. */
|
||||||
|
const drawWall = (bladeX: number, dir: 1 | -1, pushed: number) => {
|
||||||
|
if (pushed < 1) return;
|
||||||
|
// Hoeher als die Decke, damit er sich sichtbar vor der Schaufel auftuermt.
|
||||||
|
const wallHeight = Math.min(MAX_DEPTH + 8, 6 + Math.sqrt(pushed) * 1.2);
|
||||||
|
const wallWidth = wallHeight * 1.6;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(bladeX - dir * 2, height);
|
||||||
|
ctx.lineTo(bladeX - dir * 2, height - wallHeight);
|
||||||
|
ctx.quadraticCurveTo(
|
||||||
|
bladeX + dir * wallWidth * 0.55,
|
||||||
|
height - wallHeight * 1.05,
|
||||||
|
bladeX + dir * wallWidth,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
ctx.closePath();
|
||||||
|
const gradient = ctx.createLinearGradient(0, height - wallHeight, 0, height);
|
||||||
|
gradient.addColorStop(0, colors.pile);
|
||||||
|
gradient.addColorStop(1, colors.pileShade);
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.fill();
|
||||||
|
ctx.strokeStyle = colors.pileEdge;
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Eine Fahrt: Schaufel vorwaerts, geraeumte Spalten auf null, Menge in den Wall. */
|
||||||
|
const drivePlow = (now: number, dt: number) => {
|
||||||
|
if (!plow) {
|
||||||
|
if (now < nextPlowAt) return;
|
||||||
|
const dir = nextPlowDir;
|
||||||
|
plow = { x: dir === 1 ? -PLOW_FRONT - 40 : width + PLOW_FRONT + 40, dir, pushed: 0 };
|
||||||
|
nextPlowDir = dir === 1 ? -1 : 1;
|
||||||
|
nextPlowAt = now + PLOW_INTERVAL_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
plow.x += plow.dir * PLOW_SPEED * dt;
|
||||||
|
const blade = plow.x + plow.dir * PLOW_FRONT;
|
||||||
|
|
||||||
|
// Alles hinter der Schaufelkante raeumen, auch was unter das Fahrzeug
|
||||||
|
// gefallen ist - die Spur bleibt frei.
|
||||||
|
const left = plow.dir === 1 ? plow.x - PLOW_REAR - COLUMN * 4 : blade;
|
||||||
|
const right = plow.dir === 1 ? blade : plow.x + PLOW_REAR + COLUMN * 4;
|
||||||
|
const from = Math.max(0, Math.ceil(left / COLUMN));
|
||||||
|
const to = Math.min(pile.length - 1, Math.floor(right / COLUMN));
|
||||||
|
let cleared = 0;
|
||||||
|
for (let i = from; i <= to; i++) {
|
||||||
|
cleared += pile[i];
|
||||||
|
pile[i] = 0;
|
||||||
|
}
|
||||||
|
plow.pushed += cleared;
|
||||||
|
|
||||||
|
// Ein paar Brocken fliegen von der Schaufel, solange sie Schnee schiebt.
|
||||||
|
if (plow.pushed > 5 && blade > 0 && blade < width && Math.random() < dt * 14) {
|
||||||
|
chunks.push({
|
||||||
|
x: blade,
|
||||||
|
y: height - Math.min(20, 2 + Math.sqrt(plow.pushed)),
|
||||||
|
vx: plow.dir * random(40, 120),
|
||||||
|
vy: -random(60, 140),
|
||||||
|
radius: random(1.2, 2.6),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const gone = plow.dir === 1 ? plow.x - PLOW_REAR > width + 60 : plow.x + PLOW_REAR < -60;
|
||||||
|
if (gone) plow = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawChunks = (dt: number) => {
|
||||||
|
ctx.fillStyle = colors.flake;
|
||||||
|
ctx.strokeStyle = colors.flakeEdge;
|
||||||
|
ctx.lineWidth = 0.6;
|
||||||
|
for (let i = chunks.length - 1; i >= 0; i--) {
|
||||||
|
const c = chunks[i];
|
||||||
|
c.vy += 600 * dt;
|
||||||
|
c.x += c.vx * dt;
|
||||||
|
c.y += c.vy * dt;
|
||||||
|
if (c.y >= height) {
|
||||||
|
chunks.splice(i, 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(c.x, c.y, c.radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const tick = (now: number) => {
|
const tick = (now: number) => {
|
||||||
const dt = Math.min((now - last) / 1000, 0.05);
|
const dt = Math.min((now - last) / 1000, 0.05);
|
||||||
last = now;
|
last = now;
|
||||||
@@ -246,7 +444,13 @@ export const SnowWeather: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
settle(dt);
|
settle(dt);
|
||||||
|
drivePlow(now, dt);
|
||||||
drawPile();
|
drawPile();
|
||||||
|
if (plow) {
|
||||||
|
drawWall(plow.x + plow.dir * PLOW_FRONT, plow.dir, plow.pushed);
|
||||||
|
drawPlow(plow.x, plow.dir, t);
|
||||||
|
}
|
||||||
|
drawChunks(dt);
|
||||||
|
|
||||||
frame = requestAnimationFrame(tick);
|
frame = requestAnimationFrame(tick);
|
||||||
};
|
};
|
||||||
@@ -254,7 +458,7 @@ export const SnowWeather: React.FC = () => {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelAnimationFrame(frame);
|
cancelAnimationFrame(frame);
|
||||||
window.removeEventListener("resize", resize);
|
window.removeEventListener('resize', resize);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user