├── .gitignore ├── README.md ├── dist ├── .DS_Store ├── app.js ├── app.js.map ├── index.html └── public │ ├── .DS_Store │ ├── btn-default.png │ ├── colyseus.png │ ├── ground.jpg │ └── textures │ ├── skybox_nx.jpg │ ├── skybox_ny.jpg │ ├── skybox_nz.jpg │ ├── skybox_px.jpg │ ├── skybox_py.jpg │ └── skybox_pz.jpg ├── index.html ├── package-lock.json ├── package.json ├── public ├── btn-default.png ├── colyseus.png ├── ground.jpg └── textures │ ├── skybox_nx.jpg │ ├── skybox_ny.jpg │ ├── skybox_nz.jpg │ ├── skybox_px.jpg │ ├── skybox_py.jpg │ └── skybox_pz.jpg ├── src ├── app.ts ├── game.ts ├── menu.ts └── utils.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.swp 10 | 11 | pids 12 | logs 13 | results 14 | tmp 15 | 16 | # Build 17 | public/css/main.css 18 | dist/ 19 | 20 | # Coverage reports 21 | coverage 22 | 23 | # API keys and secrets 24 | .env 25 | 26 | # Dependency directory 27 | node_modules 28 | bower_components 29 | 30 | # Editors 31 | .idea 32 | *.iml 33 | 34 | # OS metadata 35 | .DS_Store 36 | Thumbs.db 37 | 38 | # Ignore built ts files 39 | dist/**/* 40 | 41 | # ignore yarn.lock 42 | yarn.lock 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (Client-side) BabylonJS: Real-time Multiplayer with Colyseus 2 | 3 | This is the client code for a step-by-step tutorial on how to use BabylonJS + Colyseus together. 4 | 5 | - [See step-by-step Tutorial](https://doc.babylonjs.com/guidedLearning/multiplayer/Colyseus) 6 | - [See server-side Project](https://github.com/colyseus/tutorial-babylonjs-server) 7 | - [See Colyseus documentation](https://docs.colyseus.io/) 8 | 9 | ## How to run 10 | 11 | - Download and install [Node.js LTS](https://nodejs.org/en/download/) 12 | - Clone or download this repository. 13 | - Run `npm install` 14 | - Run `npm start` 15 | 16 | The client should be accessible at [`http://localhost:8080`](http://localhost:8080). 17 | 18 | ## License 19 | 20 | MIT -------------------------------------------------------------------------------- /dist/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/.DS_Store -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Colyseus + Babylon.js Demo 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dist/public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/.DS_Store -------------------------------------------------------------------------------- /dist/public/btn-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/btn-default.png -------------------------------------------------------------------------------- /dist/public/colyseus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/colyseus.png -------------------------------------------------------------------------------- /dist/public/ground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/ground.jpg -------------------------------------------------------------------------------- /dist/public/textures/skybox_nx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/textures/skybox_nx.jpg -------------------------------------------------------------------------------- /dist/public/textures/skybox_ny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/textures/skybox_ny.jpg -------------------------------------------------------------------------------- /dist/public/textures/skybox_nz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/textures/skybox_nz.jpg -------------------------------------------------------------------------------- /dist/public/textures/skybox_px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/textures/skybox_px.jpg -------------------------------------------------------------------------------- /dist/public/textures/skybox_py.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/textures/skybox_py.jpg -------------------------------------------------------------------------------- /dist/public/textures/skybox_pz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/dist/public/textures/skybox_pz.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Colyseus + Babylon.js Demo 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babylon-demo", 3 | "version": "0.1.0", 4 | "description": "Babylon.js Colyseus demo", 5 | "scripts": { 6 | "start": "webpack serve", 7 | "build": "webpack", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@babylonjs/core": "preview", 14 | "@babylonjs/inspector": "preview", 15 | "@types/node": "^17.0.12", 16 | "ts-loader": "^9.2.6", 17 | "typescript": "^4.5.4", 18 | "webpack": "^5.66.0", 19 | "webpack-cli": "^4.9.1", 20 | "webpack-dev-server": "^4.7.4" 21 | }, 22 | "dependencies": { 23 | "babylonjs": "preview", 24 | "babylonjs-gui": "preview", 25 | "babylonjs-loaders": "preview", 26 | "babylonjs-materials": "preview", 27 | "babylonjs-procedural-textures": "preview", 28 | "colyseus.js": "^0.15.0-preview.2", 29 | "copy-webpack-plugin": "^10.2.4", 30 | "dotenv-webpack": "^7.0.3" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/btn-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/btn-default.png -------------------------------------------------------------------------------- /public/colyseus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/colyseus.png -------------------------------------------------------------------------------- /public/ground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/ground.jpg -------------------------------------------------------------------------------- /public/textures/skybox_nx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/textures/skybox_nx.jpg -------------------------------------------------------------------------------- /public/textures/skybox_ny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/textures/skybox_ny.jpg -------------------------------------------------------------------------------- /public/textures/skybox_nz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/textures/skybox_nz.jpg -------------------------------------------------------------------------------- /public/textures/skybox_px.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/textures/skybox_px.jpg -------------------------------------------------------------------------------- /public/textures/skybox_py.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/textures/skybox_py.jpg -------------------------------------------------------------------------------- /public/textures/skybox_pz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colyseus/tutorial-babylonjs-client/bd1b9f1635acf92458adf9bd3ccf0b7b044f7a2e/public/textures/skybox_pz.jpg -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import Menu from './menu' 2 | 3 | window.addEventListener('DOMContentLoaded', () => { 4 | // Create the game using the 'renderCanvas'. 5 | let menu = new Menu('renderCanvas'); 6 | 7 | // Create the scene. 8 | menu.createMenu(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/game.ts: -------------------------------------------------------------------------------- 1 | import * as BABYLON from 'babylonjs'; 2 | import * as GUI from 'babylonjs-gui'; 3 | import { Room } from "colyseus.js"; 4 | 5 | import Menu from "./menu"; 6 | import { createSkyBox } from "./utils"; 7 | 8 | const GROUND_SIZE = 500; 9 | 10 | export default class Game { 11 | private canvas: HTMLCanvasElement; 12 | private engine: BABYLON.Engine; 13 | private scene: BABYLON.Scene; 14 | private camera: BABYLON.ArcRotateCamera; 15 | private light: BABYLON.Light; 16 | 17 | private room: Room; 18 | private playerEntities: { [playerId: string]: BABYLON.Mesh } = {}; 19 | private playerNextPosition: { [playerId: string]: BABYLON.Vector3 } = {}; 20 | 21 | constructor(canvas: HTMLCanvasElement, engine: BABYLON.Engine, room: Room) { 22 | this.canvas = canvas; 23 | this.engine = engine; 24 | this.room = room; 25 | } 26 | 27 | initPlayers(): void { 28 | this.room.state.players.onAdd((player, sessionId) => { 29 | const isCurrentPlayer = (sessionId === this.room.sessionId); 30 | 31 | const sphere = BABYLON.MeshBuilder.CreateSphere(`player-${sessionId}`, { 32 | segments: 8, 33 | diameter: 40 34 | }, this.scene); 35 | 36 | // Set player mesh properties 37 | const sphereMaterial = new BABYLON.StandardMaterial(`playerMat-${sessionId}`, this.scene); 38 | sphereMaterial.emissiveColor = (isCurrentPlayer) ? BABYLON.Color3.FromHexString("#ff9900") : BABYLON.Color3.Gray(); 39 | sphere.material = sphereMaterial; 40 | 41 | // Set player spawning position 42 | sphere.position.set(player.x, player.y, player.z); 43 | 44 | this.playerEntities[sessionId] = sphere; 45 | this.playerNextPosition[sessionId] = sphere.position.clone(); 46 | 47 | // update local target position 48 | player.onChange(() => { 49 | this.playerNextPosition[sessionId].set(player.x, player.y, player.z); 50 | }); 51 | }); 52 | 53 | this.room.state.players.onRemove((player, playerId) => { 54 | this.playerEntities[playerId].dispose(); 55 | delete this.playerEntities[playerId]; 56 | delete this.playerNextPosition[playerId]; 57 | }); 58 | 59 | this.room.onLeave(code => { 60 | this.gotoMenu(); 61 | }) 62 | } 63 | 64 | createGround(): void { 65 | // Create ground plane 66 | const plane = BABYLON.MeshBuilder.CreatePlane("plane", { size: GROUND_SIZE }, this.scene); 67 | plane.position.y = -15; 68 | plane.rotation.x = Math.PI / 2; 69 | 70 | let floorPlane = new BABYLON.StandardMaterial('floorTexturePlane', this.scene); 71 | floorPlane.diffuseTexture = new BABYLON.Texture('./public/ground.jpg', this.scene); 72 | floorPlane.backFaceCulling = false; // Always show the front and the back of an element 73 | 74 | let materialPlane = new BABYLON.MultiMaterial('materialPlane', this.scene); 75 | materialPlane.subMaterials.push(floorPlane); 76 | 77 | plane.material = materialPlane; 78 | } 79 | 80 | displayGameControls() { 81 | const advancedTexture = GUI.AdvancedDynamicTexture.CreateFullscreenUI("textUI"); 82 | 83 | const playerInfo = new GUI.TextBlock("playerInfo"); 84 | playerInfo.text = `Room name: ${this.room.name} Player: ${this.room.sessionId}`.toUpperCase(); 85 | playerInfo.color = "#eaeaea"; 86 | playerInfo.fontFamily = "Roboto"; 87 | playerInfo.fontSize = 20; 88 | playerInfo.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_LEFT; 89 | playerInfo.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 90 | playerInfo.paddingTop = "10px"; 91 | playerInfo.paddingLeft = "10px"; 92 | playerInfo.outlineColor = "#000000"; 93 | advancedTexture.addControl(playerInfo); 94 | 95 | const instructions = new GUI.TextBlock("instructions"); 96 | instructions.text = "CLICK ANYWHERE ON THE GROUND!"; 97 | instructions.color = "#fff000" 98 | instructions.fontFamily = "Roboto"; 99 | instructions.fontSize = 24; 100 | instructions.textHorizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; 101 | instructions.textVerticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM; 102 | instructions.paddingBottom = "10px"; 103 | advancedTexture.addControl(instructions); 104 | 105 | // back to menu button 106 | const button = GUI.Button.CreateImageWithCenterTextButton("back", "<- BACK", "./public/btn-default.png"); 107 | button.width = "100px"; 108 | button.height = "50px"; 109 | button.fontFamily = "Roboto"; 110 | button.thickness = 0; 111 | button.color = "#f8f8f8"; 112 | button.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT; 113 | button.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 114 | button.paddingTop = "10px"; 115 | button.paddingRight = "10px"; 116 | button.onPointerClickObservable.add(async () => { 117 | await this.room.leave(true); 118 | }); 119 | advancedTexture.addControl(button); 120 | } 121 | 122 | bootstrap(): void { 123 | this.scene = new BABYLON.Scene(this.engine); 124 | this.light = new BABYLON.HemisphericLight("pointLight", new BABYLON.Vector3(), this.scene); 125 | this.camera = new BABYLON.ArcRotateCamera("camera", Math.PI / 2, 1.0, 550, BABYLON.Vector3.Zero(), this.scene); 126 | this.camera.setTarget(BABYLON.Vector3.Zero()); 127 | 128 | createSkyBox(this.scene); 129 | this.createGround(); 130 | this.displayGameControls(); 131 | this.initPlayers(); 132 | 133 | this.scene.onPointerDown = (event, pointer) => { 134 | if (event.button == 0) { 135 | const targetPosition = pointer.pickedPoint.clone(); 136 | 137 | // Position adjustments for the current play ground. 138 | targetPosition.y = -1; 139 | if (targetPosition.x > 245) targetPosition.x = 245; 140 | else if (targetPosition.x < -245) targetPosition.x = -245; 141 | if (targetPosition.z > 245) targetPosition.z = 245; 142 | else if (targetPosition.z < -245) targetPosition.z = -245; 143 | 144 | this.playerNextPosition[this.room.sessionId] = targetPosition; 145 | 146 | // Send position update to the server 147 | this.room.send("updatePosition", { 148 | x: targetPosition.x, 149 | y: targetPosition.y, 150 | z: targetPosition.z, 151 | }); 152 | } 153 | }; 154 | 155 | this.doRender(); 156 | } 157 | 158 | private gotoMenu() { 159 | this.scene.dispose(); 160 | const menu = new Menu('renderCanvas'); 161 | menu.createMenu(); 162 | } 163 | 164 | private doRender(): void { 165 | // constantly lerp players 166 | this.scene.registerBeforeRender(() => { 167 | for (let sessionId in this.playerEntities) { 168 | const entity = this.playerEntities[sessionId]; 169 | const targetPosition = this.playerNextPosition[sessionId]; 170 | entity.position = BABYLON.Vector3.Lerp(entity.position, targetPosition, 0.05); 171 | } 172 | }); 173 | 174 | // Run the render loop. 175 | this.engine.runRenderLoop(() => { 176 | this.scene.render(); 177 | }); 178 | 179 | // The canvas/window resize event handler. 180 | window.addEventListener('resize', () => { 181 | this.engine.resize(); 182 | }); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/menu.ts: -------------------------------------------------------------------------------- 1 | import * as BABYLON from 'babylonjs'; 2 | import * as GUI from 'babylonjs-gui'; 3 | import { Client } from "colyseus.js"; 4 | 5 | import Game from './game'; 6 | import { createSkyBox } from "./utils"; 7 | 8 | const ROOM_NAME = "my_room"; 9 | const ENDPOINT = "ws://localhost:2567"; 10 | // const ENDPOINT = "wss://tutorial-babylonjs-server.glitch.me"; 11 | 12 | export default class Menu { 13 | private _canvas: HTMLCanvasElement; 14 | private _engine: BABYLON.Engine; 15 | private _scene: BABYLON.Scene; 16 | private _camera: BABYLON.ArcRotateCamera; 17 | private _advancedTexture: GUI.AdvancedDynamicTexture; 18 | 19 | private _colyseus: Client = new Client(ENDPOINT); 20 | 21 | private _errorMessage: GUI.TextBlock = new GUI.TextBlock("errorText"); 22 | 23 | constructor(canvasElement: string) { 24 | // Create canvas and engine. 25 | this._canvas = document.getElementById(canvasElement) as HTMLCanvasElement; 26 | this._engine = new BABYLON.Engine(this._canvas, true); 27 | } 28 | 29 | createMenu(): void { 30 | this._scene = new BABYLON.Scene(this._engine); 31 | this._camera = new BABYLON.ArcRotateCamera("camera", Math.PI / 2, 1.0, 110, BABYLON.Vector3.Zero(), this._scene); 32 | this._camera.useAutoRotationBehavior = true; 33 | this._camera.setTarget(BABYLON.Vector3.Zero()); 34 | this._advancedTexture = GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI"); 35 | 36 | createSkyBox(this._scene); 37 | 38 | // Colyseus logo 39 | const controlBox = new GUI.Rectangle("controlBox"); 40 | controlBox.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; 41 | controlBox.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 42 | controlBox.height = "100%"; 43 | controlBox.width = "40%"; 44 | controlBox.thickness = 0; 45 | 46 | const logo = new GUI.Image("ColyseusLogo", "./public/colyseus.png"); 47 | logo.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; 48 | logo.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_TOP; 49 | logo.height = "40%"; 50 | logo.paddingTop = "10px"; 51 | logo.stretch = GUI.Image.STRETCH_UNIFORM; 52 | controlBox.addControl(logo); 53 | 54 | // Button positioning 55 | const stackPanel = new GUI.StackPanel(); 56 | stackPanel.isVertical = true; 57 | stackPanel.height = "50%"; 58 | stackPanel.horizontalAlignment = GUI.Control.HORIZONTAL_ALIGNMENT_CENTER; 59 | stackPanel.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM; 60 | 61 | const createGameButton = this.createMenuButton("createGame", "CREATE GAME"); 62 | createGameButton.onPointerClickObservable.add(async () => { 63 | this.swapControls(false); 64 | await this.createGame("create"); 65 | }); 66 | stackPanel.addControl(createGameButton); 67 | 68 | const joinGameButton = this.createMenuButton("joinGame", "JOIN GAME"); 69 | joinGameButton.onPointerClickObservable.add(async () => { 70 | this.swapControls(false); 71 | await this.createGame("join"); 72 | }); 73 | stackPanel.addControl(joinGameButton); 74 | 75 | const createOrJoinButton = this.createMenuButton("createOrJoinGame", "CREATE OR JOIN"); 76 | createOrJoinButton.onPointerClickObservable.add(async () => { 77 | this.swapControls(false); 78 | await this.createGame("joinOrCreate"); 79 | }); 80 | stackPanel.addControl(createOrJoinButton); 81 | 82 | controlBox.addControl(stackPanel); 83 | 84 | this._advancedTexture.addControl(controlBox); 85 | 86 | this.initLoadingMessageBox(); 87 | this.initErrorMessageBox(); 88 | this.swapLoadingMessageBox(false); 89 | this.swapErrorMessageBox(false); 90 | 91 | this.doRender(); 92 | } 93 | 94 | private createMenuButton(name: string, text: string): GUI.Button { 95 | const button = GUI.Button.CreateImageWithCenterTextButton(name, text, "./public/btn-default.png"); 96 | button.width = "45%"; 97 | button.height = "55px"; 98 | button.fontFamily = "Roboto"; 99 | button.fontSize = "6%"; 100 | button.thickness = 0; 101 | button.paddingTop = "10px" 102 | button.color = "#c0c0c0"; 103 | return button; 104 | } 105 | 106 | private swapControls(isEnabled: boolean) { 107 | for (let btn of this._advancedTexture.getControlsByType("Button")) { 108 | btn.isEnabled = isEnabled; 109 | } 110 | } 111 | 112 | private async createGame(method: string): Promise { 113 | let game: Game; 114 | try { 115 | switch (method) { 116 | case "create": 117 | this.swapLoadingMessageBox(true); 118 | game = new Game(this._canvas, this._engine, await this._colyseus.create(ROOM_NAME)); 119 | break; 120 | case "join": 121 | this.swapLoadingMessageBox(true); 122 | game = new Game(this._canvas, this._engine, await this._colyseus.join(ROOM_NAME)); 123 | break; 124 | default: 125 | this.swapLoadingMessageBox(true); 126 | game = new Game(this._canvas, this._engine, await this._colyseus.joinOrCreate(ROOM_NAME)); 127 | } 128 | this._scene.dispose(); 129 | game.bootstrap(); 130 | } catch (error) { 131 | this._errorMessage.text = error.message; 132 | this.swapErrorMessageBox(true); 133 | } 134 | } 135 | 136 | private doRender(): void { 137 | // Run the render loop. 138 | this._engine.runRenderLoop(() => { 139 | this._scene.render(); 140 | }); 141 | 142 | // The canvas/window resize event handler. 143 | window.addEventListener('resize', () => { 144 | this._engine.resize(); 145 | }); 146 | } 147 | 148 | private initLoadingMessageBox() { 149 | const loadingMessage = new GUI.Rectangle("messageBox"); 150 | loadingMessage.thickness = 0; 151 | loadingMessage.background = "#131313"; 152 | 153 | const loadingText = new GUI.TextBlock("loadingText"); 154 | loadingText.text = "LOADING..." 155 | loadingText.fontFamily = "Roboto"; 156 | loadingText.color = "#fad836"; 157 | loadingText.fontSize = "30px"; 158 | loadingMessage.addControl(loadingText); 159 | 160 | this._advancedTexture.addControl(loadingMessage); 161 | } 162 | 163 | private initErrorMessageBox() { 164 | const errorMessageBox = new GUI.Rectangle("errorMessageBox"); 165 | errorMessageBox.thickness = 0; 166 | errorMessageBox.background = "#131313"; 167 | 168 | this._errorMessage.fontFamily = "Roboto"; 169 | this._errorMessage.color = "#ff1616"; 170 | this._errorMessage.fontSize = "20px"; 171 | this._errorMessage.textWrapping = true; 172 | errorMessageBox.addControl(this._errorMessage); 173 | 174 | const button = GUI.Button.CreateImageWithCenterTextButton("tryAgainButton", "<- TRY AGAIN", "./public/btn-default.png"); 175 | button.width = "200px"; 176 | button.height = "60px"; 177 | button.fontFamily = "Roboto"; 178 | button.thickness = 0; 179 | button.color = "#c0c0c0"; 180 | button.verticalAlignment = GUI.Control.VERTICAL_ALIGNMENT_BOTTOM; 181 | button.paddingBottom = "20px"; 182 | button.onPointerClickObservable.add(() => { 183 | this.swapControls(true); 184 | this.swapLoadingMessageBox(false); 185 | this.swapErrorMessageBox(false); 186 | }); 187 | errorMessageBox.addControl(button); 188 | 189 | this._advancedTexture.addControl(errorMessageBox); 190 | } 191 | 192 | private swapLoadingMessageBox(isEnabled: boolean) { 193 | const messageBox = this._advancedTexture.getControlByName("messageBox"); 194 | messageBox.isEnabled = isEnabled; 195 | messageBox.alpha = isEnabled ? 0.75 : 0; 196 | } 197 | 198 | private swapErrorMessageBox(isEnabled: boolean) { 199 | this.swapLoadingMessageBox(false); 200 | 201 | const messageBox = this._advancedTexture.getControlByName("errorMessageBox"); 202 | this._advancedTexture.getControlByName("tryAgainButton").isEnabled = true; 203 | messageBox.isEnabled = isEnabled; 204 | messageBox.alpha = isEnabled ? 0.75 : 0; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as BABYLON from "babylonjs"; 2 | 3 | export const createSkyBox = (scene: BABYLON.Scene) => { 4 | const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", {size: 1000.0}, scene); 5 | const skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene); 6 | skyboxMaterial.backFaceCulling = false; 7 | skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("./public/textures/skybox", scene); 8 | skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE; 9 | skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0); 10 | skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0); 11 | skybox.material = skyboxMaterial; 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es6", 5 | "module": "commonjs", 6 | "sourceMap": true, 7 | "outDir": "./dist", 8 | "types": [ 9 | "babylonjs", 10 | "babylonjs-gui", 11 | "babylonjs-materials", 12 | "node" 13 | ] 14 | }, 15 | "include": [ 16 | "src/**/*" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | "**/*.spec.js" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | const path = require('path') 3 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: "development", 7 | entry: { 8 | app: "./src/app.ts" 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: '[name].js' 13 | }, 14 | resolve: { 15 | extensions: ['.ts', 'tsx', '.js'] 16 | }, 17 | devtool: 'source-map', 18 | plugins: [ 19 | new CopyWebpackPlugin({ 20 | patterns: [ 21 | { from: 'public', to: 'public' }, 22 | { from: 'index.html'} 23 | ] 24 | }) 25 | ], 26 | module: { 27 | rules: [{ 28 | test: /\.tsx?$/, 29 | loader: 'ts-loader', 30 | exclude: /node_modules/ 31 | }] 32 | } 33 | } 34 | --------------------------------------------------------------------------------