Spaces:
Running
Running
Add 2 files
Browse files- index.html +453 -27
- prompts.txt +2 -1
index.html
CHANGED
@@ -46,6 +46,7 @@
|
|
46 |
#gameArea {
|
47 |
touch-action: manipulation;
|
48 |
background: radial-gradient(circle at center, #1a202c 0%, #111827 100%);
|
|
|
49 |
}
|
50 |
|
51 |
.kitchen-counter {
|
@@ -76,30 +77,181 @@
|
|
76 |
50% { box-shadow: 0 0 30px 15px rgba(255, 255, 255, 0.7); }
|
77 |
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
|
78 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
</style>
|
80 |
</head>
|
81 |
<body class="bg-gray-900 text-white font-sans overflow-hidden">
|
82 |
<!-- Start Screen -->
|
83 |
<div id="startScreen" class="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800 z-10 transition-opacity duration-500">
|
84 |
-
<div class="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
<h1 class="text-7xl font-bold mb-4 text-yellow-400 glow">π³ RHYTHM CHEF</h1>
|
86 |
<h2 class="text-4xl mb-6 text-yellow-300 glow">Beat Bites Deluxe</h2>
|
87 |
<div class="text-xl text-gray-300 max-w-md mx-auto">
|
88 |
Tap the ingredients in rhythm to cook up perfect dishes!
|
89 |
</div>
|
90 |
</div>
|
91 |
-
<button id="startButton" class="bg-gradient-to-r from-yellow-500 to-yellow-600 hover:from-yellow-400 hover:to-yellow-500 text-4xl text-gray-900 font-bold py-5 px-16 rounded-full transition-all transform hover:scale-105 shadow-lg glow">
|
92 |
START COOKING
|
93 |
</button>
|
94 |
-
<div class="absolute bottom-8 text-gray-400 text-sm">
|
95 |
Tap the beat when ingredients reach the counter
|
96 |
</div>
|
97 |
</div>
|
98 |
|
99 |
<!-- Game Screen -->
|
100 |
<div id="gameScreen" class="fixed inset-0 hidden flex flex-col">
|
|
|
|
|
|
|
101 |
<!-- Score Display -->
|
102 |
-
<div class="flex justify-between px-8 pt-6">
|
103 |
<div class="text-4xl font-bold score-glow">
|
104 |
π <span id="score">0</span>
|
105 |
</div>
|
@@ -113,6 +265,9 @@
|
|
113 |
<!-- Floating particles background -->
|
114 |
<div id="particles"></div>
|
115 |
|
|
|
|
|
|
|
116 |
<!-- Target Zone (Kitchen Counter) -->
|
117 |
<div id="targetZone" class="kitchen-counter absolute left-0 right-0 h-24 rounded-t-3xl flex items-center justify-center" style="bottom: 20%;">
|
118 |
<div class="w-full h-1 bg-yellow-300 bg-opacity-50 rounded-full mx-8"></div>
|
@@ -120,12 +275,18 @@
|
|
120 |
|
121 |
<!-- Feedback Text -->
|
122 |
<div id="feedbackText" class="absolute left-1/2 transform -translate-x-1/2 text-6xl font-bold text-center opacity-0" style="bottom: 30%;"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
</div>
|
124 |
</div>
|
125 |
|
126 |
<!-- End Screen -->
|
127 |
<div id="endScreen" class="fixed inset-0 hidden flex flex-col items-center justify-center bg-gray-900 bg-opacity-95 z-20">
|
128 |
-
<div class="text-center">
|
129 |
<h2 class="text-6xl font-bold mb-8 text-yellow-400 glow">DISH COMPLETE! π</h2>
|
130 |
<div class="text-5xl mb-8">Final Score: <span id="finalScore" class="text-yellow-300">0</span></div>
|
131 |
<div id="finalDish" class="text-9xl mb-12">π²</div>
|
@@ -136,6 +297,9 @@
|
|
136 |
</div>
|
137 |
</div>
|
138 |
|
|
|
|
|
|
|
139 |
<script>
|
140 |
// Game state
|
141 |
const gameState = {
|
@@ -172,7 +336,8 @@
|
|
172 |
currentSequenceIndex: 0,
|
173 |
audioContext: null,
|
174 |
metronomeInterval: null,
|
175 |
-
backgroundParticles: []
|
|
|
176 |
};
|
177 |
|
178 |
// DOM elements
|
@@ -191,6 +356,11 @@
|
|
191 |
const maxComboDisplay = document.getElementById('maxCombo');
|
192 |
const finalDishDisplay = document.getElementById('finalDish');
|
193 |
const particlesContainer = document.getElementById('particles');
|
|
|
|
|
|
|
|
|
|
|
194 |
|
195 |
// Initialize audio context
|
196 |
function initAudio() {
|
@@ -226,7 +396,7 @@
|
|
226 |
gameState.backgroundParticles = [];
|
227 |
|
228 |
// Create new particles
|
229 |
-
for (let i = 0; i <
|
230 |
createParticle(true);
|
231 |
}
|
232 |
}
|
@@ -236,20 +406,30 @@
|
|
236 |
particle.className = 'particle';
|
237 |
|
238 |
// Random properties
|
239 |
-
const size = Math.random() *
|
240 |
const x = Math.random() * 100;
|
241 |
const y = Math.random() * 100;
|
242 |
const duration = Math.random() * 20 + 10;
|
243 |
const delay = Math.random() * 5;
|
244 |
-
const opacity = Math.random() * 0.
|
245 |
const color = `hsl(${Math.random() * 60 + 20}, 70%, 60%)`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
|
247 |
-
particle.style.width = `${size}px`;
|
248 |
-
particle.style.height = `${size}px`;
|
249 |
particle.style.left = `${x}%`;
|
250 |
particle.style.top = `${y}%`;
|
251 |
-
particle.style.backgroundColor = color;
|
252 |
-
particle.style.borderRadius = '50%';
|
253 |
particle.style.opacity = opacity;
|
254 |
particle.style.animation = `float ${duration}s linear ${delay}s infinite`;
|
255 |
|
@@ -283,6 +463,10 @@
|
|
283 |
gameState.metronomeInterval = setInterval(() => {
|
284 |
playSound(880, 0.05, 'triangle');
|
285 |
nextBeatTime += secondsPerBeat;
|
|
|
|
|
|
|
|
|
286 |
}, secondsPerBeat * 1000);
|
287 |
}
|
288 |
|
@@ -305,6 +489,9 @@
|
|
305 |
cue.style.left = `${Math.random() * 60 + 20}%`;
|
306 |
cue.textContent = ingredient;
|
307 |
|
|
|
|
|
|
|
308 |
// Add to DOM and active cues array
|
309 |
gameArea.appendChild(cue);
|
310 |
const cueObj = {
|
@@ -325,6 +512,37 @@
|
|
325 |
return cueObj;
|
326 |
}
|
327 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
328 |
// Show feedback
|
329 |
function showFeedback(text, isGood) {
|
330 |
feedbackText.textContent = text;
|
@@ -336,6 +554,14 @@
|
|
336 |
// Create hit effect
|
337 |
if (isGood) {
|
338 |
createHitEffect(feedbackText.getBoundingClientRect());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
339 |
}
|
340 |
|
341 |
// Hide feedback after delay
|
@@ -348,14 +574,14 @@
|
|
348 |
function createHitEffect(rect) {
|
349 |
const colors = ['#FFD700', '#FF6347', '#7FFFD4', '#FF69B4', '#9370DB'];
|
350 |
|
351 |
-
for (let i = 0; i <
|
352 |
const particle = document.createElement('div');
|
353 |
particle.className = 'particle';
|
354 |
|
355 |
-
const size = Math.random() *
|
356 |
const color = colors[Math.floor(Math.random() * colors.length)];
|
357 |
const angle = Math.random() * Math.PI * 2;
|
358 |
-
const distance = Math.random() *
|
359 |
const duration = Math.random() * 1 + 0.5;
|
360 |
|
361 |
particle.style.width = `${size}px`;
|
@@ -377,6 +603,91 @@
|
|
377 |
setTimeout(() => particle.remove(), duration * 1000);
|
378 |
}, 10);
|
379 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
380 |
}
|
381 |
|
382 |
// Handle successful hit
|
@@ -387,12 +698,17 @@
|
|
387 |
|
388 |
// Visual feedback on the cue
|
389 |
cue.element.classList.add('perfect-hit');
|
390 |
-
cue.element.style.transform = 'scale(1.
|
391 |
|
392 |
setTimeout(() => {
|
393 |
cue.element.remove();
|
394 |
}, 200);
|
395 |
|
|
|
|
|
|
|
|
|
|
|
396 |
// Determine hit quality
|
397 |
let points = 0;
|
398 |
let feedback = '';
|
@@ -404,6 +720,13 @@
|
|
404 |
soundFreq = 1046.50; // C note
|
405 |
playSound(soundFreq, 0.2, 'sine');
|
406 |
playSound(soundFreq/2, 0.3, 'sine');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
} else if (Math.abs(accuracy) <= gameState.goodWindow) {
|
408 |
points = 60;
|
409 |
feedback = 'GREAT! οΏ½οΏ½';
|
@@ -429,6 +752,28 @@
|
|
429 |
// Show combo if > 1
|
430 |
if (gameState.combo > 1) {
|
431 |
comboContainer.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
432 |
}
|
433 |
|
434 |
// Show feedback
|
@@ -438,6 +783,41 @@
|
|
438 |
createIngredientEffect(cue);
|
439 |
}
|
440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 |
// Create ingredient transformation effect
|
442 |
function createIngredientEffect(cue) {
|
443 |
const rect = cue.element.getBoundingClientRect();
|
@@ -457,7 +837,7 @@
|
|
457 |
// Animate transformation
|
458 |
setTimeout(() => {
|
459 |
effect.style.transition = 'all 0.5s ease-out';
|
460 |
-
effect.style.transform = 'translateY(-
|
461 |
effect.style.opacity = '0';
|
462 |
|
463 |
// Change to a cooked version
|
@@ -495,6 +875,11 @@
|
|
495 |
setTimeout(() => {
|
496 |
cue.element.remove();
|
497 |
}, 500);
|
|
|
|
|
|
|
|
|
|
|
498 |
}
|
499 |
|
500 |
// Reset combo
|
@@ -509,22 +894,22 @@
|
|
509 |
// Create break effect
|
510 |
if (cue.element) {
|
511 |
const rect = cue.element.getBoundingClientRect();
|
512 |
-
for (let i = 0; i <
|
513 |
const piece = document.createElement('div');
|
514 |
piece.className = 'particle absolute text-xl';
|
515 |
piece.textContent = cue.ingredient;
|
516 |
piece.style.left = `${rect.left + rect.width/2}px`;
|
517 |
piece.style.top = `${rect.top + rect.height/2}px`;
|
518 |
-
piece.style.transform = `translate(${(Math.random() - 0.5) *
|
519 |
piece.style.opacity = '0.7';
|
520 |
-
piece.style.transition = 'all 0.
|
521 |
|
522 |
document.body.appendChild(piece);
|
523 |
|
524 |
setTimeout(() => {
|
525 |
piece.style.opacity = '0';
|
526 |
-
piece.style.transform += ` translateY(
|
527 |
-
setTimeout(() => piece.remove(),
|
528 |
}, 10);
|
529 |
}
|
530 |
}
|
@@ -543,6 +928,7 @@
|
|
543 |
scoreDisplay.textContent = '0';
|
544 |
comboDisplay.textContent = '0';
|
545 |
comboContainer.classList.add('hidden');
|
|
|
546 |
|
547 |
// Create background particles
|
548 |
createParticles();
|
@@ -607,18 +993,40 @@
|
|
607 |
endScreen.classList.remove('hidden');
|
608 |
|
609 |
// Celebration particles
|
610 |
-
for (let i = 0; i <
|
611 |
setTimeout(() => {
|
612 |
-
createParticle();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
613 |
}, i * 50);
|
614 |
}
|
615 |
}, gameState.cueSpeed * 1000);
|
616 |
}
|
617 |
|
618 |
// Handle tap input
|
619 |
-
function handleTap() {
|
620 |
if (!gameState.isPlaying) return;
|
621 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
622 |
const now = Date.now();
|
623 |
let closestCue = null;
|
624 |
let closestDiff = Infinity;
|
@@ -640,6 +1048,9 @@
|
|
640 |
targetZone.classList.remove('perfect-hit');
|
641 |
}, 300);
|
642 |
|
|
|
|
|
|
|
643 |
// Check if tap was close enough to any cue
|
644 |
if (closestCue && Math.abs(closestDiff) <= gameState.goodWindow * 2) {
|
645 |
handleHit(closestCue, closestDiff);
|
@@ -657,6 +1068,21 @@
|
|
657 |
function initGame() {
|
658 |
initAudio();
|
659 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
660 |
// Start button
|
661 |
startButton.addEventListener('click', () => {
|
662 |
startScreen.classList.add('opacity-0');
|
@@ -679,7 +1105,7 @@
|
|
679 |
// Touch support
|
680 |
gameArea.addEventListener('touchstart', (e) => {
|
681 |
e.preventDefault();
|
682 |
-
handleTap();
|
683 |
});
|
684 |
}
|
685 |
|
|
|
46 |
#gameArea {
|
47 |
touch-action: manipulation;
|
48 |
background: radial-gradient(circle at center, #1a202c 0%, #111827 100%);
|
49 |
+
overflow: hidden;
|
50 |
}
|
51 |
|
52 |
.kitchen-counter {
|
|
|
77 |
50% { box-shadow: 0 0 30px 15px rgba(255, 255, 255, 0.7); }
|
78 |
100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
|
79 |
}
|
80 |
+
|
81 |
+
/* New effects */
|
82 |
+
.ripple {
|
83 |
+
position: absolute;
|
84 |
+
border-radius: 50%;
|
85 |
+
background: rgba(255, 255, 255, 0.3);
|
86 |
+
transform: scale(0);
|
87 |
+
pointer-events: none;
|
88 |
+
animation: ripple 1s linear;
|
89 |
+
}
|
90 |
+
|
91 |
+
@keyframes ripple {
|
92 |
+
to {
|
93 |
+
transform: scale(4);
|
94 |
+
opacity: 0;
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
.floating-bg-element {
|
99 |
+
position: absolute;
|
100 |
+
opacity: 0.1;
|
101 |
+
animation: floatBg 30s linear infinite;
|
102 |
+
z-index: 0;
|
103 |
+
}
|
104 |
+
|
105 |
+
@keyframes floatBg {
|
106 |
+
0% { transform: translate(0, 0) rotate(0deg); }
|
107 |
+
25% { transform: translate(50px, 50px) rotate(5deg); }
|
108 |
+
50% { transform: translate(100px, 0) rotate(0deg); }
|
109 |
+
75% { transform: translate(50px, -50px) rotate(-5deg); }
|
110 |
+
100% { transform: translate(0, 0) rotate(0deg); }
|
111 |
+
}
|
112 |
+
|
113 |
+
.combo-explosion {
|
114 |
+
position: absolute;
|
115 |
+
width: 100%;
|
116 |
+
height: 100%;
|
117 |
+
pointer-events: none;
|
118 |
+
z-index: 5;
|
119 |
+
}
|
120 |
+
|
121 |
+
.streak-light {
|
122 |
+
position: absolute;
|
123 |
+
height: 2px;
|
124 |
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.8), transparent);
|
125 |
+
transform-origin: left center;
|
126 |
+
pointer-events: none;
|
127 |
+
z-index: 4;
|
128 |
+
}
|
129 |
+
|
130 |
+
.ingredient-trail {
|
131 |
+
position: absolute;
|
132 |
+
width: 10px;
|
133 |
+
height: 10px;
|
134 |
+
border-radius: 50%;
|
135 |
+
pointer-events: none;
|
136 |
+
z-index: 3;
|
137 |
+
}
|
138 |
+
|
139 |
+
.energy-wave {
|
140 |
+
position: absolute;
|
141 |
+
width: 300px;
|
142 |
+
height: 300px;
|
143 |
+
border-radius: 50%;
|
144 |
+
background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, transparent 70%);
|
145 |
+
transform: scale(0);
|
146 |
+
opacity: 1;
|
147 |
+
pointer-events: none;
|
148 |
+
animation: energyWave 1.5s ease-out forwards;
|
149 |
+
}
|
150 |
+
|
151 |
+
@keyframes energyWave {
|
152 |
+
0% { transform: scale(0); opacity: 1; }
|
153 |
+
100% { transform: scale(3); opacity: 0; }
|
154 |
+
}
|
155 |
+
|
156 |
+
.floating-sparkle {
|
157 |
+
position: absolute;
|
158 |
+
width: 6px;
|
159 |
+
height: 6px;
|
160 |
+
border-radius: 50%;
|
161 |
+
background-color: white;
|
162 |
+
pointer-events: none;
|
163 |
+
animation: sparkleFloat 2s ease-out forwards;
|
164 |
+
}
|
165 |
+
|
166 |
+
@keyframes sparkleFloat {
|
167 |
+
0% { transform: translate(0, 0) scale(1); opacity: 1; }
|
168 |
+
100% { transform: translate(random(100) - 50px, -100px) scale(0); opacity: 0; }
|
169 |
+
}
|
170 |
+
|
171 |
+
.screen-flash {
|
172 |
+
position: fixed;
|
173 |
+
top: 0;
|
174 |
+
left: 0;
|
175 |
+
width: 100%;
|
176 |
+
height: 100%;
|
177 |
+
background-color: white;
|
178 |
+
opacity: 0;
|
179 |
+
pointer-events: none;
|
180 |
+
z-index: 10;
|
181 |
+
}
|
182 |
+
|
183 |
+
.combo-streak {
|
184 |
+
position: absolute;
|
185 |
+
width: 100%;
|
186 |
+
height: 100%;
|
187 |
+
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
188 |
+
pointer-events: none;
|
189 |
+
z-index: 2;
|
190 |
+
}
|
191 |
+
|
192 |
+
.kitchen-light {
|
193 |
+
position: absolute;
|
194 |
+
width: 100%;
|
195 |
+
height: 100%;
|
196 |
+
background: radial-gradient(circle at 50% 30%, rgba(255,215,0,0.1) 0%, transparent 70%);
|
197 |
+
pointer-events: none;
|
198 |
+
z-index: 1;
|
199 |
+
animation: kitchenLightPulse 2s infinite alternate;
|
200 |
+
}
|
201 |
+
|
202 |
+
@keyframes kitchenLightPulse {
|
203 |
+
0% { opacity: 0.3; }
|
204 |
+
100% { opacity: 0.7; }
|
205 |
+
}
|
206 |
+
|
207 |
+
.perfect-ring {
|
208 |
+
position: absolute;
|
209 |
+
border-radius: 50%;
|
210 |
+
border: 3px solid rgba(255,255,255,0.8);
|
211 |
+
transform: scale(0);
|
212 |
+
pointer-events: none;
|
213 |
+
animation: perfectRing 0.8s ease-out forwards;
|
214 |
+
}
|
215 |
+
|
216 |
+
@keyframes perfectRing {
|
217 |
+
0% { transform: scale(0); opacity: 1; }
|
218 |
+
100% { transform: scale(3); opacity: 0; }
|
219 |
+
}
|
220 |
</style>
|
221 |
</head>
|
222 |
<body class="bg-gray-900 text-white font-sans overflow-hidden">
|
223 |
<!-- Start Screen -->
|
224 |
<div id="startScreen" class="fixed inset-0 flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800 z-10 transition-opacity duration-500">
|
225 |
+
<div id="startScreenBg" class="absolute inset-0 overflow-hidden">
|
226 |
+
<!-- Floating background elements -->
|
227 |
+
<div class="floating-bg-element text-6xl" style="top:20%; left:10%;">π³</div>
|
228 |
+
<div class="floating-bg-element text-6xl" style="top:30%; left:70%;">π₯</div>
|
229 |
+
<div class="floating-bg-element text-6xl" style="top:60%; left:20%;">π</div>
|
230 |
+
<div class="floating-bg-element text-6xl" style="top:70%; left:80%;">π£</div>
|
231 |
+
</div>
|
232 |
+
|
233 |
+
<div class="text-center mb-12 relative z-10">
|
234 |
<h1 class="text-7xl font-bold mb-4 text-yellow-400 glow">π³ RHYTHM CHEF</h1>
|
235 |
<h2 class="text-4xl mb-6 text-yellow-300 glow">Beat Bites Deluxe</h2>
|
236 |
<div class="text-xl text-gray-300 max-w-md mx-auto">
|
237 |
Tap the ingredients in rhythm to cook up perfect dishes!
|
238 |
</div>
|
239 |
</div>
|
240 |
+
<button id="startButton" class="bg-gradient-to-r from-yellow-500 to-yellow-600 hover:from-yellow-400 hover:to-yellow-500 text-4xl text-gray-900 font-bold py-5 px-16 rounded-full transition-all transform hover:scale-105 shadow-lg glow relative z-10">
|
241 |
START COOKING
|
242 |
</button>
|
243 |
+
<div class="absolute bottom-8 text-gray-400 text-sm z-10">
|
244 |
Tap the beat when ingredients reach the counter
|
245 |
</div>
|
246 |
</div>
|
247 |
|
248 |
<!-- Game Screen -->
|
249 |
<div id="gameScreen" class="fixed inset-0 hidden flex flex-col">
|
250 |
+
<!-- Kitchen ambient light -->
|
251 |
+
<div class="kitchen-light"></div>
|
252 |
+
|
253 |
<!-- Score Display -->
|
254 |
+
<div class="flex justify-between px-8 pt-6 relative z-10">
|
255 |
<div class="text-4xl font-bold score-glow">
|
256 |
π <span id="score">0</span>
|
257 |
</div>
|
|
|
265 |
<!-- Floating particles background -->
|
266 |
<div id="particles"></div>
|
267 |
|
268 |
+
<!-- Combo streak effect -->
|
269 |
+
<div id="comboStreak" class="combo-streak hidden"></div>
|
270 |
+
|
271 |
<!-- Target Zone (Kitchen Counter) -->
|
272 |
<div id="targetZone" class="kitchen-counter absolute left-0 right-0 h-24 rounded-t-3xl flex items-center justify-center" style="bottom: 20%;">
|
273 |
<div class="w-full h-1 bg-yellow-300 bg-opacity-50 rounded-full mx-8"></div>
|
|
|
275 |
|
276 |
<!-- Feedback Text -->
|
277 |
<div id="feedbackText" class="absolute left-1/2 transform -translate-x-1/2 text-6xl font-bold text-center opacity-0" style="bottom: 30%;"></div>
|
278 |
+
|
279 |
+
<!-- Combo explosion container -->
|
280 |
+
<div id="comboExplosion" class="combo-explosion"></div>
|
281 |
+
|
282 |
+
<!-- Energy waves container -->
|
283 |
+
<div id="energyWaves"></div>
|
284 |
</div>
|
285 |
</div>
|
286 |
|
287 |
<!-- End Screen -->
|
288 |
<div id="endScreen" class="fixed inset-0 hidden flex flex-col items-center justify-center bg-gray-900 bg-opacity-95 z-20">
|
289 |
+
<div class="text-center relative">
|
290 |
<h2 class="text-6xl font-bold mb-8 text-yellow-400 glow">DISH COMPLETE! π</h2>
|
291 |
<div class="text-5xl mb-8">Final Score: <span id="finalScore" class="text-yellow-300">0</span></div>
|
292 |
<div id="finalDish" class="text-9xl mb-12">π²</div>
|
|
|
297 |
</div>
|
298 |
</div>
|
299 |
|
300 |
+
<!-- Screen flash effect -->
|
301 |
+
<div id="screenFlash" class="screen-flash"></div>
|
302 |
+
|
303 |
<script>
|
304 |
// Game state
|
305 |
const gameState = {
|
|
|
336 |
currentSequenceIndex: 0,
|
337 |
audioContext: null,
|
338 |
metronomeInterval: null,
|
339 |
+
backgroundParticles: [],
|
340 |
+
lastTapPosition: { x: 0, y: 0 }
|
341 |
};
|
342 |
|
343 |
// DOM elements
|
|
|
356 |
const maxComboDisplay = document.getElementById('maxCombo');
|
357 |
const finalDishDisplay = document.getElementById('finalDish');
|
358 |
const particlesContainer = document.getElementById('particles');
|
359 |
+
const comboExplosion = document.getElementById('comboExplosion');
|
360 |
+
const comboStreak = document.getElementById('comboStreak');
|
361 |
+
const energyWaves = document.getElementById('energyWaves');
|
362 |
+
const screenFlash = document.getElementById('screenFlash');
|
363 |
+
const startScreenBg = document.getElementById('startScreenBg');
|
364 |
|
365 |
// Initialize audio context
|
366 |
function initAudio() {
|
|
|
396 |
gameState.backgroundParticles = [];
|
397 |
|
398 |
// Create new particles
|
399 |
+
for (let i = 0; i < 50; i++) {
|
400 |
createParticle(true);
|
401 |
}
|
402 |
}
|
|
|
406 |
particle.className = 'particle';
|
407 |
|
408 |
// Random properties
|
409 |
+
const size = Math.random() * 10 + 2;
|
410 |
const x = Math.random() * 100;
|
411 |
const y = Math.random() * 100;
|
412 |
const duration = Math.random() * 20 + 10;
|
413 |
const delay = Math.random() * 5;
|
414 |
+
const opacity = Math.random() * 0.5 + 0.1;
|
415 |
const color = `hsl(${Math.random() * 60 + 20}, 70%, 60%)`;
|
416 |
+
const shape = Math.random() > 0.7 ? 'text' : 'circle';
|
417 |
+
|
418 |
+
if (shape === 'text') {
|
419 |
+
const emojis = ['β¨', 'π', 'β', 'β‘', 'π«', 'π₯', 'π³', 'π₯', 'π²'];
|
420 |
+
particle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
|
421 |
+
particle.style.fontSize = `${size}px`;
|
422 |
+
particle.style.width = 'auto';
|
423 |
+
particle.style.height = 'auto';
|
424 |
+
} else {
|
425 |
+
particle.style.width = `${size}px`;
|
426 |
+
particle.style.height = `${size}px`;
|
427 |
+
particle.style.backgroundColor = color;
|
428 |
+
particle.style.borderRadius = '50%';
|
429 |
+
}
|
430 |
|
|
|
|
|
431 |
particle.style.left = `${x}%`;
|
432 |
particle.style.top = `${y}%`;
|
|
|
|
|
433 |
particle.style.opacity = opacity;
|
434 |
particle.style.animation = `float ${duration}s linear ${delay}s infinite`;
|
435 |
|
|
|
463 |
gameState.metronomeInterval = setInterval(() => {
|
464 |
playSound(880, 0.05, 'triangle');
|
465 |
nextBeatTime += secondsPerBeat;
|
466 |
+
|
467 |
+
// Visual metronome effect
|
468 |
+
createEnergyWave(targetZone.getBoundingClientRect().left + targetZone.offsetWidth / 2,
|
469 |
+
targetZone.getBoundingClientRect().top + targetZone.offsetHeight / 2);
|
470 |
}, secondsPerBeat * 1000);
|
471 |
}
|
472 |
|
|
|
489 |
cue.style.left = `${Math.random() * 60 + 20}%`;
|
490 |
cue.textContent = ingredient;
|
491 |
|
492 |
+
// Add trail effect
|
493 |
+
createIngredientTrail(cue);
|
494 |
+
|
495 |
// Add to DOM and active cues array
|
496 |
gameArea.appendChild(cue);
|
497 |
const cueObj = {
|
|
|
512 |
return cueObj;
|
513 |
}
|
514 |
|
515 |
+
// Create ingredient trail effect
|
516 |
+
function createIngredientTrail(cue) {
|
517 |
+
const trailInterval = setInterval(() => {
|
518 |
+
if (!cue.parentElement) {
|
519 |
+
clearInterval(trailInterval);
|
520 |
+
return;
|
521 |
+
}
|
522 |
+
|
523 |
+
const rect = cue.getBoundingClientRect();
|
524 |
+
const trail = document.createElement('div');
|
525 |
+
trail.className = 'ingredient-trail';
|
526 |
+
trail.style.left = `${rect.left + rect.width / 2}px`;
|
527 |
+
trail.style.top = `${rect.top + rect.height / 2}px`;
|
528 |
+
trail.style.backgroundColor = `hsl(${Math.random() * 60 + 20}, 80%, 60%)`;
|
529 |
+
trail.style.opacity = Math.random() * 0.6 + 0.2;
|
530 |
+
trail.style.transform = `scale(${Math.random() * 0.5 + 0.5})`;
|
531 |
+
|
532 |
+
document.body.appendChild(trail);
|
533 |
+
|
534 |
+
setTimeout(() => {
|
535 |
+
trail.style.transition = 'all 0.5s ease-out';
|
536 |
+
trail.style.opacity = '0';
|
537 |
+
trail.style.transform = 'scale(0)';
|
538 |
+
setTimeout(() => trail.remove(), 500);
|
539 |
+
}, 10);
|
540 |
+
}, 50);
|
541 |
+
|
542 |
+
// Clean up interval when cue is removed
|
543 |
+
cue.dataset.trailInterval = trailInterval;
|
544 |
+
}
|
545 |
+
|
546 |
// Show feedback
|
547 |
function showFeedback(text, isGood) {
|
548 |
feedbackText.textContent = text;
|
|
|
554 |
// Create hit effect
|
555 |
if (isGood) {
|
556 |
createHitEffect(feedbackText.getBoundingClientRect());
|
557 |
+
|
558 |
+
// Add ripple effect
|
559 |
+
createRippleEffect(gameState.lastTapPosition.x, gameState.lastTapPosition.y);
|
560 |
+
|
561 |
+
// Add perfect ring for perfect hits
|
562 |
+
if (text.includes('PERFECT')) {
|
563 |
+
createPerfectRing(gameState.lastTapPosition.x, gameState.lastTapPosition.y);
|
564 |
+
}
|
565 |
}
|
566 |
|
567 |
// Hide feedback after delay
|
|
|
574 |
function createHitEffect(rect) {
|
575 |
const colors = ['#FFD700', '#FF6347', '#7FFFD4', '#FF69B4', '#9370DB'];
|
576 |
|
577 |
+
for (let i = 0; i < 25; i++) {
|
578 |
const particle = document.createElement('div');
|
579 |
particle.className = 'particle';
|
580 |
|
581 |
+
const size = Math.random() * 16 + 4;
|
582 |
const color = colors[Math.floor(Math.random() * colors.length)];
|
583 |
const angle = Math.random() * Math.PI * 2;
|
584 |
+
const distance = Math.random() * 100 + 30;
|
585 |
const duration = Math.random() * 1 + 0.5;
|
586 |
|
587 |
particle.style.width = `${size}px`;
|
|
|
603 |
setTimeout(() => particle.remove(), duration * 1000);
|
604 |
}, 10);
|
605 |
}
|
606 |
+
|
607 |
+
// Add floating sparkles
|
608 |
+
for (let i = 0; i < 10; i++) {
|
609 |
+
createFloatingSparkle(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
610 |
+
}
|
611 |
+
}
|
612 |
+
|
613 |
+
// Create ripple effect
|
614 |
+
function createRippleEffect(x, y) {
|
615 |
+
const ripple = document.createElement('div');
|
616 |
+
ripple.className = 'ripple';
|
617 |
+
ripple.style.left = `${x}px`;
|
618 |
+
ripple.style.top = `${y}px`;
|
619 |
+
ripple.style.width = '50px';
|
620 |
+
ripple.style.height = '50px';
|
621 |
+
ripple.style.backgroundColor = `rgba(255, 255, 255, ${Math.random() * 0.2 + 0.1})`;
|
622 |
+
|
623 |
+
document.body.appendChild(ripple);
|
624 |
+
|
625 |
+
setTimeout(() => {
|
626 |
+
ripple.remove();
|
627 |
+
}, 1000);
|
628 |
+
}
|
629 |
+
|
630 |
+
// Create perfect ring effect
|
631 |
+
function createPerfectRing(x, y) {
|
632 |
+
const ring = document.createElement('div');
|
633 |
+
ring.className = 'perfect-ring';
|
634 |
+
ring.style.left = `${x}px`;
|
635 |
+
ring.style.top = `${y}px`;
|
636 |
+
ring.style.width = '50px';
|
637 |
+
ring.style.height = '50px';
|
638 |
+
|
639 |
+
document.body.appendChild(ring);
|
640 |
+
|
641 |
+
setTimeout(() => {
|
642 |
+
ring.remove();
|
643 |
+
}, 800);
|
644 |
+
}
|
645 |
+
|
646 |
+
// Create floating sparkle
|
647 |
+
function createFloatingSparkle(x, y) {
|
648 |
+
const sparkle = document.createElement('div');
|
649 |
+
sparkle.className = 'floating-sparkle';
|
650 |
+
sparkle.style.left = `${x}px`;
|
651 |
+
sparkle.style.top = `${y}px`;
|
652 |
+
sparkle.style.backgroundColor = `hsl(${Math.random() * 60 + 20}, 100%, 80%)`;
|
653 |
+
|
654 |
+
document.body.appendChild(sparkle);
|
655 |
+
|
656 |
+
setTimeout(() => {
|
657 |
+
sparkle.remove();
|
658 |
+
}, 2000);
|
659 |
+
}
|
660 |
+
|
661 |
+
// Create energy wave
|
662 |
+
function createEnergyWave(x, y) {
|
663 |
+
const wave = document.createElement('div');
|
664 |
+
wave.className = 'energy-wave';
|
665 |
+
wave.style.left = `${x - 150}px`;
|
666 |
+
wave.style.top = `${y - 150}px`;
|
667 |
+
|
668 |
+
energyWaves.appendChild(wave);
|
669 |
+
|
670 |
+
setTimeout(() => {
|
671 |
+
wave.remove();
|
672 |
+
}, 1500);
|
673 |
+
}
|
674 |
+
|
675 |
+
// Create streak light effect
|
676 |
+
function createStreakLight(x, y, angle, length) {
|
677 |
+
const light = document.createElement('div');
|
678 |
+
light.className = 'streak-light';
|
679 |
+
light.style.left = `${x}px`;
|
680 |
+
light.style.top = `${y}px`;
|
681 |
+
light.style.width = `${length}px`;
|
682 |
+
light.style.transform = `rotate(${angle}rad)`;
|
683 |
+
|
684 |
+
document.body.appendChild(light);
|
685 |
+
|
686 |
+
setTimeout(() => {
|
687 |
+
light.style.opacity = '0';
|
688 |
+
light.style.transition = 'opacity 0.5s ease-out';
|
689 |
+
setTimeout(() => light.remove(), 500);
|
690 |
+
}, 10);
|
691 |
}
|
692 |
|
693 |
// Handle successful hit
|
|
|
698 |
|
699 |
// Visual feedback on the cue
|
700 |
cue.element.classList.add('perfect-hit');
|
701 |
+
cue.element.style.transform = 'scale(1.5)';
|
702 |
|
703 |
setTimeout(() => {
|
704 |
cue.element.remove();
|
705 |
}, 200);
|
706 |
|
707 |
+
// Clear trail interval
|
708 |
+
if (cue.element.dataset.trailInterval) {
|
709 |
+
clearInterval(parseInt(cue.element.dataset.trailInterval));
|
710 |
+
}
|
711 |
+
|
712 |
// Determine hit quality
|
713 |
let points = 0;
|
714 |
let feedback = '';
|
|
|
720 |
soundFreq = 1046.50; // C note
|
721 |
playSound(soundFreq, 0.2, 'sine');
|
722 |
playSound(soundFreq/2, 0.3, 'sine');
|
723 |
+
|
724 |
+
// Screen flash for perfect hits
|
725 |
+
screenFlash.style.opacity = '0.3';
|
726 |
+
screenFlash.style.transition = 'opacity 0.3s ease-out';
|
727 |
+
setTimeout(() => {
|
728 |
+
screenFlash.style.opacity = '0';
|
729 |
+
}, 300);
|
730 |
} else if (Math.abs(accuracy) <= gameState.goodWindow) {
|
731 |
points = 60;
|
732 |
feedback = 'GREAT! οΏ½οΏ½';
|
|
|
752 |
// Show combo if > 1
|
753 |
if (gameState.combo > 1) {
|
754 |
comboContainer.classList.remove('hidden');
|
755 |
+
|
756 |
+
// Combo streak effect
|
757 |
+
if (gameState.combo % 5 === 0) {
|
758 |
+
comboStreak.classList.remove('hidden');
|
759 |
+
setTimeout(() => {
|
760 |
+
comboStreak.classList.add('hidden');
|
761 |
+
}, 300);
|
762 |
+
|
763 |
+
// Combo explosion for every 5 hits
|
764 |
+
if (gameState.combo % 10 === 0) {
|
765 |
+
createComboExplosion();
|
766 |
+
}
|
767 |
+
}
|
768 |
+
|
769 |
+
// Streak lights for high combos
|
770 |
+
if (gameState.combo > 3) {
|
771 |
+
for (let i = 0; i < 3; i++) {
|
772 |
+
const angle = Math.random() * Math.PI * 2;
|
773 |
+
const length = Math.random() * 200 + 100;
|
774 |
+
createStreakLight(gameState.lastTapPosition.x, gameState.lastTapPosition.y, angle, length);
|
775 |
+
}
|
776 |
+
}
|
777 |
}
|
778 |
|
779 |
// Show feedback
|
|
|
783 |
createIngredientEffect(cue);
|
784 |
}
|
785 |
|
786 |
+
// Create combo explosion effect
|
787 |
+
function createComboExplosion() {
|
788 |
+
comboExplosion.innerHTML = '';
|
789 |
+
|
790 |
+
for (let i = 0; i < 30; i++) {
|
791 |
+
const particle = document.createElement('div');
|
792 |
+
particle.className = 'particle';
|
793 |
+
|
794 |
+
const size = Math.random() * 20 + 10;
|
795 |
+
const color = `hsl(${Math.random() * 60 + 20}, 100%, 70%)`;
|
796 |
+
const angle = Math.random() * Math.PI * 2;
|
797 |
+
const distance = Math.random() * 300 + 100;
|
798 |
+
const duration = Math.random() * 1 + 0.5;
|
799 |
+
|
800 |
+
particle.style.width = `${size}px`;
|
801 |
+
particle.style.height = `${size}px`;
|
802 |
+
particle.style.left = '50%';
|
803 |
+
particle.style.top = '50%';
|
804 |
+
particle.style.backgroundColor = color;
|
805 |
+
particle.style.borderRadius = '50%';
|
806 |
+
particle.style.opacity = '0.8';
|
807 |
+
particle.style.transform = `translate(-50%, -50%) translate(${Math.cos(angle) * distance}px, ${Math.sin(angle) * distance}px)`;
|
808 |
+
particle.style.transition = `all ${duration}s ease-out`;
|
809 |
+
particle.style.boxShadow = `0 0 ${size/2}px ${color}`;
|
810 |
+
|
811 |
+
comboExplosion.appendChild(particle);
|
812 |
+
|
813 |
+
setTimeout(() => {
|
814 |
+
particle.style.opacity = '0';
|
815 |
+
particle.style.transform += ` scale(0.5)`;
|
816 |
+
setTimeout(() => particle.remove(), duration * 1000);
|
817 |
+
}, 10);
|
818 |
+
}
|
819 |
+
}
|
820 |
+
|
821 |
// Create ingredient transformation effect
|
822 |
function createIngredientEffect(cue) {
|
823 |
const rect = cue.element.getBoundingClientRect();
|
|
|
837 |
// Animate transformation
|
838 |
setTimeout(() => {
|
839 |
effect.style.transition = 'all 0.5s ease-out';
|
840 |
+
effect.style.transform = 'translateY(-50px) scale(2)';
|
841 |
effect.style.opacity = '0';
|
842 |
|
843 |
// Change to a cooked version
|
|
|
875 |
setTimeout(() => {
|
876 |
cue.element.remove();
|
877 |
}, 500);
|
878 |
+
|
879 |
+
// Clear trail interval
|
880 |
+
if (cue.element.dataset.trailInterval) {
|
881 |
+
clearInterval(parseInt(cue.element.dataset.trailInterval));
|
882 |
+
}
|
883 |
}
|
884 |
|
885 |
// Reset combo
|
|
|
894 |
// Create break effect
|
895 |
if (cue.element) {
|
896 |
const rect = cue.element.getBoundingClientRect();
|
897 |
+
for (let i = 0; i < 8; i++) {
|
898 |
const piece = document.createElement('div');
|
899 |
piece.className = 'particle absolute text-xl';
|
900 |
piece.textContent = cue.ingredient;
|
901 |
piece.style.left = `${rect.left + rect.width/2}px`;
|
902 |
piece.style.top = `${rect.top + rect.height/2}px`;
|
903 |
+
piece.style.transform = `translate(${(Math.random() - 0.5) * 80}px, ${(Math.random() - 0.5) * 80}px) rotate(${Math.random() * 360}deg)`;
|
904 |
piece.style.opacity = '0.7';
|
905 |
+
piece.style.transition = 'all 0.8s ease-out';
|
906 |
|
907 |
document.body.appendChild(piece);
|
908 |
|
909 |
setTimeout(() => {
|
910 |
piece.style.opacity = '0';
|
911 |
+
piece.style.transform += ` translateY(50px)`;
|
912 |
+
setTimeout(() => piece.remove(), 800);
|
913 |
}, 10);
|
914 |
}
|
915 |
}
|
|
|
928 |
scoreDisplay.textContent = '0';
|
929 |
comboDisplay.textContent = '0';
|
930 |
comboContainer.classList.add('hidden');
|
931 |
+
comboStreak.classList.add('hidden');
|
932 |
|
933 |
// Create background particles
|
934 |
createParticles();
|
|
|
993 |
endScreen.classList.remove('hidden');
|
994 |
|
995 |
// Celebration particles
|
996 |
+
for (let i = 0; i < 100; i++) {
|
997 |
setTimeout(() => {
|
998 |
+
const particle = createParticle();
|
999 |
+
particle.style.position = 'fixed';
|
1000 |
+
particle.style.left = `${Math.random() * 100}%`;
|
1001 |
+
particle.style.top = `${Math.random() * 100}%`;
|
1002 |
+
particle.style.fontSize = `${Math.random() * 30 + 20}px`;
|
1003 |
+
particle.style.opacity = '0.8';
|
1004 |
+
particle.style.animation = `float ${Math.random() * 3 + 2}s ease-out forwards`;
|
1005 |
+
|
1006 |
+
const emojis = ['β¨', 'π', 'β', 'β‘', 'π«', 'π₯', 'π', 'π', 'π₯³'];
|
1007 |
+
particle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
|
1008 |
+
|
1009 |
+
endScreen.appendChild(particle);
|
1010 |
+
|
1011 |
+
setTimeout(() => {
|
1012 |
+
particle.remove();
|
1013 |
+
}, 3000);
|
1014 |
}, i * 50);
|
1015 |
}
|
1016 |
}, gameState.cueSpeed * 1000);
|
1017 |
}
|
1018 |
|
1019 |
// Handle tap input
|
1020 |
+
function handleTap(e) {
|
1021 |
if (!gameState.isPlaying) return;
|
1022 |
|
1023 |
+
// Store tap position for effects
|
1024 |
+
const rect = gameArea.getBoundingClientRect();
|
1025 |
+
gameState.lastTapPosition = {
|
1026 |
+
x: (e.clientX || e.touches[0].clientX) - rect.left,
|
1027 |
+
y: (e.clientY || e.touches[0].clientY) - rect.top
|
1028 |
+
};
|
1029 |
+
|
1030 |
const now = Date.now();
|
1031 |
let closestCue = null;
|
1032 |
let closestDiff = Infinity;
|
|
|
1048 |
targetZone.classList.remove('perfect-hit');
|
1049 |
}, 300);
|
1050 |
|
1051 |
+
// Create energy wave at tap position
|
1052 |
+
createEnergyWave(gameState.lastTapPosition.x, gameState.lastTapPosition.y);
|
1053 |
+
|
1054 |
// Check if tap was close enough to any cue
|
1055 |
if (closestCue && Math.abs(closestDiff) <= gameState.goodWindow * 2) {
|
1056 |
handleHit(closestCue, closestDiff);
|
|
|
1068 |
function initGame() {
|
1069 |
initAudio();
|
1070 |
|
1071 |
+
// Add floating elements to start screen
|
1072 |
+
for (let i = 0; i < 10; i++) {
|
1073 |
+
const element = document.createElement('div');
|
1074 |
+
element.className = 'floating-bg-element text-4xl';
|
1075 |
+
element.style.left = `${Math.random() * 100}%`;
|
1076 |
+
element.style.top = `${Math.random() * 100}%`;
|
1077 |
+
element.style.animationDuration = `${Math.random() * 30 + 20}s`;
|
1078 |
+
element.style.animationDelay = `${Math.random() * 10}s`;
|
1079 |
+
|
1080 |
+
const emojis = ['π³', 'π₯', 'π', 'π', 'π', 'π', 'πΏ', 'π§', 'π₯', 'π£'];
|
1081 |
+
element.textContent = emojis[Math.floor(Math.random() * emojis.length)];
|
1082 |
+
|
1083 |
+
startScreenBg.appendChild(element);
|
1084 |
+
}
|
1085 |
+
|
1086 |
// Start button
|
1087 |
startButton.addEventListener('click', () => {
|
1088 |
startScreen.classList.add('opacity-0');
|
|
|
1105 |
// Touch support
|
1106 |
gameArea.addEventListener('touchstart', (e) => {
|
1107 |
e.preventDefault();
|
1108 |
+
handleTap(e);
|
1109 |
});
|
1110 |
}
|
1111 |
|
prompts.txt
CHANGED
@@ -1,2 +1,3 @@
|
|
1 |
Okay, here is a detailed, implementation-grade plan for building an interactive demonstrator for the core game mechanics of **Rhythm Chef: Beat Bites**. This focuses on creating a minimal viable product (MVP) specifically for user feedback on the core rhythm interaction feel, keeping the codebase limited. **Assumptions:** * **Engine:** Unity (common choice for hypercasual, good for rapid prototyping). * **Language:** C#. * **Target Platform:** Android (easy for distributing APKs for testing). * **Input Focus:** Start with **Tap** only for simplicity. Hold/Swipe can be added later if the Tap feels good. **I. Demonstrator Objectives** 1. Implement the core rhythm interaction: spawning cues, moving cues, detecting timed player input within a target zone. 2. Provide immediate, clear visual and audio feedback for successful hits and misses. 3. Synchronize cue movement and required input timing to a consistent beat (can be a simple metronome track or internal timer). 4. Allow a user to play through a short, predefined sequence of cues. 5. Keep the scope strictly limited to the core mechanic loop for fast iteration and focused feedback. **II. Scope Definition** * **IN SCOPE:** * Single, non-scrolling conveyor belt or lane for cues. * One type of cue requiring a **Tap** input. * One clearly defined target zone. * Precise timing mechanism for cue spawning and hit detection. * Simple visual representation of cues (e.g., colored circles/squares representing ingredients/actions). * Visual feedback (e.g., color change, simple particle effect, text popup like "Perfect!"/"Miss!"). * Basic audio feedback (hit sound, miss sound, beat/metronome sound). * A predefined, short sequence (~15-30 seconds) of Tap cues. * Minimal UI: Start Button, potentially a simple score/combo counter (optional but helpful). * **OUT OF SCOPE:** * Multiple lanes or complex patterns. * Hold or Swipe input types. * Multiple cue types/actions (chop, fry, etc.). * Any meta-game (restaurant, currency, upgrades). * Scoring beyond basic hit/miss feedback (no stars, complex calculations). * Multiple songs or difficulty levels. * Polished art assets (use placeholders). * Advanced UI, menus, settings. * Saving/loading progress. * Performance optimization beyond basic functionality. **III. Key Components & Implementation Details** 1. **`GameManager.cs`** * **Purpose:** Controls the overall state, timing, and sequence playback. * **Properties:** * `float bpm`: Beats per minute for the rhythm. * `float cueSpeed`: Speed at which cues travel towards the target. * `float targetZonePosition`: Y-coordinate (or X if horizontal) of the target zone center. * `float spawnPosition`: Y-coordinate (or X) where cues appear. * `GameObject cuePrefab`: Prefab for the visual cue object. * `Transform cueSpawnPoint`: Transform where cues are instantiated. * `AudioSource metronomeSource`: Optional, for playing a beat sound. * `List<CueData> sequence`: Holds the predefined sequence of cues (timing). * `float songStartTime`: Time when the sequence began (using `AudioSettings.dspTime` or `Time.timeSinceLevelLoad`). * `int currentSequenceIndex`: Tracks the next cue to spawn. * `bool isPlaying`: Game state flag. * **Methods:** * `StartSequence()`: Initializes timing, resets index, sets `isPlaying = true`. Starts metronome if used. * `Update()`: * If `isPlaying`, check `sequence` if it's time to spawn the `currentSequenceIndex` cue based on `bpm` and `songStartTime`. * If spawning, instantiate `cuePrefab` at `cueSpawnPoint`, calculate its `targetTime` (arrival time at target zone), and pass necessary data to the cue's script. Increment `currentSequenceIndex`. * Handles end of sequence. * `StopSequence()`: Sets `isPlaying = false`. 2. **`CueData.cs` (or Struct)** * **Purpose:** Simple data structure to define a single cue in the sequence. * **Properties:** * `float beatTimestamp`: The beat number (e.g., 1, 1.5, 2) within the sequence when this cue should *hit* the target zone. * `CueType type`: Enum (e.g., `Tap`). (Initially only Tap needed). 3. **`CueObject.cs` (attached to `cuePrefab`)** * **Purpose:** Represents a single moving cue. * **Properties:** * `float speed`: Movement speed (set by `GameManager`). * `float targetTime`: The exact game time (`Time.timeSinceLevelLoad` or `dspTime`) this cue should ideally be hit. * `bool isHit`: Flag to prevent multiple hits. * `bool canBeHit`: Flag to indicate if it's currently within the hittable window around the target zone. * **Methods:** * `Initialize(float targetTime, float speed)`: Called by `GameManager` on spawn. * `Update()`: * Move the cue downwards (or towards the target) at `speed * Time.deltaTime`. * Check if the cue has passed the target zone *without* being hit and *after* its hittable window - trigger a Miss condition if so and destroy/disable self. * `OnTriggerEnter/Exit2D(Collider2D other)`: If using physics triggers for the target zone, set `canBeHit = true/false`. * `ProcessHit()`: Called by `InputHandler` when a hit is registered on this cue. Sets `isHit = true`, triggers success feedback, potentially disables the cue visually, and schedules destruction. 4. **`TargetZone.cs` (attached to a GameObject with a Collider2D)** * **Purpose:** Defines the area where input is registered and cues are evaluated. * **Properties:** * `float perfectWindow`: Time window (+/- seconds) for a perfect hit. * `float okWindow`: Time window (+/- seconds) for an acceptable (non-miss) hit. (Keep simple first: just Perfect/Miss). * **Methods:** * `OnTriggerEnter2D/Exit2D(Collider2D other)`: Detects `CueObject` entering/leaving the zone's collider. Could be used by `CueObject` to set its `canBeHit` flag. * **Note:** Hit timing validation logic might live more centrally in `InputHandler` or `GameManager` rather than distributed here. 5. **`InputHandler.cs`** * **Purpose:** Detects player input and checks if it corresponds to a hittable cue. * **Properties:** * `TargetZone targetZone`: Reference to the target zone script/object. * `LayerMask cueLayer`: Physics layer for cues. * **Methods:** * `Update()`: * Check for Tap input (`Input.GetMouseButtonDown(0)` or `Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began`). * If Tap detected: * Determine the current game time (`Time.timeSinceLevelLoad` or `AudioSettings.dspTime`). * Find the "closest" active `CueObject` that is currently `canBeHit` (e.g., using Physics2D overlap checks near the target zone, or iterating through active cues and checking position/timing). * If a potential cue is found: * Calculate `timeDifference = currentTime - cue.targetTime`. * If `Mathf.Abs(timeDifference) <= targetZone.perfectWindow`: * Call `cue.ProcessHit()`. * Trigger "Perfect" feedback via `FeedbackManager`. * Else (if outside window but maybe an OK window exists later): * Trigger "Miss" feedback via `FeedbackManager` (or OK if implementing). * Potentially destroy/disable the cue to prevent late hits. * Else (no cue was hittable): * Optional: Trigger a generic "Miss" feedback for tapping empty space. 6. **`FeedbackManager.cs` (Singleton or easily accessible)** * **Purpose:** Centralized handler for triggering visual/audio feedback. * **Properties:** * `GameObject perfectHitEffectPrefab`: Particle effect/animation for perfect hits. * `GameObject missEffectPrefab`: Effect for misses. * `AudioClip hitSound`: Sound for successful hit. * `AudioClip missSound`: Sound for miss/error. * `AudioSource feedbackAudioSource`: Source to play feedback sounds. * `TextMeshProUGUI feedbackText`: UI Text element to display "Perfect!"/"Miss!". * **Methods:** * `ShowHitFeedback(Vector3 position)`: Instantiate `perfectHitEffectPrefab` at `position`, play `hitSound`, show "Perfect!" text briefly. * `ShowMissFeedback(Vector3 position)`: Instantiate `missEffectPrefab` at `position`, play `missSound`, show "Miss!" text briefly. **IV. Timing Implementation (CRITICAL)** * **Consistency is Key:** Use a consistent time source. * **Option A (Simpler):** `Time.timeSinceLevelLoad`. Easier for basic logic but can drift slightly from audio. Good enough for a demonstrator. * **Option B (More Accurate):** `AudioSettings.dspTime`. Provides highly accurate timing synced with the audio engine. Requires careful handling, especially when scheduling future events based on audio playback. * **Calculations:** * Convert `bpm` to seconds per beat (`60f / bpm`). * Calculate cue `targetTime`: `songStartTime + cue.beatTimestamp * secondsPerBeat`. * Calculate spawn time: `targetTime - travelDuration`, where `travelDuration` is `distance / cueSpeed`. Spawn the cue at this calculated spawn time. **V. UI Implementation (Minimal)** * Canvas with: * `Button` to call `GameManager.StartSequence()`. * `TextMeshProUGUI` (optional) for Score/Combo (incremented in `FeedbackManager` on hit, reset on miss). * `TextMeshProUGUI` for "Perfect!"/"Miss!" feedback text (controlled by `FeedbackManager`). **VI. Placeholder Assets** * **Cue:** Simple white Circle or Square sprite. * **Target Zone:** Semi-transparent rectangle/line across the lane. * **Hit Effect:** Simple particle burst (e.g., Unity's default particle system emitting a few sparks) or a quick scaling/fading circle. * **Miss Effect:** Red flash or different particle effect. * **Audio:** * Metronome: Simple "tick" sound. * Hit Sound: Short, satisfying "click" or "pop". * Miss Sound: Dull "thud" or "buzz". **VII. Build & Test Plan** 1. Implement core components (`GameManager`, `CueObject`, `InputHandler`, `TargetZone`, `FeedbackManager`). 2. Define a simple `sequence` in `GameManager` (e.g., 10 taps on consecutive beats). 3. Use precise timing based on `Time.timeSinceLevelLoad` initially. 4. Integrate basic visual and audio feedback. 5. Test internally: Does it *feel* responsive? Is the timing right? 6. Refine timing windows (`perfectWindow`) based on feel. 7. Build an Android APK. 8. Distribute APK to testers. 9. Gather feedback specifically on: * Timing sensitivity (too strict, too lenient?). * Clarity of visual cues. * Effectiveness of feedback (did they know if they hit/missed?). * Overall satisfaction/fun of the core interaction. **VIII. Success Criteria for Demonstrator** * The demonstrator runs smoothly on a target Android device. * Cues spawn and travel predictably, synchronized with the beat. * User taps near the target zone trigger immediate hit/miss feedback. * Feedback (visual and audio) is clear and correctly corresponds to input timing accuracy. * Testers can understand the core mechanic and provide feedback on its "feel" within 5 minutes of playing. * The codebase is minimal and focused solely on the described scope. Okay, here's an ASCII art representation of the UI and user flow for the **Rhythm Chef: Beat Bites** core mechanics demonstrator. **1. User Flow Diagram** ```ascii +-------------------+ +----------------------+ +----------------------+ | Start Screen | ---+->| Gameplay Loop |---+->| End Screen (Simple)| | (Title, [Start]) | User | (Cues, Target, Input)| User | (Score?, [Restart])| +-------------------+ Taps | | Finishes Sequence +----------------------+ Start +--<-------------------+ Or Fails | (Optional) | | | | Back to Start Screen | +----------------------+ +----------------------+ ``` **2. UI Mockups (ASCII Art)** **A. Start Screen** ```ascii +-----------------------------------------+ | | | // RHYTHM CHEF: BEAT BITES // | | (Core Demo) | | | | | | | | | | +-----------+ | | | [ START ]| | | +-----------+ | | | | | +-----------------------------------------+ User taps [ START ] --> Transitions to Gameplay Screen ``` **B. Gameplay Screen (Mid-Sequence)** * Imagine cues `(*)` moving downwards `v` towards the `TARGET ZONE`. * The player needs to tap when a cue is inside the `TARGET ZONE`. ```ascii +-----------------------------------------+ | SCORE: 005 COMBO: 3x | <--- Optional Score/Combo Display |-----------------------------------------| | | | | | (*) | <--- Incoming Cue 1 | v | | | | | | (*) | <--- Incoming Cue 2 | v | | | | ===================================== | \ | | TARGET ZONE | | <--- The Area to Tap In | ===================================== | / | v | | (*) | <--- Cue that just passed (or was missed) | | | | | | |-----------------------------------------| | FEEDBACK: | <--- Area for text feedback +-----------------------------------------+ ``` **C. Gameplay Screen (Moment of Tap - Perfect Hit)** * Player taps as `(*)` enters the `TARGET ZONE`. * Visual & text feedback appears. ```ascii +-----------------------------------------+ | SCORE: 006 COMBO: 4x | <--- Score/Combo Updated |-----------------------------------------| | | | | | (*) | | v | | | | | | (*) | | v | | | | ===================================== | | | > > > PERFECT! < < < | | <--- Cue hit inside, feedback shown | ===================================== | | | | | (Effect) | <--- Visual effect (e.g., ASCII explosion *`*`) | | | | |-----------------------------------------| | FEEDBACK: Perfect! | <--- Text feedback updated +-----------------------------------------+ ``` **D. Gameplay Screen (Moment of Tap - Miss)** * Player taps too early/late, or misses a cue. ```ascii +-----------------------------------------+ | SCORE: 006 COMBO: 0x | <--- Combo Reset |-----------------------------------------| | | | | | (*) | | v | | | | | | (*) | | v | | | | ===================================== | | | TARGET ZONE | | | ===================================== | | v | | (X) | <--- Missed cue indication / effect | | | | |-----------------------------------------| | FEEDBACK: Miss! | <--- Text feedback updated +-----------------------------------------+ ``` **E. End Screen (Simple Demo End)** * Appears after the predefined sequence finishes. ```ascii +-----------------------------------------+ | | | SEQUENCE COMPLETE! | | | | (Final Score: 025) | <--- Optional final score | | | | | +-------------+ | | | [ RESTART ] | | | +-------------+ | | | | | +-----------------------------------------+ User taps [ RESTART ] --> Transitions back to Gameplay Screen (restarts sequence) (Or could go back to the Start Screen) ``` This ASCII representation outlines the essential visual elements and the flow between them for the core mechanics demonstrator, focusing on clarity for user feedback on the rhythm interaction. Okay, here are specific pixel-based dimensions and positions for the UI elements based on a **reference screen resolution of 1080x1920 pixels (Width x Height - Portrait Orientation)**. **Important Considerations:** * **Reference Only:** These are *fixed pixel values* for the specified reference resolution. In a real implementation (e.g., using Unity Canvas), you would use anchors, pivots, and potentially scaling components (like Canvas Scaler UI Scale Mode) to ensure the UI adapts correctly to different screen sizes and aspect ratios. * **Origin Point:** Assume the (0, 0) coordinate is the **bottom-left** corner of the screen, common in some UI systems, or **top-left** (common in others - I will specify based on typical Unity Canvas usage: **(0,0) is Bottom-Left, (1080, 1920) is Top-Right** unless stated otherwise). Positions usually refer to the object's **pivot point** (often the center, unless specified). * **Font Sizes:** Pixel sizes for fonts are approximate and depend heavily on the specific font file used. * **Actors:** Actors like Cues and Effects have positions that change dynamically or are instantiated at specific world/UI coordinates. --- **A. Start Screen (1080x1920)** * **Widget:** `Title Text` (e.g., "RHYTHM CHEF: BEAT BITES") * **Position (Pivot: Center):** (540, 1600) `(Center X, ~83% Height)` * **Dimensions:** Auto-sized by text, constrained if needed. * **Font Size:** 90px * **Anchor Preset (Unity):** Top-Center * **Widget:** `Subtitle Text` (e.g., "(Core Demo)") * **Position (Pivot: Center):** (540, 1480) `(Center X, below Title)` * **Dimensions:** Auto-sized by text. * **Font Size:** 40px * **Anchor Preset (Unity):** Top-Center * **Widget:** `Start Button` * **Position (Pivot: Center):** (540, 400) `(Center X, ~21% Height)` * **Dimensions:** 450px (Width) x 150px (Height) * **Anchor Preset (Unity):** Bottom-Center * **Widget:** `Start Button Text` (e.g., "[ START ]") * **Position:** Centered within the Start Button. * **Font Size:** 60px --- **B. Gameplay Screen (1080x1920)** * **Widget:** `Score Text Label` (e.g., "SCORE:") * **Position (Pivot: Top-Left):** (40, 1880) `(Padding Left, Padding Top)` * **Dimensions:** Auto-sized by text. * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Left * **Widget:** `Score Value Text` (e.g., "000") * **Position (Pivot: Top-Left):** (200, 1880) `(Right of Label, Same Top)` * **Dimensions:** Auto-sized by text (allow space for growth). * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Left * **Widget:** `Combo Text Label` (e.g., "COMBO:") * **Position (Pivot: Top-Right):** (900, 1880) `(Approx position, adjust based on Value width)` * **Dimensions:** Auto-sized by text. * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Right * **Widget:** `Combo Value Text` (e.g., "0x") * **Position (Pivot: Top-Right):** (1040, 1880) `(Padding Right, Same Top)` * **Dimensions:** Auto-sized by text (allow space for growth). * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Right * **Area:** `Cue Travel Area` (Logical, no visual widget) * **Bounds:** Approx X = Center (e.g., 540), Y from 1800 (Spawn) down to ~200 (Despawn below target). * **Widget:** `Target Zone Visual` (The horizontal bar indicator) * **Position (Pivot: Center):** (540, 400) `(Center X, Defined Y)` * **Dimensions:** 900px (Width) x 80px (Height) * **Anchor Preset (Unity):** Position manually or Bottom-Center with Y offset. * **Note:** The *logical* hit detection center Y might be exactly 400. The timing windows (`perfectWindow`, `okWindow`) define the tolerance around this point in *time*, not necessarily pixels vertically (though they are related via cue speed). * **Actor:** `Cue Object` (Visual representation) * **Position:** Dynamic. Spawns near Y=1800, travels downwards to Y=400 (target). X is typically centered (540). * **Dimensions:** 80px (Width) x 80px (Height) (Example, adjust for visual clarity) * **Widget:** `Feedback Text` (e.g., "Perfect!", "Miss!") * **Position (Pivot: Center):** (540, 600) `(Center X, Above Target Zone)` * **Dimensions:** Auto-sized by text. * **Font Size:** 70px * **Anchor Preset (Unity):** Position manually or relative to Target Zone. (Appears temporarily). * **Actor:** `Hit/Miss Feedback Effect` (Particles/Animation) * **Position:** Instantiated at the Cue's position *when* the hit/miss occurs (approx X=540, Y=400). * **Dimensions:** Dynamic based on the effect design (e.g., expands to 150x150px briefly). --- **C. End Screen (1080x1920)** * **Widget:** `Completion Text` (e.g., "SEQUENCE COMPLETE!") * **Position (Pivot: Center):** (540, 1200) `(Center X, ~62% Height)` * **Dimensions:** Auto-sized by text. * **Font Size:** 70px * **Anchor Preset (Unity):** Center * **Widget:** `Final Score Text` (Optional, e.g., "Final Score: 025") * **Position (Pivot: Center):** (540, 1050) `(Center X, below Completion text)` * **Dimensions:** Auto-sized by text. * **Font Size:** 50px * **Anchor Preset (Unity):** Center * **Widget:** `Restart Button` * **Position (Pivot: Center):** (540, 400) `(Center X, ~21% Height - same as Start Button)` * **Dimensions:** 450px (Width) x 150px (Height) * **Anchor Preset (Unity):** Bottom-Center * **Widget:** `Restart Button Text` (e.g., "[ RESTART ]") * **Position:** Centered within the Restart Button. * **Font Size:** 60px --- Remember to implement proper UI scaling and anchoring in your chosen game engine to handle various device resolutions effectively beyond this 1080x1920 reference.
|
2 |
-
Make the look and feel 100 times better, employ procedural graphics and emojis as sprites.
|
|
|
|
1 |
Okay, here is a detailed, implementation-grade plan for building an interactive demonstrator for the core game mechanics of **Rhythm Chef: Beat Bites**. This focuses on creating a minimal viable product (MVP) specifically for user feedback on the core rhythm interaction feel, keeping the codebase limited. **Assumptions:** * **Engine:** Unity (common choice for hypercasual, good for rapid prototyping). * **Language:** C#. * **Target Platform:** Android (easy for distributing APKs for testing). * **Input Focus:** Start with **Tap** only for simplicity. Hold/Swipe can be added later if the Tap feels good. **I. Demonstrator Objectives** 1. Implement the core rhythm interaction: spawning cues, moving cues, detecting timed player input within a target zone. 2. Provide immediate, clear visual and audio feedback for successful hits and misses. 3. Synchronize cue movement and required input timing to a consistent beat (can be a simple metronome track or internal timer). 4. Allow a user to play through a short, predefined sequence of cues. 5. Keep the scope strictly limited to the core mechanic loop for fast iteration and focused feedback. **II. Scope Definition** * **IN SCOPE:** * Single, non-scrolling conveyor belt or lane for cues. * One type of cue requiring a **Tap** input. * One clearly defined target zone. * Precise timing mechanism for cue spawning and hit detection. * Simple visual representation of cues (e.g., colored circles/squares representing ingredients/actions). * Visual feedback (e.g., color change, simple particle effect, text popup like "Perfect!"/"Miss!"). * Basic audio feedback (hit sound, miss sound, beat/metronome sound). * A predefined, short sequence (~15-30 seconds) of Tap cues. * Minimal UI: Start Button, potentially a simple score/combo counter (optional but helpful). * **OUT OF SCOPE:** * Multiple lanes or complex patterns. * Hold or Swipe input types. * Multiple cue types/actions (chop, fry, etc.). * Any meta-game (restaurant, currency, upgrades). * Scoring beyond basic hit/miss feedback (no stars, complex calculations). * Multiple songs or difficulty levels. * Polished art assets (use placeholders). * Advanced UI, menus, settings. * Saving/loading progress. * Performance optimization beyond basic functionality. **III. Key Components & Implementation Details** 1. **`GameManager.cs`** * **Purpose:** Controls the overall state, timing, and sequence playback. * **Properties:** * `float bpm`: Beats per minute for the rhythm. * `float cueSpeed`: Speed at which cues travel towards the target. * `float targetZonePosition`: Y-coordinate (or X if horizontal) of the target zone center. * `float spawnPosition`: Y-coordinate (or X) where cues appear. * `GameObject cuePrefab`: Prefab for the visual cue object. * `Transform cueSpawnPoint`: Transform where cues are instantiated. * `AudioSource metronomeSource`: Optional, for playing a beat sound. * `List<CueData> sequence`: Holds the predefined sequence of cues (timing). * `float songStartTime`: Time when the sequence began (using `AudioSettings.dspTime` or `Time.timeSinceLevelLoad`). * `int currentSequenceIndex`: Tracks the next cue to spawn. * `bool isPlaying`: Game state flag. * **Methods:** * `StartSequence()`: Initializes timing, resets index, sets `isPlaying = true`. Starts metronome if used. * `Update()`: * If `isPlaying`, check `sequence` if it's time to spawn the `currentSequenceIndex` cue based on `bpm` and `songStartTime`. * If spawning, instantiate `cuePrefab` at `cueSpawnPoint`, calculate its `targetTime` (arrival time at target zone), and pass necessary data to the cue's script. Increment `currentSequenceIndex`. * Handles end of sequence. * `StopSequence()`: Sets `isPlaying = false`. 2. **`CueData.cs` (or Struct)** * **Purpose:** Simple data structure to define a single cue in the sequence. * **Properties:** * `float beatTimestamp`: The beat number (e.g., 1, 1.5, 2) within the sequence when this cue should *hit* the target zone. * `CueType type`: Enum (e.g., `Tap`). (Initially only Tap needed). 3. **`CueObject.cs` (attached to `cuePrefab`)** * **Purpose:** Represents a single moving cue. * **Properties:** * `float speed`: Movement speed (set by `GameManager`). * `float targetTime`: The exact game time (`Time.timeSinceLevelLoad` or `dspTime`) this cue should ideally be hit. * `bool isHit`: Flag to prevent multiple hits. * `bool canBeHit`: Flag to indicate if it's currently within the hittable window around the target zone. * **Methods:** * `Initialize(float targetTime, float speed)`: Called by `GameManager` on spawn. * `Update()`: * Move the cue downwards (or towards the target) at `speed * Time.deltaTime`. * Check if the cue has passed the target zone *without* being hit and *after* its hittable window - trigger a Miss condition if so and destroy/disable self. * `OnTriggerEnter/Exit2D(Collider2D other)`: If using physics triggers for the target zone, set `canBeHit = true/false`. * `ProcessHit()`: Called by `InputHandler` when a hit is registered on this cue. Sets `isHit = true`, triggers success feedback, potentially disables the cue visually, and schedules destruction. 4. **`TargetZone.cs` (attached to a GameObject with a Collider2D)** * **Purpose:** Defines the area where input is registered and cues are evaluated. * **Properties:** * `float perfectWindow`: Time window (+/- seconds) for a perfect hit. * `float okWindow`: Time window (+/- seconds) for an acceptable (non-miss) hit. (Keep simple first: just Perfect/Miss). * **Methods:** * `OnTriggerEnter2D/Exit2D(Collider2D other)`: Detects `CueObject` entering/leaving the zone's collider. Could be used by `CueObject` to set its `canBeHit` flag. * **Note:** Hit timing validation logic might live more centrally in `InputHandler` or `GameManager` rather than distributed here. 5. **`InputHandler.cs`** * **Purpose:** Detects player input and checks if it corresponds to a hittable cue. * **Properties:** * `TargetZone targetZone`: Reference to the target zone script/object. * `LayerMask cueLayer`: Physics layer for cues. * **Methods:** * `Update()`: * Check for Tap input (`Input.GetMouseButtonDown(0)` or `Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began`). * If Tap detected: * Determine the current game time (`Time.timeSinceLevelLoad` or `AudioSettings.dspTime`). * Find the "closest" active `CueObject` that is currently `canBeHit` (e.g., using Physics2D overlap checks near the target zone, or iterating through active cues and checking position/timing). * If a potential cue is found: * Calculate `timeDifference = currentTime - cue.targetTime`. * If `Mathf.Abs(timeDifference) <= targetZone.perfectWindow`: * Call `cue.ProcessHit()`. * Trigger "Perfect" feedback via `FeedbackManager`. * Else (if outside window but maybe an OK window exists later): * Trigger "Miss" feedback via `FeedbackManager` (or OK if implementing). * Potentially destroy/disable the cue to prevent late hits. * Else (no cue was hittable): * Optional: Trigger a generic "Miss" feedback for tapping empty space. 6. **`FeedbackManager.cs` (Singleton or easily accessible)** * **Purpose:** Centralized handler for triggering visual/audio feedback. * **Properties:** * `GameObject perfectHitEffectPrefab`: Particle effect/animation for perfect hits. * `GameObject missEffectPrefab`: Effect for misses. * `AudioClip hitSound`: Sound for successful hit. * `AudioClip missSound`: Sound for miss/error. * `AudioSource feedbackAudioSource`: Source to play feedback sounds. * `TextMeshProUGUI feedbackText`: UI Text element to display "Perfect!"/"Miss!". * **Methods:** * `ShowHitFeedback(Vector3 position)`: Instantiate `perfectHitEffectPrefab` at `position`, play `hitSound`, show "Perfect!" text briefly. * `ShowMissFeedback(Vector3 position)`: Instantiate `missEffectPrefab` at `position`, play `missSound`, show "Miss!" text briefly. **IV. Timing Implementation (CRITICAL)** * **Consistency is Key:** Use a consistent time source. * **Option A (Simpler):** `Time.timeSinceLevelLoad`. Easier for basic logic but can drift slightly from audio. Good enough for a demonstrator. * **Option B (More Accurate):** `AudioSettings.dspTime`. Provides highly accurate timing synced with the audio engine. Requires careful handling, especially when scheduling future events based on audio playback. * **Calculations:** * Convert `bpm` to seconds per beat (`60f / bpm`). * Calculate cue `targetTime`: `songStartTime + cue.beatTimestamp * secondsPerBeat`. * Calculate spawn time: `targetTime - travelDuration`, where `travelDuration` is `distance / cueSpeed`. Spawn the cue at this calculated spawn time. **V. UI Implementation (Minimal)** * Canvas with: * `Button` to call `GameManager.StartSequence()`. * `TextMeshProUGUI` (optional) for Score/Combo (incremented in `FeedbackManager` on hit, reset on miss). * `TextMeshProUGUI` for "Perfect!"/"Miss!" feedback text (controlled by `FeedbackManager`). **VI. Placeholder Assets** * **Cue:** Simple white Circle or Square sprite. * **Target Zone:** Semi-transparent rectangle/line across the lane. * **Hit Effect:** Simple particle burst (e.g., Unity's default particle system emitting a few sparks) or a quick scaling/fading circle. * **Miss Effect:** Red flash or different particle effect. * **Audio:** * Metronome: Simple "tick" sound. * Hit Sound: Short, satisfying "click" or "pop". * Miss Sound: Dull "thud" or "buzz". **VII. Build & Test Plan** 1. Implement core components (`GameManager`, `CueObject`, `InputHandler`, `TargetZone`, `FeedbackManager`). 2. Define a simple `sequence` in `GameManager` (e.g., 10 taps on consecutive beats). 3. Use precise timing based on `Time.timeSinceLevelLoad` initially. 4. Integrate basic visual and audio feedback. 5. Test internally: Does it *feel* responsive? Is the timing right? 6. Refine timing windows (`perfectWindow`) based on feel. 7. Build an Android APK. 8. Distribute APK to testers. 9. Gather feedback specifically on: * Timing sensitivity (too strict, too lenient?). * Clarity of visual cues. * Effectiveness of feedback (did they know if they hit/missed?). * Overall satisfaction/fun of the core interaction. **VIII. Success Criteria for Demonstrator** * The demonstrator runs smoothly on a target Android device. * Cues spawn and travel predictably, synchronized with the beat. * User taps near the target zone trigger immediate hit/miss feedback. * Feedback (visual and audio) is clear and correctly corresponds to input timing accuracy. * Testers can understand the core mechanic and provide feedback on its "feel" within 5 minutes of playing. * The codebase is minimal and focused solely on the described scope. Okay, here's an ASCII art representation of the UI and user flow for the **Rhythm Chef: Beat Bites** core mechanics demonstrator. **1. User Flow Diagram** ```ascii +-------------------+ +----------------------+ +----------------------+ | Start Screen | ---+->| Gameplay Loop |---+->| End Screen (Simple)| | (Title, [Start]) | User | (Cues, Target, Input)| User | (Score?, [Restart])| +-------------------+ Taps | | Finishes Sequence +----------------------+ Start +--<-------------------+ Or Fails | (Optional) | | | | Back to Start Screen | +----------------------+ +----------------------+ ``` **2. UI Mockups (ASCII Art)** **A. Start Screen** ```ascii +-----------------------------------------+ | | | // RHYTHM CHEF: BEAT BITES // | | (Core Demo) | | | | | | | | | | +-----------+ | | | [ START ]| | | +-----------+ | | | | | +-----------------------------------------+ User taps [ START ] --> Transitions to Gameplay Screen ``` **B. Gameplay Screen (Mid-Sequence)** * Imagine cues `(*)` moving downwards `v` towards the `TARGET ZONE`. * The player needs to tap when a cue is inside the `TARGET ZONE`. ```ascii +-----------------------------------------+ | SCORE: 005 COMBO: 3x | <--- Optional Score/Combo Display |-----------------------------------------| | | | | | (*) | <--- Incoming Cue 1 | v | | | | | | (*) | <--- Incoming Cue 2 | v | | | | ===================================== | \ | | TARGET ZONE | | <--- The Area to Tap In | ===================================== | / | v | | (*) | <--- Cue that just passed (or was missed) | | | | | | |-----------------------------------------| | FEEDBACK: | <--- Area for text feedback +-----------------------------------------+ ``` **C. Gameplay Screen (Moment of Tap - Perfect Hit)** * Player taps as `(*)` enters the `TARGET ZONE`. * Visual & text feedback appears. ```ascii +-----------------------------------------+ | SCORE: 006 COMBO: 4x | <--- Score/Combo Updated |-----------------------------------------| | | | | | (*) | | v | | | | | | (*) | | v | | | | ===================================== | | | > > > PERFECT! < < < | | <--- Cue hit inside, feedback shown | ===================================== | | | | | (Effect) | <--- Visual effect (e.g., ASCII explosion *`*`) | | | | |-----------------------------------------| | FEEDBACK: Perfect! | <--- Text feedback updated +-----------------------------------------+ ``` **D. Gameplay Screen (Moment of Tap - Miss)** * Player taps too early/late, or misses a cue. ```ascii +-----------------------------------------+ | SCORE: 006 COMBO: 0x | <--- Combo Reset |-----------------------------------------| | | | | | (*) | | v | | | | | | (*) | | v | | | | ===================================== | | | TARGET ZONE | | | ===================================== | | v | | (X) | <--- Missed cue indication / effect | | | | |-----------------------------------------| | FEEDBACK: Miss! | <--- Text feedback updated +-----------------------------------------+ ``` **E. End Screen (Simple Demo End)** * Appears after the predefined sequence finishes. ```ascii +-----------------------------------------+ | | | SEQUENCE COMPLETE! | | | | (Final Score: 025) | <--- Optional final score | | | | | +-------------+ | | | [ RESTART ] | | | +-------------+ | | | | | +-----------------------------------------+ User taps [ RESTART ] --> Transitions back to Gameplay Screen (restarts sequence) (Or could go back to the Start Screen) ``` This ASCII representation outlines the essential visual elements and the flow between them for the core mechanics demonstrator, focusing on clarity for user feedback on the rhythm interaction. Okay, here are specific pixel-based dimensions and positions for the UI elements based on a **reference screen resolution of 1080x1920 pixels (Width x Height - Portrait Orientation)**. **Important Considerations:** * **Reference Only:** These are *fixed pixel values* for the specified reference resolution. In a real implementation (e.g., using Unity Canvas), you would use anchors, pivots, and potentially scaling components (like Canvas Scaler UI Scale Mode) to ensure the UI adapts correctly to different screen sizes and aspect ratios. * **Origin Point:** Assume the (0, 0) coordinate is the **bottom-left** corner of the screen, common in some UI systems, or **top-left** (common in others - I will specify based on typical Unity Canvas usage: **(0,0) is Bottom-Left, (1080, 1920) is Top-Right** unless stated otherwise). Positions usually refer to the object's **pivot point** (often the center, unless specified). * **Font Sizes:** Pixel sizes for fonts are approximate and depend heavily on the specific font file used. * **Actors:** Actors like Cues and Effects have positions that change dynamically or are instantiated at specific world/UI coordinates. --- **A. Start Screen (1080x1920)** * **Widget:** `Title Text` (e.g., "RHYTHM CHEF: BEAT BITES") * **Position (Pivot: Center):** (540, 1600) `(Center X, ~83% Height)` * **Dimensions:** Auto-sized by text, constrained if needed. * **Font Size:** 90px * **Anchor Preset (Unity):** Top-Center * **Widget:** `Subtitle Text` (e.g., "(Core Demo)") * **Position (Pivot: Center):** (540, 1480) `(Center X, below Title)` * **Dimensions:** Auto-sized by text. * **Font Size:** 40px * **Anchor Preset (Unity):** Top-Center * **Widget:** `Start Button` * **Position (Pivot: Center):** (540, 400) `(Center X, ~21% Height)` * **Dimensions:** 450px (Width) x 150px (Height) * **Anchor Preset (Unity):** Bottom-Center * **Widget:** `Start Button Text` (e.g., "[ START ]") * **Position:** Centered within the Start Button. * **Font Size:** 60px --- **B. Gameplay Screen (1080x1920)** * **Widget:** `Score Text Label` (e.g., "SCORE:") * **Position (Pivot: Top-Left):** (40, 1880) `(Padding Left, Padding Top)` * **Dimensions:** Auto-sized by text. * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Left * **Widget:** `Score Value Text` (e.g., "000") * **Position (Pivot: Top-Left):** (200, 1880) `(Right of Label, Same Top)` * **Dimensions:** Auto-sized by text (allow space for growth). * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Left * **Widget:** `Combo Text Label` (e.g., "COMBO:") * **Position (Pivot: Top-Right):** (900, 1880) `(Approx position, adjust based on Value width)` * **Dimensions:** Auto-sized by text. * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Right * **Widget:** `Combo Value Text` (e.g., "0x") * **Position (Pivot: Top-Right):** (1040, 1880) `(Padding Right, Same Top)` * **Dimensions:** Auto-sized by text (allow space for growth). * **Font Size:** 50px * **Anchor Preset (Unity):** Top-Right * **Area:** `Cue Travel Area` (Logical, no visual widget) * **Bounds:** Approx X = Center (e.g., 540), Y from 1800 (Spawn) down to ~200 (Despawn below target). * **Widget:** `Target Zone Visual` (The horizontal bar indicator) * **Position (Pivot: Center):** (540, 400) `(Center X, Defined Y)` * **Dimensions:** 900px (Width) x 80px (Height) * **Anchor Preset (Unity):** Position manually or Bottom-Center with Y offset. * **Note:** The *logical* hit detection center Y might be exactly 400. The timing windows (`perfectWindow`, `okWindow`) define the tolerance around this point in *time*, not necessarily pixels vertically (though they are related via cue speed). * **Actor:** `Cue Object` (Visual representation) * **Position:** Dynamic. Spawns near Y=1800, travels downwards to Y=400 (target). X is typically centered (540). * **Dimensions:** 80px (Width) x 80px (Height) (Example, adjust for visual clarity) * **Widget:** `Feedback Text` (e.g., "Perfect!", "Miss!") * **Position (Pivot: Center):** (540, 600) `(Center X, Above Target Zone)` * **Dimensions:** Auto-sized by text. * **Font Size:** 70px * **Anchor Preset (Unity):** Position manually or relative to Target Zone. (Appears temporarily). * **Actor:** `Hit/Miss Feedback Effect` (Particles/Animation) * **Position:** Instantiated at the Cue's position *when* the hit/miss occurs (approx X=540, Y=400). * **Dimensions:** Dynamic based on the effect design (e.g., expands to 150x150px briefly). --- **C. End Screen (1080x1920)** * **Widget:** `Completion Text` (e.g., "SEQUENCE COMPLETE!") * **Position (Pivot: Center):** (540, 1200) `(Center X, ~62% Height)` * **Dimensions:** Auto-sized by text. * **Font Size:** 70px * **Anchor Preset (Unity):** Center * **Widget:** `Final Score Text` (Optional, e.g., "Final Score: 025") * **Position (Pivot: Center):** (540, 1050) `(Center X, below Completion text)` * **Dimensions:** Auto-sized by text. * **Font Size:** 50px * **Anchor Preset (Unity):** Center * **Widget:** `Restart Button` * **Position (Pivot: Center):** (540, 400) `(Center X, ~21% Height - same as Start Button)` * **Dimensions:** 450px (Width) x 150px (Height) * **Anchor Preset (Unity):** Bottom-Center * **Widget:** `Restart Button Text` (e.g., "[ RESTART ]") * **Position:** Centered within the Restart Button. * **Font Size:** 60px --- Remember to implement proper UI scaling and anchoring in your chosen game engine to handle various device resolutions effectively beyond this 1080x1920 reference.
|
2 |
+
Make the look and feel 100 times better, employ procedural graphics and emojis as sprites.
|
3 |
+
Act as graphical effect engineer and add many more cool effect to the game play, make it explode with cool effects, particles etc., background effect ...
|