├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── _config.yml ├── dist ├── CharacterController.d.ts ├── CharacterController.js ├── CharacterController.js.map ├── CharacterController.max.js └── CharacterController.max.js.map ├── package-lock.json ├── package.json ├── src └── CharacterController.ts ├── tsconfig.json ├── tst ├── ground │ ├── ground-normal.png │ ├── ground.jpg │ └── ground_heightMap.png ├── player │ ├── Vincent-backFacing.babylon │ ├── Vincent-backFacing.blend │ ├── Vincent-backFacing.glb │ ├── Vincent-frontFacing.babylon │ ├── Vincent-frontFacing.blend │ ├── Vincent-frontFacing.glb │ ├── Vincent_texture_image.jpg │ └── starterAvatars.babylon ├── sounds │ ├── footstep_carpet_000.ogg │ └── footstep_concrete_000.ogg ├── test.html ├── testAnimationGroup.html ├── testAnimationGroup.js ├── testAnimationRange.html ├── testAnimationRange.js ├── testCommandControl.html ├── testCommandControl.js ├── testNPC.html ├── testNPC.js └── w3.css └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | node_modules 3 | lib 4 | *.bat 5 | ig_* 6 | **/ig_*.* 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | nbproject 2 | node_modules 3 | lib 4 | src 5 | tst 6 | _config.yml 7 | *.bat 8 | **/ig_*.* 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/babylonjs-charactercontroller.svg)](https://npmjs.org/package/babylonjs-charactercontroller) 2 | [![npm](https://img.shields.io/npm/dm/babylonjs-charactercontroller.svg)](https://npmjs.org/package/babylonjs-charactercontroller) 3 | 4 | # BabylonJS-CharacterController 5 | 6 | A 3rd person CharacterController for use in [BabylonJS](http://www.babylonjs.com/) (a 3D HTML Webgl framework) applications. 7 | It uses the collider and moveWithCollision() function to move the character around. It uses physics kinematic equations to calculate movements like jump, fall, slide. It does not use any physics engine. It does not react to forces but does apply forces to other physics object. The force applied cannot be controlled. 8 | For demo see 9 | https://ssatguru.github.io/BabylonJS-CharacterController-Samples/demo/ 10 | 11 | ## About 12 | 13 | It currently supports 14 | 15 | - idle 16 | - idleJump 17 | - walk 18 | - walkBack 19 | - walkBackFast 20 | - run 21 | - runJump 22 | - fall 23 | - turnRight 24 | - turnRightFast 25 | - turnLeft 26 | - turnLeftFast 27 | - strafeLeft 28 | - strafeLeftFast 29 | - strafeRight 30 | - strafeRightFast 31 | - slideDown 32 | 33 | It supports two modes or ways of moving the avatar. 34 | One suitable for third/first person kind of game 35 | and the other suitable for top down isometric kind of game. 36 | Further within the third/first person mode, two "submodes" are supported. 37 | In one submode the left and right keys make the avatar turn left or right and the back key makes the avatar walk backward with back facing the camera. 38 | In the other submode the left and right keys make the avatar face and move left or right and the back keys makes the avatar turn around and move towards the camera. 39 | See "setMode" and "Turning On/Off" below. 40 | 41 | Further it supports constraining avatar from traversing slopes inclined at certain angles. 42 | 43 | It also supports camera "elasticity". In other words if a mesh comes between the camera and avatar/player, the camera snaps to 44 | a position in front of the mesh. This way the avatar/player is always in view. 45 | 46 | It can also enter first person view if the camera comes very close to the avatar/player 47 | 48 | ### Breaking change with 0.2.0 49 | 50 | Instead of "jump" animation it expects "idleJump" and "runJump" animations. 51 | 52 | Version 0.2.0 converts the project from a plain vanilla JavaScript project to a module based JavaScript project. 53 | With this change, the way to load the application has changed. 54 | In JavaScript, instead of 55 | 56 | ``` 57 | var CharacterControl = org.ssatguru.babylonjs.component.CharacterController; 58 | var characterControl = new CharacterControl(player, camera, scene); 59 | ``` 60 | 61 | now do 62 | 63 | ``` 64 | var characterControl = new CharacterController(player, camera, scene); 65 | ``` 66 | 67 | In TypeScript, instead of 68 | 69 | ``` 70 | import CharacterController = org.ssatguru.babylonjs.component.CharacterController; 71 | ``` 72 | 73 | now do 74 | 75 | ``` 76 | import {CharacterController} from "babylonjs-charactercontroller"; 77 | ``` 78 | 79 | See below for more details. 80 | 81 | ## Quick start 82 | 83 | 1. add the following dependencies 84 | 85 | ``` 86 | 87 | 88 | ``` 89 | 90 | See INSTALL below to find where you can get "CharacterController.js". 91 | 92 | 2. if your mesh rotation is in quaternion then switch to euler. 93 | NOTE: The GLTF/GLB files have rotation in quaternion 94 | 95 | ``` 96 | // character controller needs rotation in euler. 97 | // if your mesh has rotation in quaternion then convert that to euler. 98 | player.rotation = player.rotationQuaternion.toEulerAngles(); 99 | player.rotationQuaternion = null; 100 | 101 | ``` 102 | 103 | 3. instantiate charcater controller and start it. 104 | 105 | ``` 106 | //------------------Character Controller ------------------------------------------------- 107 | //fourth parm agMap is optional and is used when animation groups rather than animation ranges 108 | //are used. 109 | var cc = new CharacterController(player, camera, scene, agMap); 110 | cc.start(); 111 | ``` 112 | 113 | see "BabylonJS-CharacterController-Samples" [https://github.com/ssatguru/BabylonJS-CharacterController-Samples](https://github.com/ssatguru/BabylonJS-CharacterController-Samples) for a few simple samples to help you get going 114 | 115 | ## INSTALL 116 | 117 | You can get the "CharacterController.min.js" from its git repository "dist" folder or "releases" section 118 | [https://github.com/ssatguru/BabylonJS-CharacterController/tree/master/dist](https://github.com/ssatguru/BabylonJS-CharacterController/tree/master/dist) 119 | [https://github.com/ssatguru/BabylonJS-CharacterController/releases](https://github.com/ssatguru/BabylonJS-CharacterController/releases) 120 | 121 | You can also install it from npm 122 | 123 | ``` 124 | npm install babylonjs-charactercontroller 125 | ``` 126 | 127 | ## Usage 128 | 129 | This has been built as an UMD module which means you can use it as a CommonJS/NodeJS module, AMD module or as a global object 130 | loaded using the script tag. 131 | 132 | Project "BabylonJS-CharacterController-Samples" [https://github.com/ssatguru/BabylonJS-CharacterController-Samples](https://github.com/ssatguru/BabylonJS-CharacterController-Samples) has a 133 | collection of sample projects to show how to use this from TypeScript, NodeJs, AMD or plain vanilla JavaScript applications. 134 | 135 | Below is a quick summary of how you can use this as different module types. 136 | 137 | TypeScript 138 | 139 | ``` 140 | // TypeScript 141 | import * as BABYLON from "babylonjs"; 142 | import {CharacterController} from "babylonjs-charactercontroller"; 143 | ... 144 | let engine = new BABYLON.Engine(canvas, true); 145 | ... 146 | let cc = new CharacterController(player, camera, scene); 147 | ``` 148 | 149 | CommonJS/NodeJS Module 150 | 151 | ``` 152 | let BABYLON = require("babylonjs"); 153 | let CharacterController = require("babylonjs-CharacterController").CharacterController; 154 | ... 155 | let engine = new BABYLON.Engine(canvas, true); 156 | ... 157 | let characterController = new CharacterController(player, camera, scene); 158 | ... 159 | 160 | ``` 161 | 162 | AMD Module 163 | 164 | ``` 165 | 166 | 184 | ``` 185 | 186 | Global Module 187 | 188 | ``` 189 | 190 | 191 | 198 | ``` 199 | 200 | ## API ( version 0.4.4 ) 201 | 202 | #### To Instantiate 203 | 204 | ``` 205 | // JavaScript 206 | 207 | // if using animation ranges 208 | var cc = new CharacterController(player, camera, scene); 209 | 210 | // if using animation groups (.glb files use animation groups) 211 | var cc = new CharacterController(player, camera, scene, agMap); 212 | //agMap is a Map which maps an "animation name" to "animationGroup object". 213 | 214 | // if the avatar face is forward facing (positive Z direction) 215 | var cc = new CharacterController(player, camera, scene, agMap, true); 216 | ``` 217 | 218 | ``` 219 | // TypeScript 220 | 221 | import {CharacterController} from "babylonjs-charactercontroller"; 222 | 223 | // if using animation ranges 224 | let cc = new CharacterController(player, camera, scene); 225 | 226 | // if using animation groups (.glb files use animation groups) 227 | let cc = new CharacterController(player, camera, scene, agMap); 228 | //agMap is a Map which maps an "animation name" to "animationGroup object". 229 | 230 | // if the avatar face is forward facing (positive Z direction) 231 | let cc = new CharacterController(player, camera, scene, agMap, true); 232 | ``` 233 | 234 | Takes five parms 235 | 236 | - player - the player mesh containing a skeleton with appropriate animations as listed below 237 | - camera - arc rotate camera 238 | - scene - scene 239 | - agMap - This is optional and is only needed if using animation groups instead of animation ranges. ".glb" files have animation groups. 240 | It is a Map which maps an "animation name" to "animationGroup object" . 241 | In this Map the key would be the character controller animation name and 242 | the key value would be the animationGroup object. 243 | example: 244 | 245 | ``` 246 | let myWalkAnimationGroup:AnimationGroup = ...; 247 | let agMap:{} = { 248 | "walk": myWalkAnimationGroup, 249 | "run": ..., 250 | } 251 | ``` 252 | 253 | - forwardFacing - Optional. If the avatar's face is forward facing (positive Z direction) set this to true. By default it is false. 254 | 255 | Note: If camera is set to null then the camera will not follow the character and keybaord will not controll the character. You can use this for an NPC which you can move around programmatically. See the section on "Controlling Avatar programmatically". 256 | 257 | If using animation ranges the player skeleton is expected to have the animation ranges named as follows 258 | 259 | - idle 260 | - idleJump 261 | - walk 262 | - walkBack 263 | - walkBackFast 264 | - run 265 | - runJump 266 | - fall 267 | - turnRight 268 | - turnRightFast 269 | - turnLeft 270 | - turnLeftFast 271 | - strafeLeft 272 | - strafeLeftFast 273 | - strafeRight 274 | - strafeRightFast 275 | - slideDown 276 | 277 | If a particular animation is not provided then the controller will not play that animation and will continue playing the animation it was playing just before. 278 | Note that if no animations are provided then no animations will be played. This, thus, can be used to move an non skeleton based mesh around. 279 | 280 | Note that there are some animations with name ending with string "Fast". 281 | If these are not present then the controller will play the non-fast version but at twice the speed. 282 | So for example lets say you provided "strafeLeft" but not "strafeLeftFast" then the controller will play the "stafeLeft" animation whenever it has to play the "strafeLeftFast" but at twice the speed of "strafeLeft". 283 | 284 | The "Fast" animations are played when the user presses the "mod" key (usually "shift key) along with the normal key. 285 | Example: to play "strafeLeft" if the key is set to "q" then to play "strafeLeftFast" the key would be "q" and "shift". 286 | 287 | Now if your animation range is named differently from those mentioned above then use the setWalkAnim(..), setWalkBackAnim(..) etc API to specify your animation range name. 288 | 289 | If instead of animation ranges you have animation groups then you will have to provide a map of animation name to animation group object. This is explained further down below. 290 | 291 | NOTE : 292 | If your mesh rotation is in quaternion then switch to euler before creating character controller. 293 | The GLTF/GLB files have rotation in quaternion. 294 | 295 | ``` 296 | player.rotation = player.rotationQuaternion.toEulerAngles(); 297 | player.rotationQuaternion = null; 298 | ``` 299 | 300 | #### To start/stop controller 301 | 302 | ``` 303 | cc.start(); 304 | cc.stop(); 305 | ``` 306 | 307 | #### To pause playing any animations 308 | 309 | Sometimes you might want to stop the character controller from playing 310 | any animation on the character and instead play your animation instead 311 | Example instead of idle animation you might want to play a shoot animation. 312 | Use the following to pause or resume 313 | 314 | ``` 315 | cc.pauseAnim(); 316 | cc.resumeAnim(); 317 | ``` 318 | 319 | #### To Change Mode 320 | 321 | The CharacterController can run in one of two modes - 0 or 1. 322 | 323 | - Mode 0 is the default mode. 324 | This is suitable for First Person and Third Person kind of games. 325 | Here the camera follows the movement and rotation of the Avatar. 326 | Rotating the camera around the Avatar also rotates the Avatar. 327 | - Mode 1 is suitable for top down, isometric type of games. 328 | Here the camera just follows the movement of the Avatar. 329 | It is not effected by or effects the rotation of the Avatar 330 | 331 | ``` 332 | cc.setMode(n: number); // 0 or 1 333 | ``` 334 | 335 | ##### Turning on/off 336 | 337 | Use this to set turning on/off. 338 | When turining is off 339 | a) turn left or turn right keys result in avatar facing and moving left or right with respect to camera rather then just turning left or right 340 | b) walkback/runback key results in avatar facing back, towards the camera and walking/running towards camera rather than walking backwards with back to the camera 341 | 342 | This setting has no effect when mode is 1. 343 | 344 | ``` 345 | cc.setTurningOff(true/false); 346 | ``` 347 | 348 | default is false 349 | 350 | #### To change animation range name / animation group and their parameters 351 | 352 | Takes three parms 353 | 354 | - rangeName or Animation group Object 355 | - rate - rate of speed at which to play the aniamtion 356 | - loop - whether the animation should be looped or stop at end. 357 | 358 | To leave any parameter unchanged set its value to null. 359 | 360 | ``` 361 | cc.setIdleAnim(name: string|AnimationGroup, rate: number, loop: boolean); 362 | cc.setIdleJumpAnim(name: string|AnimationGroup, rate: number, loop: boolean); 363 | 364 | cc.setWalkAnim(name: string|AnimationGroup, rate: number, loop: boolean); 365 | cc.setWalkBackAnim(name: string|AnimationGroup, rate: number, loop: boolean); 366 | cc.setWalkBacFastkAnim(name: string|AnimationGroup, rate: number, loop: boolean); 367 | 368 | cc.setRunAnim(name: string|AnimationGroup, rate: number, loop: boolean); 369 | cc.setRunJumpAnim(name: string, rate: number, loop: boolean); 370 | 371 | cc.setFallAnim(name: string|AnimationGroup, rate: number, loop: boolean); 372 | 373 | cc.setTurnRightAnim(name: string|AnimationGroup, rate: number, loop: boolean); 374 | cc.setTurnLeftAnim(name: string|AnimationGroup, rate: number, loop: boolean); 375 | 376 | cc.setTurnRightFastAnim(name: string|AnimationGroup, rate: number, loop: boolean); 377 | cc.setTurnLeftFastAnim(name: string|AnimationGroup, rate: number, loop: boolean); 378 | 379 | cc.setStrafeRightAnim(name: string|AnimationGroup, rate: number, loop: boolean); 380 | cc.setStrafeLeftAnim(name: string|AnimationGroup, rate: number, loop: boolean); 381 | 382 | cc.setStrafeRightFastAnim(name: string|AnimationGroup, rate: number, loop: boolean); 383 | cc.setStrafeLeftFastAnim(name: string|AnimationGroup, rate: number, loop: boolean); 384 | 385 | cc.setSlideBackAnim(name :string|AnimationGroup, rate: number, loop: boolean); 386 | ``` 387 | 388 | So lets say your walk animation range is called "myWalk" and you want to play it at half speed and loop it continuoulsy then 389 | 390 | ``` 391 | cc.setWalkAnim("myWalk", 0.5, true); 392 | //if you donot want to change the name or the rate then use below instead 393 | cc.setWalkAnim(null, null, true); 394 | ``` 395 | 396 | If animation Group 397 | 398 | ``` 399 | let myWalkAnimationGroup:AnimationGroup = ...; 400 | cc.setWalkAnim(myWalkAnimationGroup, 0.5, true); 401 | ``` 402 | 403 | #### To change key binding 404 | 405 | By default the controller uses WASDQE, space, Capslock and arrow keys to controll your Avatar. 406 | 407 | | KEY/KEYS | ACTION | 408 | | ----------------- | -------------------------------------------------------- | 409 | | w and up arrow | walk forward | 410 | | Shift + w | run | 411 | | CapsLock | locks the Shift key and thus pressing "w" results in run | 412 | | s and down Arrow | walk backward | 413 | | a and left Arrow | turn left | 414 | | d and right Arrow | turn right | 415 | | q | strafe left | 416 | | e | strafe right | 417 | | " " | jump | 418 | 419 | To change these use 420 | 421 | ``` 422 | cc.setWalkKey(string: key); 423 | cc.setWalkBackKey(string: key); 424 | cc.setTurnLeftKey(string: key); 425 | cc.setTurnRightKey(string: key); 426 | cc.setStrafeLeftKey(string: key); 427 | cc.setStrafeRightKey(string: key); 428 | cc.setJumpKey(string: key); 429 | ``` 430 | 431 | Example: To use "x" key to walkback do 432 | 433 | ``` 434 | cc.setWalkBackKey("x"); 435 | ``` 436 | 437 | To specify spacebar key use " ". Example cc.setJumpKey(" ") 438 | If targetting IE11 and previous use the word "spacebar". 439 | Example: 440 | 441 | ``` 442 | var ua = window.navigator.userAgent; 443 | var isIE = /MSIE|Trident/.test(ua); 444 | if (isIE) { 445 | //IE specific code goes here 446 | cc.setJumpKey("spacebar"); 447 | } 448 | 449 | ``` 450 | 451 | Note: Currently you cannot reassign Shift, Capslock or Arrow Keys to other actions. This is on TODO list 452 | 453 | #### Controlling Avatar programmatically 454 | 455 | In addition to keyboard, as show above, the Avatar's movement can also be controlled from script using the following methods. 456 | You might use these to controll movement using say UI, Mouse Clicks, Touch Controllers etc. 457 | 458 | ``` 459 | cc.walk(b: boolean); 460 | cc.walkBack(b: boolean); 461 | cc.run(b: boolean); 462 | cc.turnLeft(b: boolean); 463 | cc.turnRight(b: boolean); 464 | cc.strafeLeft(b: boolean); 465 | cc.strafeRight(b: boolean); 466 | cc.jump(b: boolean); 467 | cc.fall(); 468 | ``` 469 | 470 | Example: 471 | 472 | ``` 473 | cc.walk(true); // will start walking the Avatar. 474 | cc.walk(false); // will stop walking the Avatar. 475 | ``` 476 | 477 | A word about cc.fall(). The CharacterController doesn't constantly check if the user is "grounded". This is to prevent needless computation. Once the Avatar is on a ground/floor it assumes the Avatar will continue to stand on that ground/floor until the user uses keys to move the Avatar. 478 | In some use cases the ground/floor might move away and thus leave the Avatar hanging in mid air. In such cases use cc.fall() to force the Avatar to fall to the next gound/floor below. 479 | 480 | 481 | #### Enabling/Disabling the KeyBoard controll 482 | 483 | Sometimes, when you are controlling the movement of the Avatar programmatically as shown above, you might want to disable the keyboard. 484 | Use the following method to enable disable the keyboard. 485 | 486 | ``` 487 | cc.enableKeyBoard(b: boolean); 488 | ``` 489 | 490 | cc.enableKeyBoard(true) enables the keyboard 491 | cc.enableKeyBoard(false) disables the keyboard 492 | 493 | #### To change gravity or speed at which avatar/player is moved 494 | 495 | Speed is specified in meters/second 496 | 497 | ``` 498 | setGravity(n: number); //default 9.8 m/s^2 499 | setWalkSpeed(n: number); //default 3 m/s 500 | setRunSpeed(n: number); //default 6 m/s 501 | setBackSpeed(n: number); //default 3 m/s 502 | setBackFastSpeed(n: number); //default 6 m/s 503 | setJumpSpeed(n: number); //default 6 m/s 504 | setLeftSpeed(n: number); //default 3 m/s 505 | setLeftFastSpeed(n: number); //default 6 m/s 506 | setRightSpeed(n: number); //default 3 m/s 507 | setRightFastSpeed(n: number); //default 6 m/s 508 | setTurnSpeed (n:number);//default PI/8 degree/s 509 | setTurnFastSpeed (n:number);//default PI/4 degree/s 510 | ``` 511 | 512 | #### To change the slope the avatar can traverse 513 | 514 | ``` 515 | setSlopeLimit(minSlopeLimit: number, maxSlopeLimit: number); //the slope is specified in degrees 516 | ``` 517 | 518 | Example 519 | 520 | ``` 521 | setSlopeLimit(45, 55); 522 | ``` 523 | 524 | Here if the avatar is on a slope with angle between 45 and 55 degrees then it will start sliding back when it stops moving. 525 | If the slope is 55 or more then avatar will not be able to move up on it. 526 | 527 | #### To change the height of steps the avatar can climb 528 | 529 | ``` 530 | setStepOffset(stepOffset: number); 531 | ``` 532 | 533 | Example 534 | 535 | ``` 536 | setStepOffset(0.5); 537 | ``` 538 | 539 | The avatar can only move up a step if the height of the step is less than or equal to the "stepOffset". 540 | By default the value is 0.25. 541 | 542 | #### To set/change the setup step sound. 543 | 544 | ``` 545 | setSound(Babylon.Sound); 546 | ``` 547 | 548 | Example 549 | 550 | ``` 551 | let sound = new BABYLON.Sound( 552 | "footstep", 553 | "./sounds/footstep_carpet_000.ogg", 554 | scene, 555 | () => { 556 | cc.setSound(sound); 557 | }, 558 | { loop: false } 559 | ); 560 | ``` 561 | 562 | The above will load sound from file "footstep_carpet_000.ogg" and when loaded will set the Avatar step sound to that. 563 | This sound will be played for all actions except idle. 564 | The sound will be played twice per cycle of the animation. 565 | The rate will be set automatically based on frames and fps of animation 566 | 567 | #### To change avatar or skeleton at 568 | 569 | ``` 570 | setAvatar(avatar: Mesh); 571 | setAvatarSkeleton(skeleton: Skeleton); 572 | ``` 573 | 574 | #### To change camera behavior 575 | 576 | By default the camera focuses on the avatar/player origin. To focus on a different position on the avatar/player use 577 | 578 | ``` 579 | setCameraTarget(v: Vector3); 580 | ``` 581 | 582 | Lets say your avatar origin is at its feet but instead of focusing on its feet you would like camera to focus on its head then, assuming the the head is 1.8m above ground, you would do 583 | 584 | ``` 585 | cc.setCameraTarget(new BABYLON.Vector3(0, 1.8, 0); 586 | ``` 587 | 588 | By default the camera behaves "elastically". In other words if something comes between the camera and avatar the camera snaps to 589 | a position in front of that something. This way the avatar/player is always in view. 590 | To turn this off use 591 | 592 | ``` 593 | setCameraElasticity(false); 594 | ``` 595 | 596 | You can use the arc rotate camera's "lowerRadiusLimit" and "upperRadiusLimit" property to controll how close or how far away from the avatar the camera can get. 597 | Example setting 598 | 599 | ``` 600 | camera.lowerRadiusLimit = 2; 601 | camera.upperRadiusLimit = 20; 602 | ``` 603 | 604 | will restrict the camera between 2 and 20m from the avatar/player. 605 | When the camera comes to the "lowerRadiusLimit" the controller switches to first person view. In other words it makes the avatar/player invisible and the camera collision is disabled. Pulling camera back restores the third person view. 606 | To prevent this use 607 | 608 | ``` 609 | setNoFirstPerson(true); 610 | ``` 611 | 612 | ## Build 613 | 614 | If not already installed, install node js. 615 | Switch to the project folder. 616 | Run "npm install", once, to install all the dependencies. 617 | 618 | ### To build 619 | 620 | 1. Run "npm run build" 621 | This will create a production build. 622 | This will both compile, minify and store the build called CharacterController.js in "dist" folder. 623 | 2. Run "npm run build-dev" 624 | This will create a development build. 625 | This will compile and create a non minified build called CharacterController.max.js in "dist" folder. 626 | 627 | ### To test 628 | 629 | Two ways to test. 630 | 631 | 1. using the webpack-dev-server. 632 | Start the development server 633 | "npm run start" 634 | This will start the live dev server on port 8080 (could be different if this port is already in use) and open the browser pointing at http://localhost:8080/tst/test.html. 635 | The dev server will live recompile your code any time you make changes. 636 | Note: The dev server does not write the build to disk, instead it just builds and serves from memory. In our case it builds "CharacterController.max.js" in memory and serves it from url http://localhost:8080/dist. (see "devserver.devMidleware.publicPath" in wepack.config.js file). 637 | 638 | 2. using any other http server. 639 | Start the server , say http-server, from the project root folder (not from within "/tst " folder). 640 | Goto http://localhost:8080/tst/test.html (assuming the server was started on port 8080). 641 | Everytime you make changes you will have to build using "npm start build-dev". 642 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /dist/CharacterController.d.ts: -------------------------------------------------------------------------------- 1 | import { Skeleton, ArcRotateCamera, Vector3, Mesh, Scene, AnimationGroup, Sound, LinesMesh } from "babylonjs"; 2 | export declare class CharacterController { 3 | private _avatar; 4 | private _skeleton; 5 | private _camera; 6 | private _scene; 7 | getScene(): Scene; 8 | private _gravity; 9 | private _minSlopeLimit; 10 | private _maxSlopeLimit; 11 | private _sl1; 12 | private _sl2; 13 | private _stepOffset; 14 | private _vMoveTot; 15 | private _pauseCam; 16 | private _vMovStartPos; 17 | private _actionMap; 18 | private _cameraElastic; 19 | private _cameraTarget; 20 | private _noFirstPerson; 21 | private _down; 22 | setSlopeLimit(minSlopeLimit: number, maxSlopeLimit: number): void; 23 | setStepOffset(stepOffset: number): void; 24 | setWalkSpeed(n: number): void; 25 | setRunSpeed(n: number): void; 26 | setBackSpeed(n: number): void; 27 | setBackFastSpeed(n: number): void; 28 | setJumpSpeed(n: number): void; 29 | setLeftSpeed(n: number): void; 30 | setLeftFastSpeed(n: number): void; 31 | setRightSpeed(n: number): void; 32 | setRightFastSpeed(n: number): void; 33 | setTurnSpeed(n: number): void; 34 | setTurnFastSpeed(n: number): void; 35 | setGravity(n: number): void; 36 | setAnimationGroups(agMap: {}): void; 37 | setAnimationRanges(arMap: {}): void; 38 | setActionMap(inActMap: ActionMap): string; 39 | getActionMap(): ActionMap; 40 | getSettings(): CCSettings; 41 | setSettings(ccs: CCSettings): void; 42 | private _setAnim; 43 | enableBlending(n: number): void; 44 | disableBlending(): void; 45 | setWalkAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 46 | setRunAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 47 | setWalkBackAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 48 | setWalkBackFastAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 49 | setSlideBackAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 50 | setIdleAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 51 | setTurnRightAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 52 | setTurnRightFastAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 53 | setTurnLeftAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 54 | setTurnLeftFastAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 55 | setStrafeRightAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 56 | setStrafeRightFastAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 57 | setStrafeLeftAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 58 | setStrafeLeftFastAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 59 | setIdleJumpAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 60 | setRunJumpAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 61 | setFallAnim(rangeName: string | AnimationGroup, rate: number, loop: boolean): void; 62 | _stepSound: Sound; 63 | setSound(sound: Sound): void; 64 | setWalkKey(key: string): void; 65 | setWalkBackKey(key: string): void; 66 | setTurnLeftKey(key: string): void; 67 | setTurnRightKey(key: string): void; 68 | setStrafeLeftKey(key: string): void; 69 | setStrafeRightKey(key: string): void; 70 | setJumpKey(key: string): void; 71 | setCameraElasticity(b: boolean): void; 72 | setElasticiSteps(n: number): void; 73 | makeObstructionInvisible(b: boolean): void; 74 | setCameraTarget(v: Vector3): void; 75 | cameraCollisionChanged(): void; 76 | setNoFirstPerson(b: boolean): void; 77 | private _checkAnimRanges; 78 | private _checkFastAnims; 79 | private _copySlowAnims; 80 | private _mode; 81 | private _saveMode; 82 | setMode(n: number): void; 83 | getMode(): number; 84 | setTurningOff(b: boolean): void; 85 | isTurningOff(): boolean; 86 | private _isLHS_RHS; 87 | private _signLHS_RHS; 88 | private _setRHS; 89 | private _ffSign; 90 | private _rhsSign; 91 | private _ff; 92 | private _av2cam; 93 | setFaceForward(b: boolean): void; 94 | isFaceForward(): boolean; 95 | private checkAGs; 96 | private _containsAG; 97 | private _getRoot; 98 | private _started; 99 | start(): void; 100 | stop(): void; 101 | private _stopAnim; 102 | pauseAnim(): void; 103 | resumeAnim(): void; 104 | private _prevActData; 105 | private _avStartPos; 106 | private _pickStartY; 107 | private _grounded; 108 | private _freeFallDist; 109 | private _fallFrameCountMin; 110 | private _fallFrameCount; 111 | private _inFreeFall; 112 | private _wasWalking; 113 | private _wasRunning; 114 | private _moveVector; 115 | private _isAvFacingCamera; 116 | private _moveAVandCamera; 117 | private _soundLoopTime; 118 | private _sndId; 119 | private _jumpStartPosY; 120 | private _jumpTime; 121 | private _doJump; 122 | private _calcJumpDist; 123 | private _endJump; 124 | private _areVectorsEqual; 125 | private _verticalSlope; 126 | private _movFallTime; 127 | private _sign; 128 | private _isTurning; 129 | private _noRot; 130 | private _steps; 131 | private _doMove; 132 | private _isNearGround; 133 | private _isNearGround_old; 134 | _aLine: LinesMesh; 135 | private _drawLines; 136 | private _rotateAV2C; 137 | private _rotateAVnC; 138 | private _endFreeFall; 139 | private _idleFallTime; 140 | private _doIdle; 141 | private _groundFrameCount; 142 | private _groundFrameMax; 143 | private _groundIt; 144 | private _unGroundIt; 145 | private _savedCameraCollision; 146 | private _inFP; 147 | private _updateTargetValue; 148 | private _makeMeshInvisible; 149 | private _visiblityMap; 150 | private _restoreVisiblity; 151 | private _ray; 152 | private _rayDir; 153 | private _cameraSkin; 154 | private _prevPickedMeshes; 155 | private _pickedMeshes; 156 | private _makeInvisible; 157 | private _elasticSteps; 158 | private _alreadyInvisible; 159 | private _handleObstruction; 160 | private _isSeeAble; 161 | private _move; 162 | anyMovement(): boolean; 163 | private _onKeyDown; 164 | private _onKeyUp; 165 | private _ekb; 166 | isKeyBoardEnabled(): boolean; 167 | enableKeyBoard(b: boolean): void; 168 | private _addkeylistener; 169 | private _removekeylistener; 170 | walk(b: boolean): void; 171 | walkBack(b: boolean): void; 172 | walkBackFast(b: boolean): void; 173 | run(b: boolean): void; 174 | turnLeft(b: boolean): void; 175 | turnLeftFast(b: boolean): void; 176 | turnRight(b: boolean): void; 177 | turnRightFast(b: boolean): void; 178 | strafeLeft(b: boolean): void; 179 | strafeLeftFast(b: boolean): void; 180 | strafeRight(b: boolean): void; 181 | strafeRightFast(b: boolean): void; 182 | jump(): void; 183 | fall(): void; 184 | idle(): void; 185 | private _act; 186 | private _renderer; 187 | private _handleKeyUp; 188 | private _handleKeyDown; 189 | private _isAG; 190 | isAg(): boolean; 191 | private _findSkel; 192 | private _root; 193 | private _getAbstractMeshChildren; 194 | setAvatar(avatar: Mesh, faceForward?: boolean): boolean; 195 | private _ellipsoid; 196 | showEllipsoid(show: boolean): void; 197 | getAvatar(): Mesh; 198 | setAvatarSkeleton(skeleton: Skeleton): void; 199 | private _skelDrivenByAG; 200 | getSkeleton(): Skeleton; 201 | private _hasAnims; 202 | private _hasCam; 203 | private _avChildren; 204 | constructor(avatar: Mesh, camera: ArcRotateCamera, scene: Scene, actionMap?: {}, faceForward?: boolean); 205 | } 206 | export declare class ActionData { 207 | id: string; 208 | speed: number; 209 | ds: number; 210 | sound: Sound; 211 | key: string; 212 | dk: string; 213 | name: string; 214 | ag: AnimationGroup; 215 | loop: boolean; 216 | rate: number; 217 | exist: boolean; 218 | constructor(id?: string, speed?: number, key?: string); 219 | reset(): void; 220 | } 221 | export declare class ActionMap { 222 | walk: ActionData; 223 | walkBack: ActionData; 224 | walkBackFast: ActionData; 225 | idle: ActionData; 226 | idleJump: ActionData; 227 | run: ActionData; 228 | runJump: ActionData; 229 | fall: ActionData; 230 | turnLeft: ActionData; 231 | turnLeftFast: ActionData; 232 | turnRight: ActionData; 233 | turnRightFast: ActionData; 234 | strafeLeft: ActionData; 235 | strafeLeftFast: ActionData; 236 | strafeRight: ActionData; 237 | strafeRightFast: ActionData; 238 | slideBack: ActionData; 239 | reset(): void; 240 | } 241 | export declare class CCSettings { 242 | faceForward: boolean; 243 | gravity: number; 244 | minSlopeLimit: number; 245 | maxSlopeLimit: number; 246 | stepOffset: number; 247 | cameraElastic: boolean; 248 | elasticSteps: number; 249 | makeInvisble: boolean; 250 | cameraTarget: Vector3; 251 | noFirstPerson: boolean; 252 | topDown: boolean; 253 | turningOff: boolean; 254 | keyboard: boolean; 255 | sound: Sound; 256 | } 257 | -------------------------------------------------------------------------------- /dist/CharacterController.js: -------------------------------------------------------------------------------- 1 | !function(t,i){if("object"==typeof exports&&"object"==typeof module)module.exports=i(require("babylonjs"));else if("function"==typeof define&&define.amd)define(["babylonjs"],i);else{var s="object"==typeof exports?i(require("babylonjs")):i(t.BABYLON);for(var h in s)("object"==typeof exports?exports:t)[h]=s[h]}}(self,(t=>(()=>{"use strict";var i={247:i=>{i.exports=t}},s={};function h(t){var n=s[t];if(void 0!==n)return n.exports;var e=s[t]={exports:{}};return i[t](e,e.exports,h),e.exports}h.n=t=>{var i=t&&t.t?()=>t.default:()=>t;return h.d(i,{a:i}),i},h.d=(t,i)=>{for(var s in i)h.o(i,s)&&!h.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:i[s]})},h.o=(t,i)=>Object.prototype.hasOwnProperty.call(t,i),h.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"t",{value:!0})};var n={};return(()=>{h.r(n),h.d(n,{ActionData:()=>e,ActionMap:()=>r,CCSettings:()=>u,CharacterController:()=>i});var t=h(247),i=function(){function i(i,h,n,e,u){void 0===u&&(u=!1);var a=this;this.i=null,this.h=null,this.u=9.8,this.l=30,this.v=45,this.M=Math.PI*this.l/180,this.k=Math.PI*this.v/180,this.p=.25,this.j=0,this.g=!1,this.O=t.Vector3.Zero(),this.A=new r,this.B=!0,this.C=t.Vector3.Zero(),this.F=!1,this.L=t.Vector3.DownReadOnly,this.R=0,this.S=0,this.q=!1,this.I=-1,this.J=!1,this._=!1,this.D=null,this.N=t.Vector3.Zero(),this.T=0,this.Y=!1,this.G=0,this.H=20,this.K=0,this.P=!1,this.U=!1,this.V=!1,this.W=700,this.X=null,this.Z=0,this.$=0,this.tt=0,this.it=1,this.st=!1,this.ht=!1,this.nt=!0,this.et=null,this.rt=0,this.ut=0,this.ot=10,this.ft=!0,this.ct=!1,this.lt=new Map,this.vt=new t.Ray(t.Vector3.Zero(),t.Vector3.One(),1),this.bt=t.Vector3.Zero(),this.wt=.5,this.Mt=new Array,this.dt=!1,this.kt=50,this.yt=!1,this.jt=!0,this.gt=!1,this.Ot=null,this.At=!1,this.xt=!0,this.Bt=h,null==this.Bt&&(this.xt=!1,this.setMode(1)),this.Ct=n,this.setAvatar(i,u)||console.error("unable to set avatar");null!=e&&this.setActionMap(e),this.gt||null==this.h||this.Ft(this.h),this.gt,this.xt&&(this.ft=this.Bt.checkCollisions),this.Lt=new s,this.Rt=function(){a.St()},this.qt=function(t){a.It(t)},this.Jt=function(t){a._t(t)}}return i.prototype.getScene=function(){return this.Ct},i.prototype.setSlopeLimit=function(t,i){this.l=t,this.v=i,this.M=Math.PI*this.l/180,this.k=Math.PI*this.v/180},i.prototype.setStepOffset=function(t){this.p=t},i.prototype.setWalkSpeed=function(t){this.A.walk.speed=t},i.prototype.setRunSpeed=function(t){this.A.run.speed=t},i.prototype.setBackSpeed=function(t){this.A.walkBack.speed=t},i.prototype.setBackFastSpeed=function(t){this.A.walkBackFast.speed=t},i.prototype.setJumpSpeed=function(t){this.A.idleJump.speed=t,this.A.runJump.speed=t},i.prototype.setLeftSpeed=function(t){this.A.strafeLeft.speed=t},i.prototype.setLeftFastSpeed=function(t){this.A.strafeLeftFast.speed=t},i.prototype.setRightSpeed=function(t){this.A.strafeRight.speed=t},i.prototype.setRightFastSpeed=function(t){this.A.strafeLeftFast.speed=t},i.prototype.setTurnSpeed=function(t){this.A.turnLeft.speed=t*Math.PI/180,this.A.turnRight.speed=t*Math.PI/180},i.prototype.setTurnFastSpeed=function(t){this.A.turnLeftFast.speed=t*Math.PI/180,this.A.turnRightFast.speed=t*Math.PI/180},i.prototype.setGravity=function(t){this.u=t},i.prototype.setAnimationGroups=function(t){null!=this.D&&this.D.exist&&this.D.ag.stop(),this.gt=!0,this.setActionMap(t)},i.prototype.setAnimationRanges=function(t){this.gt=!1,this.setActionMap(t)},i.prototype.setActionMap=function(i){for(var s,h=!1,n=0,r=Object.keys(this.A);n-1)return!0}return!1},i.prototype.Qt=function(t){return null==t.parent?t:this.Qt(t.parent)},i.prototype.start=function(){this.J||(this.J=!0,this.Lt.reset(),this.tt=0,this.rt=.001,this.Y=!1,this.Ut(),this.jt&&this.Vt(),this.Ct.registerBeforeRender(this.Rt))},i.prototype.stop=function(){this.J&&(this.J=!1,this.Ct.unregisterBeforeRender(this.Rt),this.Wt(),this.D=null)},i.prototype.pauseAnim=function(){this._=!0,null!=this.D&&this.D.exist&&(this.gt?this.D.ag.stop():this.Ct.stopAnimation(this.h),null!=this.D.sound&&this.D.sound.stop(),clearInterval(this.X),this.Ct.unregisterBeforeRender(this.Rt))},i.prototype.resumeAnim=function(){this._=!1,this.D=null,this.Ct.registerBeforeRender(this.Rt)},i.prototype.Xt=function(){return this.xt?t.Vector3.Dot(this.i.forward,this.i.position.subtract(this.Bt.position))<0?1:-1:1},i.prototype.St=function(){this.N.copyFrom(this.i.position);var t=null,i=this.Ct.getEngine().getDeltaTime()/1e3;if(this.Lt.Zt&&!this.P?(this.j=0,this.g=!1,this.Y=!1,this.rt=0,t=this.$t(i)):this.anyMovement()||this.P?(this.Y=!1,this.rt=0,t=this.ti(i)):this.P||(t=this.ii(i)),!this._&&this.At&&null!=t&&this.D!==t){if(t.exist){var s=void 0,h=30;if(this.gt)null!=this.D&&this.D.exist&&this.D.ag.stop(),t.ag.start(t.loop,t.rate),h=t.ag.targetedAnimations[0].animation.framePerSecond,s=t.ag.to-t.ag.from;else h=this.h.beginAnimation(t.name,t.loop,t.rate).getAnimations()[0].animation.framePerSecond,s=this.h.getAnimationRange(t.name).to-this.h.getAnimationRange(t.name).from;null!=this.D&&null!=this.D.sound&&this.D.sound.stop(),clearInterval(this.X),null!=t.sound&&(t.sound.play(),this.X=setInterval((function(){t.sound.play()}),1e3*s/(h*Math.abs(t.rate)*2)))}this.D=t}this.Ut()},i.prototype.$t=function(i){var s=null;s=this.A.runJump,0===this.$&&(this.Z=this.i.position.y),this.$=this.$+i;var h,n=0,e=0;if(this.V||this.U?(this.V?n=this.A.run.speed*i:this.U&&(n=this.A.walk.speed*i),(h=this.si.clone()).y=0,(h=h.normalize()).scaleToRef(n,h),e=this.hi(this.A.runJump.speed,i),h.y=e):(e=this.hi(this.A.idleJump.speed,i),h=new t.Vector3(0,e,0),s=this.A.idleJump),this.i.moveWithCollisions(h),e<0)if(this.i.position.y>this.N.y||this.i.position.y===this.N.y&&h.length()>.001)this.ni();else if(this.i.position.y0?this.A.strafeLeftFast:this.A.strafeRightFast):h=-this.Kt*n>0?this.A.strafeLeft:this.A.strafeRight,this.si=this.i.calcMovePOV(n*e,-this.G,0),s=!0;break;case this.Lt.li:n=-this.I*this.Xt(),e=this.A.strafeRight.speed*t,this.Lt.ci?(e=this.A.strafeRightFast.speed*t,h=-this.Kt*n>0?this.A.strafeLeftFast:this.A.strafeRightFast):h=-this.Kt*n>0?this.A.strafeLeft:this.A.strafeRight,this.si=this.i.calcMovePOV(n*e,-this.G,0),s=!0;break;case this.Lt.vi||this.ht&&0==this.R:this.Lt.ci?(this.V=!0,e=this.A.run.speed*t,h=this.A.run):(this.U=!0,e=this.A.walk.speed*t,h=this.A.walk),this.si=this.i.calcMovePOV(0,-this.G,this.Kt*e),s=!0;break;case this.Lt.bi:e=this.A.walkBack.speed*t,this.Lt.ci?(e=this.A.walkBackFast.speed*t,h=this.A.walkBackFast):h=this.A.walkBack,this.si=this.i.calcMovePOV(0,-this.G,-this.Kt*e),s=!0}}if(s&&this.si.length()>.001){this.i.moveWithCollisions(this.si);var r=this.i.position.subtract(this.N),u=this.ri(r);if(this.i.position.y-this.N.y>.01)if(0==u.slope){if(this.p>0){if(0==this.j)this.O.copyFrom(this.N),u.y-this.O.y>this.p&&this.i.position.copyFrom(this.O);this.j=this.i.position.y-this.O.y,this.j>this.p&&(this.i.position.copyFrom(this.O),this.g=!0,this.j=0)}}else{this.j=0;var a=u.slope;a>=this.k&&u.y>this.T?(this.i.position.copyFrom(this.N),this.wi(),this.T=0):(this.T=u.y,a>this.M?(this.K=0,this.P=!1):this.wi())}else this.N.y>this.i.position.y?(this.j=0,u.y>=this.i.position.y?u.slope<=this.M?this.wi():(this.K=0,this.P=!1):(this.i.position.y-u.y>1||!u.hit)&&(this.g=!1,this.P=!0,h=this.A.fall)):(this.j=0,this.wi())}return h},i.prototype.ri=function(i){var s,h=this,n=this.i.position.y-this.N.y,e=!0;(e=Math.abs(n)<.006||n>.01,i.y=0,0==i.x&&0==i.z)?s=!0:s=t.Vector3.Dot(this.i.forward,i.normalize())>=0;var r=e&&s||!e&&!s?1:-1;this.i.forward.scaleToRef(this.i.ellipsoid.x*r,this.vt.origin),this.vt.origin.addToRef(this.i.position,this.vt.origin),this.vt.origin.addToRef(this.i.ellipsoidOffset,this.vt.origin),this.vt.length=2*this.i.ellipsoid.y,this.vt.direction=this.L,this.Mi(this.vt.origin,this.vt.origin.add(new t.Vector3(0,-this.vt.length,0)));var u=this.Ct.pickWithRay(this.vt,(function(t){return!h.di.includes(t)&&!!t.checkCollisions}));if(null!=u&&u.hit){var a=u.getNormal(!0,!0),o=Math.PI/2-Math.asin(Math.abs(a.y));return{name:u.pickedMesh.name,ground:!0,slope:o,y:u.pickedPoint.y,hit:!0}}return{name:"",ground:!1,slope:0,y:0,hit:!1}},i.prototype.ki=function(){var t=this;this.i.position.addToRef(this.i.ellipsoidOffset,this.vt.origin),this.vt.origin.y=this.vt.origin.y-this.i.ellipsoid.y,this.vt.length=this.i.ellipsoid.y/2,this.vt.direction=this.L;var i=this.Ct.multiPickWithRay(this.vt,(function(i){return i!=t.i&&!!i.checkCollisions}));if(i.length>0){var s=i[0],h=s.getNormal(!0,!0),n=Math.PI/2-Math.asin(Math.abs(h.y));return{name:s.pickedMesh.name,ground:!0,slope:n}}return{name:"",ground:!1,slope:0}},i.prototype.Mi=function(i,s){null!=this.et&&this.et.dispose();var h={points:[i,s],updatable:!0};this.et=t.MeshBuilder.CreateLines("lines",h)},i.prototype.ai=function(){if(this.xt&&1!=this.R){var t=this.xt?this.Ht-this.Bt.alpha:0;if(this.ht)switch(!0){case this.Lt.vi&&this.Lt.pi:this.i.rotation.y=t+this.Gt*Math.PI/4;break;case this.Lt.vi&&this.Lt.yi:this.i.rotation.y=t-this.Gt*Math.PI/4;break;case this.Lt.bi&&this.Lt.pi:this.i.rotation.y=t+3*this.Gt*Math.PI/4;break;case this.Lt.bi&&this.Lt.yi:this.i.rotation.y=t-3*this.Gt*Math.PI/4;break;case this.Lt.vi:this.i.rotation.y=t;break;case this.Lt.bi:this.i.rotation.y=t+Math.PI;break;case this.Lt.pi:this.i.rotation.y=t+this.Gt*Math.PI/2;break;case this.Lt.yi:this.i.rotation.y=t-this.Gt*Math.PI/2}else this.xt&&(this.i.rotation.y=t)}},i.prototype.oi=function(t,i,s){if((!this.ht||0!=this.R)&&!this.Lt.fi&&!this.Lt.li&&(this.Lt.yi||this.Lt.pi)){var h=this.A.turnLeft.speed*s;this.Lt.ci&&(h*=2);var n=void 0;1==this.R?(this.st||(this.it=-this.Kt*this.Xt(),this.q&&(this.it=-this.it),this.st=!0),n=this.it,this.Lt.yi?this.Lt.vi||(this.Lt.bi?n=-this.it:t=this.it>0?this.A.turnRight:this.A.turnLeft):this.Lt.vi?n=-this.it:this.Lt.bi||(n=-this.it,t=this.it>0?this.A.turnLeft:this.A.turnRight)):(n=1,this.Lt.yi?(this.Lt.bi&&(n=-1),i||(t=this.A.turnLeft)):(this.Lt.vi&&(n=-1),i||(n=-1,t=this.A.turnRight)),this.xt&&(this.Bt.alpha=this.Bt.alpha+this.Gt*h*n)),this.i.rotation.y=this.i.rotation.y+h*n}return t},i.prototype.wi=function(){this.tt=0,this.K=0,this.P=!1},i.prototype.ii=function(i){if(this.Y)return this.A.idle;this.U=!1,this.V=!1,this.tt=0;var s=this.A.idle;if(this.K=0,0===i)this.G=5;else{var h=this.rt*this.u;this.G=h*i+this.u*i*i/2,this.rt=this.rt+i}if(this.G<.01)return s;var n=new t.Vector3(0,-this.G,0);if(this.i.moveWithCollisions(n),this.i.position.y>this.N.y||this.i.position.y===this.N.y){var e=this.i.position.subtract(this.N);this.ri(e).slope<=this.M?(this.ji(),this.i.position.copyFrom(this.N)):(this.mi(),s=this.A.slideBack)}else if(this.i.position.ythis.ot&&(this.Y=!0,this.rt=0)},i.prototype.mi=function(){this.Y=!1,this.ut=0},i.prototype.Ut=function(){this.xt&&(this.g?this.O.addToRef(this.C,this.Bt.target):this.i.position.addToRef(this.C,this.Bt.target),this.Bt.radius>this.Bt.lowerRadiusLimit&&(this.B||this.dt)&&this.gi(),this.Bt.radius<=this.Bt.lowerRadiusLimit?this.F||this.ct||(this.Oi(this.i),this.Bt.checkCollisions=!1,this.S=this.R,this.R=0,this.ct=!0):this.ct&&(this.ct=!1,this.R=this.S,this.Ai(this.i),this.Bt.checkCollisions=this.ft))},i.prototype.Oi=function(i){var s=this;this.lt.set(i,i.visibility),i.visibility=0,i.getChildMeshes(!1,(function(i){return i instanceof t.Mesh&&(s.lt.set(i,i.visibility),i.visibility=0),!1}))},i.prototype.Ai=function(i){var s=this;i.visibility=this.lt.get(i),i.getChildMeshes(!1,(function(i){return i instanceof t.Mesh&&(i.visibility=s.lt.get(i)),!1}))},i.prototype.gi=function(){var t=this;this.Bt.position.subtractToRef(this.Bt.target,this.bt),this.vt.origin=this.Bt.target,this.vt.length=this.bt.length(),this.vt.direction=this.bt.normalize();var i=this.Ct.multiPickWithRay(this.vt,(function(i){return!t.di.includes(i)&&!!i.isPickable}));if(this.dt)if(this.xi=this.Mt,i.length>0){this.Mt=new Array;for(var s=0,h=i;s0){if(!(1!=i.length||this.Bi(i[0].pickedMesh)||i[0].pickedMesh.checkCollisions&&this.Bt.checkCollisions))return;for(var f=null,c=0;c0?h[0].skeleton:null},i.prototype.Fi=function(t){return null==t.parent?t:this.Fi(t.parent)},i.prototype.Li=function(i){var s=new Array;return i instanceof t.AbstractMesh&&s.push(i),i.getChildren((function(i){return i instanceof t.AbstractMesh&&s.push(i),!1}),!1),s},i.prototype.setAvatar=function(i,s){void 0===s&&(s=!1);var h=this.Fi(i);return h instanceof t.Mesh?(this.i=h,this.di=this.Li(h),this.h=this.Ci(i),this.gt=this.Pt(i,this.Ct.animationGroups,!0),this.A.reset(),this.gt||null==this.h||this.Ft(this.h),this.zt(i),this.setFaceForward(s),!0):(console.error("Cannot move this mesh. The root node of the mesh provided is not a mesh"),!1)},i.prototype.showEllipsoid=function(i){if(!i)return null!=this.Ot&&this.Ot.dispose(),void(this.Ot=null);for(var s=new t.TransformNode("ellipsoid",this.Ct),h=this.i.ellipsoid.x,n=this.i.ellipsoid.y,e=[],r=-Math.PI/2;r", 14 | "license": "Apache-2.0", 15 | "contributors": [], 16 | "main": "dist/CharacterController.js", 17 | "types": "dist/CharacterController.d.ts", 18 | "devDependencies": { 19 | "babylonjs": "^8.4.0", 20 | "babylonjs-inspector": "^8.4.0", 21 | "babylonjs-loaders": "^8.4.0", 22 | "terser-webpack-plugin": "^5.3.6", 23 | "ts-loader": "^9.4.2", 24 | "typescript": "^4.9.4", 25 | "webpack": "^5.75.0", 26 | "webpack-cli": "^5.0.1", 27 | "webpack-dev-server": "^4.11.1" 28 | }, 29 | "scripts": { 30 | "dev": "webpack serve --open /tst/test.html", 31 | "build-dev": "webpack --mode=development", 32 | "build": "webpack --mode=production" 33 | } 34 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration":true, 4 | "target": "es5", 5 | "module": "es2015", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "removeComments": true, 11 | "noImplicitAny": false, 12 | "outDir":"dist" 13 | }, 14 | "include": ["src/**/*.ts"], 15 | "lib":["dom"] 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tst/ground/ground-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/ground/ground-normal.png -------------------------------------------------------------------------------- /tst/ground/ground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/ground/ground.jpg -------------------------------------------------------------------------------- /tst/ground/ground_heightMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/ground/ground_heightMap.png -------------------------------------------------------------------------------- /tst/player/Vincent-backFacing.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/player/Vincent-backFacing.blend -------------------------------------------------------------------------------- /tst/player/Vincent-backFacing.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/player/Vincent-backFacing.glb -------------------------------------------------------------------------------- /tst/player/Vincent-frontFacing.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/player/Vincent-frontFacing.blend -------------------------------------------------------------------------------- /tst/player/Vincent-frontFacing.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/player/Vincent-frontFacing.glb -------------------------------------------------------------------------------- /tst/player/Vincent_texture_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/player/Vincent_texture_image.jpg -------------------------------------------------------------------------------- /tst/sounds/footstep_carpet_000.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/sounds/footstep_carpet_000.ogg -------------------------------------------------------------------------------- /tst/sounds/footstep_concrete_000.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssatguru/BabylonJS-CharacterController/1132bbda84a1cf3ba8397af19d602f6941646155/tst/sounds/footstep_concrete_000.ogg -------------------------------------------------------------------------------- /tst/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 | 17 |

Test Suite

18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tst/testAnimationGroup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CharacterController Demo 7 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 | 68 |
69 | 70 |
71 |
72 |

Demo of CharacterController for Babylonjs

73 |

74 | For more information and source code head on over to
75 | https://ssatguru.github.io/BabylonJS-CharacterController/ 76 |

77 | 78 |
79 |
80 | 81 | 82 | -------------------------------------------------------------------------------- /tst/testAnimationGroup.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | main(); 3 | }; 4 | 5 | let animPaused = false; 6 | let cc; 7 | let scene; 8 | let skeleton; 9 | let allAGs; 10 | 11 | function main() { 12 | let helpButton = document.getElementById("help"); 13 | let closeButton = document.getElementById("closehelp"); 14 | let pauseButton = document.getElementById("pause"); 15 | let el = document.getElementById("overlay"); 16 | 17 | let canvasElement = document.getElementById("renderCanvas"); 18 | 19 | helpButton.onclick = closeButton.onclick = () => { 20 | el.style.visibility = el.style.visibility == "visible" ? "hidden" : "visible"; 21 | }; 22 | 23 | pauseButton.onclick = () => { 24 | if (animPaused) { 25 | pauseButton.innerHTML = "Pause"; 26 | allAGs[7].stop(); 27 | cc.enableKeyBoard(true); 28 | cc.resumeAnim(); 29 | canvasElement.focus(); 30 | } else { 31 | cc.pauseAnim(); 32 | cc.enableKeyBoard(false); 33 | pauseButton.innerHTML = "Resume"; 34 | allAGs[7].start(false, 1); 35 | canvasElement.focus(); 36 | } 37 | animPaused = !animPaused; 38 | }; 39 | 40 | /* 41 | * The scene 42 | */ 43 | var canvas = document.querySelector("#renderCanvas"); 44 | var engine = new BABYLON.Engine(canvas, true); 45 | scene = new BABYLON.Scene(engine); 46 | 47 | scene.clearColor = new BABYLON.Color3(0.75, 0.75, 0.75); 48 | scene.ambientColor = new BABYLON.Color3(1, 1, 1); 49 | 50 | scene.debugLayer.show({ showExplorer: true, embedMode: true }); 51 | 52 | var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); 53 | light.intensity = 0.3; 54 | 55 | var light2 = new BABYLON.DirectionalLight("light2", new BABYLON.Vector3(-1, -1, -1), scene); 56 | light2.position = new BABYLON.Vector3(0, 128, 0); 57 | light2.intensity = 0.7; 58 | 59 | var groundMaterial = createGroundMaterial(scene); 60 | var ground = createGround(scene, groundMaterial); 61 | 62 | loadPlayer(scene, engine, canvas); 63 | 64 | //box to test view obstruction 65 | var box = BABYLON.Mesh.CreateBox("box", 2, scene); 66 | box.checkCollisions = true; 67 | box.position = new BABYLON.Vector3(0, 8, 5); 68 | 69 | window.addEventListener("resize", function () { 70 | engine.resize(); 71 | }); 72 | } 73 | 74 | function loadPlayer(scene, engine, canvas) { 75 | BABYLON.SceneLoader.ImportMesh("", "player/", "Vincent-frontFacing.glb", scene, (meshes, particleSystems, skeletons) => { 76 | var player = meshes[0]; 77 | 78 | //clean up this player mesh 79 | //it has camera and lights, lets remove them 80 | let m = meshes[0].getChildren(); 81 | let l = m.length - 1; 82 | for (let i = l; i >= 0; i--) { 83 | if (m[i].name == "Camera" || m[i].name == "Hemi" || m[i].name == "Lamp") m[i].dispose(); 84 | } 85 | 86 | 87 | player.position = new BABYLON.Vector3(0, 12, 0); 88 | player.checkCollisions = true; 89 | 90 | player.ellipsoid = new BABYLON.Vector3(0.5, 1, 0.5); 91 | player.ellipsoidOffset = new BABYLON.Vector3(0, 1, 0); 92 | 93 | // character controller needs rotation in euler. 94 | // if your mesh has rotation in quaternion then convert that to euler. 95 | // NOTE: The GLTF/GLB files have rotation in quaternion 96 | player.rotation = player.rotationQuaternion.toEulerAngles(); 97 | player.rotationQuaternion = null; 98 | 99 | //rotate the camera behind the player 100 | //.glbs are RHS 101 | player.rotation.y = Math.PI / 4; 102 | var alpha = (3 * Math.PI) / 2 - player.rotation.y; 103 | var beta = Math.PI / 2.5; 104 | var target = new BABYLON.Vector3(player.position.x, player.position.y + 1.5, player.position.z); 105 | var camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", alpha, beta, 5, target, scene); 106 | 107 | // make sure the keyboard keys controlling camera are different from those controlling player 108 | // here we will not use any keyboard keys to control camera 109 | camera.keysLeft = []; 110 | camera.keysRight = []; 111 | camera.keysUp = []; 112 | camera.keysDown = []; 113 | 114 | // below are all standard camera settings. 115 | // nothing specific to charcter controller 116 | camera.wheelPrecision = 15; 117 | camera.checkCollisions = false; 118 | // how close can the camera come to player 119 | camera.lowerRadiusLimit = 2; 120 | // how far can the camera go from the player 121 | camera.upperRadiusLimit = 20; 122 | camera.attachControl(canvas, false); 123 | 124 | // provide all your animation groups as a map to the character controller 125 | // the map should have 126 | // key = the name of the character controller animation 127 | // and 128 | // value = the AnimationGroup corresponding to that animation. 129 | 130 | allAGs = scene.animationGroups; 131 | 132 | //stop all animations 133 | //also lets print to console the list of animation groups we have in this file, to help map them properly 134 | for (i = 0; i < allAGs.length; i++) { 135 | allAGs[i].stop(); 136 | console.log(i + "," + allAGs[i].name); 137 | } 138 | 139 | var agMap = createAGmap(allAGs); 140 | 141 | cc = new CharacterController(player, camera, scene, agMap, true); 142 | 143 | cc.setMode(0); 144 | //below makes the controller point the camera at the player head which is approx 145 | //1.5m above the player origin 146 | cc.setCameraTarget(new BABYLON.Vector3(0, 2, 0)); 147 | 148 | //if the camera comes close to the player then we want cc to enter first person mode. 149 | cc.setNoFirstPerson(false); 150 | //the height of steps which the player can climb 151 | cc.setStepOffset(0.4); 152 | //the minimum and maximum slope the player can go up 153 | //between the two the player will start sliding down if it stops 154 | cc.setSlopeLimit(30, 60); 155 | 156 | //tell controller 157 | // - which animation range/ animation group should be used for which player animation 158 | // - rate at which to play that animation range 159 | // - wether the animation range should be looped 160 | //use this if name, rate or looping is different from default 161 | //set a parm to null if you donot want to change that 162 | 163 | cc.setIdleAnim(null, 1, true); 164 | cc.setTurnLeftAnim(null, 0.5, true); 165 | cc.setTurnRightAnim(null, 0.5, true); 166 | cc.setWalkAnim(agMap["walk2"], 1, true); 167 | cc.setWalkBackAnim(null, 0.5, true); 168 | cc.setIdleJumpAnim(null, 0.5, false); 169 | cc.setRunJumpAnim(null, 0.6, false); 170 | cc.setFallAnim(null, 2, false); 171 | cc.setSlideBackAnim(null, 1, false); 172 | 173 | //let's set footstep sound 174 | //this sound will be played for all actions except idle. 175 | //the sound will be played twice per cycle of the animation 176 | //the rate will be set automatically based on frames and fps of animation 177 | let sound = new BABYLON.Sound( 178 | "footstep", 179 | "./sounds/footstep_carpet_000.ogg", 180 | scene, 181 | () => { 182 | cc.setSound(sound); 183 | }, 184 | { loop: false } 185 | ); 186 | 187 | //set how smmothly should we transition from one animation to another 188 | cc.enableBlending(0.05); 189 | 190 | //if somehting comes between camera and avatar move camera in front of the obstruction? 191 | cc.setCameraElasticity(true); 192 | //if something comes between camera and avatar make the obstruction invisible? 193 | cc.makeObstructionInvisible(false); 194 | 195 | cc.start(); 196 | 197 | engine.runRenderLoop(function () { 198 | scene.render(); 199 | }); 200 | }); 201 | } 202 | 203 | function createAGmap(allAGs) { 204 | //lets map ag groups to the character controller actions. 205 | let agMap = { 206 | idle: allAGs[0], 207 | strafeLeft: allAGs[3], 208 | strafeRight: allAGs[4], 209 | turnRight: allAGs[5], 210 | walk: allAGs[6], 211 | fall: allAGs[8], 212 | slideBack: allAGs[9], 213 | runJump: allAGs[10], 214 | turnLeft: allAGs[11], 215 | walkBack: allAGs[12], 216 | run: allAGs[13], 217 | idleJump: allAGs[14], 218 | }; 219 | 220 | return agMap; 221 | } 222 | 223 | function createGround(scene, groundMaterial) { 224 | BABYLON.MeshBuilder.CreateGroundFromHeightMap( 225 | "ground", 226 | "ground/ground_heightMap.png", 227 | { 228 | width: 128, 229 | height: 128, 230 | minHeight: 0, 231 | maxHeight: 10, 232 | subdivisions: 32, 233 | onReady: (grnd) => { 234 | grnd.material = groundMaterial; 235 | grnd.checkCollisions = true; 236 | grnd.isPickable = true; 237 | grnd.freezeWorldMatrix(); 238 | }, 239 | }, 240 | scene 241 | ); 242 | } 243 | 244 | function createGroundMaterial(scene) { 245 | let groundMaterial = new BABYLON.StandardMaterial("groundMat", scene); 246 | groundMaterial.diffuseTexture = new BABYLON.Texture("ground/ground.jpg", scene); 247 | groundMaterial.diffuseTexture.uScale = 4.0; 248 | groundMaterial.diffuseTexture.vScale = 4.0; 249 | 250 | groundMaterial.bumpTexture = new BABYLON.Texture("ground/ground-normal.png", scene); 251 | groundMaterial.bumpTexture.uScale = 12.0; 252 | groundMaterial.bumpTexture.vScale = 12.0; 253 | 254 | groundMaterial.diffuseColor = new BABYLON.Color3(0.9, 0.6, 0.4); 255 | groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0); 256 | return groundMaterial; 257 | } 258 | -------------------------------------------------------------------------------- /tst/testAnimationRange.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CharacterController Demo 7 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 |
66 | 67 |
68 | 69 |
70 |
71 |

Demo of CharacterController for Babylonjs

72 |

73 | For more information and source code head on over to
74 | https://ssatguru.github.io/BabylonJS-CharacterController/ 75 |

76 | 77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /tst/testAnimationRange.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | main(); 3 | }; 4 | 5 | let animPaused = false; 6 | let cc; 7 | let scene; 8 | let skeleton; 9 | 10 | function main() { 11 | let helpButton = document.getElementById("help"); 12 | let closeButton = document.getElementById("closehelp"); 13 | let pauseButton = document.getElementById("pause"); 14 | let el = document.getElementById("overlay"); 15 | 16 | let canvasElement = document.getElementById("renderCanvas"); 17 | 18 | helpButton.onclick = closeButton.onclick = () => { 19 | el.style.visibility = el.style.visibility == "visible" ? "hidden" : "visible"; 20 | }; 21 | 22 | pauseButton.onclick = () => { 23 | if (animPaused) { 24 | pauseButton.innerHTML = "Pause"; 25 | scene.stopAnimation(skeleton); 26 | cc.enableKeyBoard(true); 27 | cc.resumeAnim(); 28 | canvasElement.focus(); 29 | } else { 30 | cc.pauseAnim(); 31 | cc.enableKeyBoard(false); 32 | pauseButton.innerHTML = "Resume"; 33 | skeleton.beginAnimation("sit", false, 1); 34 | canvasElement.focus(); 35 | } 36 | animPaused = !animPaused; 37 | }; 38 | 39 | /* 40 | * The scene 41 | */ 42 | var canvas = document.querySelector("#renderCanvas"); 43 | var engine = new BABYLON.Engine(canvas, true); 44 | scene = new BABYLON.Scene(engine); 45 | 46 | scene.clearColor = new BABYLON.Color3(0.75, 0.75, 0.75); 47 | scene.ambientColor = new BABYLON.Color3(1, 1, 1); 48 | 49 | scene.debugLayer.show({ showExplorer: true, embedMode: true }); 50 | 51 | var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); 52 | light.intensity = 0.3; 53 | 54 | var light2 = new BABYLON.DirectionalLight("light2", new BABYLON.Vector3(-1, -1, -1), scene); 55 | light2.position = new BABYLON.Vector3(0, 128, 0); 56 | light2.intensity = 0.7; 57 | 58 | let groundMaterial = createGroundMaterial(scene); 59 | var ground = createGround(scene, groundMaterial); 60 | 61 | loadPlayer(scene, engine, canvas); 62 | 63 | //box to test view obstruction 64 | var box = BABYLON.Mesh.CreateBox("sight-obstructing-box", 2, scene); 65 | box.checkCollisions = true; 66 | // box.position = new BABYLON.Vector3(0, 8, 5); 67 | box.position = new BABYLON.Vector3(-0.26505887508392334, 5.579569149017334, 10.406900405883789);// (debugNode as BABYLON.Mesh) 68 | box.scaling = new BABYLON.Vector3(3.7386960983276367, 1, 3.796574831008911);// (debugNode as BABYLON.Mesh) 69 | 70 | 71 | var box2 = BABYLON.Mesh.CreateBox("unwalkable-steep-slope", 2, scene); 72 | box2.checkCollisions = true; 73 | box2.position = new BABYLON.Vector3(-6, 7, 14); 74 | box2.scaling = new BABYLON.Vector3(1, .1, 5); 75 | box2.rotation = new BABYLON.Vector3(-65 * Math.PI / 180, 0, 0); 76 | 77 | 78 | var box3 = BABYLON.Mesh.CreateBox("walkable-steep-slope", 2, scene); 79 | box3.checkCollisions = true; 80 | box3.position = new BABYLON.Vector3(6, 7, 14); 81 | box3.scaling = new BABYLON.Vector3(1, .1, 5); 82 | box3.rotation = new BABYLON.Vector3(-35 * Math.PI / 180, 0, 0); 83 | 84 | var box4 = BABYLON.Mesh.CreateBox("walkable-slope", 2, scene); 85 | box4.checkCollisions = true; 86 | box4.position = new BABYLON.Vector3(12, 7, 14); 87 | box4.scaling = new BABYLON.Vector3(1, .1, 5); 88 | box4.rotation = new BABYLON.Vector3(-25 * Math.PI / 180, 0, 0); 89 | 90 | var step = 0.4 91 | var box5 = BABYLON.Mesh.CreateBox("high-step1", 2, scene); 92 | box5.checkCollisions = true; 93 | box5.position = new BABYLON.Vector3(0, 6.0, 10.25); 94 | box5.scaling = new BABYLON.Vector3(1, 1, 2); 95 | 96 | var box6 = BABYLON.Mesh.CreateBox("high-step2", 2, scene); 97 | box6.checkCollisions = true; 98 | box6.position = new BABYLON.Vector3(0.5, 6.0+step, 10.25); 99 | box6.scaling = new BABYLON.Vector3(1, 1, 2); 100 | 101 | var box7 = BABYLON.Mesh.CreateBox("high-step3", 2, scene); 102 | box7.checkCollisions = true; 103 | box7.position = new BABYLON.Vector3(1, 6.0+2*step, 10.25); 104 | box7.scaling = new BABYLON.Vector3(1, 1, 2); 105 | 106 | var box8 = BABYLON.Mesh.CreateBox("high-step4", 2, scene); 107 | box8.checkCollisions = true; 108 | box8.position = new BABYLON.Vector3(1.5, 6.0+3*step, 10.25); 109 | box8.scaling = new BABYLON.Vector3(1, 1, 2); 110 | 111 | 112 | 113 | window.addEventListener("resize", function () { 114 | engine.resize(); 115 | }); 116 | } 117 | 118 | function loadPlayer(scene, engine, canvas) { 119 | BABYLON.SceneLoader.ImportMesh("", "player/", "Vincent-frontFacing.babylon", scene, (meshes, particleSystems, skeletons) => { 120 | var player = meshes[0]; 121 | skeleton = skeletons[0]; 122 | player.skeleton = skeleton; 123 | 124 | skeleton.enableBlending(0.1); 125 | 126 | //if the skeleton does not have any animation ranges then set them as below 127 | // setAnimationRanges(skeleton); 128 | 129 | var sm = player.material; 130 | if (sm.diffuseTexture != null) { 131 | sm.backFaceCulling = true; 132 | sm.ambientColor = new BABYLON.Color3(1, 1, 1); 133 | } 134 | 135 | //player position point is the feet 136 | // player.position = new BABYLON.Vector3(0, 12, 0); 137 | player.position = new BABYLON.Vector3(-6, 12, 11); 138 | player.checkCollisions = true; 139 | 140 | //player's ellipsoid should be the size of the player - thus around 1.75m tall 141 | player.ellipsoid = new BABYLON.Vector3(0.25, 0.875, 0.25); 142 | //the ellipsoidoffset positions the center of ellipsoid relative to the player position point 143 | //we want the top of ellipsoid to be at the head of the player 144 | //and the bottom of ellipsoid to be at the feet of the player 145 | player.ellipsoidOffset = new BABYLON.Vector3(0, 0.875, 0); 146 | 147 | //rotate the camera behind the player 148 | //player.rotation.y = Math.PI / 4; 149 | var alpha = -(Math.PI / 2 + player.rotation.y); 150 | var beta = Math.PI / 2.5; 151 | var target = new BABYLON.Vector3(player.position.x, player.position.y + 1.5, player.position.z); 152 | 153 | var camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", alpha, beta, 5, target, scene); 154 | 155 | //make sure the keyboard keys controlling camera are different from those controlling player 156 | //here we will not use any keyboard keys to control camera 157 | camera.keysLeft = []; 158 | camera.keysRight = []; 159 | camera.keysUp = []; 160 | camera.keysDown = []; 161 | 162 | // below are all standard camera settings. 163 | // nothing specific to character controller 164 | camera.wheelPrecision = 15; 165 | camera.checkCollisions = false; 166 | //how close can the camera come to player 167 | camera.lowerRadiusLimit = 2; 168 | //how far can the camera go from the player 169 | camera.upperRadiusLimit = 20; 170 | 171 | camera.attachControl(canvas, false); 172 | 173 | cc = new CharacterController(player, camera, scene); 174 | 175 | cc.setFaceForward(true); 176 | cc.setMode(0); 177 | 178 | //below makes the controller point the camera at the player head which is approx 179 | //1.5m above the player origin 180 | cc.setCameraTarget(new BABYLON.Vector3(0, 1.5, 0)); 181 | 182 | //if the camera comes close to the player we want to enter first person mode. 183 | cc.setNoFirstPerson(false); 184 | //the height of steps which the player can climb 185 | cc.setStepOffset(0.25); 186 | //cc.setStepOffset(0); 187 | //the minimum and maximum slope the player can go up 188 | //between the two the player will start sliding down if it stops 189 | cc.setSlopeLimit(30, 60); 190 | 191 | //tell controller 192 | // - which animation range should be used for which player animation 193 | // - rate at which to play that animation range 194 | // - wether the animation range should be looped 195 | //use this if name, rate or looping is different from default 196 | cc.setIdleAnim("idle", 1, true); 197 | cc.setTurnLeftAnim("turnLeft", 0.5, true); 198 | cc.setTurnRightAnim("turnRight", 0.5, true); 199 | cc.setWalkBackAnim("walkBack", 0.5, true); 200 | cc.setIdleJumpAnim("idleJump", 0.5, false); 201 | cc.setRunJumpAnim("runJump", 0.6, false); 202 | cc.setFallAnim("fall", 2, false); 203 | cc.setSlideBackAnim("slideBack", 1, false); 204 | cc.setTurningOff(true); 205 | //let's set footstep sound 206 | //this sound will be played for all actions except idle. 207 | //the sound will be played twice per cycle of the animation 208 | //the rate will be set automatically based on frames and fps of animation 209 | let sound = new BABYLON.Sound( 210 | "footstep", 211 | "./sounds/footstep_carpet_000.ogg", 212 | scene, 213 | () => { 214 | cc.setSound(sound); 215 | }, 216 | { loop: false } 217 | ); 218 | 219 | //set how smmothly should we transition from one animation to another 220 | cc.enableBlending(0.05); 221 | 222 | //if somehting comes between camera and avatar move camera in front of the obstruction? 223 | cc.setCameraElasticity(true); 224 | //if somehting comes between camera and avatar make the obstruction invisible? 225 | cc.makeObstructionInvisible(false); 226 | 227 | //show the player's ellipsoid for debugging 228 | cc.showEllipsoid(true); 229 | 230 | cc.start(); 231 | 232 | engine.runRenderLoop(function () { 233 | scene.render(); 234 | }); 235 | }); 236 | } 237 | 238 | //this is how you might set the animation ranges for a skeleton 239 | function setAnimationRanges(skel) { 240 | delAnimRanges(skel); 241 | 242 | skel.createAnimationRange("fall", 0, 16); 243 | skel.createAnimationRange("idle", 21, 65); 244 | skel.createAnimationRange("jump", 70, 94); 245 | skel.createAnimationRange("run", 100, 121); 246 | skel.createAnimationRange("slideBack", 125, 129); 247 | skel.createAnimationRange("strafeLeft", 135, 179); 248 | skel.createAnimationRange("strafeRight", 185, 229); 249 | skel.createAnimationRange("turnLeft", 240, 262); 250 | skel.createAnimationRange("turnRight", 270, 292); 251 | skel.createAnimationRange("walk", 300, 335); 252 | skel.createAnimationRange("walkBack", 340, 366); 253 | } 254 | /* 255 | * delete all existing ranges 256 | * @param {type} skel 257 | * @returns {undefined} 258 | */ 259 | function delAnimRanges(skel) { 260 | let ars = skel.getAnimationRanges(); 261 | let l = ars.length; 262 | for (let i = 0; i < l; i++) { 263 | let ar = ars[i]; 264 | console.log(ar.name + "," + ar.from + "," + ar.to); 265 | skel.deleteAnimationRange(ar.name, false); 266 | } 267 | } 268 | 269 | function createGround(scene, groundMaterial) { 270 | BABYLON.MeshBuilder.CreateGroundFromHeightMap( 271 | "ground", 272 | "ground/ground_heightMap.png", 273 | { 274 | width: 128, 275 | height: 128, 276 | minHeight: 0, 277 | maxHeight: 10, 278 | subdivisions: 32, 279 | onReady: (grnd) => { 280 | grnd.material = groundMaterial; 281 | grnd.checkCollisions = true; 282 | grnd.isPickable = true; 283 | grnd.freezeWorldMatrix(); 284 | }, 285 | }, 286 | scene 287 | ); 288 | } 289 | 290 | function createGroundMaterial(scene) { 291 | let groundMaterial = new BABYLON.StandardMaterial("groundMat", scene); 292 | groundMaterial.diffuseTexture = new BABYLON.Texture("ground/ground.jpg", scene); 293 | groundMaterial.diffuseTexture.uScale = 4.0; 294 | groundMaterial.diffuseTexture.vScale = 4.0; 295 | 296 | groundMaterial.bumpTexture = new BABYLON.Texture("ground/ground-normal.png", scene); 297 | groundMaterial.bumpTexture.uScale = 12.0; 298 | groundMaterial.bumpTexture.vScale = 12.0; 299 | 300 | groundMaterial.diffuseColor = new BABYLON.Color3(0.9, 0.6, 0.4); 301 | groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0); 302 | return groundMaterial; 303 | } 304 | -------------------------------------------------------------------------------- /tst/testCommandControl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CharacterController Demo 7 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | 66 |
  • 67 |
  • 68 |
  • 69 |
  • 70 |
  • 71 |
  • 72 |
  • 73 |
  • 74 |
  • 75 | top down 76 |
    77 | third person 78 | turning off 79 |
  • 80 | 81 |
  • enable keyboard
  • 82 |
  • 83 |
    84 |
    85 | 86 |
    87 |
    88 |

    Demo of CharacterController for Babylonjs

    89 |

    90 | For more information and source code, head on over to
    91 | https://ssatguru.github.io/BabylonJS-CharacterController/ 92 |

    93 | 94 |
    95 |
    96 | 97 | 98 | -------------------------------------------------------------------------------- /tst/testCommandControl.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | main(); 3 | }; 4 | 5 | var canvas; 6 | function main() { 7 | setControls(); 8 | /* 9 | * The scene 10 | */ 11 | canvas = document.querySelector("#renderCanvas"); 12 | var engine = new BABYLON.Engine(canvas, true); 13 | var scene = new BABYLON.Scene(engine); 14 | //scene.useRightHandedSystem = true; 15 | 16 | scene.clearColor = new BABYLON.Color3(0.75, 0.75, 0.75); 17 | scene.ambientColor = new BABYLON.Color3(1, 1, 1); 18 | 19 | scene.debugLayer.show({ showExplorer: true, embedMode: true }); 20 | 21 | var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); 22 | light.intensity = 0.3; 23 | 24 | var light2 = new BABYLON.DirectionalLight("light2", new BABYLON.Vector3(-1, -1, -1), scene); 25 | light2.position = new BABYLON.Vector3(0, 128, 0); 26 | light2.intensity = 0.7; 27 | 28 | var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene); 29 | 30 | //boxes to check camera elasticity and making obstructions invisibile checks 31 | 32 | var box = BABYLON.Mesh.CreateBox("box1", 2, scene); 33 | box.checkCollisions = false; 34 | box.position = new BABYLON.Vector3(0, 8, 5); 35 | box.material = myMaterial; 36 | box.isVisible = false; 37 | 38 | var box2 = BABYLON.Mesh.CreateBox("box2", 2, scene); 39 | box2.checkCollisions = true; 40 | box2.position = new BABYLON.Vector3(0, 8, 7); 41 | 42 | let box3 = box.createInstance("box3"); 43 | box3.position = new BABYLON.Vector3(0, 8, -7); 44 | box3.checkCollisions = true; 45 | 46 | //check for visibility 47 | let box4 = BABYLON.Mesh.CreateBox("box4", 2, scene); 48 | box4.position = new BABYLON.Vector3(5, 8, 5); 49 | box4.checkCollisions = false; 50 | box4.visibility = 0; 51 | 52 | let groundMaterial = createGroundMaterial(scene); 53 | var ground = createGround(scene, groundMaterial); 54 | loadPlayer(scene, engine, canvas); 55 | 56 | window.addEventListener("resize", function () { 57 | engine.resize(); 58 | }); 59 | } 60 | 61 | var cc; 62 | 63 | function loadPlayer(scene, engine, canvas) { 64 | //BABYLON.SceneLoader.ImportMesh("", "player/", "Vincent-frontFacing.babylon", scene, function (meshes, particleSystems, skeletons) { 65 | BABYLON.SceneLoader.ImportMesh("", "player/", "starterAvatars.babylon", scene, function (meshes, particleSystems, skeletons) { 66 | var player = meshes[0]; 67 | var skeleton = skeletons[0]; 68 | player.skeleton = skeleton; 69 | 70 | skeleton.enableBlending(0.1); 71 | //if the skeleton does not have any animation ranges then set them as below 72 | // setAnimationRanges(skeleton); 73 | 74 | var sm = player.material; 75 | if (sm.diffuseTexture != null) { 76 | sm.backFaceCulling = true; 77 | sm.ambientColor = new BABYLON.Color3(1, 1, 1); 78 | } 79 | 80 | player.position = new BABYLON.Vector3(0, 12, 0); 81 | player.checkCollisions = true; 82 | player.ellipsoid = new BABYLON.Vector3(0.5, 1, 0.5); 83 | player.ellipsoidOffset = new BABYLON.Vector3(0, 1, 0); 84 | 85 | //rotate the camera behind the player 86 | //player.rotation.y = Math.PI / 4; 87 | //var alpha = -(Math.PI / 2 + player.rotation.y); 88 | var alpha = 0; 89 | var beta = Math.PI / 2.5; 90 | var target = new BABYLON.Vector3(player.position.x, player.position.y + 1.5, player.position.z); 91 | 92 | var camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", alpha, beta, 5, target, scene); 93 | 94 | //standard camera setting 95 | camera.wheelPrecision = 15; 96 | camera.checkCollisions = true; 97 | //make sure the keyboard keys controlling camera are different from those controlling player 98 | //here we will not use any keyboard keys to control camera 99 | camera.keysLeft = []; 100 | camera.keysRight = []; 101 | camera.keysUp = []; 102 | camera.keysDown = []; 103 | //how close can the camera come to player 104 | camera.lowerRadiusLimit = 2; 105 | //how far can the camera go from the player 106 | camera.upperRadiusLimit = 200; 107 | 108 | camera.attachControl(); 109 | 110 | cc = new CharacterController(player, camera, scene); 111 | cc.setFaceForward(false); 112 | cc.setMode(0); 113 | cc.setTurnSpeed(45); 114 | //below makes the controller point the camera at the player head which is approx 115 | //1.5m above the player origin 116 | cc.setCameraTarget(new BABYLON.Vector3(0, 1.5, 0)); 117 | 118 | //if the camera comes close to the player we want to enter first person mode. 119 | cc.setNoFirstPerson(false); 120 | //the height of steps which the player can climb 121 | cc.setStepOffset(0.4); 122 | //the minimum and maximum slope the player can go up 123 | //between the two the player will start sliding down if it stops 124 | cc.setSlopeLimit(30, 60); 125 | 126 | //tell controller 127 | // - which animation range should be used for which player animation 128 | // - rate at which to play that animation range 129 | // - wether the animation range should be looped 130 | //use this if name, rate or looping is different from default 131 | cc.setIdleAnim("idle", 1, true); 132 | cc.setTurnLeftAnim("turnLeft", 0.5, true); 133 | cc.setTurnRightAnim("turnRight", 0.5, true); 134 | cc.setWalkBackAnim("walkBack", 0.5, true); 135 | cc.setIdleJumpAnim("idleJump", 0.5, false); 136 | cc.setRunJumpAnim("runJump", 0.6, false); 137 | cc.setFallAnim("fall", 2, false); 138 | cc.setSlideBackAnim("slideBack", 1, false); 139 | 140 | let walkSound = new BABYLON.Sound( 141 | "walk", 142 | "./sounds/footstep_carpet_000.ogg", 143 | scene, 144 | () => { 145 | cc.setSound(walkSound); 146 | }, 147 | { loop: false } 148 | ); 149 | 150 | var ua = window.navigator.userAgent; 151 | var isIE = /MSIE|Trident/.test(ua); 152 | if (isIE) { 153 | //IE specific code goes here 154 | cc.setJumpKey("spacebar"); 155 | } 156 | 157 | cc.setCameraElasticity(true); 158 | cc.makeObstructionInvisible(true); 159 | cc.start(); 160 | 161 | engine.runRenderLoop(function () { 162 | scene.render(); 163 | }); 164 | 165 | cmds = [cc.walk, cc.walkBack, cc.run, cc.jump, cc.turnLeft, cc.turnRight, cc.strafeLeft, cc.strafeRight]; 166 | showControls(); 167 | canvas.focus(); 168 | }); 169 | } 170 | 171 | //this is how you might set the animation ranges for a skeleton 172 | function setAnimationRanges(skel) { 173 | delAnimRanges(skel); 174 | 175 | skel.createAnimationRange("fall", 0, 16); 176 | skel.createAnimationRange("idle", 21, 65); 177 | skel.createAnimationRange("jump", 70, 94); 178 | skel.createAnimationRange("run", 100, 121); 179 | skel.createAnimationRange("slideBack", 125, 129); 180 | skel.createAnimationRange("strafeLeft", 135, 179); 181 | skel.createAnimationRange("strafeRight", 185, 229); 182 | skel.createAnimationRange("turnLeft", 240, 262); 183 | skel.createAnimationRange("turnRight", 270, 292); 184 | skel.createAnimationRange("walk", 300, 335); 185 | skel.createAnimationRange("walkBack", 340, 366); 186 | } 187 | /* 188 | * delete all existing ranges 189 | * @param {type} skel 190 | * @returns {undefined} 191 | */ 192 | function delAnimRanges(skel) { 193 | let ars = skel.getAnimationRanges(); 194 | let l = ars.length; 195 | for (let i = 0; i < l; i++) { 196 | let ar = ars[i]; 197 | console.log(ar.name + "," + ar.from + "," + ar.to); 198 | skel.deleteAnimationRange(ar.name, false); 199 | } 200 | } 201 | 202 | function createGround(scene, groundMaterial) { 203 | BABYLON.MeshBuilder.CreateGroundFromHeightMap( 204 | "ground", 205 | "ground/ground_heightMap.png", 206 | { 207 | width: 128, 208 | height: 128, 209 | minHeight: 0, 210 | maxHeight: 10, 211 | subdivisions: 32, 212 | onReady: function (grnd) { 213 | grnd.material = groundMaterial; 214 | grnd.checkCollisions = true; 215 | grnd.isPickable = true; 216 | grnd.freezeWorldMatrix(); 217 | }, 218 | }, 219 | scene 220 | ); 221 | } 222 | 223 | function createGroundMaterial(scene) { 224 | let groundMaterial = new BABYLON.StandardMaterial("groundMat", scene); 225 | groundMaterial.diffuseTexture = new BABYLON.Texture("ground/ground.jpg", scene); 226 | groundMaterial.diffuseTexture.uScale = 4.0; 227 | groundMaterial.diffuseTexture.vScale = 4.0; 228 | 229 | groundMaterial.bumpTexture = new BABYLON.Texture("ground/ground-normal.png", scene); 230 | groundMaterial.bumpTexture.uScale = 12.0; 231 | groundMaterial.bumpTexture.vScale = 12.0; 232 | 233 | groundMaterial.diffuseColor = new BABYLON.Color3(0.9, 0.6, 0.4); 234 | groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0); 235 | return groundMaterial; 236 | } 237 | 238 | var showHelp = function () { 239 | var el = document.getElementById("overlay"); 240 | el.style.visibility = el.style.visibility == "visible" ? "hidden" : "visible"; 241 | canvas.focus(); 242 | }; 243 | 244 | function showControls() { 245 | var el = document.getElementById("controls"); 246 | el.style.visibility = "visible"; 247 | } 248 | 249 | var w, 250 | wb, 251 | wbf, 252 | r, 253 | j, 254 | tl, 255 | tlf, 256 | tr, 257 | trf, 258 | sl, 259 | slf, 260 | sr, 261 | srf = false; 262 | 263 | function toggleClass(e) { 264 | e.target.classList.toggle("w3-pale-red"); 265 | e.target.classList.toggle("w3-pale-green"); 266 | canvas.focus(); 267 | } 268 | function setControls() { 269 | const x = document.getElementsByTagName("button"); 270 | 271 | for (i = 0; i < x.length; i++) { 272 | x[i].className = "w3-btn w3-border w3-round w3-pale-red"; 273 | } 274 | 275 | document.getElementById("pl").onclick = function (e) { 276 | canvas.requestPointerLock = canvas.requestPointerLock || canvas.msRequestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock || false; 277 | if (canvas.requestPointerLock) { 278 | canvas.requestPointerLock(); 279 | } 280 | canvas.focus(); 281 | }; 282 | 283 | document.getElementById("w").onclick = function (e) { 284 | cc.walk((w = !w)); 285 | toggleClass(e); 286 | }; 287 | document.getElementById("wb").onclick = function (e) { 288 | cc.walkBack((wb = !wb)); 289 | toggleClass(e); 290 | }; 291 | document.getElementById("wbf").onclick = function (e) { 292 | cc.walkBackFast((wbf = !wbf)); 293 | toggleClass(e); 294 | }; 295 | document.getElementById("r").onclick = function (e) { 296 | cc.run((r = !r)); 297 | toggleClass(e); 298 | }; 299 | document.getElementById("j").onclick = function (e) { 300 | cc.jump(); 301 | canvas.focus(); 302 | }; 303 | document.getElementById("tl").onclick = function (e) { 304 | cc.turnLeft((tl = !tl)); 305 | toggleClass(e); 306 | }; 307 | document.getElementById("tlf").onclick = function (e) { 308 | cc.turnLeftFast((tlf = !tlf)); 309 | toggleClass(e); 310 | }; 311 | document.getElementById("tr").onclick = function (e) { 312 | cc.turnRight((tr = !tr)); 313 | toggleClass(e); 314 | }; 315 | document.getElementById("trf").onclick = function (e) { 316 | cc.turnRightFast((trf = !trf)); 317 | toggleClass(e); 318 | }; 319 | document.getElementById("sl").onclick = function (e) { 320 | cc.strafeLeft((sl = !sl)); 321 | toggleClass(e); 322 | }; 323 | document.getElementById("slf").onclick = function (e) { 324 | cc.strafeLeftFast((slf = !slf)); 325 | toggleClass(e); 326 | }; 327 | document.getElementById("sr").onclick = function (e) { 328 | cc.strafeRight((sr = !sr)); 329 | toggleClass(e); 330 | }; 331 | document.getElementById("srf").onclick = function (e) { 332 | cc.strafeRightFast((srf = !srf)); 333 | toggleClass(e); 334 | }; 335 | 336 | document.getElementById("tp").onclick = function (e) { 337 | cc.setMode(0); 338 | canvas.focus(); 339 | }; 340 | document.getElementById("td").onclick = function (e) { 341 | cc.setMode(1); 342 | canvas.focus(); 343 | }; 344 | document.getElementById("toff").onclick = function (e) { 345 | cc.setTurningOff(e.target.checked); 346 | canvas.focus(); 347 | }; 348 | document.getElementById("kb").onclick = function (e) { 349 | cc.enableKeyBoard(e.target.checked); 350 | canvas.focus(); 351 | }; 352 | // document.getElementById("help").onclick = showHelp; 353 | document.getElementById("closehelp").onclick = showHelp; 354 | 355 | let animPaused = false; 356 | document.getElementById("help").onclick = (e) => { 357 | if (animPaused) { 358 | cc.resumeAnim(); 359 | } else { 360 | cc.pauseAnim(); 361 | } 362 | animPaused = !animPaused; 363 | }; 364 | } 365 | -------------------------------------------------------------------------------- /tst/testNPC.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CharacterController Demo 7 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 | 66 |
  • 67 | player 68 | npc 69 |
  • 70 |
  • 71 |
  • 72 |
  • 73 |
  • 74 |
  • 75 |
  • 76 |
  • 77 |
  • 78 |
  • 79 | top down 80 |
    81 | third person 82 | turning off 83 |
  • 84 |
  • enable keyboard
  • 85 |
  • 86 |
    87 |
    88 | 89 |
    90 |
    91 |

    Demo of CharacterController for Babylonjs

    92 |

    93 | For more information and source code, head on over to
    94 | https://ssatguru.github.io/BabylonJS-CharacterController/ 95 |

    96 | 97 |
    98 |
    99 | 100 | 101 | -------------------------------------------------------------------------------- /tst/testNPC.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | main(); 3 | }; 4 | 5 | var canvas; 6 | function main() { 7 | 8 | /* 9 | * The scene 10 | */ 11 | canvas = document.querySelector("#renderCanvas"); 12 | var engine = new BABYLON.Engine(canvas, true); 13 | var scene = new BABYLON.Scene(engine); 14 | //scene.useRightHandedSystem = true; 15 | 16 | scene.clearColor = new BABYLON.Color3(0.75, 0.75, 0.75); 17 | scene.ambientColor = new BABYLON.Color3(1, 1, 1); 18 | 19 | scene.debugLayer.show({ showExplorer: true, embedMode: true }); 20 | 21 | var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene); 22 | light.intensity = 1; 23 | 24 | // var light2 = new BABYLON.DirectionalLight("light2", new BABYLON.Vector3(-1, -1, -1), scene); 25 | // light2.position = new BABYLON.Vector3(0, 128, 0); 26 | // light2.intensity = 0.7; 27 | 28 | var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene); 29 | 30 | 31 | let groundMaterial = createGroundMaterial(scene); 32 | var ground = createGround(scene, groundMaterial); 33 | loadPlayer(scene, engine, canvas); 34 | 35 | window.addEventListener("resize", function () { 36 | engine.resize(); 37 | }); 38 | } 39 | 40 | var cc, cc1, cc2; 41 | 42 | function loadPlayer(scene, engine, canvas) { 43 | BABYLON.SceneLoader.ImportMesh("", "player/", "Vincent-frontFacing.babylon", scene, function (meshes, particleSystems, skeletons) { 44 | var player = meshes[0]; 45 | var skeleton = skeletons[0]; 46 | player.skeleton = skeleton; 47 | 48 | skeleton.enableBlending(0.1); 49 | //if the skeleton does not have any animation ranges then set them as below 50 | // setAnimationRanges(skeleton); 51 | 52 | var sm = player.material; 53 | if (sm.diffuseTexture != null) { 54 | sm.backFaceCulling = true; 55 | sm.ambientColor = new BABYLON.Color3(1, 1, 1); 56 | } 57 | 58 | player.position = new BABYLON.Vector3(0, 12, 0); 59 | player.checkCollisions = true; 60 | player.ellipsoid = new BABYLON.Vector3(0.5, 1, 0.5); 61 | player.ellipsoidOffset = new BABYLON.Vector3(0, 1, 0); 62 | 63 | //rotate the camera behind the player 64 | //player.rotation.y = Math.PI / 4; 65 | //var alpha = -(Math.PI / 2 + player.rotation.y); 66 | var alpha = 0; 67 | var beta = Math.PI / 2.5; 68 | var target = new BABYLON.Vector3(player.position.x, player.position.y + 1.5, player.position.z); 69 | 70 | var camera = new BABYLON.ArcRotateCamera("ArcRotateCamera", alpha, beta, 5, target, scene); 71 | 72 | //standard camera setting 73 | camera.wheelPrecision = 15; 74 | camera.checkCollisions = true; 75 | //make sure the keyboard keys controlling camera are different from those controlling player 76 | //here we will not use any keyboard keys to control camera 77 | camera.keysLeft = []; 78 | camera.keysRight = []; 79 | camera.keysUp = []; 80 | camera.keysDown = []; 81 | //how close can the camera come to player 82 | camera.lowerRadiusLimit = 2; 83 | //how far can the camera go from the player 84 | camera.upperRadiusLimit = 200; 85 | 86 | camera.attachControl(); 87 | 88 | cc1 = new CharacterController(player, camera, scene); 89 | cc1.setFaceForward(true); 90 | cc1.setMode(0); 91 | cc1.setTurnSpeed(45); 92 | //below makes the controller point the camera at the player head which is approx 93 | //1.5m above the player origin 94 | cc1.setCameraTarget(new BABYLON.Vector3(0, 1.5, 0)); 95 | 96 | //if the camera comes close to the player we want to enter first person mode. 97 | cc1.setNoFirstPerson(false); 98 | //the height of steps which the player can climb 99 | cc1.setStepOffset(0.4); 100 | //the minimum and maximum slope the player can go up 101 | //between the two the player will start sliding down if it stops 102 | cc1.setSlopeLimit(30, 60); 103 | 104 | //tell controller 105 | // - which animation range should be used for which player animation 106 | // - rate at which to play that animation range 107 | // - wether the animation range should be looped 108 | //use this if name, rate or looping is different from default 109 | cc1.setIdleAnim("idle", 1, true); 110 | cc1.setTurnLeftAnim("turnLeft", 0.5, true); 111 | cc1.setTurnRightAnim("turnRight", 0.5, true); 112 | cc1.setWalkBackAnim("walkBack", 0.5, true); 113 | cc1.setIdleJumpAnim("idleJump", 0.5, false); 114 | cc1.setRunJumpAnim("runJump", 0.6, false); 115 | cc1.setFallAnim("fall", 2, false); 116 | cc1.setSlideBackAnim("slideBack", 1, false); 117 | 118 | let walkSound = new BABYLON.Sound( 119 | "walk", 120 | "./sounds/footstep_carpet_000.ogg", 121 | scene, 122 | () => { 123 | cc1.setSound(walkSound); 124 | }, 125 | { loop: false } 126 | ); 127 | 128 | var ua = window.navigator.userAgent; 129 | var isIE = /MSIE|Trident/.test(ua); 130 | if (isIE) { 131 | //IE specific code goes here 132 | cc1.setJumpKey("spacebar"); 133 | } 134 | 135 | cc1.setCameraElasticity(true); 136 | cc1.makeObstructionInvisible(true); 137 | cc1.start(); 138 | 139 | engine.runRenderLoop(function () { 140 | scene.render(); 141 | }); 142 | 143 | cmds = [cc1.walk, cc1.walkBack, cc1.run, cc1.jump, cc1.turnLeft, cc1.turnRight, cc1.strafeLeft, cc1.strafeRight]; 144 | cc = cc1; 145 | 146 | setControls(); 147 | showControls(); 148 | canvas.focus(); 149 | 150 | loadNPC(scene, engine, canvas); 151 | }); 152 | } 153 | 154 | function loadNPC(scene, engine, canvas) { 155 | BABYLON.SceneLoader.ImportMesh("", "player/", "starterAvatars.babylon", scene, function (meshes, particleSystems, skeletons) { 156 | var player = meshes[0]; 157 | var skeleton = skeletons[0]; 158 | player.skeleton = skeleton; 159 | 160 | skeleton.enableBlending(0.1); 161 | //if the skeleton does not have any animation ranges then set them as below 162 | // setAnimationRanges(skeleton); 163 | 164 | var sm = player.material; 165 | if (sm.diffuseTexture != null) { 166 | sm.backFaceCulling = true; 167 | sm.ambientColor = new BABYLON.Color3(1, 1, 1); 168 | } 169 | 170 | 171 | player.position = new BABYLON.Vector3(1, 12, 1); 172 | player.checkCollisions = true; 173 | player.ellipsoid = new BABYLON.Vector3(0.5, 1, 0.5); 174 | player.ellipsoidOffset = new BABYLON.Vector3(0, 1, 0); 175 | 176 | 177 | cc2 = new CharacterController(player, null, scene); 178 | cc2.setFaceForward(false); 179 | cc2.setMode(0); 180 | cc2.setTurnSpeed(45); 181 | 182 | //the height of steps which the player can climb 183 | cc2.setStepOffset(0.4); 184 | //the minimum and maximum slope the player can go up 185 | //between the two the player will start sliding down if it stops 186 | cc2.setSlopeLimit(30, 60); 187 | 188 | //tell controller 189 | // - which animation range should be used for which player animation 190 | // - rate at which to play that animation range 191 | // - wether the animation range should be looped 192 | //use this if name, rate or looping is different from default 193 | cc2.setIdleAnim("idle", 1, true); 194 | cc2.setTurnLeftAnim("turnLeft", 0.5, true); 195 | cc2.setTurnRightAnim("turnRight", 0.5, true); 196 | cc2.setWalkBackAnim("walkBack", 0.5, true); 197 | cc2.setIdleJumpAnim("idleJump", 0.5, false); 198 | cc2.setRunJumpAnim("runJump", 0.6, false); 199 | cc2.setFallAnim("fall", 2, false); 200 | cc2.setSlideBackAnim("slideBack", 1, false); 201 | 202 | let walkSound = new BABYLON.Sound( 203 | "walk", 204 | "./sounds/footstep_carpet_000.ogg", 205 | scene, 206 | () => { 207 | cc2.setSound(walkSound); 208 | }, 209 | { loop: false } 210 | ); 211 | 212 | var ua = window.navigator.userAgent; 213 | var isIE = /MSIE|Trident/.test(ua); 214 | if (isIE) { 215 | //IE specific code goes here 216 | cc2.setJumpKey("spacebar"); 217 | } 218 | 219 | cc2.enableKeyBoard(false); 220 | cc2.start(); 221 | }); 222 | } 223 | 224 | //this is how you might set the animation ranges for a skeleton 225 | function setAnimationRanges(skel) { 226 | delAnimRanges(skel); 227 | 228 | skel.createAnimationRange("fall", 0, 16); 229 | skel.createAnimationRange("idle", 21, 65); 230 | skel.createAnimationRange("jump", 70, 94); 231 | skel.createAnimationRange("run", 100, 121); 232 | skel.createAnimationRange("slideBack", 125, 129); 233 | skel.createAnimationRange("strafeLeft", 135, 179); 234 | skel.createAnimationRange("strafeRight", 185, 229); 235 | skel.createAnimationRange("turnLeft", 240, 262); 236 | skel.createAnimationRange("turnRight", 270, 292); 237 | skel.createAnimationRange("walk", 300, 335); 238 | skel.createAnimationRange("walkBack", 340, 366); 239 | } 240 | /* 241 | * delete all existing ranges 242 | * @param {type} skel 243 | * @returns {undefined} 244 | */ 245 | function delAnimRanges(skel) { 246 | let ars = skel.getAnimationRanges(); 247 | let l = ars.length; 248 | for (let i = 0; i < l; i++) { 249 | let ar = ars[i]; 250 | console.log(ar.name + "," + ar.from + "," + ar.to); 251 | skel.deleteAnimationRange(ar.name, false); 252 | } 253 | } 254 | 255 | function createGround(scene, groundMaterial) { 256 | BABYLON.MeshBuilder.CreateGroundFromHeightMap( 257 | "ground", 258 | "ground/ground_heightMap.png", 259 | { 260 | width: 128, 261 | height: 128, 262 | minHeight: 0, 263 | maxHeight: 10, 264 | subdivisions: 32, 265 | onReady: function (grnd) { 266 | grnd.material = groundMaterial; 267 | grnd.checkCollisions = true; 268 | grnd.isPickable = true; 269 | grnd.freezeWorldMatrix(); 270 | }, 271 | }, 272 | scene 273 | ); 274 | } 275 | 276 | function createGroundMaterial(scene) { 277 | let groundMaterial = new BABYLON.StandardMaterial("groundMat", scene); 278 | groundMaterial.diffuseTexture = new BABYLON.Texture("ground/ground.jpg", scene); 279 | groundMaterial.diffuseTexture.uScale = 4.0; 280 | groundMaterial.diffuseTexture.vScale = 4.0; 281 | 282 | groundMaterial.bumpTexture = new BABYLON.Texture("ground/ground-normal.png", scene); 283 | groundMaterial.bumpTexture.uScale = 12.0; 284 | groundMaterial.bumpTexture.vScale = 12.0; 285 | 286 | groundMaterial.diffuseColor = new BABYLON.Color3(0.9, 0.6, 0.4); 287 | groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0); 288 | return groundMaterial; 289 | } 290 | 291 | var showHelp = function () { 292 | var el = document.getElementById("overlay"); 293 | el.style.visibility = el.style.visibility == "visible" ? "hidden" : "visible"; 294 | canvas.focus(); 295 | }; 296 | 297 | function showControls() { 298 | var el = document.getElementById("controls"); 299 | el.style.visibility = "visible"; 300 | } 301 | 302 | var w, 303 | wb, 304 | wbf, 305 | r, 306 | j, 307 | tl, 308 | tlf, 309 | tr, 310 | trf, 311 | sl, 312 | slf, 313 | sr, 314 | srf = false; 315 | 316 | function toggleClass(e) { 317 | e.target.classList.toggle("w3-pale-red"); 318 | e.target.classList.toggle("w3-pale-green"); 319 | canvas.focus(); 320 | } 321 | 322 | function setUIValues() { 323 | 324 | document.getElementById("tp").checked = cc.getMode() == 0 ? true : false; 325 | document.getElementById("td").checked = cc.getMode() == 1 ? true : false; 326 | document.getElementById("toff").checked = cc.isTurningOff(); 327 | document.getElementById("kb").checked = cc.isKeyBoardEnabled(); 328 | 329 | //for npc third person mode is always disabled. 330 | document.getElementById("tp").disabled = (cc == cc2); 331 | document.getElementById("toff").disabled = (cc == cc2); 332 | } 333 | 334 | 335 | 336 | 337 | function setControls() { 338 | const x = document.getElementsByTagName("button"); 339 | 340 | //init style 341 | for (i = 0; i < x.length; i++) { 342 | x[i].className = "w3-btn w3-border w3-round w3-pale-red"; 343 | } 344 | //init values 345 | setUIValues(); 346 | 347 | 348 | //click handlers 349 | document.getElementById("pl").onclick = function (e) { 350 | canvas.requestPointerLock = canvas.requestPointerLock || canvas.msRequestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock || false; 351 | if (canvas.requestPointerLock) { 352 | canvas.requestPointerLock(); 353 | } 354 | canvas.focus(); 355 | }; 356 | 357 | //click handlers 358 | document.getElementById("pc").onclick = function (e) { 359 | cc = cc1; 360 | setUIValues(); 361 | canvas.focus(); 362 | }; 363 | 364 | document.getElementById("npc").onclick = function (e) { 365 | cc = cc2; 366 | setUIValues(); 367 | canvas.focus(); 368 | }; 369 | 370 | document.getElementById("w").onclick = function (e) { 371 | cc.walk((w = !w)); 372 | toggleClass(e); 373 | }; 374 | document.getElementById("wb").onclick = function (e) { 375 | cc.walkBack((wb = !wb)); 376 | toggleClass(e); 377 | }; 378 | document.getElementById("wbf").onclick = function (e) { 379 | cc.walkBackFast((wbf = !wbf)); 380 | toggleClass(e); 381 | }; 382 | document.getElementById("r").onclick = function (e) { 383 | cc.run((r = !r)); 384 | toggleClass(e); 385 | }; 386 | document.getElementById("j").onclick = function (e) { 387 | cc.jump(); 388 | canvas.focus(); 389 | }; 390 | document.getElementById("tl").onclick = function (e) { 391 | cc.turnLeft((tl = !tl)); 392 | toggleClass(e); 393 | }; 394 | document.getElementById("tlf").onclick = function (e) { 395 | cc.turnLeftFast((tlf = !tlf)); 396 | toggleClass(e); 397 | }; 398 | document.getElementById("tr").onclick = function (e) { 399 | cc.turnRight((tr = !tr)); 400 | toggleClass(e); 401 | }; 402 | document.getElementById("trf").onclick = function (e) { 403 | cc.turnRightFast((trf = !trf)); 404 | toggleClass(e); 405 | }; 406 | document.getElementById("sl").onclick = function (e) { 407 | cc.strafeLeft((sl = !sl)); 408 | toggleClass(e); 409 | }; 410 | document.getElementById("slf").onclick = function (e) { 411 | cc.strafeLeftFast((slf = !slf)); 412 | toggleClass(e); 413 | }; 414 | document.getElementById("sr").onclick = function (e) { 415 | cc.strafeRight((sr = !sr)); 416 | toggleClass(e); 417 | }; 418 | document.getElementById("srf").onclick = function (e) { 419 | cc.strafeRightFast((srf = !srf)); 420 | toggleClass(e); 421 | }; 422 | 423 | document.getElementById("tp").onclick = function (e) { 424 | cc.setMode(0); 425 | canvas.focus(); 426 | }; 427 | document.getElementById("td").onclick = function (e) { 428 | cc.setMode(1); 429 | canvas.focus(); 430 | }; 431 | document.getElementById("toff").onclick = function (e) { 432 | cc.setTurningOff(e.target.checked); 433 | canvas.focus(); 434 | }; 435 | document.getElementById("kb").onclick = function (e) { 436 | cc.enableKeyBoard(e.target.checked); 437 | canvas.focus(); 438 | }; 439 | // document.getElementById("help").onclick = showHelp; 440 | document.getElementById("closehelp").onclick = showHelp; 441 | 442 | let animPaused = false; 443 | document.getElementById("help").onclick = (e) => { 444 | if (animPaused) { 445 | cc.resumeAnim(); 446 | } else { 447 | cc.pauseAnim(); 448 | } 449 | animPaused = !animPaused; 450 | }; 451 | } 452 | -------------------------------------------------------------------------------- /tst/w3.css: -------------------------------------------------------------------------------- 1 | /* W3.CSS 4.13 June 2019 by Jan Egil and Borge Refsnes */ 2 | html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit} 3 | /* Extract from normalize.css by Nicolas Gallagher and Jonathan Neal git.io/normalize */ 4 | html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0} 5 | article,aside,details,figcaption,figure,footer,header,main,menu,nav,section{display:block}summary{display:list-item} 6 | audio,canvas,progress,video{display:inline-block}progress{vertical-align:baseline} 7 | audio:not([controls]){display:none;height:0}[hidden],template{display:none} 8 | a{background-color:transparent}a:active,a:hover{outline-width:0} 9 | abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted} 10 | b,strong{font-weight:bolder}dfn{font-style:italic}mark{background:#ff0;color:#000} 11 | small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} 12 | sub{bottom:-0.25em}sup{top:-0.5em}figure{margin:1em 40px}img{border-style:none} 13 | code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}hr{box-sizing:content-box;height:0;overflow:visible} 14 | button,input,select,textarea,optgroup{font:inherit;margin:0}optgroup{font-weight:bold} 15 | button,input{overflow:visible}button,select{text-transform:none} 16 | button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button} 17 | button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0} 18 | button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText} 19 | fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:.35em .625em .75em} 20 | legend{color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto} 21 | [type=checkbox],[type=radio]{padding:0} 22 | [type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto} 23 | [type=search]{-webkit-appearance:textfield;outline-offset:-2px} 24 | [type=search]::-webkit-search-decoration{-webkit-appearance:none} 25 | ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit} 26 | /* End extract */ 27 | html,body{font-family:Verdana,sans-serif;font-size:15px;line-height:1.5}html{overflow-x:hidden} 28 | h1{font-size:36px}h2{font-size:30px}h3{font-size:24px}h4{font-size:20px}h5{font-size:18px}h6{font-size:16px}.w3-serif{font-family:serif} 29 | h1,h2,h3,h4,h5,h6{font-family:"Segoe UI",Arial,sans-serif;font-weight:400;margin:10px 0}.w3-wide{letter-spacing:4px} 30 | hr{border:0;border-top:1px solid #eee;margin:20px 0} 31 | .w3-image{max-width:100%;height:auto}img{vertical-align:middle}a{color:inherit} 32 | .w3-table,.w3-table-all{border-collapse:collapse;border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc} 33 | .w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1} 34 | .w3-table-all tr:nth-child(odd){background-color:#fff}.w3-table-all tr:nth-child(even){background-color:#f1f1f1} 35 | .w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center} 36 | .w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top} 37 | .w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px} 38 | .w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap} 39 | .w3-btn:hover{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)} 40 | .w3-btn,.w3-button{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} 41 | .w3-disabled,.w3-btn:disabled,.w3-button:disabled{cursor:not-allowed;opacity:0.3}.w3-disabled *,:disabled *{pointer-events:none} 42 | .w3-btn.w3-disabled:hover,.w3-btn:disabled:hover{box-shadow:none} 43 | .w3-badge,.w3-tag{background-color:#000;color:#fff;display:inline-block;padding-left:8px;padding-right:8px;text-align:center}.w3-badge{border-radius:50%} 44 | .w3-ul{list-style-type:none;padding:0;margin:0}.w3-ul li{padding:8px 16px;border-bottom:1px solid #ddd}.w3-ul li:last-child{border-bottom:none} 45 | .w3-tooltip,.w3-display-container{position:relative}.w3-tooltip .w3-text{display:none}.w3-tooltip:hover .w3-text{display:inline-block} 46 | .w3-ripple:active{opacity:0.5}.w3-ripple{transition:opacity 0s} 47 | .w3-input{padding:8px;display:block;border:none;border-bottom:1px solid #ccc;width:100%} 48 | .w3-select{padding:9px 0;width:100%;border:none;border-bottom:1px solid #ccc} 49 | .w3-dropdown-click,.w3-dropdown-hover{position:relative;display:inline-block;cursor:pointer} 50 | .w3-dropdown-hover:hover .w3-dropdown-content{display:block} 51 | .w3-dropdown-hover:first-child,.w3-dropdown-click:hover{background-color:#ccc;color:#000} 52 | .w3-dropdown-hover:hover > .w3-button:first-child,.w3-dropdown-click:hover > .w3-button:first-child{background-color:#ccc;color:#000} 53 | .w3-dropdown-content{cursor:auto;color:#000;background-color:#fff;display:none;position:absolute;min-width:160px;margin:0;padding:0;z-index:1} 54 | .w3-check,.w3-radio{width:24px;height:24px;position:relative;top:6px} 55 | .w3-sidebar{height:100%;width:200px;background-color:#fff;position:fixed!important;z-index:1;overflow:auto} 56 | .w3-bar-block .w3-dropdown-hover,.w3-bar-block .w3-dropdown-click{width:100%} 57 | .w3-bar-block .w3-dropdown-hover .w3-dropdown-content,.w3-bar-block .w3-dropdown-click .w3-dropdown-content{min-width:100%} 58 | .w3-bar-block .w3-dropdown-hover .w3-button,.w3-bar-block .w3-dropdown-click .w3-button{width:100%;text-align:left;padding:8px 16px} 59 | .w3-main,#main{transition:margin-left .4s} 60 | .w3-modal{z-index:3;display:none;padding-top:100px;position:fixed;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgb(0,0,0);background-color:rgba(0,0,0,0.4)} 61 | .w3-modal-content{margin:auto;background-color:#fff;position:relative;padding:0;outline:0;width:600px} 62 | .w3-bar{width:100%;overflow:hidden}.w3-center .w3-bar{display:inline-block;width:auto} 63 | .w3-bar .w3-bar-item{padding:8px 16px;float:left;width:auto;border:none;display:block;outline:0} 64 | .w3-bar .w3-dropdown-hover,.w3-bar .w3-dropdown-click{position:static;float:left} 65 | .w3-bar .w3-button{white-space:normal} 66 | .w3-bar-block .w3-bar-item{width:100%;display:block;padding:8px 16px;text-align:left;border:none;white-space:normal;float:none;outline:0} 67 | .w3-bar-block.w3-center .w3-bar-item{text-align:center}.w3-block{display:block;width:100%} 68 | .w3-responsive{display:block;overflow-x:auto} 69 | .w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before, 70 | .w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after{content:"";display:table;clear:both} 71 | .w3-col,.w3-half,.w3-third,.w3-twothird,.w3-threequarter,.w3-quarter{float:left;width:100%} 72 | .w3-col.s1{width:8.33333%}.w3-col.s2{width:16.66666%}.w3-col.s3{width:24.99999%}.w3-col.s4{width:33.33333%} 73 | .w3-col.s5{width:41.66666%}.w3-col.s6{width:49.99999%}.w3-col.s7{width:58.33333%}.w3-col.s8{width:66.66666%} 74 | .w3-col.s9{width:74.99999%}.w3-col.s10{width:83.33333%}.w3-col.s11{width:91.66666%}.w3-col.s12{width:99.99999%} 75 | @media (min-width:601px){.w3-col.m1{width:8.33333%}.w3-col.m2{width:16.66666%}.w3-col.m3,.w3-quarter{width:24.99999%}.w3-col.m4,.w3-third{width:33.33333%} 76 | .w3-col.m5{width:41.66666%}.w3-col.m6,.w3-half{width:49.99999%}.w3-col.m7{width:58.33333%}.w3-col.m8,.w3-twothird{width:66.66666%} 77 | .w3-col.m9,.w3-threequarter{width:74.99999%}.w3-col.m10{width:83.33333%}.w3-col.m11{width:91.66666%}.w3-col.m12{width:99.99999%}} 78 | @media (min-width:993px){.w3-col.l1{width:8.33333%}.w3-col.l2{width:16.66666%}.w3-col.l3{width:24.99999%}.w3-col.l4{width:33.33333%} 79 | .w3-col.l5{width:41.66666%}.w3-col.l6{width:49.99999%}.w3-col.l7{width:58.33333%}.w3-col.l8{width:66.66666%} 80 | .w3-col.l9{width:74.99999%}.w3-col.l10{width:83.33333%}.w3-col.l11{width:91.66666%}.w3-col.l12{width:99.99999%}} 81 | .w3-rest{overflow:hidden}.w3-stretch{margin-left:-16px;margin-right:-16px} 82 | .w3-content,.w3-auto{margin-left:auto;margin-right:auto}.w3-content{max-width:980px}.w3-auto{max-width:1140px} 83 | .w3-cell-row{display:table;width:100%}.w3-cell{display:table-cell} 84 | .w3-cell-top{vertical-align:top}.w3-cell-middle{vertical-align:middle}.w3-cell-bottom{vertical-align:bottom} 85 | .w3-hide{display:none!important}.w3-show-block,.w3-show{display:block!important}.w3-show-inline-block{display:inline-block!important} 86 | @media (max-width:1205px){.w3-auto{max-width:95%}} 87 | @media (max-width:600px){.w3-modal-content{margin:0 10px;width:auto!important}.w3-modal{padding-top:30px} 88 | .w3-dropdown-hover.w3-mobile .w3-dropdown-content,.w3-dropdown-click.w3-mobile .w3-dropdown-content{position:relative} 89 | .w3-hide-small{display:none!important}.w3-mobile{display:block;width:100%!important}.w3-bar-item.w3-mobile,.w3-dropdown-hover.w3-mobile,.w3-dropdown-click.w3-mobile{text-align:center} 90 | .w3-dropdown-hover.w3-mobile,.w3-dropdown-hover.w3-mobile .w3-btn,.w3-dropdown-hover.w3-mobile .w3-button,.w3-dropdown-click.w3-mobile,.w3-dropdown-click.w3-mobile .w3-btn,.w3-dropdown-click.w3-mobile .w3-button{width:100%}} 91 | @media (max-width:768px){.w3-modal-content{width:500px}.w3-modal{padding-top:50px}} 92 | @media (min-width:993px){.w3-modal-content{width:900px}.w3-hide-large{display:none!important}.w3-sidebar.w3-collapse{display:block!important}} 93 | @media (max-width:992px) and (min-width:601px){.w3-hide-medium{display:none!important}} 94 | @media (max-width:992px){.w3-sidebar.w3-collapse{display:none}.w3-main{margin-left:0!important;margin-right:0!important}.w3-auto{max-width:100%}} 95 | .w3-top,.w3-bottom{position:fixed;width:100%;z-index:1}.w3-top{top:0}.w3-bottom{bottom:0} 96 | .w3-overlay{position:fixed;display:none;width:100%;height:100%;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,0.5);z-index:2} 97 | .w3-display-topleft{position:absolute;left:0;top:0}.w3-display-topright{position:absolute;right:0;top:0} 98 | .w3-display-bottomleft{position:absolute;left:0;bottom:0}.w3-display-bottomright{position:absolute;right:0;bottom:0} 99 | .w3-display-middle{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%)} 100 | .w3-display-left{position:absolute;top:50%;left:0%;transform:translate(0%,-50%);-ms-transform:translate(-0%,-50%)} 101 | .w3-display-right{position:absolute;top:50%;right:0%;transform:translate(0%,-50%);-ms-transform:translate(0%,-50%)} 102 | .w3-display-topmiddle{position:absolute;left:50%;top:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} 103 | .w3-display-bottommiddle{position:absolute;left:50%;bottom:0;transform:translate(-50%,0%);-ms-transform:translate(-50%,0%)} 104 | .w3-display-container:hover .w3-display-hover{display:block}.w3-display-container:hover span.w3-display-hover{display:inline-block}.w3-display-hover{display:none} 105 | .w3-display-position{position:absolute} 106 | .w3-circle{border-radius:50%} 107 | .w3-round-small{border-radius:2px}.w3-round,.w3-round-medium{border-radius:4px}.w3-round-large{border-radius:8px}.w3-round-xlarge{border-radius:16px}.w3-round-xxlarge{border-radius:32px} 108 | .w3-row-padding,.w3-row-padding>.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} 109 | .w3-container,.w3-panel{padding:0.01em 16px}.w3-panel{margin-top:16px;margin-bottom:16px} 110 | .w3-code,.w3-codespan{font-family:Consolas,"courier new";font-size:16px} 111 | .w3-code{width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #4CAF50;word-wrap:break-word} 112 | .w3-codespan{color:crimson;background-color:#f1f1f1;padding-left:4px;padding-right:4px;font-size:110%} 113 | .w3-card,.w3-card-2{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)} 114 | .w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19)} 115 | .w3-spin{animation:w3-spin 2s infinite linear}@keyframes w3-spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}} 116 | .w3-animate-fading{animation:fading 10s infinite}@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} 117 | .w3-animate-opacity{animation:opac 0.8s}@keyframes opac{from{opacity:0} to{opacity:1}} 118 | .w3-animate-top{position:relative;animation:animatetop 0.4s}@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} 119 | .w3-animate-left{position:relative;animation:animateleft 0.4s}@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} 120 | .w3-animate-right{position:relative;animation:animateright 0.4s}@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} 121 | .w3-animate-bottom{position:relative;animation:animatebottom 0.4s}@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} 122 | .w3-animate-zoom {animation:animatezoom 0.6s}@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} 123 | .w3-animate-input{transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} 124 | .w3-opacity,.w3-hover-opacity:hover{opacity:0.60}.w3-opacity-off,.w3-hover-opacity-off:hover{opacity:1} 125 | .w3-opacity-max{opacity:0.25}.w3-opacity-min{opacity:0.75} 126 | .w3-greyscale-max,.w3-grayscale-max,.w3-hover-greyscale:hover,.w3-hover-grayscale:hover{filter:grayscale(100%)} 127 | .w3-greyscale,.w3-grayscale{filter:grayscale(75%)}.w3-greyscale-min,.w3-grayscale-min{filter:grayscale(50%)} 128 | .w3-sepia{filter:sepia(75%)}.w3-sepia-max,.w3-hover-sepia:hover{filter:sepia(100%)}.w3-sepia-min{filter:sepia(50%)} 129 | .w3-tiny{font-size:10px!important}.w3-small{font-size:12px!important}.w3-medium{font-size:15px!important}.w3-large{font-size:18px!important} 130 | .w3-xlarge{font-size:24px!important}.w3-xxlarge{font-size:36px!important}.w3-xxxlarge{font-size:48px!important}.w3-jumbo{font-size:64px!important} 131 | .w3-left-align{text-align:left!important}.w3-right-align{text-align:right!important}.w3-justify{text-align:justify!important}.w3-center{text-align:center!important} 132 | .w3-border-0{border:0!important}.w3-border{border:1px solid #ccc!important} 133 | .w3-border-top{border-top:1px solid #ccc!important}.w3-border-bottom{border-bottom:1px solid #ccc!important} 134 | .w3-border-left{border-left:1px solid #ccc!important}.w3-border-right{border-right:1px solid #ccc!important} 135 | .w3-topbar{border-top:6px solid #ccc!important}.w3-bottombar{border-bottom:6px solid #ccc!important} 136 | .w3-leftbar{border-left:6px solid #ccc!important}.w3-rightbar{border-right:6px solid #ccc!important} 137 | .w3-section,.w3-code{margin-top:16px!important;margin-bottom:16px!important} 138 | .w3-margin{margin:16px!important}.w3-margin-top{margin-top:16px!important}.w3-margin-bottom{margin-bottom:16px!important} 139 | .w3-margin-left{margin-left:16px!important}.w3-margin-right{margin-right:16px!important} 140 | .w3-padding-small{padding:4px 8px!important}.w3-padding{padding:8px 16px!important}.w3-padding-large{padding:12px 24px!important} 141 | .w3-padding-16{padding-top:16px!important;padding-bottom:16px!important}.w3-padding-24{padding-top:24px!important;padding-bottom:24px!important} 142 | .w3-padding-32{padding-top:32px!important;padding-bottom:32px!important}.w3-padding-48{padding-top:48px!important;padding-bottom:48px!important} 143 | .w3-padding-64{padding-top:64px!important;padding-bottom:64px!important} 144 | .w3-left{float:left!important}.w3-right{float:right!important} 145 | .w3-button:hover{color:#000!important;background-color:#ccc!important} 146 | .w3-transparent,.w3-hover-none:hover{background-color:transparent!important} 147 | .w3-hover-none:hover{box-shadow:none!important} 148 | /* Colors */ 149 | .w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} 150 | .w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} 151 | .w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} 152 | .w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} 153 | .w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} 154 | .w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} 155 | .w3-blue-grey,.w3-hover-blue-grey:hover,.w3-blue-gray,.w3-hover-blue-gray:hover{color:#fff!important;background-color:#607d8b!important} 156 | .w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} 157 | .w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} 158 | .w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} 159 | .w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} 160 | .w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} 161 | .w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} 162 | .w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} 163 | .w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} 164 | .w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} 165 | .w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} 166 | .w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} 167 | .w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} 168 | .w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} 169 | .w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} 170 | .w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} 171 | .w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} 172 | .w3-grey,.w3-hover-grey:hover,.w3-gray,.w3-hover-gray:hover{color:#000!important;background-color:#9e9e9e!important} 173 | .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important} 174 | .w3-dark-grey,.w3-hover-dark-grey:hover,.w3-dark-gray,.w3-hover-dark-gray:hover{color:#fff!important;background-color:#616161!important} 175 | .w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffdddd!important} 176 | .w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#ddffdd!important} 177 | .w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffcc!important} 178 | .w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#ddffff!important} 179 | .w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} 180 | .w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} 181 | .w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} 182 | .w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} 183 | .w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} 184 | .w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} 185 | .w3-text-blue-grey,.w3-hover-text-blue-grey:hover,.w3-text-blue-gray,.w3-hover-text-blue-gray:hover{color:#607d8b!important} 186 | .w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} 187 | .w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} 188 | .w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} 189 | .w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} 190 | .w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} 191 | .w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} 192 | .w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} 193 | .w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} 194 | .w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} 195 | .w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} 196 | .w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} 197 | .w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} 198 | .w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} 199 | .w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} 200 | .w3-text-white,.w3-hover-text-white:hover{color:#fff!important} 201 | .w3-text-black,.w3-hover-text-black:hover{color:#000!important} 202 | .w3-text-grey,.w3-hover-text-grey:hover,.w3-text-gray,.w3-hover-text-gray:hover{color:#757575!important} 203 | .w3-text-light-grey,.w3-hover-text-light-grey:hover,.w3-text-light-gray,.w3-hover-text-light-gray:hover{color:#f1f1f1!important} 204 | .w3-text-dark-grey,.w3-hover-text-dark-grey:hover,.w3-text-dark-gray,.w3-hover-text-dark-gray:hover{color:#3a3a3a!important} 205 | .w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} 206 | .w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} 207 | .w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} 208 | .w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} 209 | .w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} 210 | .w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} 211 | .w3-border-blue-grey,.w3-hover-border-blue-grey:hover,.w3-border-blue-gray,.w3-hover-border-blue-gray:hover{border-color:#607d8b!important} 212 | .w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} 213 | .w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} 214 | .w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} 215 | .w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} 216 | .w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} 217 | .w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} 218 | .w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} 219 | .w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} 220 | .w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} 221 | .w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} 222 | .w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} 223 | .w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} 224 | .w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} 225 | .w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} 226 | .w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} 227 | .w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} 228 | .w3-border-grey,.w3-hover-border-grey:hover,.w3-border-gray,.w3-hover-border-gray:hover{border-color:#9e9e9e!important} 229 | .w3-border-light-grey,.w3-hover-border-light-grey:hover,.w3-border-light-gray,.w3-hover-border-light-gray:hover{border-color:#f1f1f1!important} 230 | .w3-border-dark-grey,.w3-hover-border-dark-grey:hover,.w3-border-dark-gray,.w3-hover-border-dark-gray:hover{border-color:#616161!important} 231 | .w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} 232 | .w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffcc!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | const TerserPlugin = require("terser-webpack-plugin"); 3 | var webpack = require("webpack"); 4 | 5 | module.exports = (env, argv) => { 6 | return { 7 | mode: "development", 8 | entry: "./src/CharacterController.ts", 9 | devtool: "source-map", 10 | devServer: { 11 | devMiddleware: { 12 | publicPath: "/dist/", 13 | }, 14 | static: { 15 | directory: "./", 16 | serveIndex: true, 17 | }, 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.tsx?$/, 23 | use: "ts-loader", 24 | exclude: /node_modules/, 25 | }, 26 | ], 27 | }, 28 | resolve: { 29 | extensions: [".tsx", ".ts", ".js"], 30 | }, 31 | output: { 32 | path: path.resolve(__dirname, "dist"), 33 | filename: argv.mode === "production" ? "CharacterController.js" : "CharacterController.max.js", 34 | libraryTarget: "umd", 35 | }, 36 | externals: { 37 | babylonjs: { 38 | commonjs: "babylonjs", 39 | commonjs2: "babylonjs", 40 | amd: "babylonjs", 41 | root: "BABYLON", 42 | }, 43 | }, 44 | optimization: { 45 | minimizer: [ 46 | new TerserPlugin({ 47 | parallel: true, 48 | terserOptions: { 49 | // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions 50 | ecma: undefined, 51 | mangle: { 52 | // mangle options 53 | properties: { 54 | // mangle property options 55 | //mangle all variables starting with underscore "_" 56 | regex: /^_/, 57 | }, 58 | }, 59 | }, 60 | }), 61 | ], 62 | }, 63 | }; 64 | }; 65 | --------------------------------------------------------------------------------