3d-2-game / index.html
WebashalarForML's picture
Add 2 files
907d72d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pixel Quest 3D: Find Your Friend</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Press Start 2P', cursive;
image-rendering: pixelated;
}
canvas {
display: block;
image-rendering: pixelated;
}
@font-face {
font-family: 'Press Start 2P';
src: url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
}
.pixel-border {
border: 4px solid #fff;
box-shadow: 0 0 0 4px #000, inset 0 0 0 4px #000;
}
.quiz-option {
transition: all 0.1s;
border: 3px solid #fff;
box-shadow: 0 0 0 3px #000;
}
.quiz-option:hover {
background-color: #4a5568;
transform: translateY(-2px);
}
.quiz-option.correct {
background-color: #48bb78;
}
.quiz-option.wrong {
background-color: #f56565;
}
.pixel-text {
text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
.health-bar {
height: 10px;
background-color: #4a5568;
border: 2px solid #000;
}
.health-fill {
height: 100%;
background-color: #48bb78;
transition: width 0.3s;
}
</style>
</head>
<body class="bg-gray-900">
<div id="game-container" class="relative w-full h-screen">
<!-- Start Screen -->
<div id="start-screen" class="absolute inset-0 flex flex-col items-center justify-center bg-gray-900 z-10">
<h1 class="text-4xl md:text-6xl text-green-400 mb-8 pixel-text">PIXEL QUEST 3D</h1>
<p class="text-xl text-white mb-12 pixel-text">Find Your Lost Friend!</p>
<div class="flex mb-8">
<div class="w-16 h-16 bg-red-500 pixel-border mr-4"></div>
<div class="w-16 h-16 bg-blue-500 pixel-border"></div>
</div>
<button id="start-btn" class="px-8 py-4 bg-green-500 hover:bg-green-600 text-white font-bold pixel-border transition-all transform hover:scale-105">
START ADVENTURE
</button>
<div class="mt-8 text-white text-sm pixel-text">
Controls: Arrow Keys to move, Space to jump
</div>
</div>
<!-- Quiz Modal -->
<div id="quiz-modal" class="hidden absolute inset-0 flex items-center justify-center z-20 bg-black bg-opacity-70">
<div class="bg-gray-800 p-6 rounded-lg w-11/12 max-w-md pixel-border">
<h2 id="quiz-question" class="text-xl text-white mb-6 pixel-text"></h2>
<div id="quiz-options" class="space-y-3"></div>
<div id="quiz-feedback" class="mt-4 text-yellow-300 hidden pixel-text"></div>
</div>
</div>
<!-- End Screen -->
<div id="end-screen" class="hidden absolute inset-0 flex flex-col items-center justify-center bg-gray-900 z-10">
<h1 id="end-title" class="text-4xl md:text-6xl text-green-400 mb-8 pixel-text">FRIEND FOUND!</h1>
<div class="flex items-center mb-8">
<div class="w-32 h-32 bg-red-500 pixel-border mr-4"></div>
<div class="w-32 h-32 bg-blue-500 pixel-border"></div>
</div>
<p id="final-score" class="text-xl text-white mb-12 pixel-text"></p>
<button id="restart-btn" class="px-8 py-4 bg-green-500 hover:bg-green-600 text-white font-bold pixel-border transition-all transform hover:scale-105">
PLAY AGAIN
</button>
</div>
<!-- Game Over Screen -->
<div id="game-over-screen" class="hidden absolute inset-0 flex flex-col items-center justify-center bg-gray-900 z-10">
<h1 class="text-4xl md:text-6xl text-red-500 mb-8 pixel-text">GAME OVER</h1>
<p id="game-over-score" class="text-xl text-white mb-12 pixel-text"></p>
<button id="game-over-restart-btn" class="px-8 py-4 bg-green-500 hover:bg-green-600 text-white font-bold pixel-border transition-all transform hover:scale-105">
TRY AGAIN
</button>
</div>
<!-- UI Elements -->
<div id="ui-container" class="absolute top-4 left-4 z-10 hidden">
<div class="bg-gray-800 bg-opacity-70 p-3 rounded pixel-border">
<p class="text-white pixel-text">Score: <span id="score-display">0</span></p>
<p class="text-white pixel-text">Quizzes: <span id="quiz-count">0</span>/4</p>
<div class="mt-2">
<p class="text-white pixel-text mb-1">Health:</p>
<div class="health-bar">
<div id="health-fill" class="health-fill" style="width: 100%"></div>
</div>
</div>
</div>
</div>
</div>
<script>
// Game variables
let scene, camera, renderer;
let player, platforms = [], checkpoints = [], coins = [], obstacles = [];
let playerVelocity = { x: 0, y: 0, z: 0 };
let playerSpeed = 0.2;
let jumpForce = 0.4;
let gravity = 0.02;
let isJumping = false;
let gamePaused = false;
let score = 0;
let quizzesCompleted = 0;
let currentCheckpoint = null;
let friendFound = false;
let keys = {};
let worldWidth = 100;
let worldDepth = 20;
let friend;
let playerHealth = 100;
let lastObstacleHitTime = 0;
let obstacleHitCooldown = 1000; // 1 second cooldown
// Quiz questions
const quizQuestions = [
{
question: "What is your friend's favorite color?",
options: ["Blue", "Green", "Red", "Yellow"],
correct: 0
},
{
question: "Where did you first meet your friend?",
options: ["At school", "At the park", "At a party", "Online"],
correct: 1
},
{
question: "What's your friend's favorite food?",
options: ["Pizza", "Sushi", "Burgers", "Tacos"],
correct: 2
},
{
question: "What was your last adventure together?",
options: ["Hiking", "Movie marathon", "Road trip", "Camping"],
correct: 3
}
];
// Initialize game
function init() {
// Scene setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Camera (perspective for better 3D feel)
const aspect = window.innerWidth / window.innerHeight;
camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setPixelRatio(window.devicePixelRatio > 1 ? 2 : 1);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById('game-container').appendChild(renderer.domElement);
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
scene.add(directionalLight);
// Create world
createWorld();
// Create player
createPlayer();
// Create checkpoints
createCheckpoints();
// Create obstacles
createObstacles();
// Create friend
createFriend();
// Event listeners
window.addEventListener('resize', onWindowResize);
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
// Start button
document.getElementById('start-btn').addEventListener('click', startGame);
document.getElementById('restart-btn').addEventListener('click', restartGame);
document.getElementById('game-over-restart-btn').addEventListener('click', restartGame);
}
function createWorld() {
// Ground
const groundGeometry = new THREE.BoxGeometry(worldWidth, 1, worldDepth);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x2e8b57,
roughness: 1.0
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.position.y = -0.5;
ground.receiveShadow = true;
scene.add(ground);
// Platforms
const platformPositions = [
{ x: 10, y: 2, z: 0, width: 8, depth: 8 },
{ x: 25, y: 3, z: 5, width: 6, depth: 6 },
{ x: 40, y: 4, z: -3, width: 10, depth: 10 },
{ x: 60, y: 2, z: 2, width: 12, depth: 8 },
{ x: 80, y: 5, z: 0, width: 8, depth: 8 }
];
platformPositions.forEach(pos => {
const platformGeometry = new THREE.BoxGeometry(pos.width, 1, pos.depth);
const platformMaterial = new THREE.MeshStandardMaterial({
color: 0x8b4513,
roughness: 1.0
});
const platform = new THREE.Mesh(platformGeometry, platformMaterial);
platform.position.set(pos.x, pos.y, pos.z);
platform.receiveShadow = true;
scene.add(platform);
platforms.push(platform);
});
// Coins
for (let i = 0; i < 20; i++) {
const coinGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.2, 16);
const coinMaterial = new THREE.MeshStandardMaterial({ color: 0xffd700 });
const coin = new THREE.Mesh(coinGeometry, coinMaterial);
// Position coins randomly above platforms or ground
let x, y, z;
if (Math.random() > 0.5) {
// On platforms
const platform = platforms[Math.floor(Math.random() * platforms.length)];
x = platform.position.x + (Math.random() - 0.5) * (platform.geometry.parameters.width - 2);
y = platform.position.y + 1;
z = platform.position.z + (Math.random() - 0.5) * (platform.geometry.parameters.depth - 2);
} else {
// On ground
x = Math.random() * worldWidth;
y = 0.5;
z = (Math.random() - 0.5) * (worldDepth - 4);
}
coin.position.set(x, y, z);
coin.rotation.x = Math.PI / 2;
coin.userData.isCoin = true;
scene.add(coin);
coins.push(coin);
}
}
function createPlayer() {
// Simple pixel-style character
const playerGeometry = new THREE.BoxGeometry(1, 2, 1);
const playerMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
player = new THREE.Mesh(playerGeometry, playerMaterial);
player.position.set(0, 1, 0);
player.castShadow = true;
scene.add(player);
}
function createCheckpoints() {
// Create 4 checkpoints along the path
const checkpointPositions = [
{ x: 15, y: 0, z: 0 },
{ x: 30, y: 0, z: 0 },
{ x: 50, y: 0, z: 0 },
{ x: 70, y: 0, z: 0 }
];
checkpointPositions.forEach((pos, index) => {
const checkpointGeometry = new THREE.BoxGeometry(2, 2, 2);
const checkpointMaterial = new THREE.MeshStandardMaterial({
color: 0xffff00,
transparent: true,
opacity: 0.7
});
const checkpoint = new THREE.Mesh(checkpointGeometry, checkpointMaterial);
checkpoint.position.set(pos.x, pos.y + 1, pos.z);
checkpoint.userData.isCheckpoint = true;
checkpoint.userData.quizIndex = index;
scene.add(checkpoint);
checkpoints.push(checkpoint);
});
}
function createObstacles() {
// Create destroyable obstacles
const obstaclePositions = [
{ x: 5, y: 1, z: 3, width: 1, height: 2, depth: 1 },
{ x: 20, y: 1, z: -2, width: 1, height: 2, depth: 1 },
{ x: 35, y: 1, z: 4, width: 1, height: 2, depth: 1 },
{ x: 45, y: 1, z: -3, width: 1, height: 2, depth: 1 },
{ x: 65, y: 1, z: 2, width: 1, height: 2, depth: 1 }
];
obstaclePositions.forEach(pos => {
const obstacleGeometry = new THREE.BoxGeometry(pos.width, pos.height, pos.depth);
const obstacleMaterial = new THREE.MeshStandardMaterial({
color: 0x8b0000,
roughness: 1.0
});
const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial);
obstacle.position.set(pos.x, pos.y + pos.height/2, pos.z);
obstacle.castShadow = true;
obstacle.receiveShadow = true;
obstacle.userData.isObstacle = true;
obstacle.userData.health = 30; // Obstacle health
scene.add(obstacle);
obstacles.push(obstacle);
});
}
function createFriend() {
// Create the friend character at the end
const friendGeometry = new THREE.BoxGeometry(1, 2, 1);
const friendMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff });
friend = new THREE.Mesh(friendGeometry, friendMaterial);
friend.position.set(worldWidth - 5, 1, 0);
friend.castShadow = true;
friend.userData.isFriend = true;
scene.add(friend);
}
function startGame() {
document.getElementById('start-screen').classList.add('hidden');
document.getElementById('ui-container').classList.remove('hidden');
animate();
}
function restartGame() {
// Reset game state
score = 0;
quizzesCompleted = 0;
friendFound = false;
gamePaused = false;
playerHealth = 100;
// Reset player position
player.position.set(0, 1, 0);
playerVelocity = { x: 0, y: 0, z: 0 };
// Reset UI
document.getElementById('score-display').textContent = '0';
document.getElementById('quiz-count').textContent = '0/4';
document.getElementById('health-fill').style.width = '100%';
document.getElementById('end-screen').classList.add('hidden');
document.getElementById('game-over-screen').classList.add('hidden');
document.getElementById('ui-container').classList.remove('hidden');
// Clear and recreate all game objects
clearScene();
createWorld();
createPlayer();
createCheckpoints();
createObstacles();
createFriend();
// Restart animation
animate();
}
function clearScene() {
// Remove all objects from scene except lights and camera
while(scene.children.length > 0) {
const obj = scene.children[0];
if (!(obj instanceof THREE.Light) && !(obj instanceof THREE.Camera)) {
scene.remove(obj);
}
}
// Clear arrays
platforms = [];
checkpoints = [];
coins = [];
obstacles = [];
}
function showQuiz(quizIndex) {
gamePaused = true;
currentCheckpoint = quizIndex;
const quiz = quizQuestions[quizIndex];
document.getElementById('quiz-question').textContent = quiz.question;
const optionsContainer = document.getElementById('quiz-options');
optionsContainer.innerHTML = '';
quiz.options.forEach((option, i) => {
const optionBtn = document.createElement('button');
optionBtn.className = 'w-full p-3 bg-gray-700 text-white quiz-option pixel-text';
optionBtn.textContent = option;
optionBtn.addEventListener('click', () => checkAnswer(i));
optionsContainer.appendChild(optionBtn);
});
document.getElementById('quiz-feedback').classList.add('hidden');
document.getElementById('quiz-modal').classList.remove('hidden');
}
function checkAnswer(answerIndex) {
const quiz = quizQuestions[currentCheckpoint];
const options = document.querySelectorAll('.quiz-option');
if (answerIndex === quiz.correct) {
// Correct answer
options[answerIndex].classList.add('correct');
document.getElementById('quiz-feedback').textContent = 'Correct!';
document.getElementById('quiz-feedback').classList.remove('hidden');
setTimeout(() => {
quizzesCompleted++;
document.getElementById('quiz-count').textContent = `${quizzesCompleted}/4`;
document.getElementById('quiz-modal').classList.add('hidden');
gamePaused = false;
// Remove the checkpoint
const checkpoint = checkpoints[currentCheckpoint];
scene.remove(checkpoint);
checkpoints[currentCheckpoint] = null;
// Add score
score += 100;
document.getElementById('score-display').textContent = score;
}, 1000);
} else {
// Wrong answer
options[answerIndex].classList.add('wrong');
options[quiz.correct].classList.add('correct');
document.getElementById('quiz-feedback').textContent = 'Try again!';
document.getElementById('quiz-feedback').classList.remove('hidden');
}
}
function onWindowResize() {
const aspect = window.innerWidth / window.innerHeight;
camera.aspect = aspect;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onKeyDown(event) {
keys[event.code] = true;
// Jump
if (event.code === 'Space' && !isJumping && !gamePaused) {
playerVelocity.y = jumpForce;
isJumping = true;
}
// Attack (destroy obstacles)
if (event.code === 'KeyF' && !gamePaused) {
attackObstacle();
}
}
function onKeyUp(event) {
keys[event.code] = false;
}
function attackObstacle() {
const playerBox = new THREE.Box3().setFromObject(player);
obstacles.forEach((obstacle, index) => {
if (obstacle && new THREE.Box3().setFromObject(player).intersectsBox(new THREE.Box3().setFromObject(obstacle))) {
// Reduce obstacle health
obstacle.userData.health -= 10;
// If obstacle is destroyed
if (obstacle.userData.health <= 0) {
scene.remove(obstacle);
obstacles[index] = null;
score += 50;
document.getElementById('score-display').textContent = score;
}
}
});
}
function checkCollisions() {
// Check platform collisions
let onGround = false;
const playerBox = new THREE.Box3().setFromObject(player);
platforms.forEach(platform => {
const platformBox = new THREE.Box3().setFromObject(platform);
if (playerBox.intersectsBox(platformBox)) {
// Check if player is on top of platform
if (player.position.y > platform.position.y + 0.9) {
player.position.y = platform.position.y + 1 + player.geometry.parameters.height / 2;
playerVelocity.y = 0;
isJumping = false;
onGround = true;
}
}
});
// Ground collision
if (player.position.y <= 0) {
player.position.y = 0;
playerVelocity.y = 0;
isJumping = false;
onGround = true;
}
// Check coin collisions
coins.forEach((coin, index) => {
if (coin && new THREE.Box3().setFromObject(player).intersectsBox(new THREE.Box3().setFromObject(coin))) {
scene.remove(coin);
coins[index] = null;
score += 10;
document.getElementById('score-display').textContent = score;
}
});
// Check checkpoint collisions
checkpoints.forEach((checkpoint, index) => {
if (checkpoint && new THREE.Box3().setFromObject(player).intersectsBox(new THREE.Box3().setFromObject(checkpoint))) {
showQuiz(index);
}
});
// Check obstacle collisions (damage player)
const currentTime = Date.now();
obstacles.forEach(obstacle => {
if (obstacle && new THREE.Box3().setFromObject(player).intersectsBox(new THREE.Box3().setFromObject(obstacle))) {
if (currentTime - lastObstacleHitTime > obstacleHitCooldown) {
playerHealth -= 10;
lastObstacleHitTime = currentTime;
document.getElementById('health-fill').style.width = `${playerHealth}%`;
// Flash player red when hit
player.material.color.setHex(0xff0000);
setTimeout(() => {
player.material.color.setHex(0xff0000);
}, 200);
// Check if player died
if (playerHealth <= 0) {
gameOver();
}
}
}
});
// Check friend collision
if (!friendFound && new THREE.Box3().setFromObject(player).intersectsBox(new THREE.Box3().setFromObject(friend)) && quizzesCompleted === 4) {
friendFound = true;
gamePaused = true;
// Show end screen
document.getElementById('final-score').textContent = `Final Score: ${score}`;
document.getElementById('end-screen').classList.remove('hidden');
document.getElementById('ui-container').classList.add('hidden');
}
return onGround;
}
function gameOver() {
gamePaused = true;
document.getElementById('game-over-score').textContent = `Score: ${score}`;
document.getElementById('game-over-screen').classList.remove('hidden');
document.getElementById('ui-container').classList.add('hidden');
}
function animate() {
if (gamePaused) return;
requestAnimationFrame(animate);
// Player movement
if (keys['KeyA'] || keys['ArrowLeft']) {
playerVelocity.x = -playerSpeed;
player.rotation.y = Math.PI;
} else if (keys['KeyD'] || keys['ArrowRight']) {
playerVelocity.x = playerSpeed;
player.rotation.y = 0;
} else {
playerVelocity.x = 0;
}
// Apply gravity
playerVelocity.y -= gravity;
// Update player position
player.position.x += playerVelocity.x;
player.position.y += playerVelocity.y;
// Check collisions
checkCollisions();
// Update camera to follow player with better angle
camera.position.x = player.position.x;
camera.position.y = player.position.y + 5;
camera.position.z = player.position.z + 15;
camera.lookAt(player.position.x, player.position.y, player.position.z);
renderer.render(scene, camera);
}
// Start the game
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=WebashalarForML/3d-2-game" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>