window.Minecraft = class { /** * Create Minecraft instance and render it on a canvas */ constructor(canvasWrapperId) { this.currentScreen = null; this.loadingScreen = null; this.fps = 0; // Tick timer this.timer = new Timer(20); this.settings = new GameSettings(); this.settings.load(); // Create window and world renderer this.window = new GameWindow(this, canvasWrapperId); // Create renderers this.worldRenderer = new WorldRenderer(this, this.window); this.screenRenderer = new ScreenRenderer(this, this.window); this.itemRenderer = new ItemRenderer(this, this.window); // Create current screen and overlay this.ingameOverlay = new IngameOverlay(this, this.window); // Display loading screen this.loadingScreen = new GuiLoadingScreen(); this.loadingScreen.setTitle("Building terrain..."); this.frames = 0; this.lastTime = Date.now(); // Create all blocks Block.create(); this.itemRenderer.initialize(); // Update window size this.window.updateWindowSize(); // Create world this.world = new World(this); this.worldRenderer.scene.add(this.world.group); // Create player this.player = new Player(this, this.world); this.inventory = new Inventory(); this.displayScreen(this.loadingScreen); // Initialize this.init(); } init() { // Load spawn chunk for (let x = -WorldRenderer.RENDER_DISTANCE; x <= WorldRenderer.RENDER_DISTANCE; x++) { for (let z = -WorldRenderer.RENDER_DISTANCE; z <= WorldRenderer.RENDER_DISTANCE; z++) { this.world.getChunkAt(x, z); } } this.player.respawn(); // Start render loop this.running = true; this.requestNextFrame(); } hasInGameFocus() { return this.window.mouseLocked && this.currentScreen === null; } requestNextFrame() { let scope = this; requestAnimationFrame(function () { if (scope.running) { scope.requestNextFrame(); scope.onLoop(); } }); } onLoop() { this.window.statsFps.begin(); this.window.statsMs.begin(); // Update the timer this.timer.advanceTime(); // Call the tick to reach updates 20 per seconds for (let i = 0; i < this.timer.ticks; i++) { this.onTick(); } // Render the game this.onRender(this.timer.partialTicks); // Increase rendered frame this.frames++; // Loop if a second passed while (Date.now() >= this.lastTime + 1000) { this.fps = this.frames; this.lastTime += 1000; this.frames = 0; } this.window.statsFps.end(); this.window.statsMs.end(); } onRender(partialTicks) { // Player rotation if (!this.isPaused()) { this.player.turn(this.window.mouseMotionX, this.window.mouseMotionY); this.window.mouseMotionX = 0; this.window.mouseMotionY = 0; } // Update lights while (this.world.updateLights()) { // Empty } // Render the game if (this.hasInGameFocus()) { this.worldRenderer.render(partialTicks); } this.screenRenderer.render(partialTicks); this.itemRenderer.render(partialTicks); } displayScreen(screen) { if (typeof screen === "undefined") { console.log("Tried to display an undefined screen"); return; } // Close previous screen if (!(this.currentScreen === null)) { this.currentScreen.onClose(); } // Switch screen this.currentScreen = screen; // Update window size this.window.updateWindowSize(); // Initialize new screen if (screen === null) { this.window.requestFocus(); } else { this.window.exitFocus(); screen.setup(this, this.window.width, this.window.height); } } onTick() { if (!this.isPaused()) { // Tick world this.world.onTick(); // Tick the player this.player.onTick(); } // Update loading progress if (!(this.loadingScreen === null)) { let progress = Math.max(0, 1 - this.world.lightUpdateQueue.length / 10000); this.loadingScreen.setProgress(progress); // Finish loading if (progress >= 1) { this.loadingScreen = null; this.displayScreen(null); } } } onKeyPressed(button) { for (let i = 1; i <= 9; i++) { if (button === 'Digit' + i) { this.inventory.selectedSlotIndex = i - 1; } } } onMouseClicked(button) { if (this.window.mouseLocked) { let hitResult = this.player.rayTrace(5, this.timer.partialTicks); // Destroy block if (button === 0) { if (hitResult != null) { this.world.setBlockAt(hitResult.x, hitResult.y, hitResult.z, 0); } } // Pick block if (button === 1) { if (hitResult != null) { let typeId = this.world.getBlockAt(hitResult.x, hitResult.y, hitResult.z); if (typeId !== 0) { this.inventory.setItemInSelectedSlot(typeId); } } } // Place block if (button === 2) { if (hitResult != null) { let x = hitResult.x + hitResult.face.x; let y = hitResult.y + hitResult.face.y; let z = hitResult.z + hitResult.face.z; let placedBoundingBox = new BoundingBox(x, y, z, x + 1, y + 1, z + 1); // Don't place blocks if the player is standing there if (!placedBoundingBox.intersects(this.player.boundingBox)) { let typeId = this.inventory.getItemInSelectedSlot(); if (typeId !== 0) { // Place block this.world.setBlockAt(x, y, z, typeId); // Handle block abilities let block = Block.getById(typeId); block.onBlockPlaced(this.world, x, y, z, hitResult.face); } } } } } } onMouseScroll(delta) { this.inventory.shiftSelectedSlot(delta); } isPaused() { return !this.hasInGameFocus() && this.loadingScreen === null; } }