# Add Touch Support and Documentation ## 📋 Summary This PR adds comprehensive touch support for mobile devices and tablets, enabling players to control the game using touch gestures. Additionally, a complete README.md file has been added to document the game features, controls, and gameplay mechanics. ## 🎯 Changes Made ### ✨ New Features - **Touch Controls**: Full touch support for smartphones and tablets - Touch and drag anywhere on the screen to move the player - Movement direction calculated relative to screen center - Smooth, responsive touch input handling - Multi-touch support with proper touch ID tracking - **Documentation**: Comprehensive README.md - Game overview and features - Detailed controls (keyboard and touch) - Game mechanics explanation - Technical details and architecture - Getting started guide - Tips for high scores ### 🔧 Technical Implementation #### Touch Event Handling - Added touch event listeners (`touchstart`, `touchmove`, `touchend`, `touchcancel`) - Implemented touch position tracking relative to screen center - Converted touch input to WASD key states for seamless integration - Proper touch ID tracking to handle multiple simultaneous touches - Touch movement threshold (10px) to prevent accidental movements #### Code Structure - Added touch-related properties to `Game` class: - `touchActive`, `touchStartX/Y`, `touchCurrentX/Y`, `touchId` - New methods: - `setupTouchControls()`: Initializes touch event listeners - `handleTouchStart()`: Handles touch initiation - `handleTouchMove()`: Updates touch position during drag - `handleTouchEnd()`: Resets touch state - `updateTouchMovement()`: Converts touch input to movement keys #### UI Updates - Updated instructions to mention touch controls - Responsive design maintained for mobile devices ## 📊 Statistics - **Files Changed**: 2 - `index.html`: +123 lines, -1 line - `README.md`: +171 lines (new file) - **Total Changes**: +293 insertions, -1 deletion - **Commits**: 2 ## 🧪 Testing ### Desktop Testing - ✅ Keyboard controls (WASD and Arrow Keys) still work correctly - ✅ No regression in existing functionality - ✅ Game performance unchanged ### Mobile Testing - ✅ Touch controls work on smartphones - ✅ Touch controls work on tablets - ✅ Movement is smooth and responsive - ✅ Touch input properly resets on touch end - ✅ Multiple touches handled correctly (only first touch tracked) ## 📱 Mobile Compatibility The game now fully supports: - iOS devices (iPhone, iPad) - Android devices (phones and tablets) - Other touch-enabled devices Touch controls use the center of the screen as a reference point, making it intuitive for users to drag in any direction to move. ## 🎮 User Experience Improvements 1. **Accessibility**: Game is now playable on mobile devices without keyboard 2. **Intuitive Controls**: Touch and drag is natural for mobile users 3. **Documentation**: README provides clear instructions for all users 4. **No Visual Clutter**: Touch controls work without on-screen joystick ## 🔍 Code Quality - Clean, maintainable code structure - Proper event handling with `preventDefault()` to avoid browser defaults - Touch events use `{ passive: false }` for proper scroll prevention - Consistent with existing code style and patterns - No breaking changes to existing functionality ## 📝 Notes - Touch controls integrate seamlessly with existing keyboard controls - Both input methods can work simultaneously (though not recommended) - Touch movement uses the same movement system as keyboard input - README includes comprehensive documentation for future developers ## ✅ Checklist - [x] Touch controls implemented and tested - [x] Mobile compatibility verified - [x] README.md created with comprehensive documentation - [x] No regression in desktop functionality - [x] Code follows existing patterns and style - [x] Instructions updated to mention touch controls --- **Ready for Review** 🚀 Reviewed-on: #4 Co-authored-by: Juan Sebastian Montoya <juansmm@outlook.com> Co-committed-by: Juan Sebastian Montoya <juansmm@outlook.com>
653 lines
No EOL
23 KiB
HTML
653 lines
No EOL
23 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>3D Coin Collector Game</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
overflow: hidden;
|
|
font-family: 'Arial', sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
#gameCanvas {
|
|
display: block;
|
|
}
|
|
#ui {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
color: white;
|
|
font-size: 24px;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
z-index: 100;
|
|
}
|
|
#gameOver {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0,0,0,0.8);
|
|
padding: 40px;
|
|
border-radius: 20px;
|
|
text-align: center;
|
|
color: white;
|
|
display: none;
|
|
z-index: 200;
|
|
}
|
|
#gameOver h1 {
|
|
font-size: 48px;
|
|
margin-bottom: 20px;
|
|
color: #ff6b6b;
|
|
}
|
|
#gameOver p {
|
|
font-size: 24px;
|
|
margin-bottom: 30px;
|
|
}
|
|
#restartBtn {
|
|
background: #4CAF50;
|
|
border: none;
|
|
color: white;
|
|
padding: 15px 40px;
|
|
font-size: 20px;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
#restartBtn:hover {
|
|
background: #45a049;
|
|
transform: scale(1.1);
|
|
}
|
|
#instructions {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: white;
|
|
text-align: center;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
}
|
|
@media (max-width: 768px) {
|
|
#instructions {
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="ui">
|
|
<div>Score: <span id="score">0</span></div>
|
|
<div>Health: <span id="health">100</span></div>
|
|
</div>
|
|
|
|
<div id="gameOver">
|
|
<h1>Game Over!</h1>
|
|
<p>Final Score: <span id="finalScore">0</span></p>
|
|
<button id="restartBtn">Play Again</button>
|
|
</div>
|
|
|
|
<div id="instructions">
|
|
<p><strong>Controls:</strong> WASD or Arrow Keys to move | Touch and drag to move (mobile) | Collect yellow coins | Avoid red obstacles!</p>
|
|
</div>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
<script>
|
|
// Base GameObject class
|
|
class GameObject {
|
|
constructor(scene, groundSize) {
|
|
this.scene = scene;
|
|
this.groundSize = groundSize;
|
|
this.mesh = this.createMesh();
|
|
this.initialize();
|
|
scene.add(this.mesh);
|
|
}
|
|
|
|
// Override in child classes
|
|
createMesh() {
|
|
throw new Error('createMesh() must be implemented by child class');
|
|
}
|
|
|
|
// Override in child classes for initialization
|
|
initialize() {
|
|
// Default implementation - can be overridden
|
|
}
|
|
|
|
// Override in child classes for update logic
|
|
update(...args) {
|
|
// Default implementation - can be overridden
|
|
}
|
|
|
|
// Common collision detection
|
|
checkCollision(otherPosition, collisionRadius) {
|
|
const distance = this.mesh.position.distanceTo(otherPosition);
|
|
return distance < collisionRadius;
|
|
}
|
|
|
|
// Common position getter
|
|
getPosition() {
|
|
return this.mesh.position;
|
|
}
|
|
|
|
// Remove from scene
|
|
remove() {
|
|
this.scene.remove(this.mesh);
|
|
}
|
|
|
|
// Set position
|
|
setPosition(x, y, z) {
|
|
this.mesh.position.set(x, y, z);
|
|
}
|
|
|
|
// Get position vector
|
|
getPositionVector() {
|
|
return this.mesh.position.clone();
|
|
}
|
|
}
|
|
|
|
// Player class extends GameObject
|
|
class Player extends GameObject {
|
|
constructor(scene, groundSize) {
|
|
super(scene, groundSize);
|
|
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,
|
|
metalness: 0.8,
|
|
roughness: 0.2,
|
|
emissive: 0xFFD700,
|
|
emissiveIntensity: 0.3
|
|
});
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
mesh.castShadow = true;
|
|
mesh.receiveShadow = true;
|
|
return mesh;
|
|
}
|
|
|
|
initialize() {
|
|
this.setRandomPosition();
|
|
}
|
|
|
|
setRandomPosition() {
|
|
const x = (Math.random() - 0.5) * (this.groundSize - 4);
|
|
const z = (Math.random() - 0.5) * (this.groundSize - 4);
|
|
this.setPosition(x, 0.5, z);
|
|
}
|
|
|
|
update() {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Obstacle class extends GameObject
|
|
class Obstacle extends GameObject {
|
|
constructor(scene, groundSize) {
|
|
super(scene, groundSize);
|
|
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,
|
|
metalness: 0.3,
|
|
roughness: 0.7
|
|
});
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
mesh.castShadow = true;
|
|
mesh.receiveShadow = true;
|
|
return mesh;
|
|
}
|
|
|
|
initialize() {
|
|
this.setRandomPosition();
|
|
this.mesh.position.y = 1;
|
|
}
|
|
|
|
createRandomDirection() {
|
|
return new THREE.Vector3(
|
|
(Math.random() - 0.5) * 0.05,
|
|
0,
|
|
(Math.random() - 0.5) * 0.05
|
|
);
|
|
}
|
|
|
|
setRandomPosition() {
|
|
let posX, posZ;
|
|
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);
|
|
|
|
this.setPosition(posX, 1, posZ);
|
|
}
|
|
|
|
update() {
|
|
this.mesh.position.add(this.direction);
|
|
|
|
// Bounce off boundaries
|
|
const boundary = this.groundSize / 2 - 1;
|
|
if (Math.abs(this.mesh.position.x) > boundary) {
|
|
this.direction.x *= -1;
|
|
}
|
|
if (Math.abs(this.mesh.position.z) > boundary) {
|
|
this.direction.z *= -1;
|
|
}
|
|
}
|
|
|
|
checkCollisionWithPlayer(playerPosition) {
|
|
return this.checkCollision(playerPosition, this.collisionRadius);
|
|
}
|
|
}
|
|
|
|
// Game class
|
|
class Game {
|
|
constructor() {
|
|
this.groundSize = 30;
|
|
this.score = 0;
|
|
this.health = 100;
|
|
this.gameActive = true;
|
|
this.keys = {};
|
|
this.coins = [];
|
|
this.obstacles = [];
|
|
this.touchActive = false;
|
|
this.touchStartX = 0;
|
|
this.touchStartY = 0;
|
|
this.touchCurrentX = 0;
|
|
this.touchCurrentY = 0;
|
|
this.touchId = null;
|
|
|
|
this.init();
|
|
this.setupEventListeners();
|
|
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());
|
|
|
|
// Touch event listeners
|
|
this.setupTouchControls();
|
|
}
|
|
|
|
setupTouchControls() {
|
|
const canvas = this.renderer.domElement;
|
|
|
|
// Prevent default touch behaviors
|
|
canvas.addEventListener('touchstart', (e) => {
|
|
e.preventDefault();
|
|
this.handleTouchStart(e);
|
|
}, { passive: false });
|
|
|
|
canvas.addEventListener('touchmove', (e) => {
|
|
e.preventDefault();
|
|
this.handleTouchMove(e);
|
|
}, { passive: false });
|
|
|
|
canvas.addEventListener('touchend', (e) => {
|
|
e.preventDefault();
|
|
this.handleTouchEnd(e);
|
|
}, { passive: false });
|
|
|
|
canvas.addEventListener('touchcancel', (e) => {
|
|
e.preventDefault();
|
|
this.handleTouchEnd(e);
|
|
}, { passive: false });
|
|
}
|
|
|
|
handleTouchStart(e) {
|
|
if (this.touchActive) return;
|
|
|
|
const touch = e.touches[0];
|
|
const rect = this.renderer.domElement.getBoundingClientRect();
|
|
|
|
// Use center of screen as reference point
|
|
this.touchStartX = rect.left + rect.width / 2;
|
|
this.touchStartY = rect.top + rect.height / 2;
|
|
|
|
this.touchId = touch.identifier;
|
|
this.touchCurrentX = touch.clientX;
|
|
this.touchCurrentY = touch.clientY;
|
|
this.touchActive = true;
|
|
}
|
|
|
|
handleTouchMove(e) {
|
|
if (!this.touchActive) return;
|
|
|
|
const touch = Array.from(e.touches).find(t => t.identifier === this.touchId);
|
|
if (!touch) return;
|
|
|
|
this.touchCurrentX = touch.clientX;
|
|
this.touchCurrentY = touch.clientY;
|
|
|
|
this.updateTouchMovement();
|
|
}
|
|
|
|
handleTouchEnd(e) {
|
|
if (!this.touchActive) return;
|
|
|
|
// Check if the touch that ended is our tracked touch
|
|
const touch = Array.from(e.changedTouches).find(t => t.identifier === this.touchId);
|
|
if (!touch) return;
|
|
|
|
this.touchActive = false;
|
|
this.touchId = null;
|
|
this.touchCurrentX = this.touchStartX;
|
|
this.touchCurrentY = this.touchStartY;
|
|
this.updateTouchMovement();
|
|
}
|
|
|
|
updateTouchMovement() {
|
|
if (!this.touchActive) {
|
|
// Reset all movement keys
|
|
this.keys['w'] = false;
|
|
this.keys['s'] = false;
|
|
this.keys['a'] = false;
|
|
this.keys['d'] = false;
|
|
return;
|
|
}
|
|
|
|
const deltaX = this.touchCurrentX - this.touchStartX;
|
|
const deltaY = this.touchCurrentY - this.touchStartY;
|
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
|
|
// Normalize movement direction
|
|
const threshold = 10; // Minimum movement to register
|
|
if (distance < threshold) {
|
|
this.keys['w'] = false;
|
|
this.keys['s'] = false;
|
|
this.keys['a'] = false;
|
|
this.keys['d'] = false;
|
|
return;
|
|
}
|
|
|
|
// Calculate direction
|
|
const normalizedX = deltaX / distance;
|
|
const normalizedY = deltaY / distance;
|
|
|
|
// Update movement keys based on direction
|
|
this.keys['w'] = normalizedY < -0.3;
|
|
this.keys['s'] = normalizedY > 0.3;
|
|
this.keys['a'] = normalizedX < -0.3;
|
|
this.keys['d'] = normalizedX > 0.3;
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
updateObstacles() {
|
|
if (!this.gameActive) return;
|
|
|
|
this.obstacles.forEach(obstacle => {
|
|
obstacle.update();
|
|
|
|
if (obstacle.checkCollisionWithPlayer(this.player.getPosition())) {
|
|
this.health -= 1;
|
|
this.updateUI();
|
|
this.player.handleCollision(obstacle.getPosition());
|
|
|
|
if (this.health <= 0) {
|
|
this.gameOver();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
updateCamera() {
|
|
const playerPos = this.player.getPosition();
|
|
this.camera.position.x = playerPos.x;
|
|
this.camera.position.z = playerPos.z + 15;
|
|
this.camera.lookAt(playerPos);
|
|
}
|
|
|
|
updateUI() {
|
|
document.getElementById('score').textContent = this.score;
|
|
document.getElementById('health').textContent = Math.max(0, this.health);
|
|
}
|
|
|
|
gameOver() {
|
|
this.gameActive = false;
|
|
document.getElementById('finalScore').textContent = this.score;
|
|
document.getElementById('gameOver').style.display = 'block';
|
|
}
|
|
|
|
restart() {
|
|
// Remove all coins and obstacles
|
|
this.coins.forEach(coin => coin.remove());
|
|
this.obstacles.forEach(obstacle => obstacle.remove());
|
|
this.coins = [];
|
|
this.obstacles = [];
|
|
|
|
// Reset player
|
|
this.player.reset();
|
|
|
|
// Reset game state
|
|
this.score = 0;
|
|
this.health = 100;
|
|
this.gameActive = true;
|
|
|
|
// Recreate game objects
|
|
this.createGameObjects();
|
|
|
|
// 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());
|
|
|
|
// Update touch movement if active
|
|
if (this.touchActive) {
|
|
this.updateTouchMovement();
|
|
}
|
|
|
|
this.updatePlayer();
|
|
this.updateCoins();
|
|
this.updateObstacles();
|
|
this.updateCamera();
|
|
|
|
this.renderer.render(this.scene, this.camera);
|
|
}
|
|
}
|
|
|
|
// Start the game
|
|
const game = new Game();
|
|
</script>
|
|
</body>
|
|
</html> |