game.js
game.js
2';
import { PointerLockControls } from
'https://cdn.skypack.dev/[email protected]/examples/jsm/controls/PointerLockControls.js
';
class Weapon {
constructor(type, damage, ammo, reloadTime, fireRate) {
this.type = type;
this.damage = damage;
this.ammo = ammo;
this.maxAmmo = ammo;
this.reloadTime = reloadTime;
this.fireRate = fireRate;
this.lastFired = 0;
this.reloading = false;
}
}
class Game {
constructor() {
// Initialize WebSocket connection
this.room = new WebsimSocket();
this.setupWebsim();
// Three.js setup
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth /
window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.controls = new PointerLockControls(this.camera, document.body);
// Movement state
this.moveForward = false;
this.moveBackward = false;
this.moveLeft = false;
this.moveRight = false;
this.canJump = true;
// Physics
this.velocity = new THREE.Vector3();
this.direction = new THREE.Vector3();
// Game state
this.prevTime = performance.now();
this.health = 100;
this.score = 0;
// Weapons setup
this.weapons = {
pistol: new Weapon('pistol', 25, 12, 1000, 250),
rifle: new Weapon('rifle', 20, 30, 2000, 100),
rpg: new Weapon('rpg', 100, 1, 3000, 1000)
};
this.currentWeapon = this.weapons.pistol;
// Collections
this.players = new Map();
this.projectiles = [];
// Sound effects
this.sounds = {
pistol: new Howl({ src: ['https://assets.codepen.io/21542/pistol.mp3'] }),
rifle: new Howl({ src: ['https://assets.codepen.io/21542/rifle.mp3'] }),
rpg: new Howl({ src: ['https://assets.codepen.io/21542/rpg.mp3'] }),
hit: new Howl({ src: ['https://assets.codepen.io/21542/hit.mp3'] }),
reload: new Howl({ src: ['https://assets.codepen.io/21542/reload.mp3'] })
};
this.init();
}
setupWebsim() {
this.room.onmessage = (event) => {
const data = event.data;
switch (data.type) {
case 'position':
if (data.clientId !== this.room.party.client.id) {
this.updatePlayerPosition(data);
}
break;
case 'shoot':
if (data.clientId !== this.room.party.client.id) {
this.handleRemoteShot(data);
}
break;
case 'hit':
if (data.targetId === this.room.party.client.id) {
this.takeDamage(data.damage);
}
break;
}
};
}
init() {
// Renderer setup
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
this.renderer.setClearColor(new THREE.Color(0x87CEEB)); // Sky blue
document.body.appendChild(this.renderer.domElement);
// Scene setup
this.setupLighting();
this.createEnvironment();
this.setupSkybox();
// Camera setup
this.camera.position.set(0, 2, 0);
// Controls setup
this.controls.addEventListener('lock', () => {
document.getElementById('menu').style.display = 'none';
});
this.controls.addEventListener('unlock', () => {
document.getElementById('menu').style.display = 'block';
});
// Event listeners
this.setupEventListeners();
// Mobile-specific initialization
if (this.isMobile) {
// Adjust camera FOV for mobile
this.camera.fov = 80;
this.camera.updateProjectionMatrix();
setupLighting() {
// Ambient light
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
this.scene.add(ambientLight);
setupSkybox() {
const vertexShader = `
varying vec3 vWorldPosition;
void main() {
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
vWorldPosition = worldPosition.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
varying vec3 vWorldPosition;
void main() {
vec3 worldPosition = normalize(vWorldPosition);
vec3 topColor = vec3(0.4, 0.6, 1.0);
vec3 bottomColor = vec3(0.8, 0.9, 1.0);
float h = normalize(worldPosition).y;
gl_FragColor = vec4(mix(bottomColor, topColor, h * 0.5 + 0.5), 1.0);
}
`;
createEnvironment() {
// Ground with vibrant desert texture
const groundGeometry = new THREE.PlaneGeometry(400, 400, 50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x90EE90, // Light green for grass areas
roughness: 0.8,
metalness: 0.1
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
this.scene.add(ground);
// Roads
this.createRoads();
// Desert elements
this.createDesertElements();
// Vehicles
this.createVehicles();
// Weather system
this.setupWeatherSystem();
}
createRoads() {
// Main roads - darker asphalt color
const roadGeometry = new THREE.PlaneGeometry(20, 200);
const roadMaterial = new THREE.MeshStandardMaterial({
color: 0x1C1C1C, // Darker asphalt
roughness: 0.9
});
// Horizontal road
const horizontalRoad = new THREE.Mesh(roadGeometry, roadMaterial);
horizontalRoad.rotation.x = -Math.PI / 2;
horizontalRoad.position.y = 0.01;
this.scene.add(horizontalRoad);
// Vertical road
const verticalRoad = new THREE.Mesh(roadGeometry, roadMaterial);
verticalRoad.rotation.x = -Math.PI / 2;
verticalRoad.rotation.z = Math.PI / 2;
verticalRoad.position.y = 0.01;
this.scene.add(verticalRoad);
createFuturisticStructures() {
// Futuristic skyscrapers with vibrant colors
const buildingPositions = [
{ pos: [50, 30, -50], size: [20, 60, 20], color: 0x00FFFF, emissive: 0x00FFFF
}, // Cyan
{ pos: [-40, 25, 60], size: [15, 50, 15], color: 0xFF1493, emissive: 0xFF1493
}, // Deep pink
{ pos: [60, 20, 40], size: [18, 40, 18], color: 0x7B68EE, emissive:
0x7B68EE }, // Medium slate blue
{ pos: [-50, 35, -40], size: [22, 70, 22], color: 0x32CD32, emissive:
0x32CD32 } // Lime green
];
createDesertElements() {
// Desert rocks with warm colors
const rockColors = [0xCD853F, 0xDEB887, 0xD2691E, 0x8B4513]; // Various sand
and rock colors
const rockGeometry = new THREE.DodecahedronGeometry(5);
const rockPositions = [
[80, 2.5, 80],
[-70, 2.5, -60],
[90, 2.5, -40],
[-80, 2.5, 70]
];
createVehicles() {
// Futuristic vehicles with metallic colors
const vehicleColors = [0xFF4500, 0x4169E1, 0x9370DB, 0x20B2AA]; // Various
vibrant colors
const vehicleGeometry = new THREE.BoxGeometry(6, 2, 4);
const vehiclePositions = [
[10, 1, 10],
[-15, 1, -8],
[25, 1, -20],
[-30, 1, 15]
];
// Add wheels
const wheelGeometry = new THREE.CylinderGeometry(0.5, 0.5, 0.5, 32);
const wheelMaterial = new THREE.MeshStandardMaterial({
color: 0x1a1a1a,
metalness: 0.9,
roughness: 0.1
});
const wheelPositions = [
[-2, -0.5, 1.5],
[-2, -0.5, -1.5],
[2, -0.5, 1.5],
[2, -0.5, -1.5]
];
wheelPositions.forEach(wheelPos => {
const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(
pos[0] + wheelPos[0],
pos[1] + wheelPos[1],
pos[2] + wheelPos[2]
);
this.scene.add(wheel);
});
});
}
createVegetation() {
// Trees with richer colors
const treePositions = [
[30, 0, 30],
[-25, 0, -35],
[40, 0, -20],
[-35, 0, 25]
];
treePositions.forEach(pos => {
// Tree trunk - rich brown color
const trunkGeometry = new THREE.CylinderGeometry(0.5, 1, 8);
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9,
metalness: 0.1
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.set(pos[0], pos[1] + 4, pos[2]);
trunk.castShadow = true;
this.scene.add(trunk);
setupWeatherSystem() {
// Rain particles
const rainCount = 15000;
const rainGeometry = new THREE.BufferGeometry();
const rainPositions = new Float32Array(rainCount * 3);
setupEventListeners() {
document.addEventListener('click', () => this.shoot());
document.addEventListener('keydown', (e) => this.onKeyDown(e));
document.addEventListener('keyup', (e) => this.onKeyUp(e));
window.addEventListener('resize', () => this.onWindowResize());
// Weapon selection
document.querySelectorAll('.weapon-slot').forEach(slot => {
slot.addEventListener('click', () => {
const weapon = slot.dataset.weapon;
this.switchWeapon(weapon);
});
});
// Play button
document.getElementById('playButton').addEventListener('click', () => {
this.controls.lock();
});
// Joystick controls
joystickArea.addEventListener('touchstart', (e) => {
this.touchController.active = true;
const touch = e.touches[0];
const rect = joystickArea.getBoundingClientRect();
this.touchController.startX = touch.clientX - rect.left;
this.touchController.startY = touch.clientY - rect.top;
});
joystickArea.addEventListener('touchend', () => {
this.touchController.active = false;
this.touchController.moveX = 0;
this.touchController.moveY = 0;
this.moveForward = false;
this.moveBackward = false;
this.moveLeft = false;
this.moveRight = false;
joystick.style.transform = 'translate(-50%, -50%)';
});
// Action buttons
shootButton.addEventListener('touchstart', (e) => {
e.preventDefault();
this.shoot();
});
shoot() {
if (!this.controls.isLocked ||
this.currentWeapon.reloading ||
performance.now() - this.currentWeapon.lastFired <
this.currentWeapon.fireRate ||
this.currentWeapon.ammo <= 0) return;
this.currentWeapon.lastFired = performance.now();
this.currentWeapon.ammo--;
// Play sound
this.sounds[this.currentWeapon.type].play();
// Update UI
document.getElementById('ammo').textContent =
`${this.currentWeapon.ammo}/${this.currentWeapon.maxAmmo}`;
// Create projectile
const direction = new THREE.Vector3();
this.camera.getWorldDirection(direction);
this.scene.add(projectile);
this.projectiles.push(projectile);
reload() {
if (this.currentWeapon.reloading ||
this.currentWeapon.ammo === this.currentWeapon.maxAmmo) return;
this.currentWeapon.reloading = true;
this.sounds.reload.play();
setTimeout(() => {
this.currentWeapon.ammo = this.currentWeapon.maxAmmo;
this.currentWeapon.reloading = false;
document.getElementById('ammo').textContent =
`${this.currentWeapon.ammo}/${this.currentWeapon.maxAmmo}`;
}, this.currentWeapon.reloadTime);
}
switchWeapon(type) {
if (this.currentWeapon.type === type) return;
document.querySelectorAll('.weapon-slot').forEach(slot => {
slot.classList.toggle('active', slot.dataset.weapon === type);
});
this.currentWeapon = this.weapons[type];
document.getElementById('ammo').textContent =
`${this.currentWeapon.ammo}/${this.currentWeapon.maxAmmo}`;
}
takeDamage(amount) {
this.health = Math.max(0, this.health - amount);
document.getElementById('healthBar').style.width = `${this.health}%`;
if (this.health <= 0) {
this.die();
}
}
die() {
this.health = 100;
document.getElementById('healthBar').style.width = '100%';
this.camera.position.set(0, 2, 0);
this.velocity.set(0, 0, 0);
}
updatePlayerPosition(data) {
let playerMesh = this.players.get(data.clientId);
if (!playerMesh) {
const geometry = new THREE.BoxGeometry(1, 2, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
playerMesh = new THREE.Mesh(geometry, material);
playerMesh.castShadow = true;
this.scene.add(playerMesh);
this.players.set(data.clientId, playerMesh);
}
playerMesh.position.fromArray(data.position);
}
animate() {
requestAnimationFrame(() => this.animate());
if (this.controls.isLocked) {
const time = performance.now();
const delta = (time - this.prevTime) / 1000;
// Update projectiles
this.updateProjectiles(delta);
this.prevTime = time;
}
if (this.raindrops) {
const positions = this.raindrops.geometry.attributes.position.array;
for (let i = 0; i < positions.length; i += 3) {
positions[i + 1] -= 1;
if (positions[i + 1] < 0) {
positions[i + 1] = 200;
}
}
this.raindrops.geometry.attributes.position.needsUpdate = true;
}
this.renderer.render(this.scene, this.camera);
}
updateMovement(delta) {
this.velocity.x -= this.velocity.x * 10.0 * delta;
this.velocity.z -= this.velocity.z * 10.0 * delta;
this.velocity.y -= 9.8 * 10.0 * delta;
if (this.moveForward || this.moveBackward)
this.velocity.z -= this.direction.z * 400.0 * delta;
if (this.moveLeft || this.moveRight)
this.velocity.x -= this.direction.x * 400.0 * delta;
this.controls.moveRight(-this.velocity.x * delta);
this.controls.moveForward(-this.velocity.z * delta);
if (this.camera.position.y < 2) {
this.velocity.y = 0;
this.camera.position.y = 2;
this.canJump = true;
}
}
updateProjectiles(delta) {
for (let i = this.projectiles.length - 1; i >= 0; i--) {
const projectile = this.projectiles[i];
projectile.position.add(projectile.velocity.clone().multiplyScalar(delta));
onKeyDown(event) {
switch (event.code) {
case 'KeyW': this.moveForward = true; break;
case 'KeyS': this.moveBackward = true; break;
case 'KeyA': this.moveLeft = true; break;
case 'KeyD': this.moveRight = true; break;
case 'Space':
if (this.canJump) {
this.velocity.y += 20;
this.canJump = false;
}
break;
case 'KeyR': this.reload(); break;
case 'Digit1': this.switchWeapon('pistol'); break;
case 'Digit2': this.switchWeapon('rifle'); break;
case 'Digit3': this.switchWeapon('rpg'); break;
}
}
onKeyUp(event) {
switch (event.code) {
case 'KeyW': this.moveForward = false; break;
case 'KeyS': this.moveBackward = false; break;
case 'KeyA': this.moveLeft = false; break;
case 'KeyD': this.moveRight = false; break;
}
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
}