Refactor to ES6 classes with GameObject inheritance pattern
- Convert from function-based to class-based architecture - Create GameObject base class with common functionality (position, collision, scene management) - Implement Player, Coin, and Obstacle classes extending GameObject - Add Game class to manage game lifecycle and state - Fix shadow rendering by configuring directional light shadow camera bounds - Fix player collision to prevent sinking below ground (horizontal push only) - Improve code organization and maintainability with OOP principles
This commit is contained in:
parent
0bf4afd499
commit
fc24028ed9
1 changed files with 388 additions and 260 deletions
622
index.html
622
index.html
|
|
@ -92,313 +92,441 @@
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// Game variables
|
// Base GameObject class
|
||||||
let scene, camera, renderer;
|
class GameObject {
|
||||||
let player;
|
constructor(scene, groundSize) {
|
||||||
let coins = [];
|
this.scene = scene;
|
||||||
let obstacles = [];
|
this.groundSize = groundSize;
|
||||||
let score = 0;
|
this.mesh = this.createMesh();
|
||||||
let health = 100;
|
this.initialize();
|
||||||
let gameActive = true;
|
scene.add(this.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
// Movement
|
// Override in child classes
|
||||||
const keys = {};
|
createMesh() {
|
||||||
const playerSpeed = 0.15;
|
throw new Error('createMesh() must be implemented by child class');
|
||||||
const groundSize = 30;
|
}
|
||||||
|
|
||||||
// Initialize the game
|
// Override in child classes for initialization
|
||||||
function init() {
|
initialize() {
|
||||||
// Scene setup
|
// Default implementation - can be overridden
|
||||||
scene = new THREE.Scene();
|
}
|
||||||
scene.background = new THREE.Color(0x87CEEB);
|
|
||||||
scene.fog = new THREE.Fog(0x87CEEB, 0, 50);
|
|
||||||
|
|
||||||
// Camera setup
|
// Override in child classes for update logic
|
||||||
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
update(...args) {
|
||||||
camera.position.set(0, 10, 15);
|
// Default implementation - can be overridden
|
||||||
camera.lookAt(0, 0, 0);
|
}
|
||||||
|
|
||||||
// Renderer setup
|
// Common collision detection
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
checkCollision(otherPosition, collisionRadius) {
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
const distance = this.mesh.position.distanceTo(otherPosition);
|
||||||
renderer.shadowMap.enabled = true;
|
return distance < collisionRadius;
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
}
|
||||||
document.body.appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// Lights
|
// Common position getter
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
getPosition() {
|
||||||
scene.add(ambientLight);
|
return this.mesh.position;
|
||||||
|
}
|
||||||
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
// Remove from scene
|
||||||
directionalLight.position.set(10, 20, 10);
|
remove() {
|
||||||
directionalLight.castShadow = true;
|
this.scene.remove(this.mesh);
|
||||||
directionalLight.shadow.mapSize.width = 2048;
|
}
|
||||||
directionalLight.shadow.mapSize.height = 2048;
|
|
||||||
directionalLight.shadow.camera.left = -20;
|
|
||||||
directionalLight.shadow.camera.right = 20;
|
|
||||||
directionalLight.shadow.camera.top = 20;
|
|
||||||
directionalLight.shadow.camera.bottom = -20;
|
|
||||||
directionalLight.shadow.camera.near = 0.5;
|
|
||||||
directionalLight.shadow.camera.far = 50;
|
|
||||||
scene.add(directionalLight);
|
|
||||||
|
|
||||||
// Ground
|
// Set position
|
||||||
const groundGeometry = new THREE.PlaneGeometry(groundSize, groundSize);
|
setPosition(x, y, z) {
|
||||||
const groundMaterial = new THREE.MeshStandardMaterial({
|
this.mesh.position.set(x, y, z);
|
||||||
color: 0x90EE90,
|
}
|
||||||
roughness: 0.8
|
|
||||||
});
|
|
||||||
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
|
||||||
ground.rotation.x = -Math.PI / 2;
|
|
||||||
ground.receiveShadow = true;
|
|
||||||
scene.add(ground);
|
|
||||||
|
|
||||||
// Grid helper
|
// Get position vector
|
||||||
const gridHelper = new THREE.GridHelper(groundSize, 20, 0x000000, 0x000000);
|
getPositionVector() {
|
||||||
gridHelper.material.opacity = 0.2;
|
return this.mesh.position.clone();
|
||||||
gridHelper.material.transparent = true;
|
}
|
||||||
scene.add(gridHelper);
|
|
||||||
|
|
||||||
// Player
|
|
||||||
const playerGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
||||||
const playerMaterial = new THREE.MeshStandardMaterial({
|
|
||||||
color: 0x4169E1,
|
|
||||||
metalness: 0.3,
|
|
||||||
roughness: 0.4
|
|
||||||
});
|
|
||||||
player = new THREE.Mesh(playerGeometry, playerMaterial);
|
|
||||||
player.position.y = 0.5;
|
|
||||||
player.castShadow = true;
|
|
||||||
player.receiveShadow = true;
|
|
||||||
scene.add(player);
|
|
||||||
|
|
||||||
// Create initial game objects
|
|
||||||
createCoins(10);
|
|
||||||
createObstacles(8);
|
|
||||||
|
|
||||||
// Event listeners
|
|
||||||
window.addEventListener('keydown', (e) => keys[e.key.toLowerCase()] = true);
|
|
||||||
window.addEventListener('keyup', (e) => keys[e.key.toLowerCase()] = false);
|
|
||||||
window.addEventListener('resize', onWindowResize);
|
|
||||||
document.getElementById('restartBtn').addEventListener('click', restartGame);
|
|
||||||
|
|
||||||
// Start animation loop
|
|
||||||
animate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create coins
|
// Player class extends GameObject
|
||||||
function createCoins(count) {
|
class Player extends GameObject {
|
||||||
for (let i = 0; i < count; i++) {
|
constructor(scene, groundSize) {
|
||||||
const coinGeometry = new THREE.SphereGeometry(0.3, 16, 16);
|
super(scene, groundSize);
|
||||||
const coinMaterial = new THREE.MeshStandardMaterial({
|
this.speed = 0.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMesh() {
|
||||||
|
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
|
color: 0x4169E1,
|
||||||
|
metalness: 0.3,
|
||||||
|
roughness: 0.4
|
||||||
|
});
|
||||||
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.setPosition(0, 0.5, 0);
|
||||||
|
this.mesh.rotation.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(keys) {
|
||||||
|
const moveVector = new THREE.Vector3();
|
||||||
|
|
||||||
|
if (keys['w'] || keys['arrowup']) moveVector.z -= this.speed;
|
||||||
|
if (keys['s'] || keys['arrowdown']) moveVector.z += this.speed;
|
||||||
|
if (keys['a'] || keys['arrowleft']) moveVector.x -= this.speed;
|
||||||
|
if (keys['d'] || keys['arrowright']) moveVector.x += this.speed;
|
||||||
|
|
||||||
|
this.mesh.position.add(moveVector);
|
||||||
|
|
||||||
|
// Boundary checks
|
||||||
|
const boundary = this.groundSize / 2 - 0.5;
|
||||||
|
this.mesh.position.x = Math.max(-boundary, Math.min(boundary, this.mesh.position.x));
|
||||||
|
this.mesh.position.z = Math.max(-boundary, Math.min(boundary, this.mesh.position.z));
|
||||||
|
|
||||||
|
// Rotate player based on movement
|
||||||
|
if (moveVector.length() > 0) {
|
||||||
|
this.mesh.rotation.y += 0.1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCollision(obstaclePosition) {
|
||||||
|
const pushDirection = this.mesh.position.clone().sub(obstaclePosition);
|
||||||
|
pushDirection.y = 0;
|
||||||
|
pushDirection.normalize();
|
||||||
|
this.mesh.position.add(pushDirection.multiplyScalar(0.3));
|
||||||
|
this.mesh.position.y = 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coin class extends GameObject
|
||||||
|
class Coin extends GameObject {
|
||||||
|
constructor(scene, groundSize, index = 0) {
|
||||||
|
super(scene, groundSize);
|
||||||
|
this.index = index;
|
||||||
|
this.rotationSpeed = 0.02;
|
||||||
|
this.collisionRadius = 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMesh() {
|
||||||
|
const geometry = new THREE.SphereGeometry(0.3, 16, 16);
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
color: 0xFFD700,
|
color: 0xFFD700,
|
||||||
metalness: 0.8,
|
metalness: 0.8,
|
||||||
roughness: 0.2,
|
roughness: 0.2,
|
||||||
emissive: 0xFFD700,
|
emissive: 0xFFD700,
|
||||||
emissiveIntensity: 0.3
|
emissiveIntensity: 0.3
|
||||||
});
|
});
|
||||||
const coin = new THREE.Mesh(coinGeometry, coinMaterial);
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
// Random position
|
initialize() {
|
||||||
coin.position.x = (Math.random() - 0.5) * (groundSize - 4);
|
this.setRandomPosition();
|
||||||
coin.position.z = (Math.random() - 0.5) * (groundSize - 4);
|
}
|
||||||
coin.position.y = 0.5;
|
|
||||||
|
|
||||||
coin.castShadow = true;
|
setRandomPosition() {
|
||||||
coin.receiveShadow = true;
|
const x = (Math.random() - 0.5) * (this.groundSize - 4);
|
||||||
coin.userData.rotationSpeed = 0.02;
|
const z = (Math.random() - 0.5) * (this.groundSize - 4);
|
||||||
|
this.setPosition(x, 0.5, z);
|
||||||
|
}
|
||||||
|
|
||||||
scene.add(coin);
|
update() {
|
||||||
coins.push(coin);
|
this.mesh.rotation.y += this.rotationSpeed;
|
||||||
|
this.mesh.position.y = 0.5 + Math.sin(Date.now() * 0.003 + this.index) * 0.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCollisionWithPlayer(playerPosition) {
|
||||||
|
return this.checkCollision(playerPosition, this.collisionRadius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create obstacles
|
// Obstacle class extends GameObject
|
||||||
function createObstacles(count) {
|
class Obstacle extends GameObject {
|
||||||
for (let i = 0; i < count; i++) {
|
constructor(scene, groundSize) {
|
||||||
const obstacleGeometry = new THREE.BoxGeometry(1.5, 2, 1.5);
|
super(scene, groundSize);
|
||||||
const obstacleMaterial = new THREE.MeshStandardMaterial({
|
this.direction = this.createRandomDirection();
|
||||||
|
this.collisionRadius = 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
createMesh() {
|
||||||
|
const geometry = new THREE.BoxGeometry(1.5, 2, 1.5);
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
color: 0xFF4500,
|
color: 0xFF4500,
|
||||||
metalness: 0.3,
|
metalness: 0.3,
|
||||||
roughness: 0.7
|
roughness: 0.7
|
||||||
});
|
});
|
||||||
const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial);
|
const mesh = new THREE.Mesh(geometry, material);
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
return mesh;
|
||||||
|
}
|
||||||
|
|
||||||
// Random position (ensure not too close to player start)
|
initialize() {
|
||||||
let posX, posZ;
|
this.setRandomPosition();
|
||||||
do {
|
this.mesh.position.y = 1;
|
||||||
posX = (Math.random() - 0.5) * (groundSize - 4);
|
}
|
||||||
posZ = (Math.random() - 0.5) * (groundSize - 4);
|
|
||||||
} while (Math.abs(posX) < 3 && Math.abs(posZ) < 3);
|
|
||||||
|
|
||||||
obstacle.position.x = posX;
|
createRandomDirection() {
|
||||||
obstacle.position.z = posZ;
|
return new THREE.Vector3(
|
||||||
obstacle.position.y = 1;
|
|
||||||
|
|
||||||
obstacle.castShadow = true;
|
|
||||||
obstacle.receiveShadow = true;
|
|
||||||
|
|
||||||
// Random movement pattern
|
|
||||||
obstacle.userData.direction = new THREE.Vector3(
|
|
||||||
(Math.random() - 0.5) * 0.05,
|
(Math.random() - 0.5) * 0.05,
|
||||||
0,
|
0,
|
||||||
(Math.random() - 0.5) * 0.05
|
(Math.random() - 0.5) * 0.05
|
||||||
);
|
);
|
||||||
|
|
||||||
scene.add(obstacle);
|
|
||||||
obstacles.push(obstacle);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update player movement
|
setRandomPosition() {
|
||||||
function updatePlayer() {
|
let posX, posZ;
|
||||||
if (!gameActive) return;
|
do {
|
||||||
|
posX = (Math.random() - 0.5) * (this.groundSize - 4);
|
||||||
|
posZ = (Math.random() - 0.5) * (this.groundSize - 4);
|
||||||
|
} while (Math.abs(posX) < 3 && Math.abs(posZ) < 3);
|
||||||
|
|
||||||
const moveVector = new THREE.Vector3();
|
this.setPosition(posX, 1, posZ);
|
||||||
|
|
||||||
if (keys['w'] || keys['arrowup']) moveVector.z -= playerSpeed;
|
|
||||||
if (keys['s'] || keys['arrowdown']) moveVector.z += playerSpeed;
|
|
||||||
if (keys['a'] || keys['arrowleft']) moveVector.x -= playerSpeed;
|
|
||||||
if (keys['d'] || keys['arrowright']) moveVector.x += playerSpeed;
|
|
||||||
|
|
||||||
// Apply movement
|
|
||||||
player.position.add(moveVector);
|
|
||||||
|
|
||||||
// Boundary checks
|
|
||||||
const boundary = groundSize / 2 - 0.5;
|
|
||||||
player.position.x = Math.max(-boundary, Math.min(boundary, player.position.x));
|
|
||||||
player.position.z = Math.max(-boundary, Math.min(boundary, player.position.z));
|
|
||||||
|
|
||||||
// Rotate player based on movement
|
|
||||||
if (moveVector.length() > 0) {
|
|
||||||
player.rotation.y += 0.1;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update coins
|
update() {
|
||||||
function updateCoins() {
|
this.mesh.position.add(this.direction);
|
||||||
coins.forEach((coin, index) => {
|
|
||||||
// Rotate coins
|
|
||||||
coin.rotation.y += coin.userData.rotationSpeed;
|
|
||||||
coin.position.y = 0.5 + Math.sin(Date.now() * 0.003 + index) * 0.2;
|
|
||||||
|
|
||||||
// Check collision with player
|
|
||||||
const distance = player.position.distanceTo(coin.position);
|
|
||||||
if (distance < 0.8) {
|
|
||||||
// Collect coin
|
|
||||||
scene.remove(coin);
|
|
||||||
coins.splice(index, 1);
|
|
||||||
score += 10;
|
|
||||||
updateUI();
|
|
||||||
|
|
||||||
// Create new coin
|
|
||||||
createCoins(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update obstacles
|
|
||||||
function updateObstacles() {
|
|
||||||
if (!gameActive) return;
|
|
||||||
|
|
||||||
obstacles.forEach(obstacle => {
|
|
||||||
// Move obstacle
|
|
||||||
obstacle.position.add(obstacle.userData.direction);
|
|
||||||
|
|
||||||
// Bounce off boundaries
|
// Bounce off boundaries
|
||||||
const boundary = groundSize / 2 - 1;
|
const boundary = this.groundSize / 2 - 1;
|
||||||
if (Math.abs(obstacle.position.x) > boundary) {
|
if (Math.abs(this.mesh.position.x) > boundary) {
|
||||||
obstacle.userData.direction.x *= -1;
|
this.direction.x *= -1;
|
||||||
}
|
}
|
||||||
if (Math.abs(obstacle.position.z) > boundary) {
|
if (Math.abs(this.mesh.position.z) > boundary) {
|
||||||
obstacle.userData.direction.z *= -1;
|
this.direction.z *= -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check collision with player
|
checkCollisionWithPlayer(playerPosition) {
|
||||||
const distance = player.position.distanceTo(obstacle.position);
|
return this.checkCollision(playerPosition, this.collisionRadius);
|
||||||
if (distance < 1.5) {
|
}
|
||||||
health -= 1;
|
}
|
||||||
updateUI();
|
|
||||||
|
|
||||||
// Push player away (only on X and Z axis, keep Y constant)
|
// Game class
|
||||||
const pushDirection = player.position.clone().sub(obstacle.position);
|
class Game {
|
||||||
pushDirection.y = 0; // Keep player on ground level
|
constructor() {
|
||||||
pushDirection.normalize();
|
this.groundSize = 30;
|
||||||
player.position.add(pushDirection.multiplyScalar(0.3));
|
this.score = 0;
|
||||||
player.position.y = 0.5; // Ensure player stays at ground level
|
this.health = 100;
|
||||||
|
this.gameActive = true;
|
||||||
|
this.keys = {};
|
||||||
|
this.coins = [];
|
||||||
|
this.obstacles = [];
|
||||||
|
|
||||||
// Check game over
|
this.init();
|
||||||
if (health <= 0) {
|
this.setupEventListeners();
|
||||||
gameOver();
|
this.animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.setupScene();
|
||||||
|
this.setupCamera();
|
||||||
|
this.setupRenderer();
|
||||||
|
this.setupLights();
|
||||||
|
this.setupGround();
|
||||||
|
this.setupPlayer();
|
||||||
|
this.createGameObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupScene() {
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
this.scene.background = new THREE.Color(0x87CEEB);
|
||||||
|
this.scene.fog = new THREE.Fog(0x87CEEB, 0, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupCamera() {
|
||||||
|
this.camera = new THREE.PerspectiveCamera(
|
||||||
|
75,
|
||||||
|
window.innerWidth / window.innerHeight,
|
||||||
|
0.1,
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
this.camera.position.set(0, 10, 15);
|
||||||
|
this.camera.lookAt(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupRenderer() {
|
||||||
|
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
this.renderer.shadowMap.enabled = true;
|
||||||
|
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||||
|
document.body.appendChild(this.renderer.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupLights() {
|
||||||
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||||
|
this.scene.add(ambientLight);
|
||||||
|
|
||||||
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||||
|
directionalLight.position.set(10, 20, 10);
|
||||||
|
directionalLight.castShadow = true;
|
||||||
|
directionalLight.shadow.mapSize.width = 2048;
|
||||||
|
directionalLight.shadow.mapSize.height = 2048;
|
||||||
|
directionalLight.shadow.camera.left = -20;
|
||||||
|
directionalLight.shadow.camera.right = 20;
|
||||||
|
directionalLight.shadow.camera.top = 20;
|
||||||
|
directionalLight.shadow.camera.bottom = -20;
|
||||||
|
directionalLight.shadow.camera.near = 0.5;
|
||||||
|
directionalLight.shadow.camera.far = 50;
|
||||||
|
this.scene.add(directionalLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupGround() {
|
||||||
|
const groundGeometry = new THREE.PlaneGeometry(this.groundSize, this.groundSize);
|
||||||
|
const groundMaterial = new THREE.MeshStandardMaterial({
|
||||||
|
color: 0x90EE90,
|
||||||
|
roughness: 0.8
|
||||||
|
});
|
||||||
|
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
||||||
|
ground.rotation.x = -Math.PI / 2;
|
||||||
|
ground.receiveShadow = true;
|
||||||
|
this.scene.add(ground);
|
||||||
|
|
||||||
|
const gridHelper = new THREE.GridHelper(this.groundSize, 20, 0x000000, 0x000000);
|
||||||
|
gridHelper.material.opacity = 0.2;
|
||||||
|
gridHelper.material.transparent = true;
|
||||||
|
this.scene.add(gridHelper);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPlayer() {
|
||||||
|
this.player = new Player(this.scene, this.groundSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
createGameObjects() {
|
||||||
|
this.createCoins(10);
|
||||||
|
this.createObstacles(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCoins(count) {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const coin = new Coin(this.scene, this.groundSize, this.coins.length);
|
||||||
|
this.coins.push(coin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createObstacles(count) {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const obstacle = new Obstacle(this.scene, this.groundSize);
|
||||||
|
this.obstacles.push(obstacle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
this.keys[e.key.toLowerCase()] = true;
|
||||||
|
});
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
this.keys[e.key.toLowerCase()] = false;
|
||||||
|
});
|
||||||
|
window.addEventListener('resize', () => this.onWindowResize());
|
||||||
|
document.getElementById('restartBtn').addEventListener('click', () => this.restart());
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePlayer() {
|
||||||
|
if (!this.gameActive) return;
|
||||||
|
this.player.update(this.keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCoins() {
|
||||||
|
this.coins.forEach((coin, index) => {
|
||||||
|
coin.update();
|
||||||
|
|
||||||
|
if (coin.checkCollisionWithPlayer(this.player.getPosition())) {
|
||||||
|
coin.remove();
|
||||||
|
this.coins.splice(index, 1);
|
||||||
|
this.score += 10;
|
||||||
|
this.updateUI();
|
||||||
|
this.createCoins(1);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Update UI
|
updateObstacles() {
|
||||||
function updateUI() {
|
if (!this.gameActive) return;
|
||||||
document.getElementById('score').textContent = score;
|
|
||||||
document.getElementById('health').textContent = Math.max(0, health);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Game over
|
this.obstacles.forEach(obstacle => {
|
||||||
function gameOver() {
|
obstacle.update();
|
||||||
gameActive = false;
|
|
||||||
document.getElementById('finalScore').textContent = score;
|
|
||||||
document.getElementById('gameOver').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart game
|
if (obstacle.checkCollisionWithPlayer(this.player.getPosition())) {
|
||||||
function restartGame() {
|
this.health -= 1;
|
||||||
// Remove all coins and obstacles
|
this.updateUI();
|
||||||
coins.forEach(coin => scene.remove(coin));
|
this.player.handleCollision(obstacle.getPosition());
|
||||||
obstacles.forEach(obstacle => scene.remove(obstacle));
|
|
||||||
coins = [];
|
|
||||||
obstacles = [];
|
|
||||||
|
|
||||||
// Reset player
|
if (this.health <= 0) {
|
||||||
player.position.set(0, 0.5, 0);
|
this.gameOver();
|
||||||
player.rotation.y = 0;
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Reset game state
|
updateCamera() {
|
||||||
score = 0;
|
const playerPos = this.player.getPosition();
|
||||||
health = 100;
|
this.camera.position.x = playerPos.x;
|
||||||
gameActive = true;
|
this.camera.position.z = playerPos.z + 15;
|
||||||
|
this.camera.lookAt(playerPos);
|
||||||
|
}
|
||||||
|
|
||||||
// Recreate game objects
|
updateUI() {
|
||||||
createCoins(10);
|
document.getElementById('score').textContent = this.score;
|
||||||
createObstacles(8);
|
document.getElementById('health').textContent = Math.max(0, this.health);
|
||||||
|
}
|
||||||
|
|
||||||
// Hide game over screen
|
gameOver() {
|
||||||
document.getElementById('gameOver').style.display = 'none';
|
this.gameActive = false;
|
||||||
updateUI();
|
document.getElementById('finalScore').textContent = this.score;
|
||||||
}
|
document.getElementById('gameOver').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
// Handle window resize
|
restart() {
|
||||||
function onWindowResize() {
|
// Remove all coins and obstacles
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
this.coins.forEach(coin => coin.remove());
|
||||||
camera.updateProjectionMatrix();
|
this.obstacles.forEach(obstacle => obstacle.remove());
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
this.coins = [];
|
||||||
}
|
this.obstacles = [];
|
||||||
|
|
||||||
// Animation loop
|
// Reset player
|
||||||
function animate() {
|
this.player.reset();
|
||||||
requestAnimationFrame(animate);
|
|
||||||
|
|
||||||
updatePlayer();
|
// Reset game state
|
||||||
updateCoins();
|
this.score = 0;
|
||||||
updateObstacles();
|
this.health = 100;
|
||||||
|
this.gameActive = true;
|
||||||
|
|
||||||
// Camera follows player
|
// Recreate game objects
|
||||||
camera.position.x = player.position.x;
|
this.createGameObjects();
|
||||||
camera.position.z = player.position.z + 15;
|
|
||||||
camera.lookAt(player.position);
|
|
||||||
|
|
||||||
renderer.render(scene, camera);
|
// Hide game over screen
|
||||||
|
document.getElementById('gameOver').style.display = 'none';
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowResize() {
|
||||||
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
requestAnimationFrame(() => this.animate());
|
||||||
|
|
||||||
|
this.updatePlayer();
|
||||||
|
this.updateCoins();
|
||||||
|
this.updateObstacles();
|
||||||
|
this.updateCamera();
|
||||||
|
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the game
|
// Start the game
|
||||||
init();
|
const game = new Game();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue