added obstacle factory, worldscene, game controller, game manager and structured images&glbs into folders

This commit is contained in:
Mak
2026-05-10 15:07:58 +09:00
parent 17d578fc79
commit 845c4de6ee
28 changed files with 422 additions and 180 deletions

View File

Before

Width:  |  Height:  |  Size: 614 KiB

After

Width:  |  Height:  |  Size: 614 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

Before

Width:  |  Height:  |  Size: 717 KiB

After

Width:  |  Height:  |  Size: 717 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

Before

Width:  |  Height:  |  Size: 331 KiB

After

Width:  |  Height:  |  Size: 331 KiB

View File

Before

Width:  |  Height:  |  Size: 819 KiB

After

Width:  |  Height:  |  Size: 819 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -11,48 +11,59 @@ import { loadLeaderboard, saveScore, playerName, leaderboard , hasSubmitted } fr
import { updateEnvironment, skyColors } from './environment.js';
import { createObstacle, handleCollisions } from './obstacles.js';
import { createSketch } from './p5overlay.js';
import {
handleInput,
processInteraction,
updatePhysics,
updateGameFlow // Add this
} from './GameController.js';
import {
createWorldChunk,
createClouds,
moveWorld,
animateClouds // Added this
} from './WorldScene.js';
import { ObstacleFactory } from './ObstacleFactory.js';
import { GameManager } from './GameManager.js';
let showLanding = true;
let lastTime = performance.now();
async function handleStart() {
showLanding = false;
// 1. Wait for Svelte to render the div so p5Container is NOT null
await tick();
// 2. YOU FORGOT THIS: Initialize Three.js scene
init();
// 3. Setup the Bridge Object
const gameState = {
get isPlaying() { return isPlaying; },
get score() { return score; },
set score(v) { score = v; }, // Allows p5 to update the score variable here
set score(v) { score = v; },
get lives() { return lives; },
set lives(v) { lives = v; }, // Allows p5 to update the lives variable here
set lives(v) { lives = v; },
get multiplierTimer() { return multiplierTimer; },
get targetType() { return targetType; },
get instructionTimer() { return instructionTimer; },
set instructionTimer(v) { instructionTimer = v; },
get gamePhase() { return gamePhase; },
set gamePhase(v) { gamePhase = v; },
get lastStarScore() { return lastStarScore; },
set lastStarScore(v) { lastStarScore = v; },
targets,
scorePopups,
// FIX: Use getters for the arrays so they don't get stale!
get targets() { return targets; },
get scorePopups() { return scorePopups; },
onHit: (t) => {
// This logic runs in App.svelte scope when p5 calls it
if (t.type === targetType) {
const gain = 100 * scoreMultiplier;
score += gain;
scorePopups.push({ x: t.x, y: t.y, opacity: 255, life: 1, val: `+${gain}` });
} else if (t.type === "STAR") {
scoreMultiplier = 2;
multiplierTimer = BOOST_DURATION;
scorePopups.push({ x: t.x, y: t.y, opacity: 255, life: 1, val: "X2 BOOST!" });
} else {
if (lives > 0) lives--;
}
// MVC: Delegate the 'Decision' to the Controller
processInteraction(t, gameState, scoreMultiplier, BOOST_DURATION);
},
onActivateBoost: (mult, dur) => {
scoreMultiplier = mult;
multiplierTimer = dur;
}
};
};
// 4. Initialize p5 now that p5Container exists
p5Instance = new p5(createSketch(gameState, textures), p5Container);
@@ -120,69 +131,6 @@ let multiplierTimer = 0; // Remaining seconds of boost
let lastStarScore = 0; // Add this line to prevent the crash
const BOOST_DURATION = 20; // 20 seconds
const grassVertex = `
varying vec2 vUv;
uniform float uTime;
void main() {
vUv = uv;
vec3 pos = position;
float sway = sin(uTime * 2.0 + (instanceMatrix[3][0] * 0.5) + (instanceMatrix[3][2] * 0.5)) * 0.15 * uv.y;
pos.x += sway;
gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(pos, 1.0);
}
`;
const grassFragment = `
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(vec3(0.12, 0.28, 0.18), vec3(0.5, 0.72, 0.4), vUv.y), 1.0);
}
`;
const createClouds = (group) => {
const cloudMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 });
const thickness = 2;
for (let i = 0; i < 20; i++) {
const w = 10 + Math.random() * 20;
const d = 10 + Math.random() * 20;
const cloud = new THREE.Mesh(new THREE.BoxGeometry(w, thickness, d), cloudMaterial);
const y = 30 + Math.random() * 25;
cloud.position.set((Math.random() - 0.5) * 280, y, (Math.random() - 0.5) * 300);
group.add(cloud);
}
};
const createWorldChunk = (zOffset) => {
const group = new THREE.Group();
group.position.z = zOffset;
const floor = new THREE.Mesh(new THREE.PlaneGeometry(160, CHUNK_SIZE + 0.1), new THREE.MeshStandardMaterial({ color: 0x1e2b21 }));
floor.rotation.x = -Math.PI / 2;
group.add(floor);
const count = 7000;
const geo = new THREE.PlaneGeometry(0.4, 0.9, 1, 2);
geo.translate(0, 0.45, 0);
const mat = new THREE.ShaderMaterial({
uniforms: { uTime }, vertexShader: grassVertex, fragmentShader: grassFragment,
side: THREE.DoubleSide, alphaToCoverage: true
});
const mesh = new THREE.InstancedMesh(geo, mat, count);
const dummy = new THREE.Object3D();
for(let i=0; i<count; i++) {
let x = (Math.random() - 0.5) * 120;
if (x > -10 && x < 10) x += (x > 0) ? 10 : -10;
dummy.position.set(x, 0, (Math.random() - 0.5) * CHUNK_SIZE);
dummy.rotation.y = Math.random() * Math.PI;
dummy.scale.setScalar(0.7 + Math.random() * 1.6);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
group.add(mesh);
return group;
};
async function getCachedGLTF(file) {
if (!glbCache.has(file)) glbCache.set(file, await loader.loadAsync(file));
return glbCache.get(file);
@@ -190,7 +138,7 @@ async function getCachedGLTF(file) {
async function swapCharacter(file, isDeathAnimation = false) {
const myToken = ++swapToken;
const source = await getCachedGLTF(file);
const source = await getCachedGLTF(`3dmodels/${file}`);
if (myToken !== swapToken) return;
const model = cloneSkeleton(source.scene);
model.scale.setScalar(CONFIG.playerScale);
@@ -233,7 +181,7 @@ function init() {
scene.add(cloudGroup);
CHUNKS = Array.from({ length: CHUNK_COUNT }).map((_, i) => {
const chunk = createWorldChunk(-i * CHUNK_SIZE);
const chunk = createWorldChunk(-i * CHUNK_SIZE, uTime);
scene.add(chunk);
return chunk;
});
@@ -286,7 +234,6 @@ async function spawn() {
worldObjects = [...worldObjects, obstacleData];
}
function update() {
const now = performance.now();
const delta = (now - lastTime) / 1000;
@@ -296,92 +243,76 @@ function update() {
if (currentMixer) currentMixer.update(delta);
if (!isPlaying) return;
// --- MULTIPLIER COUNTDOWN ---
if (multiplierTimer > 0) {
multiplierTimer -= delta;
if (multiplierTimer <= 0) {
multiplierTimer = 0;
scoreMultiplier = 1;
}
}
// --- ADD THIS LINE HERE ---
// This updates the instruction timer and switches the phase
updateGameFlow({
get gamePhase() { return gamePhase; },
set gamePhase(v) { gamePhase = v; },
get instructionTimer() { return instructionTimer; },
set instructionTimer(v) { instructionTimer = v; }
}, delta);
// --- NEW MODULAR ENVIRONMENT CALL ---
updateEnvironment(uTime.value, scene,
{ ambientLight, sunLight, headLight },
{ sun, moon }
);
if (gamePhase === "INSTRUCTIONS") {
instructionTimer -= delta;
if (instructionTimer <= 0) gamePhase = "PLAYING";
return;
}
if (currentSpeed < CONFIG.MAX_SPEED) {
currentSpeed += CONFIG.ACCELERATION * delta;
}
// If we are still in instructions, stop the rest of the game logic
// (like movement and spawning) so the player doesn't die while reading.
if (gamePhase === "INSTRUCTIONS") return;
const moveStep = currentSpeed * delta;
// --- BOOSTED DISTANCE SCORE ---
// We apply the multiplier to the floor calculation
score += Math.floor((currentSpeed / 40) * scoreMultiplier);
// 1. Environment Controller
updateEnvironment(uTime.value, scene, { ambientLight, sunLight, headLight }, { sun, moon });
if (cloudGroup) {
// Moving at 40% speed (moveStep * 0.4) creates a nice parallax depth
cloudGroup.children.forEach(cloud => {
cloud.position.z += moveStep * 0.4;
// 2. World Controller
moveWorld(CHUNKS, moveStep, CHUNK_SIZE, CHUNK_COUNT);
animateClouds(cloudGroup, moveStep);
// Reset cloud position if it goes too far behind the camera
if (cloud.position.z > 50) {
cloud.position.z = -250;
cloud.position.x = (Math.random() - 0.5) * 280; // Randomize X again for variety
}
});
}
// 3. Player Physics Controller
updatePhysics(
{
get lane() { return lane; },
get currX() { return currX; }, set currX(v) { currX = v; },
get playerY() { return playerY; }, set playerY(v) { playerY = v; },
get isJumping() { return isJumping; }, set isJumping(v) { isJumping = v; },
get jumpV() { return jumpV; }, set jumpV(v) { jumpV = v; },
isDying
},
CONFIG, delta, swapCharacter
);
if (lives <= 0) triggerGameOver();
CHUNKS.forEach(chunk => {
chunk.position.z += moveStep;
if (chunk.position.z > CHUNK_SIZE) chunk.position.z -= CHUNK_SIZE * CHUNK_COUNT;
});
currX += (lane * CONFIG.lane - currX) * 0.18;
// Apply visual positions to the actual 3D objects
playerAnchor.position.x = currX;
if (isJumping) {
jumpV -= CONFIG.grav;
playerY += jumpV;
if (playerY <= 0) {
playerY = 0;
isJumping = false;
if (!isDying) swapCharacter("Running.glb");
}
}
playerAnchor.position.y = playerY;
// Update object positions
// 4. Obstacle Controller
worldObjects.forEach(obj => { obj.mesh.position.z += moveStep; });
// Handle Collisions using the module
worldObjects = handleCollisions(worldObjects, lane, playerY, triggerGameOver);
// Filter out-of-bounds objects
worldObjects = worldObjects.filter(obj => {
const active = obj.mesh.position.z < 25;
if (!active) scene.remove(obj.mesh);
return active;
});
// Normal Obstacle Spawning
spawnDistanceTracker += moveStep;
if (spawnDistanceTracker >= SPAWN_INTERVAL) {
spawn();
spawnDistanceTracker = 0;
// 5. Scoring & Spawning Logic
if (multiplierTimer > 0) {
multiplierTimer -= delta;
if (multiplierTimer <= 0) { multiplierTimer = 0; scoreMultiplier = 1; }
}
score += Math.floor((currentSpeed / 40) * scoreMultiplier);
if (currentSpeed < CONFIG.MAX_SPEED) currentSpeed += CONFIG.ACCELERATION * delta;
// 1. Ask the Static Manager if we should spawn
if (GameManager.shouldSpawn(moveStep)) {
// 2. Pass 'loader' so the factory can fetch models if needed
ObstacleFactory.spawnRandom(glbCache, loader, CONFIG.lane).then(obstacle => {
scene.add(obstacle.mesh);
worldObjects = [...worldObjects, obstacle];
});
}
// spawnDistanceTracker += moveStep;
// if (spawnDistanceTracker >= SPAWN_INTERVAL) {
// spawn();
// spawnDistanceTracker = 0;
// }
}
@@ -400,8 +331,9 @@ async function startGame() {
const types = ["STRAWBERRY", "WATERMELON", "BLUEBERRY"];
targetType = types[Math.floor(Math.random() * types.length)];
worldObjects.forEach(obj => scene.remove(obj.mesh));
targets.length = 0;
scorePopups.length = 0;
worldObjects = [];
targets = []; scorePopups = [];
spawnDistanceTracker = 0;
score = 0; isPlaying = true; gameOver = false; startScreen = false; lives = 5; // Reset lives
gamePhase = "INSTRUCTIONS"; instructionTimer = 3;
@@ -411,16 +343,22 @@ async function startGame() {
}
const handleKeyDown = (e) => {
if (!isPlaying || isDying) return;
const actions = {
ArrowLeft: () => lane > -2 && lane--, a: () => lane > -2 && lane--, A: () => lane > -2 && lane--,
ArrowRight: () => lane < 2 && lane++, d: () => lane < 2 && lane++, D: () => lane < 2 && lane++,
" ": () => !isJumping && (isJumping = true, jumpV = CONFIG.jump, swapCharacter("Jump.glb")),
ArrowUp: () => !isJumping && (isJumping = true, jumpV = CONFIG.jump, swapCharacter("Jump.glb")),
w: () => !isJumping && (isJumping = true, jumpV = CONFIG.jump, swapCharacter("Jump.glb")),
W: () => !isJumping && (isJumping = true, jumpV = CONFIG.jump, swapCharacter("Jump.glb"))
};
actions[e.key]?.();
// MVC: Delegate keyboard input to the Controller
handleInput(e,
{
isPlaying,
isDying,
// Use getters/setters so the Controller can actually change the Model
get lane() { return lane; },
set lane(v) { lane = v; },
get isJumping() { return isJumping; },
set isJumping(v) { isJumping = v; },
get jumpV() { return jumpV; },
set jumpV(v) { jumpV = v; }
},
CONFIG,
swapCharacter
);
};
onMount(() => {

64
src/GameController.js Normal file
View File

@@ -0,0 +1,64 @@
// GameController.js
// @ts-nocheck
export function handleInput(event, state, config, swapFn) {
if (!state.isPlaying || state.isDying) return;
const actions = {
ArrowLeft: () => state.lane > -2 && state.lane--,
a: () => state.lane > -2 && state.lane--,
ArrowRight: () => state.lane < 2 && state.lane++,
d: () => state.lane < 2 && state.lane++,
" ": () => !state.isJumping && triggerJump(state, config, swapFn),
w: () => !state.isJumping && triggerJump(state, config, swapFn),
ArrowUp: () => !state.isJumping && triggerJump(state, config, swapFn)
};
actions[event.key]?.();
}
function triggerJump(state, config, swapFn) {
// This now triggers the 'set' in App.svelte
state.isJumping = true;
state.jumpV = config.jump;
swapFn("Jump.glb");
}
export function processInteraction(target, state, multiplier, duration) {
if (target.type === state.targetType) {
const gain = 100 * multiplier;
state.score += gain;
state.scorePopups.push({ x: target.x, y: target.y, opacity: 255, life: 1, val: `+${gain}` });
} else if (target.type === "STAR") {
state.onActivateBoost(2, duration); // Ask the app to set the boost
state.scorePopups.push({ x: target.x, y: target.y, opacity: 255, life: 1, val: "X2 BOOST!" });
} else {
if (state.lives > 0) state.lives--;
}
}
export function updatePhysics(state, config, delta, swapFn) {
// 1. Smooth Lane Shifting
state.currX += (state.lane * config.lane - state.currX) * 0.18;
// 2. Jump/Gravity Logic
if (state.isJumping) {
state.jumpV -= config.grav;
state.playerY += state.jumpV;
if (state.playerY <= 0) {
state.playerY = 0;
state.isJumping = false;
if (!state.isDying) swapFn("Running.glb");
}
}
}
// GameController.js
export function updateGameFlow(state, delta) {
if (state.gamePhase === "INSTRUCTIONS") {
state.instructionTimer -= delta;
if (state.instructionTimer <= 0) {
state.gamePhase = "PLAYING";
}
}
}

22
src/GameManager.js Normal file
View File

@@ -0,0 +1,22 @@
// @ts-nocheck
export class GameManager {
static SPAWN_INTERVAL = 40;
static currentDistance = 0;
static shouldSpawn(moveStep) {
this.currentDistance += moveStep;
if (this.currentDistance >= this.SPAWN_INTERVAL) {
this.currentDistance = 0;
return true;
}
return false;
}
// Example of a static utility for difficulty scaling
static calculateSpeed(currentSpeed, acceleration, delta, maxSpeed) {
if (currentSpeed < maxSpeed) {
return currentSpeed + (acceleration * delta);
}
return currentSpeed;
}
}

View File

@@ -31,7 +31,7 @@
charScene.add(dirLight);
const loader = new GLTFLoader();
loader.load("/hiphop.glb", (gltf) => {
loader.load("3dmodels/hiphop.glb", (gltf) => {
const model = gltf.scene;
model.scale.setScalar(3.5);
model.position.y = 0;
@@ -73,7 +73,7 @@
<div class="left-section">
<div class="spacer"></div>
<img src="/overload_trans.png" alt="Overload Logo" class="game-logo" />
<img src="images/overload_trans.png" alt="Overload Logo" class="game-logo" />
<button class="start-btn" on:click={onStart}>
START RUN
@@ -93,7 +93,7 @@
width: 100vw;
height: 100vh;
z-index: 9999;
background-image: url('/bggame.png');
background-image: url('images/bggame.png');
background-size: cover;
background-position: center;
display: flex;

18
src/ObstacleFactory.js Normal file
View File

@@ -0,0 +1,18 @@
// ObstacleFactory.js
// @ts-nocheck
import { createObstacle } from './obstacles.js';
export class ObstacleFactory {
static async spawnRandom(glbCache, loader, laneWidth) {
const isRare = Math.random() < 0.2;
const modelFile = isRare ? "bird_in_a_claw_machine.glb" : "Simple computer.glb";
const fullPath = `3dmodels/${modelFile}`;
// Check if we have it, if not, load it (Standard Factory behavior)
if (!glbCache.has(fullPath)) {
glbCache.set(fullPath, await loader.loadAsync(fullPath));
}
const source = glbCache.get(fullPath);
return createObstacle(isRare, source, laneWidth);
}
}

104
src/WorldScene.js Normal file
View File

@@ -0,0 +1,104 @@
// @ts-nocheck
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { clone as cloneSkeleton } from "three/examples/jsm/utils/SkeletonUtils.js";
const CHUNK_SIZE = 140;
const grassVertex = `
varying vec2 vUv;
uniform float uTime;
void main() {
vUv = uv;
vec3 pos = position;
float sway = sin(uTime * 2.0 + (instanceMatrix[3][0] * 0.5) + (instanceMatrix[3][2] * 0.5)) * 0.15 * uv.y;
pos.x += sway;
gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(pos, 1.0);
}
`;
const grassFragment = `
varying vec2 vUv;
void main() {
gl_FragColor = vec4(mix(vec3(0.12, 0.28, 0.18), vec3(0.5, 0.72, 0.4), vUv.y), 1.0);
}
`;
export function createWorldChunk(zOffset, uTime) {
const group = new THREE.Group();
group.position.z = zOffset;
const floor = new THREE.Mesh(new THREE.PlaneGeometry(160, CHUNK_SIZE + 0.1), new THREE.MeshStandardMaterial({ color: 0x1e2b21 }));
floor.rotation.x = -Math.PI / 2;
group.add(floor);
const count = 7000;
const geo = new THREE.PlaneGeometry(0.4, 0.9, 1, 2);
geo.translate(0, 0.45, 0);
const mat = new THREE.ShaderMaterial({
uniforms: { uTime }, vertexShader: grassVertex, fragmentShader: grassFragment,
side: THREE.DoubleSide, alphaToCoverage: true
});
const mesh = new THREE.InstancedMesh(geo, mat, count);
const dummy = new THREE.Object3D();
for(let i=0; i<count; i++) {
let x = (Math.random() - 0.5) * 120;
if (x > -10 && x < 10) x += (x > 0) ? 10 : -10;
dummy.position.set(x, 0, (Math.random() - 0.5) * CHUNK_SIZE);
dummy.rotation.y = Math.random() * Math.PI;
dummy.scale.setScalar(0.7 + Math.random() * 1.6);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
group.add(mesh);
return group;
}
export function createClouds(group) {
const cloudMaterial = new THREE.MeshLambertMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.8
});
const thickness = 2;
// Increase count to 40 for a denser sky
for (let i = 0; i < 40; i++) {
const w = 10 + Math.random() * 20;
const d = 10 + Math.random() * 20;
const cloud = new THREE.Mesh(new THREE.BoxGeometry(w, thickness, d), cloudMaterial);
const y = 30 + Math.random() * 25;
// FIX: Spread clouds from +50 (behind camera) to -400 (deep horizon)
// This ensures that as soon as the game starts, there are clouds
// already "waiting" far in the distance.
const z = (Math.random() * -450) + 50;
cloud.position.set((Math.random() - 0.5) * 280, y, z);
group.add(cloud);
}
}
export function moveWorld(chunks, moveStep, chunkSize, count) {
chunks.forEach(chunk => {
chunk.position.z += moveStep;
// Logic for looping the world
if (chunk.position.z > chunkSize) {
chunk.position.z -= chunkSize * count;
}
});
}
export function animateClouds(cloudGroup, moveStep) {
if (!cloudGroup) return;
cloudGroup.children.forEach(cloud => {
cloud.position.z += moveStep * 0.4;
// Reset cloud position further back to -400
// This way they stay hidden behind the fog until they naturally drift forward
if (cloud.position.z > 100) {
cloud.position.z = -400;
cloud.position.x = (Math.random() - 0.5) * 280;
}
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -648,4 +648,101 @@ onMount(() => {
button:hover { transform: translateY(-2px); filter: brightness(1.1); }
.flash { position: absolute; inset: 0; background: white; z-index: 20; pointer-events: none; }
</style>
</style>
// function update() {
// const now = performance.now();
// const delta = (now - lastTime) / 1000;
// lastTime = now;
// uTime.value += delta;
// if (currentMixer) currentMixer.update(delta);
// if (!isPlaying) return;
// // --- MULTIPLIER COUNTDOWN ---
// if (multiplierTimer > 0) {
// multiplierTimer -= delta;
// if (multiplierTimer <= 0) {
// multiplierTimer = 0;
// scoreMultiplier = 1;
// }
// }
// // --- NEW MODULAR ENVIRONMENT CALL ---
// updateEnvironment(uTime.value, scene,
// { ambientLight, sunLight, headLight },
// { sun, moon }
// );
// if (gamePhase === "INSTRUCTIONS") {
// instructionTimer -= delta;
// if (instructionTimer <= 0) gamePhase = "PLAYING";
// return;
// }
// if (currentSpeed < CONFIG.MAX_SPEED) {
// currentSpeed += CONFIG.ACCELERATION * delta;
// }
// const moveStep = currentSpeed * delta;
// // --- BOOSTED DISTANCE SCORE ---
// // We apply the multiplier to the floor calculation
// score += Math.floor((currentSpeed / 40) * scoreMultiplier);
// if (cloudGroup) {
// // Moving at 40% speed (moveStep * 0.4) creates a nice parallax depth
// cloudGroup.children.forEach(cloud => {
// cloud.position.z += moveStep * 0.4;
// // Reset cloud position if it goes too far behind the camera
// if (cloud.position.z > 50) {
// cloud.position.z = -250;
// cloud.position.x = (Math.random() - 0.5) * 280; // Randomize X again for variety
// }
// });
// }
// if (lives <= 0) triggerGameOver();
// CHUNKS.forEach(chunk => {
// chunk.position.z += moveStep;
// if (chunk.position.z > CHUNK_SIZE) chunk.position.z -= CHUNK_SIZE * CHUNK_COUNT;
// });
// currX += (lane * CONFIG.lane - currX) * 0.18;
// playerAnchor.position.x = currX;
// if (isJumping) {
// jumpV -= CONFIG.grav;
// playerY += jumpV;
// if (playerY <= 0) {
// playerY = 0;
// isJumping = false;
// if (!isDying) swapCharacter("Running.glb");
// }
// }
// playerAnchor.position.y = playerY;
// // Update object positions
// worldObjects.forEach(obj => { obj.mesh.position.z += moveStep; });
// // Handle Collisions using the module
// worldObjects = handleCollisions(worldObjects, lane, playerY, triggerGameOver);
// // Filter out-of-bounds objects
// worldObjects = worldObjects.filter(obj => {
// const active = obj.mesh.position.z < 25;
// if (!active) scene.remove(obj.mesh);
// return active;
// });
// // Normal Obstacle Spawning
// spawnDistanceTracker += moveStep;
// if (spawnDistanceTracker >= SPAWN_INTERVAL) {
// spawn();
// spawnDistanceTracker = 0;
// }
// }

View File

@@ -18,7 +18,7 @@ export const createSketch = (state, texturesRef) => {
p.setup = async () => {
p.createCanvas(p.windowWidth, p.windowHeight);
const loadImg = (path) => new Promise(resolve => {
p.loadImage(path, img => resolve(img), () => resolve(null));
p.loadImage(`images/${path}`, img => resolve(img), () => resolve(null));
});
// Load into the reference object passed from App.svelte
@@ -115,6 +115,7 @@ export const createSketch = (state, texturesRef) => {
const renderPopups = (p, popups) => {
for (let i = popups.length - 1; i >= 0; i--) {
let pop = popups[i];
if (!pop || !pop.val) continue; // SKIP IF DATA IS MISSING
p.push();
p.fill(255, 230, 0, pop.opacity);
p.textSize(32 + (1 - pop.life) * 20);
@@ -134,15 +135,15 @@ export const createSketch = (state, texturesRef) => {
};
p.mousePressed = () => {
if (state.gamePhase !== "PLAYING") return;
for (let i = state.targets.length - 1; i >= 0; i--) {
let t = state.targets[i];
if (p.dist(p.mouseX, p.mouseY, t.x, t.y) < 60) {
state.onHit(t);
state.targets.splice(i, 1);
break;
}
}
// The View (p5) detects the raw click...
const clickedTarget = state.targets.find(t => p.dist(p.mouseX, p.mouseY, t.x, t.y) < 60);
if (clickedTarget) {
// ...but the Controller (state.onHit) decides what the rules are.
state.onHit(clickedTarget);
// Remove from View list
state.targets.splice(state.targets.indexOf(clickedTarget), 1);
}
};
};
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 819 KiB