0% found this document useful (0 votes)
12 views14 pages

game.js

This document outlines the implementation of a 3D game using Three.js, featuring a player-controlled character with various weapons, a WebSocket for multiplayer functionality, and a detailed environment including futuristic structures, vehicles, and vegetation. It also includes mobile controls, sound effects, and a weather system with rain particles. The game setup involves initializing the scene, camera, lighting, and event listeners, as well as managing player interactions and game state updates.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views14 pages

game.js

This document outlines the implementation of a 3D game using Three.js, featuring a player-controlled character with various weapons, a WebSocket for multiplayer functionality, and a detailed environment including futuristic structures, vehicles, and vegetation. It also includes mobile controls, sound effects, and a weather system with rain particles. The game setup involves initializing the scene, camera, lighting, and event listeners, as well as managing player interactions and game state updates.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 14

import * as THREE from 'https://cdn.skypack.dev/[email protected].

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'] })
};

// Mobile controls state


this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera
Mini/i.test(navigator.userAgent);
this.touchController = {
active: false,
startX: 0,
startY: 0,
moveX: 0,
moveY: 0
};

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();

// Start game loop


this.animate();

// Mobile-specific initialization
if (this.isMobile) {
// Adjust camera FOV for mobile
this.camera.fov = 80;
this.camera.updateProjectionMatrix();

// Show mobile controls


document.querySelector('.mobile-controls').style.display = 'block';
}
}

setupLighting() {
// Ambient light
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
this.scene.add(ambientLight);

// Directional light (sun)


const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(100, 100, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 500;
this.scene.add(directionalLight);
}

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);
}
`;

const skyGeo = new THREE.SphereGeometry(500, 32, 32);


const skyMat = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
side: THREE.BackSide
});
const sky = new THREE.Mesh(skyGeo, skyMat);
this.scene.add(sky);
}

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();

// Buildings and structures


this.createFuturisticStructures();

// Desert elements
this.createDesertElements();

// Vehicles
this.createVehicles();

// Trees and vegetation


this.createVegetation();

// 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);

// Road markings - bright yellow


const lineGeometry = new THREE.PlaneGeometry(1, 10);
const lineMaterial = new THREE.MeshStandardMaterial({
color: 0xFFFF00, // Bright yellow road lines
emissive: 0xFFFF00,
emissiveIntensity: 0.2
});

for (let i = -90; i < 90; i += 20) {


const line = new THREE.Mesh(lineGeometry, lineMaterial);
line.rotation.x = -Math.PI / 2;
line.position.set(0, 0.02, i);
this.scene.add(line);

const horizontalLine = new THREE.Mesh(lineGeometry, lineMaterial);


horizontalLine.rotation.x = -Math.PI / 2;
horizontalLine.rotation.z = Math.PI / 2;
horizontalLine.position.set(i, 0.02, 0);
this.scene.add(horizontalLine);
}
}

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
];

buildingPositions.forEach(({ pos, size, color, emissive }) => {


const geometry = new THREE.BoxGeometry(...size);
const material = new THREE.MeshPhongMaterial({
color,
emissive,
emissiveIntensity: 0.3,
opacity: 0.9,
transparent: true,
shininess: 100
});
const building = new THREE.Mesh(geometry, material);
building.position.set(...pos);
building.castShadow = true;
building.receiveShadow = true;
this.scene.add(building);
// Add glowing windows with matching colors
const windowGeometry = new THREE.PlaneGeometry(2, 2);
const windowMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFFFF,
emissive: color,
emissiveIntensity: 0.5,
side: THREE.DoubleSide
});

for (let i = 2; i < size[1]; i += 4) {


for (let j = -size[0]/3; j < size[0]/3; j += 4) {
const window = new THREE.Mesh(windowGeometry, windowMaterial);
window.position.set(pos[0] + j, i, pos[2] + size[2]/2);
this.scene.add(window);
}
}
});
}

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]
];

rockPositions.forEach((pos, index) => {


const rockMaterial = new THREE.MeshStandardMaterial({
color: rockColors[index % rockColors.length],
roughness: 0.9,
metalness: 0.1
});
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
rock.position.set(...pos);
rock.scale.set(
1 + Math.random() * 0.5,
1 + Math.random() * 0.5,
1 + Math.random() * 0.5
);
rock.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
rock.castShadow = true;
this.scene.add(rock);
});
}

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]
];

vehiclePositions.forEach((pos, index) => {


const vehicleMaterial = new THREE.MeshPhongMaterial({
color: vehicleColors[index],
metalness: 0.8,
roughness: 0.2,
shininess: 100
});
const vehicle = new THREE.Mesh(vehicleGeometry, vehicleMaterial);
vehicle.position.set(...pos);
vehicle.castShadow = true;
this.scene.add(vehicle);

// 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);

// Tree leaves - varied green colors


const leafColors = [0x228B22, 0x006400, 0x32CD32, 0x2E8B57];
const leavesGeometry = new THREE.ConeGeometry(4, 8, 8);
const leavesMaterial = new THREE.MeshStandardMaterial({
color: leafColors[Math.floor(Math.random() * leafColors.length)],
roughness: 0.8,
metalness: 0.2
});
const leaves = new THREE.Mesh(leavesGeometry, leavesMaterial);
leaves.position.set(pos[0], pos[1] + 8, pos[2]);
leaves.castShadow = true;
this.scene.add(leaves);
});
}

setupWeatherSystem() {
// Rain particles
const rainCount = 15000;
const rainGeometry = new THREE.BufferGeometry();
const rainPositions = new Float32Array(rainCount * 3);

for (let i = 0; i < rainCount * 3; i += 3) {


rainPositions[i] = Math.random() * 400 - 200;
rainPositions[i + 1] = Math.random() * 200;
rainPositions[i + 2] = Math.random() * 400 - 200;
}

rainGeometry.setAttribute('position', new THREE.BufferAttribute(rainPositions,


3));

const rainMaterial = new THREE.PointsMaterial({


color: 0xaaaaaa,
size: 0.1,
transparent: true,
opacity: 0.6
});

this.raindrops = new THREE.Points(rainGeometry, rainMaterial);


this.scene.add(this.raindrops);
}

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();
});

// Mobile touch controls


if (this.isMobile) {
const joystickArea = document.querySelector('.joystick-area');
const joystick = document.querySelector('.joystick');
const shootButton = document.querySelector('.shoot-button');
const jumpButton = document.querySelector('.jump-button');

// 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('touchmove', (e) => {


if (!this.touchController.active) return;
e.preventDefault();

const touch = e.touches[0];


const rect = joystickArea.getBoundingClientRect();

this.touchController.moveX = touch.clientX - rect.left -


this.touchController.startX;
this.touchController.moveY = touch.clientY - rect.top -
this.touchController.startY;

// Limit joystick movement


const maxDistance = 50;
const distance = Math.sqrt(
this.touchController.moveX ** 2 +
this.touchController.moveY ** 2
);

if (distance > maxDistance) {


const angle = Math.atan2(this.touchController.moveY,
this.touchController.moveX);
this.touchController.moveX = Math.cos(angle) * maxDistance;
this.touchController.moveY = Math.sin(angle) * maxDistance;
}

// Update joystick position


joystick.style.transform = `translate(${this.touchController.moveX}px, $
{this.touchController.moveY}px)`;
// Update movement
this.moveForward = this.touchController.moveY < -10;
this.moveBackward = this.touchController.moveY > 10;
this.moveLeft = this.touchController.moveX < -10;
this.moveRight = this.touchController.moveX > 10;
});

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();
});

jumpButton.addEventListener('touchstart', (e) => {


e.preventDefault();
if (this.canJump) {
this.velocity.y += 20;
this.canJump = false;
}
});
}
}

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);

const projectileGeometry = new THREE.SphereGeometry(0.1);


const projectileMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const projectile = new THREE.Mesh(projectileGeometry, projectileMaterial);
projectile.position.copy(this.camera.position);
projectile.velocity = direction.multiplyScalar(50);
projectile.created = performance.now();

this.scene.add(projectile);
this.projectiles.push(projectile);

// Send shoot event


this.room.send({
type: 'shoot',
position: this.camera.position.toArray(),
direction: direction.toArray(),
weapon: this.currentWeapon.type
});
}

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 velocity and movement


this.updateMovement(delta);

// Update projectiles
this.updateProjectiles(delta);

// Send position update


this.room.send({
type: 'position',
position: this.camera.position.toArray()
});

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;

this.direction.z = Number(this.moveForward) - Number(this.moveBackward);


this.direction.x = Number(this.moveRight) - Number(this.moveLeft);
this.direction.normalize();

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);

this.camera.position.y += this.velocity.y * 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));

// Remove old projectiles


if (performance.now() - projectile.created > 2000) {
this.scene.remove(projectile);
this.projectiles.splice(i, 1);
}
}
}

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);
}
}

// Initialize game when the page loads


window.addEventListener('load', () => {
document.getElementById('menu').style.display = 'block';
});

You might also like