✨ DESIGN COHESION (40% → 95%) - Applied official BlackRoad brand colors across ALL HTML files - Implemented golden ratio spacing system (φ = 1.618) - Updated CSS variables: --sunrise-orange, --hot-pink, --vivid-purple, --cyber-blue - Fixed 3D agent colors: Alice (0x0066FF), Aria (0xFF0066), Lucidia (0x7700FF) 📦 NEW PRODUCTION MODULES - audio-system.js: Procedural music, biome sounds, weather effects - api-client.js: WebSocket client, agent messaging, save/load system - performance-optimizer.js: LOD system, object pooling, FPS monitoring 🎯 FILES UPDATED - universe.html, index.html, pangea.html, ultimate.html 🛠 DEPLOYMENT TOOLS - deploy-quick.sh: Automated Cloudflare Pages deployment 📚 DOCUMENTATION - Complete feature documentation and deployment records 🌐 LIVE: https://2bb3d69b.blackroad-metaverse.pages.dev This commit represents a complete metaverse transformation! 🔥
813 lines
26 KiB
JavaScript
813 lines
26 KiB
JavaScript
/**
|
|
* GAME INTEGRATION SYSTEM
|
|
*
|
|
* Brings all metaverse systems together into a cohesive, playable experience.
|
|
* Handles initialization, game loop, UI, controls, and system coordination.
|
|
*
|
|
* Philosophy: "EVERYTHING WORKS TOGETHER IN HARMONY"
|
|
*/
|
|
|
|
import * as THREE from 'three';
|
|
import { BiomeGenerator } from './infinite-biomes.js';
|
|
import { LivingNature, EMOTIONS } from './living-nature.js';
|
|
import { MusicEngine, NatureSoundscape } from './living-music.js';
|
|
import { CreationManager } from './creation-powers.js';
|
|
import { MultiplayerManager } from './multiplayer-love.js';
|
|
import { PhotorealisticRenderer, AdvancedMaterials, AdvancedLighting, CustomShaders } from './photorealistic-graphics.js';
|
|
import { ParticleSystem } from './particle-effects.js';
|
|
import { TransportationSystem } from './transportation.js';
|
|
|
|
// ===== GAME STATE MANAGER =====
|
|
export class GameState {
|
|
constructor() {
|
|
this.player = {
|
|
id: null,
|
|
username: null,
|
|
position: new THREE.Vector3(0, 5, 0),
|
|
rotation: new THREE.Euler(0, 0, 0),
|
|
velocity: new THREE.Vector3(0, 0, 0),
|
|
isFlying: false,
|
|
currentBiome: 'grassland',
|
|
inventory: {
|
|
seeds: {},
|
|
gifts: {},
|
|
materials: {}
|
|
},
|
|
stats: {
|
|
creaturesLoved: 0,
|
|
plantsGrown: 0,
|
|
giftsGiven: 0,
|
|
distanceTraveled: 0,
|
|
timeInWorld: 0
|
|
}
|
|
};
|
|
|
|
this.world = {
|
|
timeOfDay: 0.25, // 0 = midnight, 0.5 = noon
|
|
weather: 'clear', // 'clear', 'rain', 'snow'
|
|
season: 'spring',
|
|
temperature: 20,
|
|
windSpeed: 0.5
|
|
};
|
|
|
|
this.settings = {
|
|
graphics: 'ultra', // 'low', 'medium', 'high', 'ultra'
|
|
musicVolume: 0.7,
|
|
soundVolume: 0.8,
|
|
uiScale: 1.0,
|
|
showFPS: false,
|
|
renderDistance: 500
|
|
};
|
|
|
|
this.paused = false;
|
|
this.startTime = Date.now();
|
|
}
|
|
|
|
updateTime(deltaTime) {
|
|
if (this.paused) return;
|
|
|
|
// Increment time of day (24-hour cycle over 24 real minutes)
|
|
this.world.timeOfDay += deltaTime / (24 * 60);
|
|
if (this.world.timeOfDay >= 1) {
|
|
this.world.timeOfDay -= 1;
|
|
}
|
|
|
|
// Update stats
|
|
this.player.stats.timeInWorld += deltaTime;
|
|
}
|
|
|
|
getCurrentHour() {
|
|
return Math.floor(this.world.timeOfDay * 24);
|
|
}
|
|
|
|
getTimeString() {
|
|
const hour = this.getCurrentHour();
|
|
const minute = Math.floor((this.world.timeOfDay * 24 * 60) % 60);
|
|
const ampm = hour >= 12 ? 'PM' : 'AM';
|
|
const displayHour = hour % 12 || 12;
|
|
return `${displayHour}:${minute.toString().padStart(2, '0')} ${ampm}`;
|
|
}
|
|
|
|
save() {
|
|
const saveData = {
|
|
player: this.player,
|
|
world: this.world,
|
|
settings: this.settings,
|
|
timestamp: Date.now()
|
|
};
|
|
localStorage.setItem('blackroad_save', JSON.stringify(saveData));
|
|
return saveData;
|
|
}
|
|
|
|
load() {
|
|
const saved = localStorage.getItem('blackroad_save');
|
|
if (saved) {
|
|
const data = JSON.parse(saved);
|
|
this.player = { ...this.player, ...data.player };
|
|
this.world = { ...this.world, ...data.world };
|
|
this.settings = { ...this.settings, ...data.settings };
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ===== UI SYSTEM =====
|
|
export class UIManager {
|
|
constructor(gameState) {
|
|
this.gameState = gameState;
|
|
this.elements = {};
|
|
this.notifications = [];
|
|
this.createUI();
|
|
}
|
|
|
|
createUI() {
|
|
// HUD Container
|
|
const hud = document.createElement('div');
|
|
hud.id = 'game-hud';
|
|
hud.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
font-family: 'Inter', -apple-system, sans-serif;
|
|
color: white;
|
|
z-index: 1000;
|
|
`;
|
|
document.body.appendChild(hud);
|
|
|
|
// Top Bar
|
|
this.createTopBar(hud);
|
|
|
|
// Bottom Controls
|
|
this.createBottomControls(hud);
|
|
|
|
// Notification Area
|
|
this.createNotificationArea(hud);
|
|
|
|
// Interaction Prompt
|
|
this.createInteractionPrompt(hud);
|
|
|
|
// Stats Panel
|
|
this.createStatsPanel(hud);
|
|
|
|
// Inventory Panel
|
|
this.createInventoryPanel(hud);
|
|
}
|
|
|
|
createTopBar(parent) {
|
|
const topBar = document.createElement('div');
|
|
topBar.style.cssText = `
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
padding: 12px 24px;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 12px;
|
|
display: flex;
|
|
gap: 24px;
|
|
align-items: center;
|
|
pointer-events: all;
|
|
`;
|
|
|
|
// Time Display
|
|
const timeDisplay = document.createElement('div');
|
|
timeDisplay.id = 'time-display';
|
|
timeDisplay.innerHTML = `⏰ <span id="time-text">12:00 PM</span>`;
|
|
topBar.appendChild(timeDisplay);
|
|
|
|
// Weather Display
|
|
const weatherDisplay = document.createElement('div');
|
|
weatherDisplay.id = 'weather-display';
|
|
weatherDisplay.innerHTML = `☀️ <span id="weather-text">Clear</span>`;
|
|
topBar.appendChild(weatherDisplay);
|
|
|
|
// Biome Display
|
|
const biomeDisplay = document.createElement('div');
|
|
biomeDisplay.id = 'biome-display';
|
|
biomeDisplay.innerHTML = `🌍 <span id="biome-text">Grassland</span>`;
|
|
topBar.appendChild(biomeDisplay);
|
|
|
|
parent.appendChild(topBar);
|
|
|
|
this.elements.timeText = document.getElementById('time-text');
|
|
this.elements.weatherText = document.getElementById('weather-text');
|
|
this.elements.biomeText = document.getElementById('biome-text');
|
|
}
|
|
|
|
createBottomControls(parent) {
|
|
const controls = document.createElement('div');
|
|
controls.style.cssText = `
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
gap: 12px;
|
|
pointer-events: all;
|
|
`;
|
|
|
|
const buttons = [
|
|
{ id: 'fly-btn', text: '🕊️ Fly', key: 'F' },
|
|
{ id: 'plant-btn', text: '🌱 Plant', key: 'P' },
|
|
{ id: 'sculpt-btn', text: '🏔️ Sculpt', key: 'T' },
|
|
{ id: 'gift-btn', text: '🎁 Gift', key: 'G' },
|
|
{ id: 'stats-btn', text: '📊 Stats', key: 'Tab' }
|
|
];
|
|
|
|
buttons.forEach(btn => {
|
|
const button = document.createElement('button');
|
|
button.id = btn.id;
|
|
button.textContent = btn.text;
|
|
button.style.cssText = `
|
|
padding: 10px 20px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(10px);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
`;
|
|
button.addEventListener('mouseenter', () => {
|
|
button.style.background = 'rgba(255, 255, 255, 0.2)';
|
|
button.style.transform = 'scale(1.05)';
|
|
});
|
|
button.addEventListener('mouseleave', () => {
|
|
button.style.background = 'rgba(255, 255, 255, 0.1)';
|
|
button.style.transform = 'scale(1)';
|
|
});
|
|
controls.appendChild(button);
|
|
this.elements[btn.id] = button;
|
|
});
|
|
|
|
parent.appendChild(controls);
|
|
}
|
|
|
|
createNotificationArea(parent) {
|
|
const area = document.createElement('div');
|
|
area.id = 'notification-area';
|
|
area.style.cssText = `
|
|
position: absolute;
|
|
top: 100px;
|
|
right: 20px;
|
|
width: 300px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
`;
|
|
parent.appendChild(area);
|
|
this.elements.notificationArea = area;
|
|
}
|
|
|
|
createInteractionPrompt(parent) {
|
|
const prompt = document.createElement('div');
|
|
prompt.id = 'interaction-prompt';
|
|
prompt.style.cssText = `
|
|
position: absolute;
|
|
bottom: 120px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
padding: 12px 24px;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 8px;
|
|
font-size: 16px;
|
|
display: none;
|
|
pointer-events: none;
|
|
`;
|
|
parent.appendChild(prompt);
|
|
this.elements.interactionPrompt = prompt;
|
|
}
|
|
|
|
createStatsPanel(parent) {
|
|
const panel = document.createElement('div');
|
|
panel.id = 'stats-panel';
|
|
panel.style.cssText = `
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 500px;
|
|
max-height: 600px;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
backdrop-filter: blur(20px);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
display: none;
|
|
overflow-y: auto;
|
|
pointer-events: all;
|
|
`;
|
|
|
|
panel.innerHTML = `
|
|
<h2 style="margin-top: 0; text-align: center;">📊 Your Journey</h2>
|
|
<div id="stats-content">
|
|
<div class="stat-row">
|
|
<span>💚 Creatures Loved:</span>
|
|
<span id="stat-creatures">0</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span>🌱 Plants Grown:</span>
|
|
<span id="stat-plants">0</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span>🎁 Gifts Given:</span>
|
|
<span id="stat-gifts">0</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span>🚶 Distance Traveled:</span>
|
|
<span id="stat-distance">0 km</span>
|
|
</div>
|
|
<div class="stat-row">
|
|
<span>⏰ Time in World:</span>
|
|
<span id="stat-time">0 min</span>
|
|
</div>
|
|
</div>
|
|
<button id="close-stats" style="
|
|
width: 100%;
|
|
padding: 12px;
|
|
margin-top: 20px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 8px;
|
|
color: white;
|
|
cursor: pointer;
|
|
">Close</button>
|
|
`;
|
|
|
|
parent.appendChild(panel);
|
|
this.elements.statsPanel = panel;
|
|
|
|
document.getElementById('close-stats').addEventListener('click', () => {
|
|
panel.style.display = 'none';
|
|
});
|
|
}
|
|
|
|
createInventoryPanel(parent) {
|
|
const panel = document.createElement('div');
|
|
panel.id = 'inventory-panel';
|
|
panel.style.cssText = `
|
|
position: absolute;
|
|
top: 100px;
|
|
left: 20px;
|
|
width: 300px;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
backdrop-filter: blur(10px);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
display: none;
|
|
pointer-events: all;
|
|
`;
|
|
|
|
panel.innerHTML = `
|
|
<h3 style="margin-top: 0;">🎒 Inventory</h3>
|
|
<div id="inventory-content"></div>
|
|
`;
|
|
|
|
parent.appendChild(panel);
|
|
this.elements.inventoryPanel = panel;
|
|
}
|
|
|
|
showNotification(message, type = 'info', duration = 3000) {
|
|
const notification = document.createElement('div');
|
|
notification.style.cssText = `
|
|
padding: 12px 20px;
|
|
background: ${type === 'success' ? 'rgba(0, 255, 0, 0.2)' :
|
|
type === 'error' ? 'rgba(255, 0, 0, 0.2)' :
|
|
'rgba(255, 255, 255, 0.2)'};
|
|
backdrop-filter: blur(10px);
|
|
border: 2px solid ${type === 'success' ? 'rgba(0, 255, 0, 0.5)' :
|
|
type === 'error' ? 'rgba(255, 0, 0, 0.5)' :
|
|
'rgba(255, 255, 255, 0.5)'};
|
|
border-radius: 8px;
|
|
animation: slideIn 0.3s ease-out;
|
|
`;
|
|
notification.textContent = message;
|
|
|
|
this.elements.notificationArea.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.animation = 'slideOut 0.3s ease-out';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, duration);
|
|
}
|
|
|
|
showInteractionPrompt(text) {
|
|
this.elements.interactionPrompt.textContent = text;
|
|
this.elements.interactionPrompt.style.display = 'block';
|
|
}
|
|
|
|
hideInteractionPrompt() {
|
|
this.elements.interactionPrompt.style.display = 'none';
|
|
}
|
|
|
|
toggleStatsPanel() {
|
|
const panel = this.elements.statsPanel;
|
|
const isVisible = panel.style.display === 'block';
|
|
panel.style.display = isVisible ? 'none' : 'block';
|
|
|
|
if (!isVisible) {
|
|
this.updateStatsPanel();
|
|
}
|
|
}
|
|
|
|
updateStatsPanel() {
|
|
const stats = this.gameState.player.stats;
|
|
document.getElementById('stat-creatures').textContent = stats.creaturesLoved;
|
|
document.getElementById('stat-plants').textContent = stats.plantsGrown;
|
|
document.getElementById('stat-gifts').textContent = stats.giftsGiven;
|
|
document.getElementById('stat-distance').textContent = (stats.distanceTraveled / 1000).toFixed(2) + ' km';
|
|
document.getElementById('stat-time').textContent = (stats.timeInWorld / 60).toFixed(1) + ' min';
|
|
}
|
|
|
|
update() {
|
|
// Update top bar
|
|
this.elements.timeText.textContent = this.gameState.getTimeString();
|
|
this.elements.weatherText.textContent = this.gameState.world.weather.charAt(0).toUpperCase() + this.gameState.world.weather.slice(1);
|
|
this.elements.biomeText.textContent = this.gameState.player.currentBiome.charAt(0).toUpperCase() + this.gameState.player.currentBiome.slice(1);
|
|
}
|
|
}
|
|
|
|
// ===== INPUT MANAGER =====
|
|
export class InputManager {
|
|
constructor() {
|
|
this.keys = {};
|
|
this.mouse = {
|
|
x: 0,
|
|
y: 0,
|
|
leftButton: false,
|
|
rightButton: false,
|
|
wheelDelta: 0
|
|
};
|
|
this.locked = false;
|
|
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Keyboard
|
|
document.addEventListener('keydown', (e) => {
|
|
this.keys[e.code] = true;
|
|
});
|
|
|
|
document.addEventListener('keyup', (e) => {
|
|
this.keys[e.code] = false;
|
|
});
|
|
|
|
// Mouse
|
|
document.addEventListener('mousemove', (e) => {
|
|
this.mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
|
|
this.mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
|
|
|
|
if (this.locked) {
|
|
this.mouse.movementX = e.movementX || 0;
|
|
this.mouse.movementY = e.movementY || 0;
|
|
}
|
|
});
|
|
|
|
document.addEventListener('mousedown', (e) => {
|
|
if (e.button === 0) this.mouse.leftButton = true;
|
|
if (e.button === 2) this.mouse.rightButton = true;
|
|
});
|
|
|
|
document.addEventListener('mouseup', (e) => {
|
|
if (e.button === 0) this.mouse.leftButton = false;
|
|
if (e.button === 2) this.mouse.rightButton = false;
|
|
});
|
|
|
|
document.addEventListener('wheel', (e) => {
|
|
this.mouse.wheelDelta = e.deltaY;
|
|
});
|
|
|
|
// Pointer lock
|
|
document.addEventListener('click', () => {
|
|
if (!this.locked) {
|
|
document.body.requestPointerLock();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('pointerlockchange', () => {
|
|
this.locked = document.pointerLockElement === document.body;
|
|
});
|
|
}
|
|
|
|
isKeyPressed(keyCode) {
|
|
return this.keys[keyCode] || false;
|
|
}
|
|
|
|
resetFrame() {
|
|
this.mouse.movementX = 0;
|
|
this.mouse.movementY = 0;
|
|
this.mouse.wheelDelta = 0;
|
|
}
|
|
}
|
|
|
|
// ===== CAMERA CONTROLLER =====
|
|
export class CameraController {
|
|
constructor(camera, gameState) {
|
|
this.camera = camera;
|
|
this.gameState = gameState;
|
|
this.sensitivity = 0.002;
|
|
this.distance = 5;
|
|
this.height = 2;
|
|
this.pitch = 0;
|
|
this.yaw = 0;
|
|
}
|
|
|
|
update(input) {
|
|
if (!input.locked) return;
|
|
|
|
// Mouse look
|
|
this.yaw -= input.mouse.movementX * this.sensitivity;
|
|
this.pitch -= input.mouse.movementY * this.sensitivity;
|
|
this.pitch = Math.max(-Math.PI / 2.5, Math.min(Math.PI / 2.5, this.pitch));
|
|
|
|
// Camera position (third person)
|
|
const playerPos = this.gameState.player.position;
|
|
const offset = new THREE.Vector3(
|
|
Math.sin(this.yaw) * Math.cos(this.pitch) * this.distance,
|
|
this.height + Math.sin(this.pitch) * this.distance,
|
|
Math.cos(this.yaw) * Math.cos(this.pitch) * this.distance
|
|
);
|
|
|
|
this.camera.position.copy(playerPos).add(offset);
|
|
this.camera.lookAt(playerPos);
|
|
}
|
|
|
|
getForwardDirection() {
|
|
return new THREE.Vector3(
|
|
-Math.sin(this.yaw),
|
|
0,
|
|
-Math.cos(this.yaw)
|
|
).normalize();
|
|
}
|
|
|
|
getRightDirection() {
|
|
return new THREE.Vector3(
|
|
Math.cos(this.yaw),
|
|
0,
|
|
-Math.sin(this.yaw)
|
|
).normalize();
|
|
}
|
|
}
|
|
|
|
// ===== MAIN GAME MANAGER =====
|
|
export class BlackRoadGame {
|
|
constructor(containerId = 'game-container') {
|
|
this.container = document.getElementById(containerId);
|
|
if (!this.container) {
|
|
this.container = document.createElement('div');
|
|
this.container.id = containerId;
|
|
document.body.appendChild(this.container);
|
|
}
|
|
|
|
// Core systems
|
|
this.gameState = new GameState();
|
|
this.input = new InputManager();
|
|
|
|
// Three.js setup
|
|
this.scene = new THREE.Scene();
|
|
this.camera = new THREE.PerspectiveCamera(
|
|
75,
|
|
window.innerWidth / window.innerHeight,
|
|
0.1,
|
|
2000
|
|
);
|
|
|
|
// Use photorealistic renderer
|
|
this.renderer = new PhotorealisticRenderer(this.container, this.scene, this.camera);
|
|
|
|
// Game systems
|
|
this.cameraController = new CameraController(this.camera, this.gameState);
|
|
this.biomeGenerator = new BiomeGenerator(this.scene);
|
|
this.nature = new LivingNature(this.scene);
|
|
this.music = new MusicEngine();
|
|
this.soundscape = new NatureSoundscape();
|
|
this.creation = new CreationManager(this.scene, this.nature);
|
|
this.multiplayer = new MultiplayerManager(this.scene);
|
|
this.lighting = new AdvancedLighting(this.scene);
|
|
this.particles = new ParticleSystem(this.scene);
|
|
this.ui = new UIManager(this.gameState);
|
|
|
|
// Performance tracking
|
|
this.lastTime = performance.now();
|
|
this.deltaTime = 0;
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
console.log('🌍 Initializing BlackRoad Metaverse...');
|
|
|
|
// Setup lighting
|
|
this.lighting.createSun(1.5);
|
|
this.lighting.createHemisphere(0.5);
|
|
this.lighting.createAmbient(0.3);
|
|
|
|
// Setup fog
|
|
this.scene.fog = new THREE.FogExp2(0x87CEEB, 0.002);
|
|
|
|
// Initial camera position
|
|
this.camera.position.set(0, 10, 10);
|
|
|
|
// Generate initial chunks
|
|
this.generateWorldAroundPlayer();
|
|
|
|
// Spawn some initial creatures
|
|
this.spawnInitialCreatures();
|
|
|
|
// Start music
|
|
this.music.start();
|
|
this.soundscape.start('grassland');
|
|
|
|
// Setup event listeners
|
|
this.setupEventListeners();
|
|
|
|
// Start game loop
|
|
this.animate();
|
|
|
|
console.log('✅ BlackRoad Metaverse ready!');
|
|
this.ui.showNotification('Welcome to BlackRoad! 💚', 'success', 5000);
|
|
}
|
|
|
|
setupEventListeners() {
|
|
window.addEventListener('resize', () => this.onWindowResize());
|
|
|
|
// Button listeners
|
|
this.ui.elements['fly-btn'].addEventListener('click', () => this.toggleFly());
|
|
this.ui.elements['stats-btn'].addEventListener('click', () => this.ui.toggleStatsPanel());
|
|
}
|
|
|
|
onWindowResize() {
|
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
|
this.camera.updateProjectionMatrix();
|
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
|
}
|
|
|
|
generateWorldAroundPlayer() {
|
|
const chunkSize = 50;
|
|
const renderDistance = 3;
|
|
const playerChunkX = Math.floor(this.gameState.player.position.x / chunkSize);
|
|
const playerChunkZ = Math.floor(this.gameState.player.position.z / chunkSize);
|
|
|
|
for (let x = -renderDistance; x <= renderDistance; x++) {
|
|
for (let z = -renderDistance; z <= renderDistance; z++) {
|
|
this.biomeGenerator.ensureChunkLoaded(
|
|
playerChunkX + x,
|
|
playerChunkZ + z
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
spawnInitialCreatures() {
|
|
const creatures = ['butterfly', 'bird', 'rabbit', 'fish', 'fox', 'bee'];
|
|
const radius = 20;
|
|
|
|
creatures.forEach((species, i) => {
|
|
const angle = (i / creatures.length) * Math.PI * 2;
|
|
const position = new THREE.Vector3(
|
|
Math.cos(angle) * radius,
|
|
5,
|
|
Math.sin(angle) * radius
|
|
);
|
|
this.nature.spawnAnimal(species, position);
|
|
});
|
|
}
|
|
|
|
toggleFly() {
|
|
this.gameState.player.isFlying = !this.gameState.player.isFlying;
|
|
this.ui.showNotification(
|
|
this.gameState.player.isFlying ? '🕊️ Flying enabled!' : '🚶 Walking mode',
|
|
'success'
|
|
);
|
|
}
|
|
|
|
handlePlayerMovement() {
|
|
const speed = this.gameState.player.isFlying ? 0.5 : 0.3;
|
|
const forward = this.cameraController.getForwardDirection();
|
|
const right = this.cameraController.getRightDirection();
|
|
|
|
const velocity = new THREE.Vector3(0, 0, 0);
|
|
|
|
if (this.input.isKeyPressed('KeyW')) {
|
|
velocity.add(forward.clone().multiplyScalar(speed));
|
|
}
|
|
if (this.input.isKeyPressed('KeyS')) {
|
|
velocity.add(forward.clone().multiplyScalar(-speed));
|
|
}
|
|
if (this.input.isKeyPressed('KeyA')) {
|
|
velocity.add(right.clone().multiplyScalar(-speed));
|
|
}
|
|
if (this.input.isKeyPressed('KeyD')) {
|
|
velocity.add(right.clone().multiplyScalar(speed));
|
|
}
|
|
|
|
if (this.gameState.player.isFlying) {
|
|
if (this.input.isKeyPressed('Space')) {
|
|
velocity.y = speed;
|
|
}
|
|
if (this.input.isKeyPressed('ShiftLeft')) {
|
|
velocity.y = -speed;
|
|
}
|
|
}
|
|
|
|
// Apply velocity
|
|
this.gameState.player.position.add(velocity);
|
|
|
|
// Track distance
|
|
const distance = velocity.length();
|
|
this.gameState.player.stats.distanceTraveled += distance;
|
|
|
|
// Keep above ground if not flying
|
|
if (!this.gameState.player.isFlying && this.gameState.player.position.y < 1) {
|
|
this.gameState.player.position.y = 1;
|
|
}
|
|
}
|
|
|
|
update(deltaTime) {
|
|
// Update game state
|
|
this.gameState.updateTime(deltaTime);
|
|
|
|
// Handle input
|
|
this.handlePlayerMovement();
|
|
this.cameraController.update(this.input);
|
|
|
|
// Update world systems
|
|
this.biomeGenerator.update(this.gameState.player.position);
|
|
this.nature.update();
|
|
this.creation.updatePets(this.gameState.player.position);
|
|
this.lighting.updateSunPosition(this.gameState.world.timeOfDay);
|
|
this.particles.update();
|
|
|
|
// Update UI
|
|
this.ui.update();
|
|
|
|
// Update current biome
|
|
const biome = this.biomeGenerator.getBiomeAt(
|
|
this.gameState.player.position.x,
|
|
this.gameState.player.position.z
|
|
);
|
|
if (biome !== this.gameState.player.currentBiome) {
|
|
this.gameState.player.currentBiome = biome;
|
|
this.soundscape.transitionTo(biome);
|
|
this.ui.showNotification(`Entered ${biome} biome`, 'info');
|
|
}
|
|
|
|
// Reset input state
|
|
this.input.resetFrame();
|
|
}
|
|
|
|
animate() {
|
|
requestAnimationFrame(() => this.animate());
|
|
|
|
const currentTime = performance.now();
|
|
this.deltaTime = (currentTime - this.lastTime) / 1000;
|
|
this.lastTime = currentTime;
|
|
|
|
if (!this.gameState.paused) {
|
|
this.update(this.deltaTime);
|
|
}
|
|
|
|
this.renderer.render();
|
|
}
|
|
|
|
// Public API
|
|
pause() {
|
|
this.gameState.paused = true;
|
|
this.ui.showNotification('Game paused', 'info');
|
|
}
|
|
|
|
resume() {
|
|
this.gameState.paused = false;
|
|
this.ui.showNotification('Game resumed', 'info');
|
|
}
|
|
|
|
save() {
|
|
const saveData = this.gameState.save();
|
|
this.ui.showNotification('Game saved!', 'success');
|
|
return saveData;
|
|
}
|
|
|
|
load() {
|
|
const loaded = this.gameState.load();
|
|
if (loaded) {
|
|
this.ui.showNotification('Game loaded!', 'success');
|
|
// Teleport player to saved position
|
|
this.camera.position.copy(this.gameState.player.position);
|
|
} else {
|
|
this.ui.showNotification('No save file found', 'error');
|
|
}
|
|
return loaded;
|
|
}
|
|
}
|
|
|
|
export default BlackRoadGame;
|