feat: add Worlds 2-3, patch persistence, and zoom controls
- World 2 (Filtros): 8 levels teaching filters, resonance, LFO modulation, acid bass - World 3 (Envelopes): 8 levels teaching VCA, ADSR, pluck, tremolo, full synth lead - Star-based world unlock system (12 stars for W2, 24 for W3) - Level patch persistence: auto-saves player patches, restores on revisit - Google Maps-style zoom controls (+/−/reset) in both puzzle and sandbox views - Multi-world navigation in GameApp and WorldMap - Target audio now supports filter chain for World 2 levels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,43 +2,58 @@ import React, { useState, useCallback } from 'react';
|
||||
import WorldMap from './WorldMap.jsx';
|
||||
import PuzzleView from './PuzzleView.jsx';
|
||||
import { WORLD_1 } from './levels/world1.js';
|
||||
import { WORLD_2 } from './levels/world2.js';
|
||||
import { WORLD_3 } from './levels/world3.js';
|
||||
|
||||
const allWorlds = [WORLD_1, WORLD_2, WORLD_3];
|
||||
|
||||
export default function GameApp({ onSwitchToSandbox }) {
|
||||
const [view, setView] = useState('map'); // 'map' | 'puzzle'
|
||||
const [view, setView] = useState('map');
|
||||
const [currentLevel, setCurrentLevel] = useState(null);
|
||||
const [currentLevelIndex, setCurrentLevelIndex] = useState(0);
|
||||
const [currentWorld, setCurrentWorld] = useState(null);
|
||||
|
||||
const worldLevels = WORLD_1.levels;
|
||||
|
||||
const handleSelectLevel = useCallback((level) => {
|
||||
const idx = worldLevels.findIndex(l => l.id === level.id);
|
||||
const handleSelectLevel = useCallback((level, world) => {
|
||||
const idx = world.levels.findIndex(l => l.id === level.id);
|
||||
setCurrentLevel(level);
|
||||
setCurrentLevelIndex(idx);
|
||||
setCurrentWorld(world);
|
||||
setView('puzzle');
|
||||
}, [worldLevels]);
|
||||
}, []);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
setView('map');
|
||||
setCurrentLevel(null);
|
||||
setCurrentWorld(null);
|
||||
}, []);
|
||||
|
||||
const handleNextLevel = useCallback(() => {
|
||||
if (!currentWorld) return;
|
||||
const nextIdx = currentLevelIndex + 1;
|
||||
if (nextIdx < worldLevels.length) {
|
||||
setCurrentLevel(worldLevels[nextIdx]);
|
||||
if (nextIdx < currentWorld.levels.length) {
|
||||
setCurrentLevel(currentWorld.levels[nextIdx]);
|
||||
setCurrentLevelIndex(nextIdx);
|
||||
} else {
|
||||
setView('map');
|
||||
// Move to next world's first level if unlocked
|
||||
const worldIdx = allWorlds.findIndex(w => w.id === currentWorld.id);
|
||||
if (worldIdx < allWorlds.length - 1) {
|
||||
const nextWorld = allWorlds[worldIdx + 1];
|
||||
setCurrentWorld(nextWorld);
|
||||
setCurrentLevel(nextWorld.levels[0]);
|
||||
setCurrentLevelIndex(0);
|
||||
} else {
|
||||
setView('map');
|
||||
}
|
||||
}
|
||||
}, [currentLevelIndex, worldLevels]);
|
||||
}, [currentLevelIndex, currentWorld]);
|
||||
|
||||
if (view === 'puzzle' && currentLevel) {
|
||||
if (view === 'puzzle' && currentLevel && currentWorld) {
|
||||
return (
|
||||
<PuzzleView
|
||||
key={currentLevel.id}
|
||||
level={currentLevel}
|
||||
levelIndex={currentLevelIndex}
|
||||
worldLevels={worldLevels}
|
||||
worldLevels={currentWorld.levels}
|
||||
onBack={handleBack}
|
||||
onNextLevel={handleNextLevel}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user