├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── examples ├── .htaccess ├── css │ ├── app.css │ ├── bootstrap.min.css │ └── styles.css ├── index.html └── js │ ├── creator.js │ ├── fsm │ ├── index.html │ ├── index.js │ ├── model │ │ └── yuka.glb │ └── src │ │ ├── Girl.js │ │ └── States.js │ ├── fuzzy │ ├── index.html │ ├── index.js │ ├── model │ │ ├── README.md │ │ ├── assaultRifle.glb │ │ ├── soldier.glb │ │ └── zombie.glb │ └── src │ │ └── Soldier.js │ ├── goal │ ├── index.html │ ├── index.js │ ├── model │ │ ├── yan-ots-UuBR5kbvt4Y-unsplash (1).jpg │ │ └── yuka.glb │ └── src │ │ ├── Collectible.js │ │ ├── Evaluators.js │ │ ├── Girl.js │ │ └── Goals.js │ ├── graph │ ├── GraphHelper.js │ ├── corridor │ │ ├── index.html │ │ └── index.js │ └── tictactoe │ │ ├── index.html │ │ ├── index.js │ │ ├── scene.js │ │ └── src │ │ ├── TTTEdge.js │ │ ├── TTTGraph.js │ │ └── TTTNode.js │ ├── math │ └── orientation │ │ ├── index.html │ │ └── index.js │ ├── misc │ ├── savegame │ │ ├── index.html │ │ ├── index.js │ │ └── src │ │ │ ├── CustomEntity.js │ │ │ └── CustomVehicle.js │ └── trigger │ │ ├── index.html │ │ ├── index.js │ │ └── src │ │ └── CustomTrigger.js │ ├── navigation │ ├── common │ │ ├── CellSpacePartitioningHelper.js │ │ ├── NavMeshHelper.js │ │ └── navmeshes │ │ │ ├── basic │ │ │ ├── buffer_navmesh.bin │ │ │ └── navmesh.gltf │ │ │ └── complex │ │ │ └── navmesh.glb │ ├── firstperson │ │ ├── audio │ │ │ ├── step1.ogg │ │ │ └── step2.ogg │ │ ├── index.html │ │ ├── index.js │ │ ├── model │ │ │ ├── README.md │ │ │ └── house.glb │ │ ├── navmesh │ │ │ └── navmesh.glb │ │ └── src │ │ │ ├── FirstPersonControls.js │ │ │ └── Player.js │ ├── navmesh │ │ ├── index.html │ │ └── index.js │ └── navmeshPerformance │ │ ├── index.html │ │ ├── index.js │ │ ├── model │ │ ├── README.md │ │ └── level.glb │ │ └── src │ │ ├── CustomVehicle.js │ │ ├── PathPlanner.js │ │ └── PathPlannerTask.js │ ├── perception │ ├── common │ │ ├── Obstacle.js │ │ └── VisionHelper.js │ ├── lineOfSight │ │ ├── index.html │ │ └── index.js │ └── memorySystem │ │ ├── index.html │ │ ├── index.js │ │ └── src │ │ └── CustomEntity.js │ ├── playground │ ├── hideAndSeek │ │ ├── audio │ │ │ ├── dead.ogg │ │ │ ├── empty.ogg │ │ │ ├── impact1.ogg │ │ │ ├── impact2.ogg │ │ │ ├── impact3.ogg │ │ │ ├── impact4.ogg │ │ │ ├── impact5.ogg │ │ │ ├── reload.ogg │ │ │ ├── shot.ogg │ │ │ ├── shot_reload.ogg │ │ │ ├── step1.ogg │ │ │ └── step2.ogg │ │ ├── index.html │ │ ├── index.js │ │ ├── model │ │ │ ├── README.md │ │ │ ├── bulletHole.png │ │ │ ├── muzzle.png │ │ │ ├── shotgun-new.glb │ │ │ └── shotgun.glb │ │ ├── src │ │ │ ├── AssetManager.js │ │ │ ├── Bullet.js │ │ │ ├── CustomObstacle.js │ │ │ ├── Enemy.js │ │ │ ├── FirstPersonControls.js │ │ │ ├── Ground.js │ │ │ ├── HideBehavior.js │ │ │ ├── Player.js │ │ │ ├── Shotgun.js │ │ │ └── World.js │ │ └── textures │ │ │ └── sparkStretched.png │ └── shooter │ │ ├── audio │ │ ├── empty.ogg │ │ ├── impact1.ogg │ │ ├── impact2.ogg │ │ ├── impact3.ogg │ │ ├── impact4.ogg │ │ ├── impact5.ogg │ │ ├── reload.ogg │ │ ├── shot.ogg │ │ ├── step1.ogg │ │ └── step2.ogg │ │ ├── index.html │ │ ├── index.js │ │ ├── model │ │ ├── README.md │ │ ├── bulletHole.png │ │ ├── gun.glb │ │ ├── muzzle.png │ │ └── target.glb │ │ └── src │ │ ├── AssetManager.js │ │ ├── Blaster.js │ │ ├── Bullet.js │ │ ├── FirstPersonControls.js │ │ ├── Ground.js │ │ ├── Player.js │ │ ├── Target.js │ │ └── World.js │ ├── showcases │ └── kickoff │ │ ├── assets │ │ ├── ball.glb │ │ └── goal.glb │ │ ├── index.html │ │ ├── index.js │ │ ├── src │ │ ├── core │ │ │ ├── AssetManager.js │ │ │ ├── Constants.js │ │ │ └── World.js │ │ ├── entities │ │ │ ├── Ball.js │ │ │ ├── FieldPlayer.js │ │ │ ├── Goal.js │ │ │ ├── Goalkeeper.js │ │ │ ├── Pitch.js │ │ │ ├── Player.js │ │ │ └── Team.js │ │ ├── etc │ │ │ ├── Region.js │ │ │ └── SupportSpotCalculator.js │ │ └── states │ │ │ ├── FieldPlayerStates.js │ │ │ ├── GoalkeeperStates.js │ │ │ └── TeamStates.js │ │ └── textures │ │ └── pitch_texture.jpg │ ├── spacecarrier │ ├── index.html │ ├── index.js │ └── src │ │ ├── carrier.js │ │ ├── states.js │ │ └── textures_flare.png │ ├── steering │ ├── arrive │ │ ├── index.html │ │ └── index.js │ ├── flee │ │ ├── index.html │ │ └── index.js │ ├── flocking │ │ ├── index.html │ │ └── index.js │ ├── followPath │ │ ├── index.html │ │ └── index.js │ ├── interpose │ │ ├── index.html │ │ └── index.js │ ├── obstacleAvoidance │ │ ├── index.html │ │ └── index.js │ ├── offsetPursuit │ │ ├── index.html │ │ └── index.js │ ├── pursuit │ │ ├── index.html │ │ └── index.js │ ├── seek │ │ ├── index.html │ │ └── index.js │ └── wander │ │ ├── index.html │ │ └── index.js │ └── templates │ ├── index.html │ └── index.js ├── lib ├── yuka.js ├── yuka.min.js └── yuka.module.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/.prettierignore -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 eldinor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yuka-babylonjs-examples 2 | 3 | ### Yuka Game AI + 3D rendering with Babylon.js. 4 | 5 | #### Live examples at https://yuka.babylonpress.org/examples/ 6 | 7 | Yuka library source: https://github.com/Mugen87/yuka 8 | 9 | Babylon.js 3D engine source: https://github.com/BabylonJS/Babylon.js 10 | 11 | # Installation 12 | 13 | ## VS Code extensions to install 14 | 15 | https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode 16 | 17 | https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer 18 | 19 | ## How to run the examples 20 | 21 | Open the `index.html` file located in `examples/js/example_dir` with LiveServer in VS Code. 22 | 23 | ## Best practices 24 | 25 | 1. try to avoid parented `TransformNodes` with YUKA. YUKA will place your object in world space.Use YUKA's parenting instead. 26 | 2. you **must** scale, rotate and position your mesh before registering it as a YUKA `renderComponent` and bake the transformations into the vertices and freeze the world matrix of your mesh before doing so. 27 | 3. you **must** register your Mesh/TransformNode/Camera on the YUKA entity by setting it as a `renderComponent` and pass the `syncFunction` which will take care of syncing your BabylonJS object's position/rotation/scaling into with the YUKA world's position. 28 | 29 | ``` 30 | const entity = new YUKA.GameEntity() 31 | entity.setRenderComponent(mesh, syncFunction) 32 | ``` 33 | 34 | 4. `syncFunctions`: 35 | For syncing a `TransformNode` with the YUKA entity use this method: 36 | 37 | ``` 38 | private _sync(entity, renderComponent) { 39 | Matrix.FromValues(...entity.worldMatrix.elements).decomposeToTransformNode(renderComponent) 40 | } 41 | ``` 42 | 43 | If it doesn't work for you try this one: 44 | 45 | ``` 46 | renderComponent.getWorldMatrix().copyFrom(BABYLON.Matrix.FromValues(...entity.worldMatrix.elements)) 47 | ``` 48 | 49 | For the `camera` use this: 50 | 51 | ``` 52 | private _syncCamera(entity, camera) { 53 | camera.getViewMatrix().copyFrom(Matrix.FromValues(...entity.worldMatrix.elements).invert()) 54 | } 55 | ``` 56 | 57 | 5. you **must** register your YUKA entity in the `YUKA.EntityManager` with it's `add` function 58 | 6. you **must** update the YUKA EntityManager's time (make steps in YUKA world) to make things moving like this: 59 | 60 | ``` 61 | private _time = new YUKA.Time() 62 | this._scene.onBeforeRenderObservable.add(() => { 63 | const delta = this._time.update().getDelta() 64 | this._entityManager.update(delta) // YUKA world step 65 | }) 66 | ``` 67 | 68 | ## License 69 | 70 | All these examples are open source, MIT License. 71 | 72 | Babylon.js: Apache-2.0 License 73 | 74 | Yuka.js: MIT License 75 | 76 | 3D Models: CC Attribution License (see readme.md in relevant folders) 77 | -------------------------------------------------------------------------------- /examples/.htaccess: -------------------------------------------------------------------------------- 1 | # To be inside the /Directory 2 | Header set Access-Control-Allow-Origin "*" 3 | 4 | 5 | RewriteEngine On 6 | RewriteCond %{HTTPS} off 7 | RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] 8 | 9 | RewriteEngine On 10 | RewriteBase / 11 | RewriteRule ^index\.html$ - [L] 12 | RewriteCond %{REQUEST_FILENAME} !-f 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteRule . /index.html [L] 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Jura'); 2 | 3 | :root { 4 | color-scheme: light dark; 5 | } 6 | 7 | body { 8 | padding-top: 3.5rem; 9 | } 10 | 11 | .jumbotron { 12 | text-align: center; 13 | background-image: url(../images/gamer.jpg); 14 | background-repeat: no-repeat; 15 | background-size: cover; 16 | border-radius: 0; 17 | color: #ffffff; 18 | margin-bottom: 4rem; 19 | } 20 | 21 | .vertical-center { 22 | min-height: 50%; 23 | min-height: 50vh; 24 | display: flex; 25 | align-items: center; 26 | } 27 | 28 | .lead { 29 | font-size: 2rem; 30 | } 31 | 32 | hr.divider { 33 | margin: 5rem 0; 34 | } 35 | 36 | .teaser h2 { 37 | text-align: center; 38 | } 39 | .teaser p { 40 | text-align: center; 41 | } 42 | 43 | .navbar { 44 | border-bottom: 1px solid #ccc; 45 | } 46 | 47 | .navbar-brand { 48 | padding: 0; 49 | } 50 | 51 | .navbar-toggler-icon { 52 | background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(238, 8, 8, 1)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 8h24M4 16h24M4 24h24'/%3E%3C/svg%3E"); 53 | } 54 | 55 | @media (max-width: 576px) { 56 | .showcase-images { 57 | width: 300px; 58 | height: 150px; 59 | } 60 | } 61 | 62 | @media (prefers-color-scheme: light) { 63 | .navbar { 64 | background-color: #fff !important; 65 | border-bottom: 1px solid #ccc; 66 | } 67 | } 68 | 69 | /* dark mode */ 70 | 71 | @media (prefers-color-scheme: dark) { 72 | body { 73 | color: #ccc; 74 | background-color: #181818; 75 | } 76 | .navbar { 77 | background-color: rgb(24, 22, 22) !important; 78 | border-bottom: 1px solid #212121; 79 | } 80 | hr { 81 | border-top: 1px solid #666; 82 | } 83 | a.nav-link { 84 | color: #ccc; 85 | } 86 | a.nav-link:hover { 87 | color: #ee0808; 88 | } 89 | .jumbotron { 90 | background-color: #181818; 91 | } 92 | 93 | .card { 94 | background-color: #181818; 95 | } 96 | footer { 97 | background-color: rgb(24, 22, 22); 98 | border-top: 1px solid #212121; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/css/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Jura'); 2 | 3 | * { 4 | -webkit-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | 10 | body { 11 | margin:0; 12 | overflow:hidden; 13 | font-family: 'Jura', sans-serif; 14 | } 15 | 16 | #info { 17 | width: 100%; 18 | color: #ffffff; 19 | position: fixed; 20 | text-align: center; 21 | } 22 | 23 | #intro { 24 | position: fixed; 25 | height: 100%; 26 | width: 100%; 27 | display: flex; 28 | flex-direction: column; 29 | align-items: center; 30 | justify-content: center; 31 | background-color: rgba(0,0,0,0.4); 32 | color: #ffffff; 33 | font-size: 40; 34 | z-index: 999; 35 | } 36 | 37 | #intro.hidden { 38 | display: none; 39 | } 40 | 41 | #intro .sub { 42 | font-size: 20; 43 | } 44 | 45 | #loading-screen{ 46 | position: fixed; 47 | height: 100%; 48 | width: 100%; 49 | display: flex; 50 | align-items: center; 51 | justify-content: center; 52 | background-color: #000000; 53 | z-index: 1000; 54 | opacity: 1; 55 | transition: 0.5s opacity; 56 | } 57 | 58 | #loading-screen.fade-out { 59 | opacity: 0; 60 | } 61 | 62 | .spinner { 63 | margin: 100px auto; 64 | width: 50px; 65 | height: 40px; 66 | text-align: center; 67 | font-size: 10px; 68 | } 69 | 70 | .spinner > div { 71 | background-color: #ffffff; 72 | height: 100%; 73 | width: 6px; 74 | display: inline-block; 75 | 76 | -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out; 77 | animation: sk-stretchdelay 1.2s infinite ease-in-out; 78 | } 79 | 80 | .spinner .rect2 { 81 | -webkit-animation-delay: -1.1s; 82 | animation-delay: -1.1s; 83 | } 84 | 85 | .spinner .rect3 { 86 | -webkit-animation-delay: -1.0s; 87 | animation-delay: -1.0s; 88 | } 89 | 90 | .spinner .rect4 { 91 | -webkit-animation-delay: -0.9s; 92 | animation-delay: -0.9s; 93 | } 94 | 95 | .spinner .rect5 { 96 | -webkit-animation-delay: -0.8s; 97 | animation-delay: -0.8s; 98 | } 99 | 100 | @-webkit-keyframes sk-stretchdelay { 101 | 0%, 40%, 100% { -webkit-transform: scaleY(0.4) } 102 | 20% { -webkit-transform: scaleY(1.0) } 103 | } 104 | 105 | @keyframes sk-stretchdelay { 106 | 0%, 40%, 100% { 107 | transform: scaleY(0.4); 108 | -webkit-transform: scaleY(0.4); 109 | } 20% { 110 | transform: scaleY(1.0); 111 | -webkit-transform: scaleY(1.0); 112 | } 113 | } 114 | 115 | /* hide some information on small devices */ 116 | 117 | @media screen and (max-width: 414px) { 118 | #info { 119 | display: none; 120 | } 121 | .dg { 122 | display: none; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /examples/js/creator.js: -------------------------------------------------------------------------------- 1 | import 'https://preview.babylonjs.com/babylon.js' 2 | 3 | export const VehicleTypes = { 4 | default: 0, 5 | cone: 1, 6 | box: 2, 7 | // etc 8 | } 9 | 10 | /* 11 | options: { 12 | type: VehicleTypes 13 | name: string 14 | size: number 15 | x: number 16 | y: number 17 | z: number 18 | ... etc 19 | } 20 | 21 | */ 22 | 23 | export const createVehicle = (scene, options) => { 24 | const vehicleType = options?.type ?? VehicleTypes.default 25 | const size = options?.size ?? 1 26 | const height = 1 * size 27 | const diameterBottom = 0.5 * size 28 | const diameterTop = 0 29 | const name = options?.name ?? 'vehicle' 30 | 31 | if (vehicleType === VehicleTypes.default) { 32 | const vehicleMesh = BABYLON.MeshBuilder.CreateCylinder('cone', { height, diameterTop, diameterBottom }, scene) 33 | vehicleMesh.rotation.x = Math.PI * 0.5 34 | 35 | const dodecahedron2 = BABYLON.MeshBuilder.CreatePolyhedron('dodecahedron', { 36 | type: 1, 37 | size: 0.22 * size, 38 | }) 39 | dodecahedron2.position.z -= 0.1 40 | 41 | const newVehicleMesh = BABYLON.Mesh.MergeMeshes([vehicleMesh, dodecahedron2], true) 42 | newVehicleMesh.name = name 43 | newVehicleMesh.position.y = options?.y ?? 0 44 | newVehicleMesh.bakeCurrentTransformIntoVertices() 45 | 46 | return newVehicleMesh 47 | } else if (vehicleType === VehicleTypes.cone) { 48 | const vehicleMesh = BABYLON.MeshBuilder.CreateCylinder(name, { height, diameterTop, diameterBottom }, scene) 49 | 50 | vehicleMesh.position.y = options?.y ?? 0 51 | vehicleMesh.rotation.x = Math.PI * 0.5 52 | vehicleMesh.bakeCurrentTransformIntoVertices() 53 | 54 | return vehicleMesh 55 | } else if (vehicleType === VehicleTypes.box) { 56 | // 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/js/fsm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | State-driven Agent Design 4 | 5 | 6 | 7 | 8 | 9 | 31 | 32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |

44 | The game entity continuously changes its status between "IDLE" and "WALK".
45 | The State-driven agent design enables a clean implementation of basic AI logic.
46 | The cone changes its color depending on the current state and uses FollowPath behavior when IDLE and Arrive (to 47 | zero point) behavior in the WALK state. 48 |

49 |
50 |
51 |
52 | Current State: 53 | 54 |
55 |
56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/js/fsm/model/yuka.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/fsm/model/yuka.glb -------------------------------------------------------------------------------- /examples/js/fsm/src/Girl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author modified at https://github.com/eldinor/yuka-babylonjs-examples 4 | */ 5 | 6 | import * as YUKA from '../../../../lib/yuka.module.js' 7 | 8 | import { IdleState, WalkState } from './States.js' 9 | 10 | class Girl extends YUKA.Vehicle { 11 | constructor(meshToManage, vehicle) { 12 | super() 13 | 14 | this.meshToManage = meshToManage 15 | this.vehicle = vehicle 16 | 17 | this.ui = { 18 | currentState: document.getElementById('currentState'), 19 | } 20 | 21 | // 22 | 23 | this.stateMachine = new YUKA.StateMachine(this) 24 | 25 | this.stateMachine.add('IDLE', new IdleState()) 26 | this.stateMachine.add('WALK', new WalkState()) 27 | 28 | this.stateMachine.changeTo('IDLE') 29 | 30 | // 31 | 32 | this.currentTime = 0 // tracks how long the entity is in the current state 33 | this.idleDuration = 6 // duration of a single state in seconds 34 | this.walkDuration = 4 // duration of a single state in seconds 35 | this.crossFadeDuration = 1 // duration of a crossfade in seconds 36 | } 37 | 38 | update(delta) { 39 | this.currentTime += delta 40 | 41 | this.stateMachine.update() 42 | } 43 | } 44 | 45 | export { Girl } 46 | -------------------------------------------------------------------------------- /examples/js/fsm/src/States.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author modified at https://github.com/eldinor/yuka-babylonjs-examples 4 | */ 5 | 6 | import * as YUKA from '../../../../lib/yuka.module.js' 7 | 8 | const IDLE = 'IDLE' 9 | const WALK = 'WALK' 10 | 11 | class IdleState extends YUKA.State { 12 | enter(girl) { 13 | girl.ui.currentState.textContent = IDLE 14 | 15 | girl.meshToManage.material.diffuseColor = BABYLON.Color3.Blue() 16 | girl.vehicle.steering.behaviors[1].active = false 17 | girl.vehicle.steering.behaviors[0].active = true 18 | console.log(girl.vehicle.steering.behaviors[0]) 19 | console.log(girl.vehicle.steering.behaviors[1]) 20 | } 21 | 22 | execute(girl) { 23 | if (girl.currentTime >= girl.idleDuration) { 24 | girl.currentTime = 0 25 | girl.stateMachine.changeTo(WALK) 26 | } 27 | } 28 | 29 | exit(girl) {} 30 | } 31 | 32 | class WalkState extends YUKA.State { 33 | enter(girl) { 34 | girl.meshToManage.material.diffuseColor = BABYLON.Color3.Red() 35 | girl.ui.currentState.textContent = WALK 36 | 37 | girl.walk.start() 38 | girl.idle.stop() 39 | girl.walk.loopAnimation = true 40 | 41 | const target = new YUKA.Vector3(5, 0, 6) 42 | 43 | girl.vehicle.steering.behaviors[0].active = false 44 | girl.vehicle.steering.behaviors[1].active = true 45 | console.log(girl.vehicle.steering.behaviors[0]) 46 | console.log(girl.vehicle.steering.behaviors[1]) 47 | } 48 | 49 | execute(girl) { 50 | if (girl.currentTime >= girl.walkDuration) { 51 | girl.currentTime = 0 52 | girl.stateMachine.changeTo(IDLE) 53 | const target = new YUKA.Vector3(-5, 0, 3) 54 | } 55 | } 56 | 57 | exit(girl) { 58 | if ((girl.ui.currentState.textContent = IDLE)) { 59 | girl.idle.start() 60 | girl.walk.stop() 61 | girl.idle.loopAnimation = true 62 | } 63 | } 64 | } 65 | export { IdleState, WalkState } 66 | -------------------------------------------------------------------------------- /examples/js/fuzzy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Fuzzy Logic 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |

46 | The soldier uses fuzzy inference to determine the best weapon
47 | based on the distance to the enemy and the available ammo. 48 |

49 |
50 | 51 |
52 |
53 | Current Weapon: 54 | 55 |
56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/js/fuzzy/model/README.md: -------------------------------------------------------------------------------- 1 | Assault Rifle from https://sketchfab.com/3d-models/sci-fi-assault-rifle-d2596ed504b84ffda761b0886c26b202 by Ptis 2 | License: CC Attribution-NonCommercial 3 | -------------------------------------------------------------------------------- /examples/js/fuzzy/model/assaultRifle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/fuzzy/model/assaultRifle.glb -------------------------------------------------------------------------------- /examples/js/fuzzy/model/soldier.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/fuzzy/model/soldier.glb -------------------------------------------------------------------------------- /examples/js/fuzzy/model/zombie.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/fuzzy/model/zombie.glb -------------------------------------------------------------------------------- /examples/js/goal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Goal-driven Agent Design 4 | 5 | 6 | 7 | 8 | 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 |
45 |

46 | The main goal of the game entity is to gather collectibles. After a while, the game entity takes a short 47 | rest.
48 | The Goal-driven agent design enables a clean implementation of more advanced AI logic. 49 |

50 |
51 | 52 |
53 |
54 | Current Goal: 55 | 56 | | Subgoal: 57 | 58 |
59 |

60 | Tired   61 | 62 |

63 |
64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/js/goal/model/yan-ots-UuBR5kbvt4Y-unsplash (1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/goal/model/yan-ots-UuBR5kbvt4Y-unsplash (1).jpg -------------------------------------------------------------------------------- /examples/js/goal/model/yuka.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/goal/model/yuka.glb -------------------------------------------------------------------------------- /examples/js/goal/src/Collectible.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | */ 4 | 5 | import { GameEntity } from '../../../../lib/yuka.module.js'; 6 | 7 | class Collectible extends GameEntity { 8 | 9 | constructor(scene){ 10 | super() 11 | this.scene = scene; // scene variable is not necessary if you don't need to change anything in the scene 12 | // here it is using only to change collectible box material after spawning 13 | } 14 | 15 | spawn() { 16 | 17 | this.position.x = Math.random() * 15 - 7.5; 18 | this.position.z = Math.random() * 15 - 7.5; 19 | 20 | if ( this.position.x < 1 && this.position.x > - 1 ) this.position.x += 1; 21 | if ( this.position.z < 1 && this.position.y > - 1 ) this.position.z += 1; 22 | 23 | console.log("Collectible spawned ") 24 | 25 | this._renderComponent.material = this.scene.getMaterialByName("collectibleMat1") 26 | setTimeout(() => { 27 | this._renderComponent.material = this.scene.getMaterialByName("collectibleMat") 28 | }, 3000); 29 | } 30 | 31 | handleMessage( telegram ) { 32 | 33 | const message = telegram.message; 34 | 35 | switch ( message ) { 36 | 37 | case 'PickedUp': 38 | 39 | this.spawn(); 40 | return true; 41 | 42 | default: 43 | 44 | console.warn( 'Collectible: Unknown message.' ); 45 | 46 | } 47 | 48 | return false; 49 | 50 | } 51 | 52 | } 53 | 54 | export { Collectible }; 55 | -------------------------------------------------------------------------------- /examples/js/goal/src/Evaluators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | */ 4 | 5 | import { GoalEvaluator } from '../../../../lib/yuka.module.js'; 6 | 7 | import { RestGoal, GatherGoal } from './Goals.js'; 8 | 9 | class RestEvaluator extends GoalEvaluator { 10 | 11 | calculateDesirability( girl ) { 12 | 13 | return ( girl.tired() === true ) ? 1 : 0; 14 | 15 | } 16 | 17 | setGoal( girl ) { 18 | 19 | const currentSubgoal = girl.brain.currentSubgoal(); 20 | 21 | if ( ( currentSubgoal instanceof RestGoal ) === false ) { 22 | 23 | girl.brain.clearSubgoals(); 24 | 25 | girl.brain.addSubgoal( new RestGoal( girl ) ); 26 | 27 | } 28 | 29 | } 30 | 31 | } 32 | 33 | class GatherEvaluator extends GoalEvaluator { 34 | 35 | calculateDesirability() { 36 | 37 | return 0.5; 38 | 39 | } 40 | 41 | setGoal( girl ) { 42 | 43 | const currentSubgoal = girl.brain.currentSubgoal(); 44 | 45 | if ( ( currentSubgoal instanceof GatherGoal ) === false ) { 46 | 47 | girl.brain.clearSubgoals(); 48 | 49 | girl.brain.addSubgoal( new GatherGoal( girl ) ); 50 | 51 | } 52 | 53 | } 54 | 55 | } 56 | 57 | export { 58 | RestEvaluator, 59 | GatherEvaluator 60 | }; 61 | -------------------------------------------------------------------------------- /examples/js/goal/src/Girl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | */ 4 | 5 | import { Vehicle, Think, ArriveBehavior } from '../../../../lib/yuka.module.js' 6 | import { RestEvaluator, GatherEvaluator } from './Evaluators.js' 7 | 8 | class Girl extends Vehicle { 9 | constructor() { 10 | super() 11 | 12 | this.maxTurnRate = Math.PI * 0.5 13 | this.maxSpeed = 1.5 14 | 15 | this.ui = { 16 | currentGoal: document.getElementById('currentGoal'), 17 | currentSubgoal: document.getElementById('currentSubgoal'), 18 | fatigueLevel: document.getElementById('tired'), 19 | } 20 | 21 | // goal-driven agent design 22 | 23 | this.brain = new Think(this) 24 | 25 | this.brain.addEvaluator(new RestEvaluator()) 26 | this.brain.addEvaluator(new GatherEvaluator()) 27 | 28 | // steering 29 | 30 | const arriveBehavior = new ArriveBehavior() 31 | arriveBehavior.deceleration = 1.5 32 | this.steering.add(arriveBehavior) 33 | 34 | // 35 | 36 | this.fatigueLevel = 0 // current level of fatigue 37 | this.restDuration = 5 // duration of a rest phase in seconds 38 | this.pickUpDuration = 6 // duration of a pick phase in seconds 39 | this.crossFadeDuration = 0.5 // duration of a crossfade in seconds 40 | this.currentTarget = null // current collectible 41 | 42 | this.currentTime = 0 // tracks the current time of an action 43 | this.deltaTime = 0 // the current time delta value 44 | 45 | this.MAX_FATIGUE = 3 // the girl needs to rest if this amount of fatigue is reached 46 | } 47 | 48 | update(delta) { 49 | super.update(delta) 50 | 51 | this.deltaTime = delta 52 | 53 | this.brain.execute() 54 | 55 | this.brain.arbitrate() 56 | 57 | return this 58 | } 59 | 60 | tired() { 61 | return this.fatigueLevel >= this.MAX_FATIGUE 62 | } 63 | } 64 | 65 | export { Girl } 66 | -------------------------------------------------------------------------------- /examples/js/graph/GraphHelper.js: -------------------------------------------------------------------------------- 1 | function createGraphHelper(scene, graph, nodeSize = 1, nodeColor = '#4e84c4', edgeColor = '#ffffff') { 2 | const nodes = [] 3 | graph.getNodes(nodes) 4 | 5 | const parent = new BABYLON.TransformNode('nodes-parent', scene) 6 | 7 | for (let node of nodes) { 8 | const nodeMaterial = new BABYLON.StandardMaterial('node', scene) 9 | nodeMaterial.emmissiveColor = BABYLON.Color3.FromHexString(nodeColor) 10 | 11 | const nodeMesh = BABYLON.MeshBuilder.CreatePolyhedron( 12 | 'node', 13 | { 14 | type: 3, // Icosahedron 15 | size: nodeSize, 16 | }, 17 | scene 18 | ) 19 | nodeMesh.parent = parent 20 | nodeMesh.material = nodeMaterial 21 | nodeMesh.position = new BABYLON.Vector3(node.position.x, node.position.y, node.position.z) 22 | 23 | // edges 24 | const edges = [] 25 | const lines = [] 26 | for (let node of nodes) { 27 | graph.getEdgesOfNode(node.index, edges) 28 | 29 | const position = [] 30 | for (let edge of edges) { 31 | const fromNode = graph.getNode(edge.from) 32 | const toNode = graph.getNode(edge.to) 33 | 34 | position.push(new BABYLON.Vector3(fromNode.position.x, fromNode.position.y, fromNode.position.z)) 35 | position.push(new BABYLON.Vector3(toNode.position.x, toNode.position.y, toNode.position.z)) 36 | } 37 | 38 | lines.push(position) 39 | } 40 | 41 | const pathHelper = BABYLON.MeshBuilder.CreateLineSystem( 42 | 'path-helper', 43 | { 44 | lines, 45 | updatable: false, 46 | }, 47 | scene 48 | ) 49 | pathHelper.color = BABYLON.Color3.Green() 50 | pathHelper.parent = parent 51 | } 52 | 53 | return parent 54 | } 55 | 56 | export { createGraphHelper } 57 | -------------------------------------------------------------------------------- /examples/js/graph/corridor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Corridor + Follow Path 4 | 5 | 9 | 10 | 15 | 16 | 17 |
18 |

19 | A corridor is a sequence of portal edges representing a walkable way 20 | within a navigation mesh.
21 | The class is able to find the shortest path through this corridor as a 22 | sequence of waypoints. 23 |

24 |
25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/js/graph/tictactoe/index.js: -------------------------------------------------------------------------------- 1 | import { TTTGraph } from './src/TTTGraph.js' 2 | 3 | let player, 4 | graph, 5 | fin = false 6 | 7 | initUI() 8 | 9 | function initUI() { 10 | // init buttons 11 | 12 | const buttons = document.querySelectorAll('#startSection button') 13 | 14 | for (let button of buttons) { 15 | button.addEventListener('click', onButtonClick) 16 | } 17 | 18 | const button = document.querySelector('#endSection button') 19 | 20 | button.addEventListener('click', onRestart) 21 | 22 | // init cells 23 | 24 | const cells = document.querySelectorAll('.cell') 25 | 26 | for (let cell of cells) { 27 | cell.addEventListener('click', onCellClick) 28 | } 29 | } 30 | 31 | const boxCells = scene.getTransformNodeByName('allBoxes').getChildren() 32 | 33 | for (let boxCell of boxCells) { 34 | boxCell.actionManager = new BABYLON.ActionManager(scene) 35 | boxCell.actionManager.registerAction( 36 | new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, (e) => { 37 | onCellBoxClick(e) 38 | }) 39 | ) 40 | } 41 | 42 | function initGame() { 43 | const intro = document.getElementById('intro') 44 | intro.classList.add('hidden') 45 | 46 | // create game state graph 47 | 48 | graph = new TTTGraph(player) 49 | 50 | // let the ai make its first move 51 | 52 | if (player === 2) { 53 | graph.aiTurn() 54 | updateUI() 55 | } 56 | } 57 | 58 | function onButtonClick(event) { 59 | const button = event.target 60 | player = parseInt(button.dataset.player) 61 | 62 | initGame() 63 | } 64 | 65 | function onRestart() { 66 | window.location.reload() 67 | } 68 | 69 | function onCellBoxClick(event) { 70 | const cellB = event.meshUnderPointer 71 | 72 | const cellBid = parseInt(cellB.name) 73 | 74 | if (fin === false) { 75 | const cell = event.target 76 | 77 | const cellid = cellBid 78 | 79 | graph.turn(cellid, graph.currentPlayer) 80 | evaluate() 81 | 82 | if (fin === false) { 83 | graph.aiTurn() 84 | evaluate() 85 | } 86 | 87 | updateUI() 88 | } 89 | } 90 | 91 | function onCellClick(event) { 92 | if (fin === false) { 93 | const cell = event.target 94 | 95 | const cellid = cell.dataset.cellid 96 | 97 | graph.turn(cellid, graph.currentPlayer) 98 | evaluate() 99 | 100 | if (fin === false) { 101 | graph.aiTurn() 102 | evaluate() 103 | } 104 | 105 | updateUI() 106 | } 107 | } 108 | 109 | function evaluate() { 110 | const board = graph.getNode(graph.currentNode) 111 | 112 | if (board.win === true || board.finished === true) fin = true 113 | } 114 | 115 | function updateUI() { 116 | const node = graph.getNode(graph.currentNode) 117 | 118 | const board = node.board 119 | const cells = document.querySelectorAll('.cell') 120 | 121 | const cellBoxes = scene.getTransformNodeByName('allBoxes').getChildren() 122 | 123 | for (let cell of cells) { 124 | const cellid = cell.dataset.cellid 125 | const status = board[cellid] 126 | 127 | switch (status) { 128 | case 1: 129 | cell.textContent = 'X' 130 | cell.removeEventListener('click', onCellClick) 131 | 132 | let crossMove = scene.getMeshByName('cross').clone('crossMove') 133 | crossMove.isVisible = true 134 | 135 | bPos(crossMove, cell.dataset.cellid) 136 | 137 | crossMove.position.y = 0.6 138 | 139 | scene.getMeshByName(cell.dataset.cellid).isPickable = false 140 | crossMove.isPickable = false 141 | 142 | break 143 | 144 | case 2: 145 | cell.textContent = 'O' 146 | cell.removeEventListener('click', onCellClick) 147 | 148 | let torusMove = scene.getMeshByName('torus').clone('crossMove') 149 | torusMove.isVisible = true 150 | 151 | bPos(torusMove, cell.dataset.cellid) 152 | torusMove.position.y = 0.6 153 | 154 | scene.getMeshByName(cell.dataset.cellid).isPickable = false 155 | torusMove.isPickable = false 156 | 157 | break 158 | 159 | default: 160 | cell.textContent = '' 161 | // console.log("DEFAULT") 162 | break 163 | } 164 | } 165 | 166 | if (fin === true) { 167 | const intro = document.getElementById('intro') 168 | intro.classList.remove('hidden') 169 | 170 | const startSection = document.getElementById('startSection') 171 | startSection.style.display = 'none' 172 | 173 | const endSection = document.getElementById('endSection') 174 | endSection.style.display = 'flex' 175 | 176 | const result = document.getElementById('result') 177 | 178 | if (node.win === true) { 179 | if (node.winPlayer === player) { 180 | result.textContent = 'You win the game!' 181 | } else { 182 | result.textContent = 'Yuka AI wins the game!' 183 | } 184 | } else { 185 | result.textContent = 'Draw!' 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /examples/js/graph/tictactoe/src/TTTEdge.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author robp94 / https://github.com/robp94 3 | */ 4 | 5 | import { Edge } from '../../../../../lib/yuka.module.js'; 6 | 7 | class TTTEdge extends Edge { 8 | 9 | constructor( from, to, cell, player ) { 10 | 11 | super( from, to ); 12 | 13 | // the following properties represent the move which 14 | // transitions from one board to the next one 15 | 16 | this.cell = cell; // the chosen cell/field 17 | this.player = player; // this player made the move 18 | 19 | } 20 | 21 | } 22 | 23 | export { TTTEdge }; 24 | -------------------------------------------------------------------------------- /examples/js/graph/tictactoe/src/TTTNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author robp94 / https://github.com/robp94 3 | */ 4 | 5 | import { Node } from '../../../../../lib/yuka.module.js'; 6 | 7 | class TTTNode extends Node { 8 | 9 | constructor( index, board = [ 9, 9, 9, 9, 9, 9, 9, 9, 9 ] ) { 10 | 11 | super( index ); 12 | 13 | // the board is represented as a flat array 14 | // 1 = cell marked by player 1 15 | // 2 = cell marked by player 2 16 | // 9 = cell is empty 17 | 18 | this.board = board; 19 | 20 | this.value = parseInt( this.board.join( '' ), 10 ); // number representation of the board array for faster comparision 21 | this.win = false; // whether this node represents a won game 22 | this.finished = false; // whether this node represents a finished game (win or draw) 23 | this.winPlayer = - 1; // represents the player who wins with the current board, - 1 if there is no winner 24 | this.weight = 0; // used for min/max algorithm 25 | 26 | this.evaluate(); 27 | 28 | } 29 | 30 | evaluate() { 31 | 32 | // check for win 33 | 34 | // horizontal 35 | 36 | if ( [ this.board[ 0 ], this.board[ 1 ], this.board[ 2 ] ].every( condition ) ) { 37 | 38 | this.finished = true; 39 | this.winPlayer = this.board[ 0 ]; 40 | 41 | } 42 | 43 | if ( [ this.board[ 3 ], this.board[ 4 ], this.board[ 5 ] ].every( condition ) ) { 44 | 45 | this.finished = true; 46 | this.winPlayer = this.board[ 3 ]; 47 | 48 | } 49 | 50 | if ( [ this.board[ 6 ], this.board[ 7 ], this.board[ 8 ] ].every( condition ) ) { 51 | 52 | this.finished = true; 53 | this.winPlayer = this.board[ 6 ]; 54 | 55 | } 56 | 57 | // vertical 58 | 59 | if ( [ this.board[ 0 ], this.board[ 3 ], this.board[ 6 ] ].every( condition ) ) { 60 | 61 | this.finished = true; 62 | this.winPlayer = this.board[ 0 ]; 63 | 64 | } 65 | 66 | if ( [ this.board[ 1 ], this.board[ 4 ], this.board[ 7 ] ].every( condition ) ) { 67 | 68 | this.finished = true; 69 | this.winPlayer = this.board[ 1 ]; 70 | 71 | } 72 | 73 | if ( [ this.board[ 2 ], this.board[ 5 ], this.board[ 8 ] ].every( condition ) ) { 74 | 75 | this.finished = true; 76 | this.winPlayer = this.board[ 2 ]; 77 | 78 | } 79 | 80 | // diagonal 81 | 82 | if ( [ this.board[ 0 ], this.board[ 4 ], this.board[ 8 ] ].every( condition ) ) { 83 | 84 | this.finished = true; 85 | this.winPlayer = this.board[ 0 ]; 86 | 87 | } 88 | 89 | if ( [ this.board[ 6 ], this.board[ 4 ], this.board[ 2 ] ].every( condition ) ) { 90 | 91 | this.finished = true; 92 | this.winPlayer = this.board[ 6 ]; 93 | 94 | } 95 | 96 | if ( this.winPlayer !== - 1 ) this.win = true; 97 | 98 | // check for draw 99 | 100 | let count = 0; 101 | 102 | for ( let i = 0; i < 9; i ++ ) { 103 | 104 | if ( this.board[ i ] !== 9 ) { 105 | 106 | count ++; 107 | 108 | } 109 | 110 | } 111 | 112 | if ( count === 9 ) { 113 | 114 | this.finished = true; 115 | 116 | } 117 | 118 | } 119 | 120 | } 121 | 122 | function condition( v, i, a ) { 123 | 124 | return ( a[ i ] === a[ 0 ] && a[ i ] !== 9 ); 125 | 126 | } 127 | 128 | export { TTTNode }; 129 | -------------------------------------------------------------------------------- /examples/js/math/orientation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Orientation 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

The entity is rotated by a defined angular step per second in order to face a specific target.

13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/math/orientation/index.js: -------------------------------------------------------------------------------- 1 | import 'https://preview.babylonjs.com/babylon.js' 2 | 3 | import * as YUKA from '../../../../../lib/yuka.module.js' 4 | import { createVehicle } from '../../creator.js' 5 | 6 | let engine, scene 7 | 8 | let entityManager, time, entity, target 9 | 10 | const entityMatrix = new BABYLON.Matrix() 11 | 12 | init() 13 | animate() 14 | 15 | function init() { 16 | const canvas = document.getElementById('renderCanvas') 17 | engine = new BABYLON.Engine(canvas, true, {}, true) 18 | 19 | scene = new BABYLON.Scene(engine) 20 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 21 | 22 | const camera = new BABYLON.ArcRotateCamera( 23 | 'camera', 24 | BABYLON.Tools.ToRadians(90), 25 | BABYLON.Tools.ToRadians(60), 26 | 10, 27 | BABYLON.Vector3.Zero(), 28 | scene 29 | ) 30 | camera.target = new BABYLON.Vector3(0, 0, 0) 31 | camera.useAutoRotationBehavior = true 32 | camera.wheelDeltaPercentage = 0.002 33 | camera.attachControl(canvas, true) 34 | 35 | const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0)) 36 | light.diffuse = BABYLON.Color3.Teal() 37 | // 38 | 39 | const entityMesh = createVehicle(scene, { size: 0.5 }) 40 | 41 | const targetMesh = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 0.1, segments: 16 }) 42 | 43 | const meshMat = new BABYLON.StandardMaterial('meshMat', scene) 44 | meshMat.disableLighting = true 45 | meshMat.emissiveColor = BABYLON.Color3.Red() 46 | targetMesh.material = meshMat 47 | 48 | const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 5, segments: 16 }) 49 | sphere.material = new BABYLON.StandardMaterial('sphereMaterial', scene) 50 | sphere.material.disableLighting = true 51 | sphere.material.emissiveColor = new BABYLON.Color3(0.8, 0.8, 0.8) 52 | sphere.material.alpha = 0.2 53 | sphere.material.wireframe = true 54 | 55 | // 56 | 57 | window.addEventListener('resize', onWindowResize, false) 58 | 59 | // game entity setup 60 | 61 | entityManager = new YUKA.EntityManager() 62 | time = new YUKA.Time() 63 | 64 | target = new YUKA.GameEntity() 65 | target.setRenderComponent(targetMesh, sync) 66 | 67 | entity = new YUKA.GameEntity() 68 | entity.maxTurnRate = Math.PI * 0.5 69 | entity.setRenderComponent(entityMesh, sync) 70 | 71 | entityManager.add(entity) 72 | entityManager.add(target) 73 | 74 | // 75 | 76 | generateTarget() 77 | } 78 | 79 | function onWindowResize() { 80 | engine.resize() 81 | } 82 | 83 | function animate() { 84 | requestAnimationFrame(animate) 85 | 86 | const delta = time.update().getDelta() 87 | 88 | entity.rotateTo(target.position, delta) 89 | 90 | entityManager.update() 91 | 92 | scene.render() 93 | } 94 | 95 | function sync(entity, renderComponent) { 96 | entity.worldMatrix.toArray(entityMatrix.m) 97 | entityMatrix.markAsUpdated() 98 | 99 | const matrix = renderComponent.getWorldMatrix() 100 | matrix.copyFrom(entityMatrix) 101 | } 102 | 103 | function generateTarget() { 104 | // generate a random point on a sphere 105 | 106 | const radius = 2 107 | const phi = Math.acos(2 * Math.random() - 1) 108 | const theta = Math.random() * Math.PI * 2 109 | 110 | target.position.fromSpherical(radius, phi, theta) 111 | 112 | setTimeout(generateTarget, 2000) 113 | } 114 | -------------------------------------------------------------------------------- /examples/js/misc/savegame/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Savegames 4 | 5 | 6 | 7 | 8 | 25 | 26 | 27 |
28 |

29 | You can save and reload the current state of the game. The savegame is also loaded when the page is refreshed. 30 |

31 |

32 | 33 | 34 | 35 |

36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/js/misc/savegame/src/CustomEntity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | */ 4 | 5 | import { GameEntity } from '../../../../../lib/yuka.module.js' 6 | 7 | class CustomEntity extends GameEntity { 8 | constructor() { 9 | super() 10 | 11 | this.name = 'target' 12 | this.currentTime = 0 13 | this.endTime = 0 14 | } 15 | 16 | update(delta) { 17 | this.currentTime += delta 18 | 19 | if (this.currentTime >= this.endTime) { 20 | this.generatePosition() 21 | } 22 | 23 | return super.update(delta) 24 | } 25 | 26 | generatePosition() { 27 | const radius = 2 28 | const phi = Math.acos(2 * Math.random() - 1) 29 | const theta = Math.random() * Math.PI * 2 30 | 31 | this.position.fromSpherical(radius, phi, theta) 32 | 33 | this.endTime += 3 // 3s 34 | } 35 | 36 | toJSON() { 37 | const json = super.toJSON() 38 | 39 | json.currentTime = this.currentTime 40 | json.endTime = this.endTime 41 | 42 | console.log(json) 43 | 44 | return json 45 | } 46 | 47 | fromJSON(json) { 48 | super.fromJSON(json) 49 | 50 | this.currentTime = json.currentTime 51 | this.endTime = json.endTime 52 | 53 | return this 54 | } 55 | } 56 | 57 | export { CustomEntity } 58 | -------------------------------------------------------------------------------- /examples/js/misc/savegame/src/CustomVehicle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | */ 4 | 5 | import { Vehicle } from '../../../../../lib/yuka.module.js' 6 | 7 | class CustomVehicle extends Vehicle { 8 | constructor() { 9 | super() 10 | 11 | this.name = 'vehicle' 12 | this.target = null 13 | } 14 | 15 | update(delta) { 16 | const seekBehavior = this.steering.behaviors[0] 17 | seekBehavior.target.copy(this.target.position) 18 | 19 | return super.update(delta) 20 | } 21 | 22 | toJSON() { 23 | const json = super.toJSON() 24 | 25 | json.target = this.target.uuid 26 | 27 | return json 28 | } 29 | 30 | fromJSON(json) { 31 | super.fromJSON(json) 32 | 33 | this.target = json.target 34 | 35 | return this 36 | } 37 | 38 | resolveReferences(entities) { 39 | super.resolveReferences(entities) 40 | 41 | this.target = entities.get(this.target) 42 | 43 | return this 44 | } 45 | } 46 | 47 | export { CustomVehicle } 48 | -------------------------------------------------------------------------------- /examples/js/misc/trigger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Trigger 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

The game entity's color changes to green when it enters a trigger region.

12 |

Wireframe meshes are shown just for the visual hint. There is no need to render them actually.

13 |

They just show RectangularTriggerRegion and SphericalTriggerRegion.

14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/misc/trigger/index.js: -------------------------------------------------------------------------------- 1 | import 'https://preview.babylonjs.com/babylon.js' 2 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 3 | 4 | import * as YUKA from '../../../../../lib/yuka.module.js' 5 | 6 | import { CustomTrigger } from './src/CustomTrigger.js' 7 | 8 | let engine, scene 9 | 10 | let entityManager, time, entity 11 | 12 | const entityMatrix = new BABYLON.Matrix() 13 | 14 | init() 15 | animate() 16 | 17 | function init() { 18 | const canvas = document.getElementById('renderCanvas') 19 | engine = new BABYLON.Engine(canvas, true, {}, true) 20 | 21 | scene = new BABYLON.Scene(engine) 22 | scene.useRightHandedSystem = true 23 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 24 | // scene.debugLayer.show(); 25 | 26 | const camera = new BABYLON.ArcRotateCamera( 27 | 'camera', 28 | BABYLON.Tools.ToRadians(90), 29 | BABYLON.Tools.ToRadians(60), 30 | 15, 31 | BABYLON.Vector3.Zero(), 32 | scene 33 | ) 34 | 35 | camera.target = new BABYLON.Vector3(0, 0, 0) 36 | camera.upperBetaLimit = 1.4 37 | camera.attachControl(canvas, true) 38 | 39 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0), scene) 40 | 41 | const ground = BABYLON.MeshBuilder.CreatePlane('plane', { width: 25, height: 25 }, scene) 42 | ground.position.y = -2 43 | ground.rotation.x = Math.PI / 2 44 | 45 | ground.material = new BABYLON.GridMaterial('grid', scene) 46 | ground.material.backFaceCulling = true 47 | 48 | // 49 | 50 | const entityMesh = BABYLON.MeshBuilder.CreateBox('entityMesh', { size: 0.5 }, scene) 51 | 52 | const meshMat = new BABYLON.StandardMaterial('meshMat', scene) 53 | meshMat.disableLighting = true 54 | meshMat.emissiveColor = BABYLON.Color3.Red() 55 | entityMesh.material = meshMat 56 | 57 | // game entity setup 58 | 59 | entityManager = new YUKA.EntityManager() 60 | time = new YUKA.Time() 61 | 62 | entity = new YUKA.GameEntity() 63 | entity.boundingRadius = 0.25 64 | entity.setRenderComponent(entityMesh, sync) 65 | 66 | entityManager.add(entity) 67 | 68 | const radius = 2 69 | const size = new YUKA.Vector3(3, 3, 3) 70 | 71 | const sphericalTriggerRegion = new YUKA.SphericalTriggerRegion(radius) 72 | const rectangularTriggerRegion = new YUKA.RectangularTriggerRegion(size) 73 | 74 | const trigger1 = new CustomTrigger(sphericalTriggerRegion, scene) 75 | trigger1.position.set(3, 0, 0) 76 | 77 | const trigger2 = new CustomTrigger(rectangularTriggerRegion, scene) 78 | trigger2.position.set(-3, 0, 0) 79 | 80 | entityManager.add(trigger1) 81 | entityManager.add(trigger2) 82 | 83 | // visualize triggers 84 | 85 | const triggerMesh1 = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 4, segments: 8 }) 86 | triggerMesh1.position = new BABYLON.Vector3(3, 0, 0) 87 | 88 | triggerMesh1.material = new BABYLON.StandardMaterial('sphereMaterial', scene) 89 | triggerMesh1.material.diffuseColor = BABYLON.Color3.Gray() 90 | triggerMesh1.material.wireframe = true 91 | 92 | trigger1.setRenderComponent(triggerMesh1, sync) 93 | 94 | const triggerMesh2 = BABYLON.MeshBuilder.CreateBox('triggerMesh2', { size: 3 }, scene) 95 | triggerMesh2.position = new BABYLON.Vector3(-3, 0, 0) 96 | 97 | triggerMesh2.material = triggerMesh1.material 98 | 99 | trigger2.setRenderComponent(triggerMesh2, sync) 100 | 101 | window.addEventListener('resize', onWindowResize, false) 102 | } 103 | 104 | function onWindowResize() { 105 | engine.resize() 106 | } 107 | 108 | function animate() { 109 | requestAnimationFrame(animate) 110 | 111 | const delta = time.update().getDelta() 112 | const elapsedTime = time.getElapsed() 113 | 114 | entity.position.x = Math.sin(elapsedTime) * 2 115 | 116 | scene.getMeshByName(entity._renderComponent.name).material.emissiveColor.r = 1 117 | scene.getMeshByName(entity._renderComponent.name).material.emissiveColor.g = 0 118 | 119 | entityManager.update(delta) 120 | 121 | scene.render() 122 | } 123 | 124 | function sync(entity, renderComponent) { 125 | entity.worldMatrix.toArray(entityMatrix.m) 126 | entityMatrix.markAsUpdated() 127 | 128 | const matrix = renderComponent.getWorldMatrix() 129 | matrix.copyFrom(entityMatrix) 130 | } 131 | -------------------------------------------------------------------------------- /examples/js/misc/trigger/src/CustomTrigger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples 4 | */ 5 | 6 | import { Trigger } from '../../../../../lib/yuka.module.js' 7 | 8 | class CustomTrigger extends Trigger { 9 | constructor(triggerRegion, scene) { 10 | // scene variable is using here only to change the entity material color 11 | super(triggerRegion) 12 | this.scene = scene 13 | } 14 | 15 | execute(entity) { 16 | super.execute() 17 | 18 | this.scene.getMeshByName(entity._renderComponent.name).material.emissiveColor.r = 0 19 | this.scene.getMeshByName(entity._renderComponent.name).material.emissiveColor.g = 1 20 | } 21 | } 22 | 23 | export { CustomTrigger } 24 | -------------------------------------------------------------------------------- /examples/js/navigation/common/CellSpacePartitioningHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | function createCellSpaceHelper(spatialIndex, scene) { 7 | const cells = spatialIndex.cells 8 | 9 | const positions = [] 10 | 11 | for (let i = 0, l = cells.length; i < l; i++) { 12 | const cell = cells[i] 13 | const min = cell.aabb.min 14 | const max = cell.aabb.max 15 | 16 | // generate data for twelve lines segments 17 | 18 | // bottom lines 19 | 20 | positions.push([new BABYLON.Vector3(min.x, min.y, min.z), new BABYLON.Vector3(max.x, min.y, min.z)]) 21 | positions.push([new BABYLON.Vector3(min.x, min.y, min.z), new BABYLON.Vector3(min.x, min.y, max.z)]) 22 | positions.push([new BABYLON.Vector3(max.x, min.y, max.z), new BABYLON.Vector3(max.x, min.y, min.z)]) 23 | positions.push([new BABYLON.Vector3(max.x, min.y, max.z), new BABYLON.Vector3(min.x, min.y, max.z)]) 24 | 25 | // top lines 26 | 27 | positions.push([new BABYLON.Vector3(min.x, max.y, min.z), new BABYLON.Vector3(max.x, max.y, min.z)]) 28 | positions.push([new BABYLON.Vector3(min.x, max.y, min.z), new BABYLON.Vector3(min.x, max.y, max.z)]) 29 | positions.push([new BABYLON.Vector3(max.x, max.y, max.z), new BABYLON.Vector3(max.x, max.y, min.z)]) 30 | positions.push([new BABYLON.Vector3(max.x, max.y, max.z), new BABYLON.Vector3(min.x, max.y, max.z)]) 31 | 32 | // torso lines 33 | 34 | positions.push([new BABYLON.Vector3(min.x, min.y, min.z), new BABYLON.Vector3(min.x, max.y, min.z)]) 35 | positions.push([new BABYLON.Vector3(max.x, min.y, min.z), new BABYLON.Vector3(max.x, max.y, min.z)]) 36 | positions.push([new BABYLON.Vector3(max.x, min.y, max.z), new BABYLON.Vector3(max.x, max.y, max.z)]) 37 | positions.push([new BABYLON.Vector3(min.x, min.y, max.z), new BABYLON.Vector3(min.x, max.y, max.z)]) 38 | } 39 | 40 | const linesMesh = BABYLON.MeshBuilder.CreateLineSystem('lines', { lines: positions }, scene) 41 | 42 | return linesMesh 43 | } 44 | 45 | export { createCellSpaceHelper } 46 | -------------------------------------------------------------------------------- /examples/js/navigation/common/NavMeshHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | function createConvexRegionHelper(navMesh, scene) { 7 | const regions = navMesh.regions 8 | 9 | const customMesh = new BABYLON.Mesh('custom', scene) 10 | const customMeshMaterial = new BABYLON.StandardMaterial('custom-mesh', scene) 11 | customMeshMaterial.emmissiveColor = BABYLON.Color3.Random() 12 | 13 | customMesh.material = customMeshMaterial 14 | 15 | const positions = [] 16 | const colors = [] 17 | 18 | for (let region of regions) { 19 | // one color for each convex region 20 | const color = BABYLON.Color3.Random() 21 | 22 | // count edges 23 | 24 | let edge = region.edge 25 | const edges = [] 26 | 27 | do { 28 | edges.push(edge) 29 | edge = edge.next 30 | } while (edge !== region.edge) 31 | 32 | // triangulate 33 | 34 | const triangleCount = edges.length - 2 35 | 36 | for (let i = 1, l = triangleCount; i <= l; i++) { 37 | const v1 = edges[0].vertex 38 | const v2 = edges[i + 0].vertex 39 | const v3 = edges[i + 1].vertex 40 | 41 | positions.push(v1.x, v1.y, v1.z) 42 | positions.push(v2.x, v2.y, v2.z) 43 | positions.push(v3.x, v3.y, v3.z) 44 | 45 | colors.push(color.r, color.g, color.b, 1) 46 | colors.push(color.r, color.g, color.b, 1) 47 | colors.push(color.r, color.g, color.b, 1) 48 | } 49 | } 50 | 51 | const indices = [] 52 | for (let i = 0; i < positions.length / 3; i++) { 53 | indices.push(i) 54 | } 55 | 56 | const normals = [] 57 | 58 | const vertexData = new BABYLON.VertexData() 59 | BABYLON.VertexData.ComputeNormals(positions, indices, normals) 60 | 61 | vertexData.positions = positions 62 | vertexData.indices = indices 63 | vertexData.normals = normals 64 | vertexData.colors = colors 65 | 66 | vertexData.applyToMesh(customMesh) 67 | 68 | var mat = new BABYLON.StandardMaterial('mat', scene) 69 | mat.backFaceCulling = false 70 | customMesh.material = mat 71 | 72 | return customMesh 73 | } 74 | 75 | export { createConvexRegionHelper } 76 | -------------------------------------------------------------------------------- /examples/js/navigation/common/navmeshes/basic/buffer_navmesh.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/common/navmeshes/basic/buffer_navmesh.bin -------------------------------------------------------------------------------- /examples/js/navigation/common/navmeshes/basic/navmesh.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "accessors": [ 3 | { 4 | "bufferView": 0, 5 | "byteOffset": 0, 6 | "componentType": 5126, 7 | "count": 30, 8 | "max": [ 9 | 9.545417785644531, 10 | -2.9802322387695312e-08, 11 | 17.01628875732422 12 | ], 13 | "min": [ 14 | -9.954584121704102, 15 | -2.9802322387695312e-08, 16 | -10.283712387084961 17 | ], 18 | "name": "accessor_buffer_Navmesh.001_POSITION_0", 19 | "type": "VEC3" 20 | }, 21 | { 22 | "bufferView": 1, 23 | "byteOffset": 0, 24 | "componentType": 5126, 25 | "count": 30, 26 | "max": [ 27 | 0.0, 28 | 1.0, 29 | 1.5949328641311016e-15 30 | ], 31 | "min": [ 32 | 0.0, 33 | 0.9999999403953552, 34 | -8.310443533419282e-15 35 | ], 36 | "name": "accessor_buffer_Navmesh.001_NORMAL_0", 37 | "type": "VEC3" 38 | }, 39 | { 40 | "bufferView": 2, 41 | "byteOffset": 0, 42 | "componentType": 5123, 43 | "count": 51, 44 | "max": [ 45 | 29 46 | ], 47 | "min": [ 48 | 0 49 | ], 50 | "name": "accessor_buffer_Navmesh.001_0", 51 | "type": "SCALAR" 52 | } 53 | ], 54 | "asset": { 55 | "copyright": "", 56 | "generator": "blendergltf v1.2.0", 57 | "version": "2.0" 58 | }, 59 | "bufferViews": [ 60 | { 61 | "buffer": 0, 62 | "byteLength": 360, 63 | "byteOffset": 0, 64 | "byteStride": 12, 65 | "name": "bufferView_buffer_Navmesh.001_POSITION_0", 66 | "target": 34962 67 | }, 68 | { 69 | "buffer": 0, 70 | "byteLength": 360, 71 | "byteOffset": 360, 72 | "byteStride": 12, 73 | "name": "bufferView_buffer_Navmesh.001_NORMAL_0", 74 | "target": 34962 75 | }, 76 | { 77 | "buffer": 0, 78 | "byteLength": 104, 79 | "byteOffset": 720, 80 | "name": "bufferView_buffer_Navmesh.001_0", 81 | "target": 34963 82 | } 83 | ], 84 | "buffers": [ 85 | { 86 | "byteLength": 824, 87 | "name": "buffer_navmesh", 88 | "uri": "buffer_navmesh.bin" 89 | } 90 | ], 91 | "meshes": [ 92 | { 93 | "name": "Navmesh.001", 94 | "primitives": [ 95 | { 96 | "attributes": { 97 | "NORMAL": 1, 98 | "POSITION": 0 99 | }, 100 | "indices": 2, 101 | "mode": 4 102 | } 103 | ] 104 | } 105 | ], 106 | "nodes": [ 107 | { 108 | "mesh": 0, 109 | "name": "Navmesh", 110 | "rotation": [ 111 | 0.0, 112 | 0.0, 113 | 0.0, 114 | 1.0 115 | ], 116 | "scale": [ 117 | 1.0, 118 | 1.0, 119 | 1.0 120 | ], 121 | "translation": [ 122 | 0.0, 123 | 0.0, 124 | 0.0 125 | ] 126 | } 127 | ], 128 | "scene": 0, 129 | "scenes": [ 130 | { 131 | "extras": { 132 | "background_color": [ 133 | 0.05087608844041824, 134 | 0.05087608844041824, 135 | 0.05087608844041824 136 | ], 137 | "frames_per_second": 24 138 | }, 139 | "name": "Scene", 140 | "nodes": [ 141 | 0 142 | ] 143 | } 144 | ] 145 | } 146 | -------------------------------------------------------------------------------- /examples/js/navigation/common/navmeshes/complex/navmesh.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/common/navmeshes/complex/navmesh.glb -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/audio/step1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/firstperson/audio/step1.ogg -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/audio/step2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/firstperson/audio/step2.ogg -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | First-Person Controls 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 |
21 |

Click to Play

22 |

A navigation mesh defines the walkable area of this level.

23 |
24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import * as YUKA from '../../../../lib/yuka.module.js' 7 | import 'https://preview.babylonjs.com/babylon.js' 8 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 9 | import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 10 | 11 | import { FirstPersonControls } from './src/FirstPersonControls.js' 12 | import { Player } from './src/Player.js' 13 | 14 | let engine, scene, camera, step1, step2 15 | let entityManager, time, controls 16 | 17 | const entityMatrix = new BABYLON.Matrix() 18 | 19 | init() 20 | 21 | // 22 | 23 | function init() { 24 | const canvas = document.getElementById('renderCanvas') 25 | engine = new BABYLON.Engine(canvas, true, {}, true) 26 | 27 | if (BABYLON.Engine.audioEngine) { 28 | BABYLON.Engine.audioEngine.useCustomUnlockedButton = true 29 | } 30 | 31 | scene = new BABYLON.Scene(engine) 32 | scene.clearColor = BABYLON.Color3.FromHexString('#a0a0a0') 33 | scene.useRightHandedSystem = true 34 | 35 | camera = new BABYLON.UniversalCamera('camera', new BABYLON.Vector3(-13, 0.75, -9), scene, true) 36 | camera.minZ = 0.1 37 | 38 | scene.fogMode = BABYLON.Scene.FOGMODE_EXP2 39 | scene.fogColor = BABYLON.Color3.FromHexString('#a0a0a0') 40 | scene.fogDensity = 0.01 41 | 42 | // 43 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 250, height: 250 }, scene) 44 | ground.position.y = -5 45 | const groundMaterial = new BABYLON.StandardMaterial('grid', scene) 46 | groundMaterial.diffuseColor = BABYLON.Color3.FromHexString('#999999') 47 | ground.material = groundMaterial 48 | 49 | // 50 | 51 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0), scene) 52 | new BABYLON.DirectionalLight('dir-light', new BABYLON.Vector3(1, 1, 0), scene) 53 | 54 | // 55 | BABYLON.SceneLoader.ImportMesh(null, 'model/', 'house.glb', scene, (meshes) => { 56 | // 3D assets are loaded, now load nav mesh 57 | 58 | const loader = new YUKA.NavMeshLoader() 59 | loader.load('./navmesh/navmesh.glb', { epsilonCoplanarTest: 0.25 }).then((navMesh) => { 60 | const loadingScreen = document.getElementById('loading-screen') 61 | 62 | loadingScreen.classList.add('fade-out') 63 | loadingScreen.addEventListener('transitionend', onTransitionEnd) 64 | 65 | // 66 | 67 | step1 = new BABYLON.Sound('step1', 'audio/step1.ogg', scene, null, { 68 | loop: false, 69 | autoplay: false, 70 | }) 71 | 72 | step2 = new BABYLON.Sound('step2', 'audio/step2.ogg', scene, null, { 73 | loop: false, 74 | autoplay: false, 75 | }) 76 | 77 | // 78 | 79 | window.addEventListener('resize', onWindowResize, false) 80 | 81 | const intro = document.getElementById('intro') 82 | 83 | intro.addEventListener( 84 | 'click', 85 | () => { 86 | if (BABYLON.Engine.audioEngine) { 87 | BABYLON.Engine.audioEngine.unlock() 88 | } 89 | 90 | controls.connect() 91 | }, 92 | false 93 | ) 94 | 95 | // game setup 96 | 97 | entityManager = new YUKA.EntityManager() 98 | time = new YUKA.Time() 99 | 100 | const player = new Player() 101 | player.navMesh = navMesh 102 | player.head.setRenderComponent(camera, syncCamera) 103 | player.position.set(-13, -0.75, -9) 104 | 105 | controls = new FirstPersonControls(player) 106 | controls.setRotation(-2.2, 0.2) 107 | 108 | controls.sounds.set('rightStep', step1) 109 | controls.sounds.set('leftStep', step2) 110 | 111 | controls.addEventListener('lock', () => { 112 | intro.classList.add('hidden') 113 | }) 114 | 115 | controls.addEventListener('unlock', () => { 116 | intro.classList.remove('hidden') 117 | }) 118 | 119 | entityManager.add(player) 120 | 121 | animate() 122 | }) 123 | }) 124 | } 125 | 126 | function onWindowResize() { 127 | engine.resize() 128 | } 129 | 130 | function animate() { 131 | requestAnimationFrame(animate) 132 | 133 | const delta = time.update().getDelta() 134 | controls.update(delta) 135 | entityManager.update(delta) 136 | 137 | scene.render() 138 | } 139 | 140 | function syncCamera(entity, renderComponent) { 141 | renderComponent.getViewMatrix().copyFrom(BABYLON.Matrix.FromValues(...entity.worldMatrix.elements).invert()) 142 | } 143 | 144 | function onTransitionEnd(event) { 145 | event.target.remove() 146 | } 147 | -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/model/README.md: -------------------------------------------------------------------------------- 1 | Model from https://sketchfab.com/models/645533aee9f448b8b20e1f4fb4b472ca by linhtatoo 2 | License: CC Attribution 3 | -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/model/house.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/firstperson/model/house.glb -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/navmesh/navmesh.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/firstperson/navmesh/navmesh.glb -------------------------------------------------------------------------------- /examples/js/navigation/firstperson/src/Player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { MovingEntity, Vector3, GameEntity } from '../../../../../lib/yuka.module.js' 7 | 8 | const startPosition = new Vector3() 9 | const endPosition = new Vector3() 10 | 11 | class Player extends MovingEntity { 12 | constructor() { 13 | super() 14 | 15 | this.maxSpeed = 4 16 | this.height = 2 17 | 18 | this.head = new GameEntity() 19 | this.add(this.head) 20 | 21 | this.updateOrientation = false 22 | this.navMesh = null 23 | this.currentRegion = null 24 | } 25 | 26 | update(delta) { 27 | startPosition.copy(this.position) 28 | 29 | super.update(delta) 30 | 31 | endPosition.copy(this.position) 32 | 33 | // ensure the entity stays inside its navmesh 34 | 35 | this.currentRegion = this.navMesh.clampMovement(this.currentRegion, startPosition, endPosition, this.position) 36 | 37 | // adjust height of player according to the ground 38 | 39 | const distance = this.currentRegion.distanceToPoint(this.position) 40 | 41 | this.position.y -= distance * 0.2 // smooth transition 42 | } 43 | } 44 | 45 | export { Player } 46 | -------------------------------------------------------------------------------- /examples/js/navigation/navmesh/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Navmesh 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | Click on the navigation mesh to define a new target for the game entity.
13 | The colored areas represent the convex regions of the navigation mesh. 14 |

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/navigation/navmesh/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import * as YUKA from '../../../../lib/yuka.module.js' 7 | import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js' 8 | 9 | import 'https://preview.babylonjs.com/babylon.js' 10 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 11 | 12 | import { createGraphHelper } from '../../graph/GraphHelper.js' 13 | import { createConvexRegionHelper } from '../common/NavMeshHelper.js' 14 | import { createVehicle } from '../../creator.js' 15 | 16 | let engine, scene, plane, pathHelper, graphHelper, navMeshGroup, vehicleMesh 17 | let entityManager, time, vehicle, target 18 | let navMesh 19 | 20 | const entityMatrix = new BABYLON.Matrix() 21 | const pointer = new BABYLON.Vector2(1, 1) 22 | 23 | const params = { 24 | showNavigationGraph: true, 25 | } 26 | 27 | init() 28 | 29 | function init() { 30 | const canvas = document.getElementById('renderCanvas') 31 | engine = new BABYLON.Engine(canvas, true, {}, true) 32 | 33 | scene = new BABYLON.Scene(engine) 34 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 35 | scene.useRightHandedSystem = true 36 | 37 | const camera = new BABYLON.ArcRotateCamera( 38 | 'camera', 39 | BABYLON.Tools.ToRadians(40), 40 | BABYLON.Tools.ToRadians(60), 41 | 30, 42 | BABYLON.Vector3.Zero(), 43 | scene 44 | ) 45 | 46 | camera.target = new BABYLON.Vector3(0, 0, 0) 47 | camera.attachControl(canvas, true) 48 | 49 | const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, -1, 0)) 50 | light.intensity = 0.7 51 | 52 | // 53 | 54 | vehicleMesh = createVehicle(scene, { size: 1.5, y: 1 }) 55 | 56 | window.addEventListener('resize', onWindowResize, false) 57 | 58 | // gui 59 | 60 | const gui = new DAT.GUI({ width: 300 }) 61 | 62 | gui.add(params, 'showNavigationGraph', 1, 30).onChange((value) => { 63 | if (graphHelper) { 64 | graphHelper.setEnabled(value) 65 | } 66 | }) 67 | 68 | gui.open() 69 | 70 | // YUKA specific 71 | target = new YUKA.Vector3() 72 | 73 | entityManager = new YUKA.EntityManager() 74 | time = new YUKA.Time() 75 | 76 | const loader = new YUKA.NavMeshLoader() 77 | loader.load('../common/navmeshes/basic/navmesh.gltf').then((navigationMesh) => { 78 | vehicle = new YUKA.Vehicle() 79 | vehicle.maxSpeed = 1.5 80 | vehicle.maxForce = 10 81 | vehicle.setRenderComponent(vehicleMesh, sync) 82 | 83 | // visualize convex regions 84 | navMesh = navigationMesh 85 | navMeshGroup = createConvexRegionHelper(navMesh, scene) 86 | 87 | // visualize graph 88 | const graph = navMesh.graph 89 | graphHelper = createGraphHelper(scene, graph, 0.2) 90 | 91 | scene.onPointerMove = () => { 92 | var pickResult = scene.pick(scene.pointerX, scene.pointerY) 93 | if (pickResult?.pickedPoint) { 94 | target.x = pickResult.pickedPoint.x 95 | target.y = pickResult.pickedPoint.y 96 | target.z = pickResult.pickedPoint.z 97 | } 98 | } 99 | 100 | scene.onPointerPick = (e, pickResult) => { 101 | if (pickResult?.pickedPoint) { 102 | findPathTo(new YUKA.Vector3().copy(pickResult.pickedPoint)) 103 | } 104 | } 105 | const followPathBehavior = new YUKA.FollowPathBehavior() 106 | followPathBehavior.active = false 107 | vehicle.steering.add(followPathBehavior) 108 | 109 | entityManager.add(vehicle) 110 | animate() 111 | }) 112 | } 113 | 114 | function onWindowResize() { 115 | engine.resize() 116 | } 117 | 118 | function animate() { 119 | requestAnimationFrame(animate) 120 | 121 | const delta = time.update().getDelta() 122 | entityManager.update(delta) 123 | 124 | scene.render() 125 | } 126 | 127 | function findPathTo(target) { 128 | const from = vehicle.position 129 | const to = target 130 | 131 | const path = navMesh.findPath(from, to) 132 | 133 | // 134 | 135 | if (pathHelper) { 136 | pathHelper.dispose() 137 | } 138 | pathHelper = BABYLON.MeshBuilder.CreateLines( 139 | 'path-helper', 140 | { 141 | points: path, 142 | updatable: false, 143 | }, 144 | scene 145 | ) 146 | pathHelper.color = BABYLON.Color3.Red() 147 | 148 | // 149 | 150 | const followPathBehavior = vehicle.steering.behaviors[0] 151 | followPathBehavior.active = true 152 | followPathBehavior.path.clear() 153 | 154 | for (const point of path) { 155 | followPathBehavior.path.add(point) 156 | } 157 | } 158 | 159 | function sync(entity, renderComponent) { 160 | BABYLON.Matrix.FromValues(...entity.worldMatrix.elements).decomposeToTransformNode(renderComponent) 161 | } 162 | -------------------------------------------------------------------------------- /examples/js/navigation/navmeshPerformance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Navmesh with Spatial Index and Tasks 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 |

23 | Navigation Mesh with Spatial Index and Tasks
24 | Active Game Entities:
25 | Convex Regions of NavMesh:
26 | Partitions of Spatial Index:
27 |

28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/js/navigation/navmeshPerformance/model/README.md: -------------------------------------------------------------------------------- 1 | Model from https://sketchfab.com/models/7842d43173c54ec18da10246cdc6da71 by hansolocambo 2 | License: CC Attribution 3 | -------------------------------------------------------------------------------- /examples/js/navigation/navmeshPerformance/model/level.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/navigation/navmeshPerformance/model/level.glb -------------------------------------------------------------------------------- /examples/js/navigation/navmeshPerformance/src/CustomVehicle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { Vehicle } from '../../../../../lib/yuka.module.js' 7 | 8 | class CustomVehicle extends Vehicle { 9 | constructor() { 10 | super() 11 | 12 | this.navMesh = null 13 | 14 | this.currentRegion = null 15 | this.fromRegion = null 16 | this.toRegion = null 17 | } 18 | 19 | update(delta) { 20 | super.update(delta) 21 | 22 | // this code is used to adjust the height of the entity according to its current region 23 | 24 | const currentRegion = this.navMesh.getRegionForPoint(this.position, 1) 25 | 26 | if (currentRegion !== null) { 27 | this.currentRegion = currentRegion 28 | 29 | const distance = this.currentRegion.distanceToPoint(this.position) 30 | 31 | this.position.y -= distance * 0.2 32 | } 33 | 34 | return this 35 | } 36 | } 37 | 38 | export { CustomVehicle } 39 | -------------------------------------------------------------------------------- /examples/js/navigation/navmeshPerformance/src/PathPlanner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { TaskQueue } from '../../../../../lib/yuka.module.js' 7 | import { PathPlannerTask } from './PathPlannerTask.js' 8 | 9 | class PathPlanner { 10 | constructor(navMesh) { 11 | this.navMesh = navMesh 12 | 13 | this.taskQueue = new TaskQueue() 14 | } 15 | 16 | findPath(vehicle, from, to, callback) { 17 | const task = new PathPlannerTask(this, vehicle, from, to, callback) 18 | 19 | this.taskQueue.enqueue(task) 20 | } 21 | 22 | update() { 23 | this.taskQueue.update() 24 | } 25 | } 26 | 27 | export { PathPlanner } 28 | -------------------------------------------------------------------------------- /examples/js/navigation/navmeshPerformance/src/PathPlannerTask.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { Task } from '../../../../../lib/yuka.module.js' 7 | 8 | class PathPlannerTask extends Task { 9 | constructor(planner, vehicle, from, to, callback) { 10 | super() 11 | 12 | this.callback = callback 13 | this.planner = planner 14 | this.vehicle = vehicle 15 | this.from = from 16 | this.to = to 17 | } 18 | 19 | execute() { 20 | const path = this.planner.navMesh.findPath(this.from, this.to) 21 | 22 | this.callback(this.vehicle, path) 23 | } 24 | } 25 | 26 | export { PathPlannerTask } 27 | -------------------------------------------------------------------------------- /examples/js/perception/common/Obstacle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { GameEntity } from '../../../../lib/yuka.module.js' 7 | 8 | class Obstacle extends GameEntity { 9 | constructor(mesh) { 10 | super() 11 | this.mesh = mesh 12 | } 13 | 14 | lineOfSightTest(ray, intersectionPoint) { 15 | return this.mesh.intersectRay(ray, this.worldMatrix, true, intersectionPoint) 16 | } 17 | } 18 | 19 | export { Obstacle } 20 | -------------------------------------------------------------------------------- /examples/js/perception/common/VisionHelper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | function createVisionHelper(vision, division = 8, scene) { 7 | const fieldOfView = vision.fieldOfView 8 | const range = vision.range 9 | 10 | const customMesh = new BABYLON.Mesh('custom', scene) 11 | const customMeshMaterial = new BABYLON.StandardMaterial('custom-mesh', scene) 12 | customMeshMaterial.wireframe = true 13 | customMesh.material = customMeshMaterial 14 | customMeshMaterial.emissiveColor = new BABYLON.Color3(0.7, 0.7, 0.7) 15 | customMeshMaterial.disableLighting = true 16 | 17 | const positions = [] 18 | 19 | const foV05 = fieldOfView / 2 20 | const step = fieldOfView / division 21 | 22 | // for now, let's create a simple helper that lies in the xz plane 23 | 24 | for (let i = -foV05; i < foV05; i += step) { 25 | positions.push(0, 0, 0) 26 | positions.push(Math.sin(i) * range, 0, Math.cos(i) * range) 27 | positions.push(Math.sin(i + step) * range, 0, Math.cos(i + step) * range) 28 | } 29 | 30 | const indices = [] 31 | for (let i = 0; i < positions.length / 3; i++) { 32 | indices.push(i) 33 | } 34 | 35 | const normals = [] 36 | 37 | const vertexData = new BABYLON.VertexData() 38 | BABYLON.VertexData.ComputeNormals(positions, indices, normals) 39 | 40 | vertexData.positions = positions 41 | vertexData.indices = indices 42 | vertexData.normals = normals 43 | 44 | vertexData.applyToMesh(customMesh) 45 | 46 | return customMesh 47 | } 48 | 49 | export { createVisionHelper } 50 | -------------------------------------------------------------------------------- /examples/js/perception/lineOfSight/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Perception | Line of Sight 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | The white fan represents the visibility range of the game entity.
15 | When the target is visible for the game entity, the target's color changes to green. 16 |

17 |
18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js/perception/lineOfSight/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import * as YUKA from '../../../../../lib/yuka.module.js' 7 | 8 | import 'https://preview.babylonjs.com/babylon.js' 9 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 10 | 11 | import { createVisionHelper } from '../common/VisionHelper.js' 12 | import { Obstacle } from '../common/Obstacle.js' 13 | import { createVehicle, VehicleTypes } from '../../creator.js' 14 | 15 | let engine, scene, targetMaterial 16 | let entityManager, time, entity, target 17 | 18 | const entityMatrix = new BABYLON.Matrix() 19 | const pointer = new BABYLON.Vector2(1, 1) 20 | 21 | init() 22 | animate() 23 | 24 | function init() { 25 | const canvas = document.getElementById('renderCanvas') 26 | engine = new BABYLON.Engine(canvas, true, {}, true) 27 | 28 | scene = new BABYLON.Scene(engine) 29 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 30 | scene.useRightHandedSystem = true 31 | 32 | // scene.debugLayer.show() 33 | 34 | const camera = new BABYLON.ArcRotateCamera( 35 | 'camera', 36 | BABYLON.Tools.ToRadians(90), 37 | BABYLON.Tools.ToRadians(40), 38 | 12, 39 | new BABYLON.Vector3(0, 0, 0), 40 | scene 41 | ) 42 | 43 | camera.setTarget(new BABYLON.Vector3(0, -4, 0)) 44 | camera.attachControl(canvas, true) 45 | 46 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 47 | 48 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 10, height: 10 }, scene) 49 | ground.position.y = -1 50 | ground.position.z = 1 51 | const groundMaterial = new BABYLON.GridMaterial('grid', scene) 52 | groundMaterial.gridRatio = 0.4 53 | ground.visibility = 0.35 54 | ground.material = groundMaterial 55 | 56 | const obstacleMesh = BABYLON.MeshBuilder.CreateBox('obstacleMesh', { width: 2, height: 2, depth: 0.2 }, scene) 57 | obstacleMesh.rotation.y = Math.PI 58 | obstacleMesh.position.z = 2 59 | 60 | obstacleMesh.material = new BABYLON.StandardMaterial('obstacle', scene) 61 | obstacleMesh.material.backFaceCulling = false 62 | 63 | const entityMesh = createVehicle(scene, { type: VehicleTypes.cone }) 64 | 65 | const targetMesh = BABYLON.MeshBuilder.CreateSphere('target', { diameter: 0.15, segments: 8 }, scene) 66 | targetMaterial = new BABYLON.StandardMaterial('target', scene) 67 | targetMesh.material = targetMaterial 68 | targetMaterial.disableLighting = true 69 | 70 | scene.onPointerMove = () => { 71 | var pickResult = scene.pick(scene.pointerX, scene.pointerY) 72 | if (pickResult?.pickedPoint) { 73 | target.x = pickResult.pickedPoint.x 74 | target.y = pickResult.pickedPoint.y 75 | target.z = pickResult.pickedPoint.z 76 | } 77 | } 78 | 79 | window.addEventListener('resize', onWindowResize, false) 80 | 81 | // YUKA specific 82 | entityManager = new YUKA.EntityManager() 83 | time = new YUKA.Time() 84 | 85 | const vertices = obstacleMesh.getVerticesData(BABYLON.VertexBuffer.PositionKind) 86 | const indices = obstacleMesh.getIndices() 87 | const geometry = new YUKA.MeshGeometry(vertices, indices) 88 | 89 | const obstacle = new Obstacle(geometry) 90 | obstacle.position.z = 3 91 | obstacle.setRenderComponent(obstacleMesh, sync) 92 | 93 | target = new YUKA.GameEntity() 94 | target.setRenderComponent(targetMesh, sync) 95 | 96 | entity = new YUKA.GameEntity() 97 | entity.setRenderComponent(entityMesh, sync) 98 | 99 | const vision = new YUKA.Vision(entity) 100 | vision.range = 5 101 | vision.fieldOfView = Math.PI * 0.5 102 | vision.addObstacle(obstacle) 103 | entity.vision = vision 104 | 105 | const helper = createVisionHelper(vision) 106 | 107 | entityManager.add(entity) 108 | entityManager.add(obstacle) 109 | entityManager.add(target) 110 | } 111 | 112 | function onWindowResize() { 113 | engine.resize() 114 | } 115 | 116 | function animate() { 117 | requestAnimationFrame(animate) 118 | 119 | const delta = time.update().getDelta() 120 | const elapsed = time.getElapsed() 121 | 122 | // change color of target if visible 123 | target.position.set(Math.sin(elapsed * 0.5) * 4, 0, 4) 124 | 125 | if (entity.vision.visible(target.position) === true) { 126 | targetMaterial.emissiveColor = new BABYLON.Color3(0, 1, 0) 127 | } else { 128 | targetMaterial.emissiveColor = new BABYLON.Color3(1, 0, 0) 129 | } 130 | 131 | entityManager.update(delta) 132 | 133 | scene.render() 134 | } 135 | 136 | function sync(entity, renderComponent) { 137 | BABYLON.Matrix.FromValues(...entity.worldMatrix.elements).decomposeToTransformNode(renderComponent) 138 | } 139 | -------------------------------------------------------------------------------- /examples/js/perception/memorySystem/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Perception | Memory System 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

14 | The game entity uses a memory system to remember the last sensed position of the target.
15 | If the target is outside the game entity's visual range for a certain amout of time,
16 | the game entity forgets the target. 17 |

18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js/perception/memorySystem/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import * as YUKA from '../../../../lib/yuka.module.js' 7 | // import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js'; 8 | 9 | import 'https://preview.babylonjs.com/babylon.js' 10 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 11 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 12 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 13 | 14 | import { CustomEntity } from './src/CustomEntity.js' 15 | import { Obstacle } from '../common/Obstacle.js' 16 | import { createVisionHelper } from '../common/VisionHelper.js' 17 | import { createVehicle } from '../../creator.js' 18 | 19 | let engine, scene, targetMaterial 20 | let entityManager, time, entity, target 21 | 22 | const entityMatrix = new BABYLON.Matrix() 23 | const pointer = new BABYLON.Vector2(1, 1) 24 | 25 | init() 26 | animate() 27 | 28 | function init() { 29 | const canvas = document.getElementById('renderCanvas') 30 | engine = new BABYLON.Engine(canvas, true, {}, true) 31 | 32 | scene = new BABYLON.Scene(engine) 33 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 34 | scene.useRightHandedSystem = true 35 | 36 | // scene.debugLayer.show() 37 | 38 | const camera = new BABYLON.ArcRotateCamera( 39 | 'camera', 40 | BABYLON.Tools.ToRadians(90), 41 | BABYLON.Tools.ToRadians(40), 42 | 12, 43 | new BABYLON.Vector3(0, 0, 0), 44 | scene 45 | ) 46 | 47 | camera.setTarget(new BABYLON.Vector3(0, -4, 0)) 48 | camera.attachControl(canvas, true) 49 | 50 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 51 | 52 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 10, height: 10 }, scene) 53 | ground.position.y = -1 54 | ground.position.z = 1 55 | const groundMaterial = new BABYLON.GridMaterial('grid', scene) 56 | groundMaterial.gridRatio = 0.4 57 | ground.visibility = 0.35 58 | ground.material = groundMaterial 59 | 60 | const obstacleMesh = BABYLON.MeshBuilder.CreateBox('obstacleMesh', { width: 2, height: 2, depth: 0.2 }, scene) 61 | obstacleMesh.rotation.y = Math.PI 62 | obstacleMesh.position.z = 2 63 | obstacleMesh.material = new BABYLON.StandardMaterial('obstacle', scene) 64 | obstacleMesh.material.backFaceCulling = false 65 | 66 | const entityMesh = createVehicle(scene) 67 | 68 | const targetMesh = BABYLON.MeshBuilder.CreateSphere('target', { diameter: 0.15, segments: 8 }, scene) 69 | targetMaterial = new BABYLON.StandardMaterial('target', scene) 70 | targetMesh.material = targetMaterial 71 | targetMaterial.emissiveColor = BABYLON.Color3.Red() 72 | targetMaterial.disableLighting = true 73 | 74 | scene.onPointerMove = () => { 75 | var pickResult = scene.pick(scene.pointerX, scene.pointerY) 76 | if (pickResult?.pickedPoint) { 77 | target.x = pickResult.pickedPoint.x 78 | target.y = pickResult.pickedPoint.y 79 | target.z = pickResult.pickedPoint.z 80 | } 81 | } 82 | 83 | window.addEventListener('resize', onWindowResize, false) 84 | 85 | // YUKA specific 86 | entityManager = new YUKA.EntityManager() 87 | time = new YUKA.Time() 88 | 89 | const vertices = obstacleMesh.getVerticesData(BABYLON.VertexBuffer.PositionKind) 90 | const indices = obstacleMesh.getIndices() 91 | const geometry = new YUKA.MeshGeometry(vertices, indices) 92 | 93 | const obstacle = new Obstacle(geometry) 94 | obstacle.name = 'obstacle' 95 | obstacle.position.z = 3 96 | obstacle.setRenderComponent(obstacleMesh, sync) 97 | 98 | target = new YUKA.GameEntity() 99 | target.name = 'target' 100 | target.setRenderComponent(targetMesh, sync) 101 | 102 | const entity = new CustomEntity() 103 | entity.setRenderComponent(entityMesh, sync) 104 | 105 | const helper = createVisionHelper(entity.vision) 106 | 107 | entityManager.add(entity) 108 | entityManager.add(obstacle) 109 | entityManager.add(target) 110 | } 111 | 112 | function onWindowResize() { 113 | engine.resize() 114 | } 115 | 116 | function animate() { 117 | requestAnimationFrame(animate) 118 | 119 | time.update() 120 | 121 | const delta = time.getDelta() 122 | const elapsed = time.getElapsed() 123 | 124 | target.position.set(Math.sin(elapsed * 0.3) * 5, 0, 4) 125 | 126 | entityManager.update(delta) 127 | 128 | scene.render() 129 | } 130 | 131 | function sync(entity, renderComponent) { 132 | BABYLON.Matrix.FromValues(...entity.worldMatrix.elements).decomposeToTransformNode(renderComponent) 133 | } 134 | -------------------------------------------------------------------------------- /examples/js/perception/memorySystem/src/CustomEntity.js: -------------------------------------------------------------------------------- 1 | import { GameEntity, Vision, MemorySystem } from '../../../../../lib/yuka.module.js' 2 | 3 | class CustomEntity extends GameEntity { 4 | constructor() { 5 | super() 6 | 7 | this.memorySystem = new MemorySystem() 8 | this.memorySystem.memorySpan = 3 9 | 10 | this.vision = new Vision(this) 11 | this.vision.range = 5 12 | this.vision.fieldOfView = Math.PI * 0.5 13 | 14 | this.maxTurnRate = Math.PI * 0.5 15 | 16 | this.currentTime = 0 17 | this.memoryRecords = new Array() 18 | 19 | this.target = null 20 | } 21 | 22 | start() { 23 | const target = this.manager.getEntityByName('target') 24 | const obstacle = this.manager.getEntityByName('obstacle') 25 | 26 | this.target = target 27 | this.vision.addObstacle(obstacle) 28 | 29 | return this 30 | } 31 | 32 | update(delta) { 33 | this.currentTime += delta 34 | 35 | // In many scenarios it is not necessary to update the vision in each 36 | // simulation step. A regulator could be used to restrict the update rate. 37 | 38 | this.updateVision() 39 | 40 | // get a list of all recently sensed game entities 41 | 42 | this.memorySystem.getValidMemoryRecords(this.currentTime, this.memoryRecords) 43 | 44 | if (this.memoryRecords.length > 0) { 45 | // Pick the first one. It's highly application specific what record is chosen 46 | // for further processing. 47 | 48 | const record = this.memoryRecords[0] 49 | const entity = record.entity 50 | 51 | // if the game entity is visible, directly rotate towards it. Otherwise, focus 52 | // the last known position 53 | 54 | if (record.visible === true) { 55 | this.rotateTo(entity.position, delta) 56 | entity._renderComponent.material.emissiveColor = new BABYLON.Color3(0, 1, 0) // some visual feedback 57 | } else { 58 | // only rotate to the last sensed position if the entity was seen at least once 59 | 60 | if (record.timeLastSensed !== -1) { 61 | this.rotateTo(record.lastSensedPosition, delta) 62 | 63 | entity._renderComponent.material.emissiveColor = new BABYLON.Color3(1, 0, 0) // some visual feedback 64 | } 65 | } 66 | } else { 67 | // rotate back to default 68 | 69 | this.rotateTo(this.forward, delta) 70 | } 71 | 72 | return this 73 | } 74 | 75 | updateVision() { 76 | const memorySystem = this.memorySystem 77 | const vision = this.vision 78 | const target = this.target 79 | 80 | if (memorySystem.hasRecord(target) === false) { 81 | memorySystem.createRecord(target) 82 | } 83 | 84 | const record = memorySystem.getRecord(target) 85 | 86 | if (vision.visible(target.position) === true) { 87 | record.timeLastSensed = this.currentTime 88 | record.lastSensedPosition.copy(target.position) 89 | record.visible = true 90 | } else { 91 | record.visible = false 92 | } 93 | } 94 | } 95 | 96 | export { CustomEntity } 97 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/dead.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/dead.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/empty.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/empty.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/impact1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/impact1.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/impact2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/impact2.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/impact3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/impact3.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/impact4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/impact4.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/impact5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/impact5.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/reload.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/reload.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/shot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/shot.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/shot_reload.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/shot_reload.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/step1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/step1.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/audio/step2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/audio/step2.ogg -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Hide And Seek 4 | 5 | 6 | 7 | 8 | 83 | 84 | 85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | 95 |
96 |
97 |

Click to Play

98 |

Try to frag as many enemies as you can within 60 seconds. Have fun 🙌!

99 |
100 |
101 |

Game Over

102 |

Congratulations! You've fragged enemies.

103 |

104 | 105 |

106 |
107 |
108 | 109 | 112 | 113 |
114 |
115 | 116 |
117 |
118 | 119 |
120 |
Hits:
121 |
122 | 123 |
124 |
125 | 126 | | 127 | 128 |
129 |
130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/index.js: -------------------------------------------------------------------------------- 1 | import world from './src/World.js' 2 | 3 | init() 4 | 5 | function init() { 6 | void world.init() 7 | } 8 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/model/README.md: -------------------------------------------------------------------------------- 1 | Shotgun from https://sketchfab.com/models/53b158b0d5a54b4491b09d1fb3058e29 by Harry_L 2 | License: CC Attribution 3 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/model/bulletHole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/model/bulletHole.png -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/model/muzzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/model/muzzle.png -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/model/shotgun-new.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/model/shotgun-new.glb -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/model/shotgun.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/model/shotgun.glb -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/src/Bullet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { MovingEntity, MathUtils, Ray, Vector3 } from '../../../../../lib/yuka.module.js' 7 | import world from './World.js' 8 | import { Enemy } from './Enemy.js' 9 | 10 | const intersectionPoint = new Vector3() 11 | const normal = new Vector3() 12 | const ray = new Ray() 13 | 14 | class Bullet extends MovingEntity { 15 | constructor(owner = null, ray = new Ray()) { 16 | super() 17 | 18 | this.owner = owner 19 | this.ray = ray 20 | 21 | this.maxSpeed = 400 // 400 m/s 22 | 23 | this.position.copy(ray.origin) 24 | this.velocity.copy(ray.direction).multiplyScalar(this.maxSpeed) 25 | 26 | const s = 1 + Math.random() * 3 // scale the shot line a bit 27 | 28 | this.lifetime = 1 29 | this.currentTime = 0 30 | } 31 | 32 | update(delta) { 33 | this.currentTime += delta 34 | 35 | if (this.currentTime > this.lifetime) { 36 | world.remove(this) 37 | } else { 38 | ray.copy(this.ray) 39 | ray.origin.copy(this.position) 40 | 41 | super.update(delta) 42 | 43 | const obstacle = world.intersectRay(ray, intersectionPoint, normal) 44 | 45 | if (obstacle !== null) { 46 | // calculate distance from origin to intersection point 47 | 48 | const distanceToIntersection = ray.origin.squaredDistanceTo(intersectionPoint) 49 | const validDistance = ray.origin.squaredDistanceTo(this.position) 50 | 51 | if (distanceToIntersection <= validDistance) { 52 | // hit! 53 | 54 | const audio = world.audios.get('impact' + MathUtils.randInt(1, 5)) 55 | if (audio.isPlaying === true) { 56 | audio.stop() 57 | } 58 | audio.play() 59 | 60 | // inform game entity about hit 61 | 62 | this.owner.sendMessage(obstacle, 'hit') 63 | 64 | // add visual feedback 65 | if (obstacle instanceof Enemy === false) { 66 | world.addBulletHole(intersectionPoint, normal, audio) 67 | } else { 68 | // enemy hit 69 | world.assetManager.explodeEnemy(obstacle._renderComponent) 70 | } 71 | 72 | // remove bullet from world 73 | 74 | world.remove(this) 75 | } 76 | } 77 | } 78 | 79 | return this 80 | } 81 | } 82 | 83 | export { Bullet } 84 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/src/CustomObstacle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { GameEntity } from '../../../../../lib/yuka.module.js' 7 | 8 | class CustomObstacle extends GameEntity { 9 | constructor(geometry) { 10 | super() 11 | 12 | this.geometry = geometry 13 | } 14 | 15 | handleMessage() { 16 | // do nothing 17 | 18 | return true 19 | } 20 | } 21 | 22 | export { CustomObstacle } 23 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/src/Enemy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { Vehicle } from '../../../../../lib/yuka.module.js' 7 | 8 | import { HideBehavior } from './HideBehavior.js' 9 | import world from './World.js' 10 | 11 | class Enemy extends Vehicle { 12 | constructor(geometry) { 13 | super() 14 | 15 | this.name = 'enemy' 16 | 17 | this.geometry = geometry 18 | this.maxSpeed = 5 19 | this.deathAnimDuration = 0.5 20 | this.currentTime = 0 21 | this.dead = false 22 | this.notifiedWorld = false 23 | this.spawningPoint = null 24 | } 25 | 26 | start() { 27 | const player = this.manager.getEntityByName('player') 28 | 29 | const hideBehavior = new HideBehavior(this.manager, player) 30 | this.steering.add(hideBehavior) 31 | 32 | return this 33 | } 34 | 35 | update(delta) { 36 | super.update(delta) 37 | 38 | if (this.dead) { 39 | if (this.notifiedWorld === false) { 40 | this.notifiedWorld = true 41 | world.hits++ 42 | world.refreshUI() 43 | 44 | // 45 | 46 | const audio = world.audios.get('dead') 47 | if (audio.isPlaying === true) { 48 | audio.stop() 49 | } 50 | audio.play() 51 | 52 | audio.attachToMesh(this._renderComponent) 53 | } 54 | 55 | this.currentTime += delta 56 | 57 | if (this.currentTime <= this.deathAnimDuration) { 58 | const value = this.currentTime / this.deathAnimDuration 59 | 60 | // const shader = this._renderComponent.material.userData.shader 61 | 62 | // shader.uniforms.alpha.value = value <= 1 ? value : 1 63 | // this._renderComponent.material.opacity = 1 - shader.uniforms.alpha.value 64 | } else { 65 | world.remove(this) 66 | } 67 | } 68 | 69 | return this 70 | } 71 | 72 | handleMessage() { 73 | this.dead = true 74 | 75 | // this._renderComponent.castShadow = false 76 | 77 | return true 78 | } 79 | } 80 | 81 | export { Enemy } 82 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/src/Ground.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { GameEntity } from '../../../../../lib/yuka.module.js' 7 | 8 | class Ground extends GameEntity { 9 | constructor(geometry) { 10 | super() 11 | this.geometry = geometry 12 | this.name = 'ground' 13 | } 14 | 15 | handleMessage() { 16 | // do nothing 17 | 18 | return true 19 | } 20 | } 21 | 22 | export { Ground } 23 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/src/Player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { MovingEntity, GameEntity, Quaternion, AABB, Ray, Vector3 } from '../../../../../lib/yuka.module.js' 7 | import { CustomObstacle } from './CustomObstacle.js' 8 | 9 | import { Shotgun } from './Shotgun.js' 10 | import world from './World.js' 11 | 12 | const q = new Quaternion() 13 | const aabb = new AABB() 14 | const ray = new Ray() 15 | const intersectionPoint = new Vector3() 16 | const intersectionNormal = new Vector3() 17 | const reflectionVector = new Vector3() 18 | 19 | class Player extends MovingEntity { 20 | constructor(camera) { 21 | super() 22 | 23 | this.name = 'player' 24 | this.camera = camera 25 | 26 | this.boundingRadius = 1 27 | 28 | this.headContainer = new GameEntity() 29 | this.add(this.headContainer) 30 | 31 | this.head = new GameEntity() 32 | this.head.position.set(0, 2, 0) 33 | this.headContainer.add(this.head) 34 | 35 | this.weaponContainer = new GameEntity() 36 | this.head.add(this.weaponContainer) 37 | 38 | this.weapon = new Shotgun(this) 39 | this.weaponContainer.add(this.weapon) 40 | 41 | // 42 | 43 | this.forward.set(0, 0, -1) 44 | this.maxSpeed = 8 45 | this.updateOrientation = false 46 | } 47 | 48 | getDirection(result) { 49 | q.multiplyQuaternions(this.rotation, this.head.rotation) 50 | 51 | return result.copy(this.forward).applyRotation(q).normalize() 52 | } 53 | 54 | update(delta) { 55 | const obstacles = world.obstacles 56 | 57 | for (let i = 0, l = obstacles.length; i < l; i++) { 58 | const obstacle = obstacles[i] 59 | 60 | if (obstacle instanceof CustomObstacle) { 61 | // first check bounding volumes for intersection 62 | 63 | const squaredDistance = this.position.squaredDistanceTo(obstacle.position) 64 | const range = this.boundingRadius + obstacle.boundingRadius 65 | 66 | if (squaredDistance <= range * range) { 67 | // compute AABB in world space for obstacle 68 | 69 | aabb.copy(obstacle.geometry.aabb).applyMatrix4(obstacle.worldMatrix) 70 | 71 | // enhance the AABB with the bounding radius of the player 72 | 73 | aabb.max.addScalar(this.boundingRadius) 74 | aabb.min.subScalar(this.boundingRadius) 75 | 76 | // setup ray 77 | 78 | ray.origin.copy(this.position) 79 | ray.direction.copy(this.velocity).normalize() 80 | 81 | // perform ray/AABB intersection test 82 | 83 | if (ray.intersectAABB(aabb, intersectionPoint) !== null) { 84 | // derive normal vector 85 | 86 | aabb.getNormalFromSurfacePoint(intersectionPoint, intersectionNormal) 87 | 88 | // compute reflection vector 89 | 90 | reflectionVector.copy(ray.direction).reflect(intersectionNormal) 91 | 92 | // compute new velocity vector 93 | 94 | const speed = this.getSpeed() 95 | 96 | this.velocity.addVectors(ray.direction, reflectionVector).normalize() 97 | 98 | const f = 1 - Math.abs(intersectionNormal.dot(ray.direction)) 99 | 100 | this.velocity.multiplyScalar(speed * f) 101 | } 102 | } 103 | } 104 | } 105 | 106 | return super.update(delta) 107 | } 108 | } 109 | 110 | export { Player } 111 | -------------------------------------------------------------------------------- /examples/js/playground/hideAndSeek/textures/sparkStretched.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/hideAndSeek/textures/sparkStretched.png -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/empty.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/empty.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/impact1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/impact1.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/impact2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/impact2.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/impact3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/impact3.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/impact4.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/impact4.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/impact5.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/impact5.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/reload.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/reload.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/shot.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/shot.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/step1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/step1.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/audio/step2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/audio/step2.ogg -------------------------------------------------------------------------------- /examples/js/playground/shooter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | First-Person Shooter 4 | 5 | 6 | 7 | 8 | 66 | 67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | 78 |
79 |

Click to Play

80 |

81 | This demo implements some basic concepts of First-Person shooters e.g. simulating bullets and collision 82 | detection. 83 |

84 |
85 | 86 | 89 | 90 | 93 | 94 |
95 |
96 | 97 | | 98 | 99 |
100 |
101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /examples/js/playground/shooter/index.js: -------------------------------------------------------------------------------- 1 | import world from './src/World.js' 2 | 3 | init() 4 | 5 | function init() { 6 | void world.init() 7 | } 8 | -------------------------------------------------------------------------------- /examples/js/playground/shooter/model/README.md: -------------------------------------------------------------------------------- 1 | SciFi Gun from https://sketchfab.com/models/04a9f3ccb5b14dc38a28b27c1916e18e by Feche Pedroza 2 | License: CC Attribution 3 | 4 | Target (Hackathon tournament) from https://sketchfab.com/models/e2f631c75c83440887d2613fe4aeb84c by NikiYani 5 | License: CC Attribution 6 | -------------------------------------------------------------------------------- /examples/js/playground/shooter/model/bulletHole.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/model/bulletHole.png -------------------------------------------------------------------------------- /examples/js/playground/shooter/model/gun.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/model/gun.glb -------------------------------------------------------------------------------- /examples/js/playground/shooter/model/muzzle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/model/muzzle.png -------------------------------------------------------------------------------- /examples/js/playground/shooter/model/target.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/playground/shooter/model/target.glb -------------------------------------------------------------------------------- /examples/js/playground/shooter/src/Bullet.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { MovingEntity, MathUtils, Ray, Vector3 } from '../../../../../lib/yuka.module.js' 7 | import world from './World.js' 8 | 9 | const intersectionPoint = new Vector3() 10 | const normal = new Vector3() 11 | const ray = new Ray() 12 | 13 | class Bullet extends MovingEntity { 14 | constructor(owner = null, ray = new Ray()) { 15 | super() 16 | 17 | this.owner = owner 18 | this.ray = ray 19 | 20 | this.maxSpeed = 400 // 400 m/s 21 | 22 | this.position.copy(ray.origin) 23 | this.velocity.copy(ray.direction).multiplyScalar(this.maxSpeed) 24 | 25 | const s = 1 + Math.random() * 3 // scale the shot line a bit 26 | 27 | this.lifetime = 100 28 | this.currentTime = 0 29 | } 30 | 31 | update(delta) { 32 | this.currentTime += delta 33 | 34 | if (this.currentTime > this.lifetime) { 35 | world.remove(this) 36 | } else { 37 | ray.copy(this.ray) 38 | ray.origin.copy(this.position) 39 | super.update(delta) 40 | 41 | const entity = world.intersectRay(ray, intersectionPoint, normal) 42 | 43 | if (entity !== null && entity.name === 'target') { 44 | // calculate distance from origin to intersection point 45 | const distanceToIntersection = ray.origin.squaredDistanceTo(intersectionPoint) 46 | const validDistance = ray.origin.squaredDistanceTo(this.position) 47 | 48 | if (distanceToIntersection <= validDistance) { 49 | // hit! 50 | const audio = world.audios.get('impact' + MathUtils.randInt(1, 5)) 51 | 52 | if (audio.isPlaying === true) { 53 | audio.stop() 54 | } 55 | audio.play() 56 | 57 | // inform game entity about hit 58 | this.owner.sendMessage(entity, 'hit') 59 | 60 | // add visual feedback 61 | world.addBulletHole(intersectionPoint, normal, audio) 62 | 63 | // remove bullet from world 64 | world.remove(this) 65 | } 66 | } 67 | } 68 | 69 | return this 70 | } 71 | } 72 | 73 | export { Bullet } 74 | -------------------------------------------------------------------------------- /examples/js/playground/shooter/src/Ground.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { GameEntity } from '../../../../../lib/yuka.module.js' 7 | 8 | class Ground extends GameEntity { 9 | constructor(geometry) { 10 | super() 11 | this.geometry = geometry 12 | } 13 | 14 | handleMessage() { 15 | // do nothing 16 | 17 | return true 18 | } 19 | } 20 | 21 | export { Ground } 22 | -------------------------------------------------------------------------------- /examples/js/playground/shooter/src/Player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { MovingEntity, GameEntity, Quaternion } from '../../../../../lib/yuka.module.js' 7 | import { Blaster } from './Blaster.js' 8 | 9 | const q = new Quaternion() 10 | 11 | class Player extends MovingEntity { 12 | constructor(camera) { 13 | super() 14 | this.camera = camera 15 | 16 | this.headContainer = new GameEntity() 17 | this.add(this.headContainer) 18 | 19 | this.head = new GameEntity() 20 | this.head.position.set(0, 2, 10) 21 | this.headContainer.add(this.head) 22 | 23 | this.weaponContainer = new GameEntity() 24 | this.head.add(this.weaponContainer) 25 | 26 | this.weapon = new Blaster(this) 27 | this.weaponContainer.add(this.weapon) 28 | 29 | // 30 | 31 | this.forward.set(0, 0, -1) 32 | this.maxSpeed = 10 33 | this.updateOrientation = false 34 | } 35 | 36 | getDirection(result) { 37 | q.multiplyQuaternions(this.rotation, this.head.rotation) 38 | 39 | return result.copy(this.forward).applyRotation(q).normalize() 40 | } 41 | } 42 | 43 | export { Player } 44 | -------------------------------------------------------------------------------- /examples/js/playground/shooter/src/Target.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import { GameEntity } from '../../../../../lib/yuka.module.js' 7 | 8 | class Target extends GameEntity { 9 | constructor(geometry) { 10 | super() 11 | 12 | this.uiElement = document.getElementById('hit') 13 | 14 | this.name = 'target' 15 | this.endTime = Infinity 16 | this.currentTime = 0 17 | this.duration = 1 // 1 second 18 | this.geometry = geometry 19 | } 20 | 21 | update(delta) { 22 | this.currentTime += delta 23 | 24 | if (this.currentTime >= this.endTime) { 25 | this.uiElement.classList.add('hidden') 26 | this.endTime = Infinity 27 | } 28 | 29 | return this 30 | } 31 | 32 | handleMessage() { 33 | this.uiElement.classList.remove('hidden') 34 | 35 | this.endTime = this.currentTime + this.duration 36 | 37 | return true 38 | } 39 | } 40 | 41 | export { Target } 42 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/assets/ball.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/showcases/kickoff/assets/ball.glb -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/assets/goal.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/showcases/kickoff/assets/goal.glb -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | First-Person Controls 4 | 5 | 6 | 7 | 8 | 37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
0
51 |
:
52 |
0
53 |
54 |
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/index.js: -------------------------------------------------------------------------------- 1 | import world from './src/core/World.js' 2 | world.init() 3 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/core/Constants.js: -------------------------------------------------------------------------------- 1 | export const MESSAGE = { 2 | RETURN_HOME: 'RETURN_HOME', 3 | PASS_TO_ME: 'PASS_TO_ME', 4 | RECEIVE_BALL: 'RECEIVE_BALL', 5 | SUPPORT_ATTACKER: 'SUPPORT_ATTACKER', 6 | GOAL_SCORED: 'GOAL_SCORED' 7 | }; 8 | export const GOALKEEPER_STATES = { 9 | RETURN_HOME: 'RETURN_HOME', 10 | TEND_GOAL: 'TEND_GOAL', 11 | PUT_BALL_BACK_IN_PLAY: 'PUT_BALL_BACK_IN_PLAY', 12 | INTERCEPT_BALL: 'INTERCEPT_BALL' 13 | }; 14 | export const FIELDPLAYER_STATES = { 15 | CHASE_BALL: 'CHASE_BALL', 16 | DRIBBLE: 'DRIBBLE', 17 | KICK_BALL: 'KICK_BALL', 18 | RECEIVE_BALL: 'RECEIVE_BALL', 19 | RETURN_HOME: 'RETURN_HOME', 20 | SUPPORT_ATTACKER: 'SUPPORT_ATTACKER', 21 | WAIT: 'WAIT' 22 | }; 23 | export const TEAM_STATES = { 24 | ATTACKING: 'ATTACKING', 25 | DEFENDING: 'DEFENDING', 26 | PREPARE_FOR_KICKOFF: 'PREPARE_FOR_KICKOFF' 27 | }; 28 | export const CONFIG = { 29 | GOALKEEPER_IN_TARGET_RANGE: 0.5, // the goalkeeper has to be this close to the ball to be able to interact with it 30 | GOALKEEPER_INTERCEPT_RANGE: 4, // when the ball becomes within this distance of the goalkeeper he changes state to intercept the ball 31 | GOALKEEPER_MIN_PASS_DISTANCE: 2, // // the minimum distance a player must be from the goalkeeper before it will pass the ball 32 | GOALKEEPER_TENDING_DISTANCE: 2, // this is the distance the keeper puts between the back of the net and the ball when using the interpose steering behavior 33 | PLAYER_CHANCE_OF_USING_ARRIVE_TYPE_RECEIVE_BEHAVIOR: 0.5, // this is the chance that a player will receive a pass using the "arrive" steering behavior, rather than "pursuit" 34 | PLAYER_CHANCE_ATTEMPT_POT_SHOT: 0.005, // the chance a player might take a random pot shot at the goal 35 | PLAYER_COMFORT_ZONE: 2.5, // when an opponents comes within this range the player will attempt to pass the ball. Players tend to pass more often, the higher the value 36 | PLAYER_IN_TARGET_RANGE: 0.25, // the player has to be this close to its steering target to be considered as arrived 37 | PLAYER_KICK_FREQUENCY: 1, // the number of times a player can kick the ball per second 38 | PLAYER_KICKING_RANGE: 0.3, // player has to be this close to the ball to be able to kick it 39 | PLAYER_MAX_DRIBBLE_AND_TURN_FORCE: 0.4, // the force used for dribbling while turning around 40 | PLAYER_MAX_DRIBBLE_FORCE: 0.6, // the force used for dribbling 41 | PLAYER_MAX_PASSING_FORCE: 3, // the force used for passing 42 | PLAYER_MAX_SHOOTING_FORCE: 4, // the force used for shooting at the goal 43 | PLAYER_MAX_SPEED_WITH_BALL: 0.8, // max speed with ball 44 | PLAYER_MAX_SPEED_WITHOUT_BALL: 1, // max speed without ball 45 | PLAYER_MIN_PASS_DISTANCE: 5, // the minimum distance a receiving player must be from the passing player 46 | PLAYER_NUM_ATTEMPTS_TO_FIND_VALID_STRIKE: 5, // the number of times the player attempts to find a valid shot 47 | PLAYER_RECEIVING_RANGE: 1, // how close the ball must be to a receiver before he starts chasing it 48 | PLAYER_PASS_INTERCEPT_SCALE: 0.3, // this value decreases the range of possible pass targets a player can reach "in time" 49 | PLAYER_PASS_REQUEST_SUCCESS: 0.1, // the likelihood that a pass request is successful 50 | PLAYER_PASS_THREAD_RADIUS: 3, // the radius in which a pass in dangerous 51 | SUPPORT_SPOT_CALCULATOR_SLICE_X: 12, // x dimension of spot 52 | SUPPORT_SPOT_CALCULATOR_SLICE_Y: 5, // y dimension of spot 53 | SUPPORT_SPOT_CALCULATOR_SCORE_CAN_PASS: 2, // score when pass is possible 54 | SUPPORT_SPOT_CALCULATOR_SCORE_CAN_SCORE: 1, // score when a goal is possible 55 | SUPPORT_SPOT_CALCULATOR_SCORE_DISTANCE: 2, // score for pass distance 56 | SUPPORT_SPOT_CALCULATOR_OPT_DISTANCE: 5, // optimal distance for a pass 57 | SUPPORT_SPOT_CALCULATOR_UPDATE_FREQUENCY: 1 // updates per second 58 | }; 59 | 60 | export const TEAM = { 61 | RED: 0, 62 | BLUE: 1 63 | }; 64 | 65 | export const ROLE = { 66 | GOALKEEPER: 0, 67 | ATTACKER: 1, 68 | DEFENDER: 2 69 | }; 70 | 71 | CONFIG.GOALKEEPER_INTERCEPT_RANGE_SQ = CONFIG.GOALKEEPER_INTERCEPT_RANGE * CONFIG.GOALKEEPER_INTERCEPT_RANGE; 72 | CONFIG.GOALKEEPER_IN_TARGET_RANGE_SQ = CONFIG.GOALKEEPER_IN_TARGET_RANGE * CONFIG.GOALKEEPER_IN_TARGET_RANGE; 73 | CONFIG.PLAYER_COMFORT_ZONE_SQ = CONFIG.PLAYER_COMFORT_ZONE * CONFIG.PLAYER_COMFORT_ZONE; 74 | CONFIG.PLAYER_IN_TARGET_RANGE_SQ = CONFIG.PLAYER_IN_TARGET_RANGE * CONFIG.PLAYER_IN_TARGET_RANGE; 75 | CONFIG.PLAYER_KICKING_RANGE_SQ = CONFIG.PLAYER_KICKING_RANGE * CONFIG.PLAYER_KICKING_RANGE; 76 | CONFIG.PLAYER_RECEIVING_RANGE_SQ = CONFIG.PLAYER_RECEIVING_RANGE * CONFIG.PLAYER_RECEIVING_RANGE; 77 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/entities/FieldPlayer.js: -------------------------------------------------------------------------------- 1 | import { ArriveBehavior, PursuitBehavior, Regulator, SeekBehavior } from '../../../../../../lib/yuka.module.js' 2 | import { CONFIG, FIELDPLAYER_STATES } from '../core/Constants.js' 3 | import { 4 | ChaseBallState, 5 | DribbleState, 6 | GlobalState, 7 | KickBallState, 8 | ReceiveBallState, 9 | ReturnHomeState, 10 | SupportAttackerState, 11 | WaitState, 12 | } from '../states/FieldplayerStates.js' 13 | import Player from './Player.js' 14 | 15 | /** 16 | * Base class for representing a field player. 17 | * 18 | * @author {@link https://github.com/Mugen87|Mugen87} 19 | * @augments Player 20 | */ 21 | class FieldPlayer extends Player { 22 | /** 23 | * Constructs a new field player. 24 | * 25 | * @param {Number} role - The role of the player. 26 | * @param {Team} team - A reference to its team. 27 | * @param {Pitch} pitch - A reference to the pitch. 28 | * @param {Number} defaultRegionId - The id of its default home region. 29 | */ 30 | constructor(role, team, pitch, defaultRegionId) { 31 | super(role, team, pitch, defaultRegionId) 32 | 33 | /** 34 | * Regulates how often a field player is able to kick the ball in one second. 35 | * @type {Number} 36 | */ 37 | this._kickRegulator = new Regulator(CONFIG.PLAYER_KICK_FREQUENCY) 38 | 39 | // steering behaviors 40 | 41 | const seekBehavior = new SeekBehavior() 42 | seekBehavior.active = false 43 | this.steering.add(seekBehavior) 44 | 45 | const arriveBehavior = new ArriveBehavior() 46 | arriveBehavior.active = false 47 | arriveBehavior.deceleration = 1.5 48 | this.steering.add(arriveBehavior) 49 | 50 | const pursuitBehavior = new PursuitBehavior() 51 | pursuitBehavior.active = false 52 | this.steering.add(pursuitBehavior) 53 | 54 | // states 55 | 56 | this.stateMachine.globalState = new GlobalState() 57 | 58 | this.stateMachine.add(FIELDPLAYER_STATES.CHASE_BALL, new ChaseBallState()) 59 | this.stateMachine.add(FIELDPLAYER_STATES.DRIBBLE, new DribbleState()) 60 | this.stateMachine.add(FIELDPLAYER_STATES.KICK_BALL, new KickBallState()) 61 | this.stateMachine.add(FIELDPLAYER_STATES.RECEIVE_BALL, new ReceiveBallState()) 62 | this.stateMachine.add(FIELDPLAYER_STATES.RETURN_HOME, new ReturnHomeState()) 63 | this.stateMachine.add(FIELDPLAYER_STATES.SUPPORT_ATTACKER, new SupportAttackerState()) 64 | this.stateMachine.add(FIELDPLAYER_STATES.WAIT, new WaitState()) 65 | 66 | this.stateMachine.changeTo(FIELDPLAYER_STATES.WAIT) 67 | } 68 | 69 | /** 70 | * Updates the field player. 71 | * 72 | * @param {Number} delta - The time delta value. 73 | * @return {FieldPlayer} A reference to this field player. 74 | */ 75 | update(delta) { 76 | super.update(delta) 77 | 78 | // In most states field players should always focus the ball. In other states (RETURN_HOME and SUPPORT_ATTACKER) the focus point 79 | // depends on the current situation. It might be the ball or the current steering target. 80 | 81 | if ( 82 | this.stateMachine.in(FIELDPLAYER_STATES.RETURN_HOME) === false && 83 | this.stateMachine.in(FIELDPLAYER_STATES.SUPPORT_ATTACKER) === false 84 | ) { 85 | this.rotateTo(this.team.ball.position, delta) 86 | } 87 | } 88 | 89 | /** 90 | * Returns true if the field player is able to kick the ball again. 91 | * 92 | * @return {Boolean} Whether the field player is able to kick the ball again or not. 93 | */ 94 | isReadyForNextKick() { 95 | return this._kickRegulator.ready() 96 | } 97 | } 98 | 99 | export default FieldPlayer 100 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/entities/Goal.js: -------------------------------------------------------------------------------- 1 | import { GameEntity, Vector3 } from '../../../../../../lib/yuka.module.js' 2 | import { TEAM } from '../core/Constants.js' 3 | 4 | /** 5 | * Class for representing a soccer goal. 6 | * 7 | * @author {@link https://github.com/Mugen87|Mugen87} 8 | * @augments GameEntity 9 | */ 10 | class Goal extends GameEntity { 11 | /** 12 | * Constructs a new goal. 13 | * 14 | * @param {Number} width - The width of the goal. 15 | * @param {Number} height - The height of the goal. 16 | * @param {Number} color - The color of the team that owns this goal. 17 | */ 18 | constructor(width, height, color) { 19 | super() 20 | 21 | /** 22 | * The width of the goal. 23 | * @type {Number} 24 | */ 25 | this.width = width 26 | 27 | /** 28 | * The height of the goal. 29 | * @type {Number} 30 | */ 31 | this.height = height 32 | 33 | /** 34 | * The color of the team that owns this goal. 35 | * @type {Number} 36 | */ 37 | this.color = color 38 | 39 | /** 40 | * The position of the left post. Computed by computePosts(). 41 | * @type {Vector3} 42 | */ 43 | this.leftPost = null 44 | 45 | /** 46 | * The position of the right post. Computed by computePosts(). 47 | * @type {Vector3} 48 | */ 49 | this.rightPost = null 50 | } 51 | 52 | /** 53 | * Returns the direction of the goal. This overwrites the implementation of 54 | * GameEntity since the direction only depends on the team color. 55 | * 56 | * @param {Vector3} direction - The direction of the goal. 57 | * @return {Vector3} The direction of the goal. 58 | */ 59 | getDirection(direction) { 60 | if (this.color === TEAM.RED) { 61 | direction.set(-1, 0, 0) 62 | } else { 63 | direction.set(1, 0, 0) 64 | } 65 | 66 | return direction 67 | } 68 | 69 | /** 70 | * Computes the posts of the goal. 71 | */ 72 | computePosts() { 73 | this.leftPost = new Vector3() 74 | this.rightPost = new Vector3() 75 | 76 | const halfSize = this.width / 2 77 | 78 | if (this.color === TEAM.RED) { 79 | this.leftPost.x = this.position.x 80 | this.leftPost.z = this.position.z + halfSize 81 | 82 | this.rightPost.x = this.position.x 83 | this.rightPost.z = this.position.z - halfSize 84 | } else { 85 | this.leftPost.x = this.position.x 86 | this.leftPost.z = this.position.z - halfSize 87 | 88 | this.rightPost.x = this.position.x 89 | this.rightPost.z = this.position.z + halfSize 90 | } 91 | } 92 | } 93 | 94 | export default Goal 95 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/entities/Goalkeeper.js: -------------------------------------------------------------------------------- 1 | import { ArriveBehavior, PursuitBehavior, Vector3 } from '../../../../../../lib/yuka.module.js' 2 | import { GOALKEEPER_STATES, CONFIG, ROLE } from '../core/Constants.js' 3 | import { 4 | GlobalState, 5 | InterceptBallState, 6 | PutBallBackInPlayState, 7 | ReturnHomeState, 8 | TendGoalState, 9 | } from '../states/GoalkeeperStates.js' 10 | import Player from './Player.js' 11 | 12 | const _target = new Vector3() 13 | 14 | /** 15 | * Base class for representing a goalkeeper. 16 | * 17 | * @author {@link https://github.com/Mugen87|Mugen87} 18 | * @augments Player 19 | */ 20 | class Goalkeeper extends Player { 21 | /** 22 | * Constructs a new goalkeeper. 23 | * 24 | * @param {Team} team - A reference to its team. 25 | * @param {Pitch} pitch - A reference to the pitch. 26 | * @param {Number} defaultRegionId - The id of its default home region. 27 | */ 28 | constructor(team, pitch, defaultRegionId) { 29 | super(ROLE.GOALKEEPER, team, pitch, defaultRegionId) 30 | 31 | this.maxSpeed = 1.5 32 | 33 | // steering behaviors 34 | 35 | const arriveBehavior = new ArriveBehavior() 36 | arriveBehavior.deceleration = 1 37 | arriveBehavior.active = false 38 | this.steering.add(arriveBehavior) 39 | 40 | const pursuitBehavior = new PursuitBehavior() 41 | pursuitBehavior.active = false 42 | this.steering.add(pursuitBehavior) 43 | 44 | // states 45 | 46 | this.stateMachine.globalState = new GlobalState() 47 | 48 | this.stateMachine.add(GOALKEEPER_STATES.RETURN_HOME, new ReturnHomeState()) 49 | this.stateMachine.add(GOALKEEPER_STATES.TEND_GOAL, new TendGoalState()) 50 | this.stateMachine.add(GOALKEEPER_STATES.INTERCEPT_BALL, new InterceptBallState()) 51 | this.stateMachine.add(GOALKEEPER_STATES.PUT_BALL_BACK_IN_PLAY, new PutBallBackInPlayState()) 52 | 53 | this.stateMachine.changeTo(GOALKEEPER_STATES.TEND_GOAL) 54 | } 55 | 56 | /** 57 | * Updates the goalkeeper. 58 | * 59 | * @param {Number} delta - The time delta value. 60 | * @return {Goalkeeper} A reference to this goalkeeper. 61 | */ 62 | update(delta) { 63 | super.update(delta) 64 | 65 | this.rotateTo(this.team.ball.position, delta) 66 | } 67 | 68 | /** 69 | * Returns true if the ball is within the goalkeeper's target range. If so, the keeper is able 70 | * to trap the ball. 71 | * 72 | * @return {Boolean} Whether the ball is within the keeper's target range or not. 73 | */ 74 | isBallWithinKeeperRange() { 75 | const ball = this.team.ball 76 | 77 | return this.position.squaredDistanceTo(ball.position) < CONFIG.GOALKEEPER_IN_TARGET_RANGE_SQ 78 | } 79 | 80 | /** 81 | * Returns true if the ball is within the goalkeeper's interception range. If so, the keeper will 82 | * start to pursuit the ball. 83 | * 84 | * @return {Boolean} Whether the ball is within the keeper's interception range or not. 85 | */ 86 | isBallWithinRangeForIntercept() { 87 | const ball = this.team.ball 88 | const goal = this.team.homeGoal 89 | 90 | return goal.position.squaredDistanceTo(ball.position) <= CONFIG.GOALKEEPER_INTERCEPT_RANGE_SQ 91 | } 92 | 93 | /** 94 | * Returns true if the goalkeeper is too far away from the goalmouth. 95 | * 96 | * @return {Boolean} Whether the goalkeeper is too far away from the goalmouth or not. 97 | */ 98 | isTooFarFromGoalMouth() { 99 | this.getRearInterposeTarget(_target) 100 | 101 | return this.position.squaredDistanceTo(_target) > CONFIG.GOALKEEPER_INTERCEPT_RANGE_SQ 102 | } 103 | 104 | /** 105 | * This method is called by the TendGoalState to determine the spot 106 | * along the goalmouth which will act as one of the interpose targets 107 | * (the other is the ball). The specific point at the goal line that 108 | * the keeper is trying to cover is flexible and can move depending on 109 | * where the ball is on the field. To achieve this we just scale the 110 | * ball's z value by the ratio of the goal width to playing field height. 111 | * 112 | * @param {Vector3} force - The interpose target. 113 | * @returns {Vector3} The interpose target. 114 | */ 115 | getRearInterposeTarget(target) { 116 | const pitch = this.pitch 117 | const ball = this.team.ball 118 | const goal = this.team.homeGoal 119 | 120 | target.x = goal.position.x 121 | target.y = 0 122 | target.z = ball.position.z * (goal.width / pitch.playingArea.height) 123 | 124 | return target 125 | } 126 | } 127 | 128 | export default Goalkeeper 129 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/entities/Pitch.js: -------------------------------------------------------------------------------- 1 | import { GameEntity, Plane, Vector3 } from '../../../../../../lib/yuka.module.js' 2 | import { MESSAGE } from '../core/Constants.js' 3 | 4 | import Region from '../etc/Region.js' 5 | 6 | /** 7 | * Class for representing a soccer pitch. 8 | * 9 | * @author {@link https://github.com/Mugen87|Mugen87} 10 | * @augments GameEntity 11 | */ 12 | class Pitch extends GameEntity { 13 | /** 14 | * Constructs a new pitch. 15 | * 16 | * @param {Number} width - The width of the pitch. 17 | * @param {Number} height - The height of the pitch. 18 | * @param {World} world - A reference to the World class. 19 | */ 20 | constructor(width, height, world) { 21 | super() 22 | 23 | /** 24 | * A reference to the World class. 25 | * @type {World} 26 | */ 27 | this.world = world 28 | 29 | /** 30 | * Represents the walls of the soccer pitch. The ball will 31 | * collide against these walls so it can leave the playing area. 32 | * @type {Array} 33 | */ 34 | this.walls = [ 35 | new Plane(new Vector3(0, 0, -1), 7.5), // top 36 | new Plane(new Vector3(0, 0, 1), 7.5), // bottom 37 | new Plane(new Vector3(-1, 0, 0), 10), // right (red goal) 38 | new Plane(new Vector3(1, 0, 0), 10), // left (blue goal) 39 | ] 40 | 41 | /** 42 | * Whether both teams are playing or not. 43 | * @type {Boolean} 44 | */ 45 | this.isPlaying = true 46 | 47 | /** 48 | * Whether one of the goalkeepers is in ball possession or not. 49 | * @type {Boolean} 50 | */ 51 | this.isGoalKeeperInBallPossession = false 52 | 53 | /** 54 | * A reference to the soccer ball. 55 | * @type {Ball} 56 | */ 57 | this.ball = null 58 | 59 | /** 60 | * A reference to the red team. 61 | * @type {Team} 62 | */ 63 | this.teamRed = null 64 | 65 | /** 66 | * A reference to the blue team. 67 | * @type {Team} 68 | */ 69 | this.teamBlue = null 70 | 71 | /** 72 | * Represents the playing area of the pitch. 73 | * @type {Region} 74 | */ 75 | this.playingArea = new Region(this.position.clone(), width, height) 76 | 77 | /** 78 | * The region count the pitch along the x axis. 79 | * @type {Number} 80 | */ 81 | this.regionCountWidth = 6 82 | 83 | /** 84 | * The region count the pitch along the z axis. 85 | * @type {Number} 86 | */ 87 | this.regionCountHeight = 3 88 | 89 | /** 90 | * Holds the regions of the soccer pitch. 91 | * @type {Array} 92 | */ 93 | this.regions = [] 94 | 95 | this._createRegions() 96 | } 97 | 98 | /** 99 | * Holds the implementation for the message handling of this pitch. 100 | * 101 | * @param {Telegram} telegram - The telegram with the message data. 102 | * @return {Boolean} Whether the message was processed or not. 103 | */ 104 | handleMessage(telegram) { 105 | switch (telegram.message) { 106 | case MESSAGE.GOAL_SCORED: 107 | this.isPlaying = false 108 | 109 | this.world.refreshUI() 110 | 111 | return true 112 | } 113 | 114 | return false 115 | } 116 | 117 | /** 118 | * Returns the region for the given ID. 119 | * 120 | * @param {Number} id - The id for the requested region. 121 | * @return {Region} The requested region. 122 | */ 123 | getRegionById(id) { 124 | return this.regions[id] 125 | } 126 | 127 | /** 128 | * Generates the regions of this pitch. All regions lie in a XZ at the origin. 129 | */ 130 | _createRegions() { 131 | const playingArea = this.playingArea 132 | 133 | let id = 0 134 | 135 | const width = playingArea.width / this.regionCountWidth 136 | const height = playingArea.height / this.regionCountHeight 137 | 138 | for (let col = 0; col < this.regionCountWidth; col++) { 139 | for (let row = 0; row < this.regionCountHeight; row++) { 140 | const x = col * width + width / 2 - playingArea.width / 2 141 | const y = 0 142 | const z = row * height + height / 2 - playingArea.height / 2 143 | 144 | this.regions[id] = new Region(new Vector3(x, y, z), width, height, id) 145 | 146 | id++ 147 | } 148 | } 149 | } 150 | } 151 | 152 | export default Pitch 153 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/etc/Region.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines a rectangular region. This class is used to split up the 3 | * pitch into multiple regions which can be used by the AI to implement 4 | * different strategies. 5 | * 6 | * @author {@link https://github.com/Mugen87|Mugen87} 7 | */ 8 | class Region { 9 | 10 | /** 11 | * Constructs a new mesh geometry. 12 | * 13 | * @param {Vector3} center - The center point of the region. 14 | * @param {Number} width - The width of the region. 15 | * @param {Number} height - The height of the region. 16 | * @param {Number} id - The unique identifier of the region. 17 | */ 18 | constructor( center, width, height, id = 0 ) { 19 | 20 | /** 21 | * The center point of the region. 22 | * @type {Vector3} 23 | */ 24 | this.center = center; 25 | 26 | /** 27 | * The width of the region. 28 | * @type {Number} 29 | */ 30 | this.width = width; 31 | 32 | /** 33 | * The height of the region. 34 | * @type {Number} 35 | */ 36 | this.height = height; 37 | 38 | /** 39 | * The unique identifier of the region. 40 | * @type {Number} 41 | */ 42 | this.id = id; 43 | 44 | /** 45 | * The outer left position of the region. 46 | * @type {Number} 47 | */ 48 | this.left = center.x - ( width / 2 ); 49 | 50 | /** 51 | * The outer right position of the region. 52 | * @type {Number} 53 | */ 54 | this.right = center.x + ( width / 2 ); 55 | 56 | /** 57 | * The outer top position of the region. 58 | * @type {Number} 59 | */ 60 | this.top = center.z + ( height / 2 ); 61 | 62 | /** 63 | * The outer bottom position of the region. 64 | * @type {Number} 65 | */ 66 | this.bottom = center.z - ( height / 2 ); 67 | 68 | } 69 | 70 | /** 71 | * Returns true if the given position is inside this region. 72 | * 73 | * @param {Vector3} position - The position to test. 74 | * @param {Boolean} isHalfSize - Whether the region has half size or not which makes the test more strict (optional). 75 | * @return {Boolean} Whether the given position is inside the region or not. 76 | */ 77 | isInside( position, isHalfSize = false ) { 78 | 79 | let marginX, marginY; 80 | 81 | if ( isHalfSize === true ) { 82 | 83 | marginX = this.width * 0.25; 84 | marginY = this.height * 0.25; 85 | 86 | return ( ( position.x > ( this.left + marginX ) ) && 87 | ( position.x < ( this.right - marginX ) ) && 88 | ( position.z > ( this.bottom + marginY ) ) && 89 | ( position.z < ( this.top - marginY ) ) ); 90 | 91 | } else { 92 | 93 | return ( ( position.x > this.left ) && 94 | ( position.x < this.right ) && 95 | ( position.z > this.bottom ) && 96 | ( position.z < this.top ) ); 97 | 98 | } 99 | 100 | } 101 | 102 | } 103 | 104 | export default Region; 105 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/src/states/TeamStates.js: -------------------------------------------------------------------------------- 1 | import { State } from '../../../../../../lib/yuka.module.js' 2 | import { MESSAGE, TEAM_STATES } from '../core/Constants.js' 3 | 4 | /** 5 | * The global state of the team. 6 | * 7 | * @author {@link https://github.com/Mugen87|Mugen87} 8 | */ 9 | class GlobalState extends State { 10 | onMessage(team, telegram) { 11 | // This state is only used for processing messages. 12 | 13 | switch (telegram.message) { 14 | case MESSAGE.GOAL_SCORED: 15 | if (telegram.data.team === team.color) team.goals++ 16 | 17 | team.stateMachine.changeTo(TEAM_STATES.PREPARE_FOR_KICKOFF) 18 | 19 | return true 20 | } 21 | 22 | return false 23 | } 24 | } 25 | 26 | /** 27 | * In this state the team tries to make a goal. 28 | * 29 | * @author {@link https://github.com/Mugen87|Mugen87} 30 | */ 31 | class AttackingState extends State { 32 | enter(team) { 33 | // Set up the player's new home regions. 34 | 35 | team.setupTeamPositions() 36 | 37 | // If a player is in either the WAIT or RETURN_HOME states, its 38 | // steering target must be updated to that of its new home region to 39 | // enable it to move into the correct position. 40 | 41 | team.updateSteeringTargetOfPlayers() 42 | } 43 | 44 | execute(team) { 45 | // If this team is no longer in control, change to defending. 46 | 47 | if (team.inControl() === false) { 48 | team.stateMachine.changeTo(TEAM_STATES.DEFENDING) 49 | } 50 | 51 | // Compute the best position for any supporting attacker to move to. 52 | 53 | team.computeBestSupportingPosition() 54 | } 55 | 56 | exit(team) { 57 | team.lostControl() 58 | } 59 | } 60 | 61 | /** 62 | * In this state the team tries to defend its goal. 63 | * 64 | * @author {@link https://github.com/Mugen87|Mugen87} 65 | */ 66 | class DefendingState extends State { 67 | enter(team) { 68 | team.setupTeamPositions() 69 | team.updateSteeringTargetOfPlayers() 70 | } 71 | 72 | execute(team) { 73 | // If this team gets control over the ball, change to attacking. 74 | 75 | if (team.inControl()) { 76 | team.stateMachine.changeTo(TEAM_STATES.ATTACKING) 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * In this state the team prepares for kickoff. 83 | * 84 | * @author {@link https://github.com/Mugen87|Mugen87} 85 | */ 86 | class PrepareForKickOffState extends State { 87 | enter(team) { 88 | team.receivingPlayer = null 89 | team.playerClosestToBall = null 90 | team.controllingPlayer = null 91 | team.supportingPlayer = null 92 | 93 | // send all players to their default regions 94 | 95 | team.returnAllFieldPlayersToHome(true) 96 | } 97 | 98 | execute(team) { 99 | if (team.areAllPlayersAtHome() && team.opposingTeam.areAllPlayersAtHome()) { 100 | team.stateMachine.changeTo(TEAM_STATES.DEFENDING) 101 | } 102 | } 103 | 104 | exit(team) { 105 | team.pitch.isPlaying = true 106 | } 107 | } 108 | 109 | export { AttackingState, DefendingState, GlobalState, PrepareForKickOffState } 110 | -------------------------------------------------------------------------------- /examples/js/showcases/kickoff/textures/pitch_texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/showcases/kickoff/textures/pitch_texture.jpg -------------------------------------------------------------------------------- /examples/js/spacecarrier/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Space Carrier - State Machine 4 | 5 | 6 | 7 | 8 | 42 | 43 | 44 |
45 |

46 | Another example of Autonomous State-driven Agent Design.
47 | This small spaceship collects red artefacts inside the sphere. After collecting 2 artefacts the spaceship goes 48 | to the base to unload cargo. 49 |

50 |
51 |
52 |
53 | Current State: 54 |
55 | Current Time: 56 |
57 | Current Speed: 58 |
59 | Collected: 60 |
61 | Stored: 62 | 63 |
64 |
65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/js/spacecarrier/src/carrier.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | import { IdleState, RotateState, FlyState, GatherState, ToTheBaseState, UnloadState } from './states.js' 3 | class Carrier extends YUKA.Vehicle { 4 | constructor(scene, time, target) { 5 | super() 6 | 7 | this.scene = scene 8 | this.time = time 9 | this.target = target 10 | 11 | this.idleDuration = 2 12 | this.walkDuration = 5 13 | this.gatherDuration = 5 14 | this.unloadDuration = 6 15 | this.currentTime = 0 16 | this.maxTurnRate = 0.8 17 | this.currentSpeed = 0 18 | this.goods = 0 19 | 20 | this.ui = { 21 | currentState: document.getElementById('currentState'), 22 | currentTime: document.getElementById('currentTime'), 23 | currentSpeed: document.getElementById('currentSpeed'), 24 | currentGoods: document.getElementById('currentGoods'), 25 | storedGoods: document.getElementById('storedGoods'), 26 | } 27 | 28 | this.stateMachine = new YUKA.StateMachine(this) 29 | this.stateMachine.add('IDLE', new IdleState(scene)) 30 | this.stateMachine.add('ROTATE', new RotateState(time)) 31 | this.stateMachine.add('FLY', new FlyState(scene)) 32 | this.stateMachine.add('GATHER', new GatherState(scene)) 33 | this.stateMachine.add('TO THE BASE', new ToTheBaseState(scene)) 34 | this.stateMachine.add('UNLOAD', new UnloadState(scene)) 35 | } 36 | 37 | generateTarget() { 38 | // generate a random point on a sphere 39 | 40 | const radius = 2 41 | const phi = Math.acos(2 * Math.random() - 1) 42 | const theta = Math.random() * Math.PI * 2 43 | 44 | this.target.position.fromSpherical(radius, phi, theta) 45 | } 46 | update(delta) { 47 | this.currentDelta = delta 48 | 49 | this.stateMachine.update() 50 | this.currentTime += delta 51 | 52 | super.update(delta) 53 | 54 | return this 55 | } 56 | } 57 | 58 | export { Carrier } 59 | -------------------------------------------------------------------------------- /examples/js/spacecarrier/src/textures_flare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eldinor/yuka-babylonjs-examples/f8d9f36fe181dc5f3ef4c2746dbe39ea8d884e37/examples/js/spacecarrier/src/textures_flare.png -------------------------------------------------------------------------------- /examples/js/steering/arrive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Arrive | Babylon.js 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | This steering behavior produces a force that directs an agent toward a target position.
13 | Unlike "Seek", it decelerates so the agent comes to a gentle halt at the target position. 14 |

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/steering/arrive/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | import 'https://preview.babylonjs.com/babylon.js' 3 | import { createVehicle } from '../../creator.js' 4 | 5 | let engine, scene 6 | let entityManager, time, vehicle, target 7 | 8 | const entityMatrix = new BABYLON.Matrix() 9 | 10 | init() 11 | animate() 12 | 13 | function init() { 14 | const canvas = document.getElementById('renderCanvas') 15 | engine = new BABYLON.Engine(canvas, true, {}, true) 16 | 17 | scene = new BABYLON.Scene(engine) 18 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 19 | scene.useRightHandedSystem = true 20 | 21 | const camera = new BABYLON.ArcRotateCamera( 22 | 'camera', 23 | BABYLON.Tools.ToRadians(30), 24 | BABYLON.Tools.ToRadians(40), 25 | 8, 26 | BABYLON.Vector3.Zero(), 27 | scene 28 | ) 29 | 30 | camera.target = new BABYLON.Vector3(0, 0, 0) 31 | camera.attachControl() 32 | 33 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 34 | 35 | // 36 | 37 | const vehicleMesh = createVehicle(scene, { size: 0.5 }) 38 | 39 | const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { 40 | diameter: 5, 41 | segments: 16, 42 | }) 43 | sphere.material = new BABYLON.StandardMaterial('sphereMaterial', scene) 44 | sphere.material.disableLighting = true 45 | sphere.material.emissiveColor = new BABYLON.Color3(0.8, 0.8, 0.8) 46 | sphere.material.alpha = 0.2 47 | sphere.material.wireframe = true 48 | 49 | // 50 | 51 | const targetMesh = BABYLON.MeshBuilder.CreateSphere('target', { 52 | diameter: 0.1, 53 | segments: 16, 54 | }) 55 | targetMesh.material = new BABYLON.StandardMaterial('targetMaterial', scene) 56 | targetMesh.material.disableLighting = true 57 | targetMesh.material.emissiveColor = new BABYLON.Color3(1, 0, 0) 58 | 59 | // 60 | 61 | window.addEventListener('resize', onWindowResize, false) 62 | 63 | // game setup 64 | 65 | entityManager = new YUKA.EntityManager() 66 | time = new YUKA.Time() 67 | 68 | target = new YUKA.GameEntity() 69 | target.setRenderComponent(targetMesh, sync) 70 | 71 | vehicle = new YUKA.Vehicle() 72 | vehicle.setRenderComponent(vehicleMesh, sync) 73 | 74 | const arriveBehavior = new YUKA.ArriveBehavior(target.position, 2.5, 0.1) 75 | vehicle.steering.add(arriveBehavior) 76 | 77 | entityManager.add(target) 78 | entityManager.add(vehicle) 79 | 80 | generateTarget() 81 | } 82 | 83 | function onWindowResize() { 84 | engine.resize() 85 | } 86 | 87 | function animate() { 88 | requestAnimationFrame(animate) 89 | 90 | const delta = time.update().getDelta() 91 | 92 | entityManager.update(delta) 93 | 94 | scene.render() 95 | } 96 | 97 | function sync(entity, renderComponent) { 98 | entity.worldMatrix.toArray(entityMatrix.m) 99 | entityMatrix.markAsUpdated() 100 | 101 | const matrix = renderComponent.getWorldMatrix() 102 | matrix.copyFrom(entityMatrix) 103 | } 104 | 105 | function generateTarget() { 106 | // generate a random point on a sphere 107 | 108 | const radius = 2 109 | const phi = Math.acos(2 * Math.random() - 1) 110 | const theta = Math.random() * Math.PI * 2 111 | 112 | target.position.fromSpherical(radius, phi, theta) 113 | 114 | setTimeout(generateTarget, 10000) 115 | } 116 | -------------------------------------------------------------------------------- /examples/js/steering/flee/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Flocking 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | This steering behavior produces a force that steers an agent away from a target position.
13 | The target position is defined by the mouse cursor. 14 |

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/steering/flee/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mugen87 / https://github.com/Mugen87 3 | * @author Examples with Babylon.js were made at https://github.com/eldinor/yuka-babylonjs-examples / roland@babylonjs.xyz 4 | */ 5 | 6 | import * as YUKA from '../../../../lib/yuka.module.js' 7 | 8 | import 'https://preview.babylonjs.com/babylon.js' 9 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 10 | import { createVehicle } from '../../creator.js' 11 | 12 | let engine, scene, plane, ray 13 | let entityManager, time, vehicle, target 14 | 15 | const entityMatrix = new BABYLON.Matrix() 16 | const pointer = new BABYLON.Vector2(1, 1) 17 | 18 | init() 19 | animate() 20 | 21 | function init() { 22 | const canvas = document.getElementById('renderCanvas') 23 | engine = new BABYLON.Engine(canvas, true, {}, true) 24 | 25 | scene = new BABYLON.Scene(engine) 26 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 27 | scene.useRightHandedSystem = true 28 | 29 | const camera = new BABYLON.ArcRotateCamera( 30 | 'camera', 31 | BABYLON.Tools.ToRadians(90), 32 | BABYLON.Tools.ToRadians(0), 33 | 30, 34 | BABYLON.Vector3.Zero(), 35 | scene 36 | ) 37 | camera.upperBetaLimit = Math.PI / 4 38 | camera.lowerBetaLimit = Math.PI / 4 39 | 40 | camera.target = new BABYLON.Vector3(0, 0, 0) 41 | camera.attachControl(canvas, true) 42 | 43 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 44 | 45 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 40, height: 40 }, scene) 46 | ground.position.y = -1 47 | ground.material = new BABYLON.GridMaterial('grid', scene) 48 | 49 | const vehicleMesh = createVehicle(scene, { size: 2 }) 50 | 51 | const pointerMesh = BABYLON.MeshBuilder.CreateSphere('pointer', scene) 52 | 53 | scene.onPointerMove = () => { 54 | var pickResult = scene.pick(scene.pointerX, scene.pointerY) 55 | if (pickResult?.pickedPoint) { 56 | target.x = pickResult.pickedPoint.x 57 | // target.y = pickResult.pickedPoint.y; 58 | target.z = pickResult.pickedPoint.z 59 | pointerMesh.position.x = target.x 60 | pointerMesh.position.z = target.z 61 | } 62 | } 63 | 64 | window.addEventListener('resize', onWindowResize, false) 65 | 66 | // YUKA specific 67 | target = new YUKA.Vector3() 68 | 69 | entityManager = new YUKA.EntityManager() 70 | time = new YUKA.Time() 71 | vehicle = new YUKA.Vehicle() 72 | vehicle.setRenderComponent(vehicleMesh, sync) 73 | 74 | const fleeBehavior = new YUKA.FleeBehavior(target, 5) 75 | vehicle.steering.add(fleeBehavior) 76 | 77 | entityManager.add(vehicle) 78 | } 79 | 80 | function onWindowResize() { 81 | engine.resize() 82 | } 83 | 84 | function animate() { 85 | requestAnimationFrame(animate) 86 | 87 | const delta = time.update().getDelta() 88 | entityManager.update(delta) 89 | 90 | scene.render() 91 | } 92 | 93 | function sync(entity, renderComponent) { 94 | BABYLON.Matrix.FromValues(...entity.worldMatrix.elements).decomposeToTransformNode(renderComponent) 95 | } 96 | -------------------------------------------------------------------------------- /examples/js/steering/flocking/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Flocking 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

A group steering behavior defined by a combination of "Alignment", "Cohesion" and "Separation".

12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/js/steering/flocking/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js' 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | 12 | let entityManager, time 13 | 14 | const entityMatrix = new BABYLON.Matrix() 15 | 16 | const params = { 17 | alignment: 1, 18 | cohesion: 0.9, 19 | separation: 0.3, 20 | } 21 | 22 | init() 23 | animate() 24 | 25 | function init() { 26 | const canvas = document.getElementById('renderCanvas') 27 | engine = new BABYLON.Engine(canvas, true, {}, true) 28 | 29 | scene = new BABYLON.Scene(engine) 30 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 31 | scene.useRightHandedSystem = true 32 | // scene.debugLayer.show(); 33 | 34 | const camera = new BABYLON.ArcRotateCamera( 35 | 'camera', 36 | BABYLON.Tools.ToRadians(90), 37 | BABYLON.Tools.ToRadians(0), 38 | 120, 39 | BABYLON.Vector3.Zero(), 40 | scene 41 | ) 42 | 43 | camera.target = new BABYLON.Vector3(0, 0, 0) 44 | camera.attachControl(canvas, true) 45 | 46 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 47 | 48 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 80, height: 80 }, scene) 49 | ground.position.y = -1 50 | ground.material = new BABYLON.GridMaterial('grid', scene) 51 | // 52 | 53 | // game setup 54 | 55 | entityManager = new YUKA.EntityManager() 56 | time = new YUKA.Time() 57 | 58 | const alignmentBehavior = new YUKA.AlignmentBehavior() 59 | const cohesionBehavior = new YUKA.CohesionBehavior() 60 | const separationBehavior = new YUKA.SeparationBehavior() 61 | 62 | alignmentBehavior.weight = params.alignment 63 | cohesionBehavior.weight = params.cohesion 64 | separationBehavior.weight = params.separation 65 | 66 | const vehicleMeshPrefab = createVehicle(scene, { size: 2 }) 67 | vehicleMeshPrefab.setEnabled(false) 68 | 69 | for (let i = 0; i < 50; i++) { 70 | const vehicleMesh = vehicleMeshPrefab.clone('vehicle') 71 | vehicleMesh.setEnabled(true) 72 | 73 | const vehicle = new YUKA.Vehicle() 74 | vehicle.maxSpeed = 1.5 75 | vehicle.updateNeighborhood = true 76 | vehicle.neighborhoodRadius = 10 77 | vehicle.rotation.fromEuler(0, Math.PI * Math.random(), 0) 78 | vehicle.position.x = 10 - Math.random() * 20 79 | vehicle.position.z = 10 - Math.random() * 20 80 | 81 | vehicle.setRenderComponent(vehicleMesh, sync) 82 | 83 | vehicle.steering.add(alignmentBehavior) 84 | vehicle.steering.add(cohesionBehavior) 85 | vehicle.steering.add(separationBehavior) 86 | 87 | const wanderBehavior = new YUKA.WanderBehavior() 88 | wanderBehavior.weight = 0.5 89 | vehicle.steering.add(wanderBehavior) 90 | 91 | entityManager.add(vehicle) 92 | } 93 | 94 | // dat.gui 95 | 96 | const gui = new DAT.GUI({ width: 300 }) 97 | 98 | gui 99 | .add(params, 'alignment', 0.1, 2) 100 | .name('alignment') 101 | .onChange((value) => (alignmentBehavior.weight = value)) 102 | gui 103 | .add(params, 'cohesion', 0.1, 2) 104 | .name('cohesion') 105 | .onChange((value) => (cohesionBehavior.weight = value)) 106 | gui 107 | .add(params, 'separation', 0.1, 2) 108 | .name('separation') 109 | .onChange((value) => (separationBehavior.weight = value)) 110 | 111 | gui.open() 112 | 113 | // 114 | 115 | window.addEventListener('resize', onWindowResize, false) 116 | } 117 | 118 | function onWindowResize() { 119 | engine.resize() 120 | } 121 | 122 | function animate() { 123 | requestAnimationFrame(animate) 124 | 125 | const delta = time.update().getDelta() 126 | 127 | entityManager.update(delta) 128 | 129 | scene.render() 130 | } 131 | 132 | function sync(entity, renderComponent) { 133 | entity.worldMatrix.toArray(entityMatrix.m) 134 | entityMatrix.markAsUpdated() 135 | 136 | const matrix = renderComponent.getWorldMatrix() 137 | matrix.copyFrom(entityMatrix) 138 | } 139 | -------------------------------------------------------------------------------- /examples/js/steering/followPath/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Follow Path 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | This steering behavior produces a force that moves a vehicle along a series of waypoints forming a path.
13 | An additional steering behavior (OnPath) can be used to realize a more strict path following. 14 |

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/steering/followPath/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js' 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | 12 | let entityManager, time, vehicle 13 | 14 | const entityMatrix = new BABYLON.Matrix() 15 | 16 | let lines 17 | 18 | let onPathBehavior 19 | 20 | const params = { 21 | onPathActive: true, 22 | radius: 0.1, 23 | } 24 | 25 | init() 26 | animate() 27 | 28 | function init() { 29 | const canvas = document.getElementById('renderCanvas') 30 | engine = new BABYLON.Engine(canvas, true, {}, true) 31 | 32 | scene = new BABYLON.Scene(engine) 33 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 34 | scene.useRightHandedSystem = true 35 | 36 | // scene.debugLayer.show(); 37 | 38 | const camera = new BABYLON.ArcRotateCamera( 39 | 'camera', 40 | BABYLON.Tools.ToRadians(90), 41 | BABYLON.Tools.ToRadians(0), 42 | 30, 43 | BABYLON.Vector3.Zero(), 44 | scene 45 | ) 46 | camera.target = new BABYLON.Vector3(0, 0, 0) 47 | camera.attachControl(canvas, true) 48 | 49 | let light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, -1, 0)) 50 | light.intensity = 2 51 | light.diffuse = BABYLON.Color3.Red() 52 | // 53 | 54 | const vehicleMesh = createVehicle(scene) 55 | // dat.gui 56 | 57 | const gui = new DAT.GUI({ width: 300 }) 58 | 59 | gui 60 | .add(params, 'onPathActive') 61 | .name('activate onPath') 62 | .onChange((value) => (onPathBehavior.active = value)) 63 | gui 64 | .add(params, 'radius', 0.01, 1) 65 | .name('radius') 66 | .onChange((value) => (onPathBehavior.radius = value)) 67 | 68 | gui.open() 69 | 70 | // 71 | window.addEventListener('resize', onWindowResize, false) 72 | 73 | // game setup 74 | 75 | entityManager = new YUKA.EntityManager() 76 | time = new YUKA.Time() 77 | vehicle = new YUKA.Vehicle() 78 | 79 | vehicle.setRenderComponent(vehicleMesh, sync) 80 | 81 | const path = new YUKA.Path() 82 | path.loop = true 83 | path.add(new YUKA.Vector3(-4, 0, 4)) 84 | path.add(new YUKA.Vector3(-6, 0, 0)) 85 | path.add(new YUKA.Vector3(-4, 0, -4)) 86 | path.add(new YUKA.Vector3(0, 0, 0)) 87 | path.add(new YUKA.Vector3(4, 0, -4)) 88 | path.add(new YUKA.Vector3(6, 0, 0)) 89 | path.add(new YUKA.Vector3(4, 0, 4)) 90 | path.add(new YUKA.Vector3(0, 0, 6)) 91 | 92 | vehicle.position.copy(path.current()) 93 | 94 | // use "FollowPathBehavior" for basic path following 95 | 96 | const followPathBehavior = new YUKA.FollowPathBehavior(path, 0.5) 97 | vehicle.steering.add(followPathBehavior) 98 | 99 | // use "OnPathBehavior" to realize a more strict path following. 100 | // it's a separate steering behavior to provide more flexibility. 101 | 102 | onPathBehavior = new YUKA.OnPathBehavior(path) 103 | vehicle.steering.add(onPathBehavior) 104 | 105 | entityManager.add(vehicle) 106 | 107 | // 108 | 109 | const position = [] 110 | 111 | for (let i = 0; i < path._waypoints.length; i++) { 112 | const waypoint = path._waypoints[i] 113 | 114 | position.push(waypoint.x, waypoint.y, waypoint.z) 115 | } 116 | 117 | path._waypoints.push(path._waypoints[0]) // to close the line 118 | lines = BABYLON.MeshBuilder.CreateLines('lines', { 119 | points: path._waypoints, 120 | updatable: true, 121 | }) 122 | 123 | lines.color = BABYLON.Color3.Teal() 124 | } 125 | 126 | function onWindowResize() { 127 | engine.resize() 128 | } 129 | 130 | function animate() { 131 | requestAnimationFrame(animate) 132 | 133 | const delta = time.update().getDelta() 134 | 135 | entityManager.update(delta) 136 | 137 | scene.render() 138 | } 139 | 140 | function sync(entity, renderComponent) { 141 | entity.worldMatrix.toArray(entityMatrix.m) 142 | entityMatrix.markAsUpdated() 143 | 144 | const matrix = renderComponent.getWorldMatrix() 145 | matrix.copyFrom(entityMatrix) 146 | } 147 | -------------------------------------------------------------------------------- /examples/js/steering/interpose/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Interpose 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | "Interpose" produces a force that moves a vehicle to the midpoint of the imaginary line connecting two other 13 | agents. 14 |

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/steering/interpose/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | // import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js'; 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | let lines 12 | let linePoints = [] 13 | 14 | let entityManager, time, pursuer, entity1, entity2, target1, target2 15 | 16 | const entityMatrix = new BABYLON.Matrix() 17 | 18 | init() 19 | animate() 20 | 21 | function init() { 22 | const canvas = document.getElementById('renderCanvas') 23 | engine = new BABYLON.Engine(canvas, true, {}, true) 24 | 25 | scene = new BABYLON.Scene(engine) 26 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 27 | scene.useRightHandedSystem = true 28 | // scene.debugLayer.show(); 29 | 30 | const camera = new BABYLON.ArcRotateCamera( 31 | 'camera', 32 | BABYLON.Tools.ToRadians(120), 33 | BABYLON.Tools.ToRadians(40), 34 | 20, 35 | BABYLON.Vector3.Zero(), 36 | scene 37 | ) 38 | 39 | camera.target = new BABYLON.Vector3(0, 0, 0) 40 | camera.attachControl(canvas, true) 41 | 42 | const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 43 | light.diffuse = BABYLON.Color3.Magenta() 44 | 45 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 15, height: 15 }, scene) 46 | ground.position.y = -1 47 | ground.material = new BABYLON.GridMaterial('grid', scene) 48 | ground.visibility = 0.4 49 | ground.material.backFaceCulling = false 50 | 51 | // meshes 52 | 53 | const pursuerMesh = createVehicle(scene, { size: 0.8 }) 54 | 55 | const entityMesh1 = BABYLON.MeshBuilder.CreateBox('entityMesh1', { size: 0.2 }, scene) 56 | const entityMesh2 = BABYLON.MeshBuilder.CreateBox('entityMesh2', { size: 0.2 }, scene) 57 | 58 | const entityMat = new BABYLON.StandardMaterial('entityMat', scene) 59 | entityMat.disableLighting = true 60 | entityMat.emissiveColor = BABYLON.Color3.Red() 61 | 62 | entityMesh1.material = entityMat 63 | entityMesh2.material = entityMat 64 | 65 | // helper 66 | 67 | linePoints[0] = entityMesh1.position 68 | linePoints[1] = entityMesh2.position 69 | 70 | lines = BABYLON.MeshBuilder.CreateLines('lines', { 71 | points: linePoints, 72 | updatable: true, 73 | }) 74 | lines.color = BABYLON.Color3.Red() 75 | 76 | // 77 | 78 | window.addEventListener('resize', onWindowResize, false) 79 | 80 | // game setup 81 | 82 | entityManager = new YUKA.EntityManager() 83 | time = new YUKA.Time() 84 | 85 | target1 = new YUKA.Vector3() 86 | target2 = new YUKA.Vector3() 87 | 88 | entity1 = new YUKA.Vehicle() 89 | entity1.maxSpeed = 2 90 | entity1.setRenderComponent(entityMesh1, sync) 91 | 92 | const seekBehavior1 = new YUKA.SeekBehavior(target1) 93 | entity1.steering.add(seekBehavior1) 94 | 95 | entity2 = new YUKA.Vehicle() 96 | entity2.maxSpeed = 2 97 | entity2.setRenderComponent(entityMesh2, sync) 98 | 99 | const seekBehavior2 = new YUKA.SeekBehavior(target2) 100 | entity2.steering.add(seekBehavior2) 101 | 102 | pursuer = new YUKA.Vehicle() 103 | pursuer.maxSpeed = 3 104 | pursuer.setRenderComponent(pursuerMesh, sync) 105 | 106 | const interposeBehavior = new YUKA.InterposeBehavior(entity1, entity2, 1) 107 | pursuer.steering.add(interposeBehavior) 108 | 109 | entityManager.add(entity1) 110 | entityManager.add(entity2) 111 | entityManager.add(pursuer) 112 | } 113 | 114 | function onWindowResize() { 115 | engine.resize() 116 | } 117 | 118 | function animate() { 119 | requestAnimationFrame(animate) 120 | 121 | const delta = time.update().getDelta() 122 | const elapsedTime = time.getElapsed() 123 | 124 | target1.x = Math.cos(elapsedTime * 0.1) * Math.sin(elapsedTime * 0.1) * 6 125 | target1.y = Math.cos(elapsedTime * 0.1) * Math.sin(elapsedTime * 0.1) * 6 126 | target1.z = Math.sin(elapsedTime * 0.3) * 6 127 | 128 | target2.x = 1 + Math.cos(elapsedTime * 0.5) * Math.sin(elapsedTime * 0.3) * 4 129 | target2.y = 1 + Math.cos(elapsedTime * 0.5) * Math.sin(elapsedTime * 0.3) * 4 130 | target2.z = 1 + Math.sin(elapsedTime * 0.3) * 6 131 | 132 | entityManager.update(delta) 133 | 134 | linePoints[0] = entity1.position 135 | linePoints[1] = entity2.position 136 | 137 | lines = BABYLON.MeshBuilder.CreateLines('lines', { 138 | points: linePoints, 139 | instance: lines, 140 | }) 141 | 142 | scene.render() 143 | } 144 | 145 | function sync(entity, renderComponent) { 146 | entity.worldMatrix.toArray(entityMatrix.m) 147 | entityMatrix.markAsUpdated() 148 | 149 | const matrix = renderComponent.getWorldMatrix() 150 | matrix.copyFrom(entityMatrix) 151 | } 152 | -------------------------------------------------------------------------------- /examples/js/steering/obstacleAvoidance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Obstacle Avoidance 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

This steering behavior produces a force so a vehicle avoids obstacles lying in its path.

12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/js/steering/obstacleAvoidance/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | // import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js'; 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | let entityManager, time, vehicle 12 | 13 | const obstacles = new Array() 14 | const entityMatrix = new BABYLON.Matrix() 15 | 16 | init() 17 | animate() 18 | 19 | function init() { 20 | const canvas = document.getElementById('renderCanvas') 21 | engine = new BABYLON.Engine(canvas, true, {}, true) 22 | 23 | scene = new BABYLON.Scene(engine) 24 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 25 | scene.useRightHandedSystem = true 26 | // scene.debugLayer.show() 27 | 28 | const camera = new BABYLON.ArcRotateCamera( 29 | 'camera', 30 | BABYLON.Tools.ToRadians(140), 31 | BABYLON.Tools.ToRadians(40), 32 | 50, 33 | BABYLON.Vector3.Zero(), 34 | scene 35 | ) 36 | 37 | camera.target = new BABYLON.Vector3(0, 0, 0) 38 | camera.attachControl(canvas, true) 39 | camera.upperBetaLimit = 1.1 40 | 41 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 42 | 43 | const ground = BABYLON.MeshBuilder.CreatePlane('plane', { width: 25, height: 25 }, scene) 44 | ground.rotation.x = Math.PI / 2 45 | 46 | ground.material = new BABYLON.GridMaterial('grid', scene) 47 | ground.material.backFaceCulling = true 48 | ground.visibility = 0.4 49 | 50 | const wayPointsMat = new BABYLON.StandardMaterial('wayPointsMat', scene) 51 | wayPointsMat.disableLighting = true 52 | wayPointsMat.emissiveColor = BABYLON.Color3.Magenta() 53 | 54 | const vehicleMesh = createVehicle(scene, { size: 2 }) 55 | window.addEventListener('resize', onWindowResize, false) 56 | 57 | // game setup 58 | 59 | entityManager = new YUKA.EntityManager() 60 | time = new YUKA.Time() 61 | 62 | const path = new YUKA.Path() 63 | path.loop = true 64 | path.add(new YUKA.Vector3(10, 0, 10)) 65 | path.add(new YUKA.Vector3(10, 10, -10)) 66 | path.add(new YUKA.Vector3(-10, 0, -10)) 67 | path.add(new YUKA.Vector3(-10, 0, 10)) 68 | 69 | vehicle = new YUKA.Vehicle() 70 | vehicle.maxSpeed = 3 71 | vehicle.setRenderComponent(vehicleMesh, sync) 72 | 73 | vehicle.boundingRadius = vehicleMesh.getBoundingInfo().boundingSphere.radius 74 | vehicle.smoother = new YUKA.Smoother(20) 75 | 76 | entityManager.add(vehicle) 77 | 78 | const obstacleAvoidanceBehavior = new YUKA.ObstacleAvoidanceBehavior(obstacles) 79 | vehicle.steering.add(obstacleAvoidanceBehavior) 80 | 81 | const followPathBehavior = new YUKA.FollowPathBehavior(path) 82 | vehicle.steering.add(followPathBehavior) 83 | 84 | // obstacles 85 | 86 | setupObstacles() 87 | setupWaypoints(path) 88 | } 89 | 90 | function onWindowResize() { 91 | engine.resize() 92 | } 93 | 94 | function animate() { 95 | requestAnimationFrame(animate) 96 | 97 | const delta = time.update().getDelta() 98 | 99 | entityManager.update(delta) 100 | 101 | scene.render() 102 | } 103 | 104 | function sync(entity, renderComponent) { 105 | entity.worldMatrix.toArray(entityMatrix.m) 106 | entityMatrix.markAsUpdated() 107 | 108 | const matrix = renderComponent.getWorldMatrix() 109 | matrix.copyFrom(entityMatrix) 110 | } 111 | 112 | function setupObstacles() { 113 | const mesh1 = BABYLON.MeshBuilder.CreateBox('mesh1', { size: 2 }, scene) 114 | const mesh2 = BABYLON.MeshBuilder.CreateBox('mesh1', { width: 2, height: 6, depth: 2 }, scene) 115 | const mesh3 = BABYLON.MeshBuilder.CreateBox('mesh1', { size: 2 }, scene) 116 | 117 | const meshMat = new BABYLON.StandardMaterial('meshMat', scene) 118 | meshMat.disableLighting = true 119 | meshMat.emissiveColor = BABYLON.Color3.Red() 120 | 121 | mesh1.material = meshMat 122 | mesh2.material = meshMat 123 | mesh3.material = meshMat 124 | 125 | mesh1.position.set(-10, 0, 0) 126 | mesh2.position.set(11, 6, 0) 127 | mesh3.position.set(4, 8, -10) 128 | 129 | const obstacle1 = new YUKA.GameEntity() 130 | obstacle1.position.copy(mesh1.position) 131 | obstacle1.boundingRadius = mesh1.getBoundingInfo().boundingSphere.radius * 1.4 132 | entityManager.add(obstacle1) 133 | obstacles.push(obstacle1) 134 | 135 | const obstacle2 = new YUKA.GameEntity() 136 | obstacle2.position.copy(mesh2.position) 137 | obstacle2.boundingRadius = mesh2.getBoundingInfo().boundingSphere.radius 138 | entityManager.add(obstacle2) 139 | obstacles.push(obstacle2) 140 | 141 | const obstacle3 = new YUKA.GameEntity() 142 | obstacle3.position.copy(mesh3.position) 143 | obstacle3.boundingRadius = mesh3.getBoundingInfo().boundingSphere.radius 144 | entityManager.add(obstacle3) 145 | obstacles.push(obstacle3) 146 | } 147 | 148 | function setupWaypoints(path) { 149 | path._waypoints.forEach((p) => { 150 | const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 0.4 }, scene) 151 | sphere.material = scene.getMaterialByName('wayPointsMat') 152 | sphere.position.x = p.x 153 | sphere.position.y = p.y 154 | sphere.position.z = p.z 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /examples/js/steering/offsetPursuit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Offset Pursuit 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

This steering behavior produces a force that keeps a vehicle at a specified offset from a leader vehicle.

12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/js/steering/offsetPursuit/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | // import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js'; 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | let entityManager, time, target 12 | 13 | const entityMatrix = new BABYLON.Matrix() 14 | 15 | init() 16 | animate() 17 | 18 | function init() { 19 | const canvas = document.getElementById('renderCanvas') 20 | engine = new BABYLON.Engine(canvas, true, {}, true) 21 | 22 | scene = new BABYLON.Scene(engine) 23 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 24 | // scene.debugLayer.show(); 25 | 26 | const camera = new BABYLON.ArcRotateCamera( 27 | 'camera', 28 | BABYLON.Tools.ToRadians(70), 29 | BABYLON.Tools.ToRadians(60), 30 | 15, 31 | BABYLON.Vector3.Zero(), 32 | scene 33 | ) 34 | 35 | camera.target = new BABYLON.Vector3(0, 0, 0) 36 | camera.attachControl(canvas, true) 37 | 38 | const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 39 | light.diffuse = BABYLON.Color3.Green() 40 | // 41 | 42 | const leaderMesh = createVehicle(scene, { size: 0.8 }) 43 | 44 | const followerMeshTemplate = createVehicle(scene, { size: 0.5 }) 45 | followerMeshTemplate.isVisible = false 46 | 47 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 10, height: 10 }, scene) 48 | ground.position.y = -1 49 | ground.material = new BABYLON.GridMaterial('grid', scene) 50 | ground.material.backFaceCulling = false 51 | ground.visibility = 0.4 52 | 53 | // 54 | 55 | window.addEventListener('resize', onWindowResize, false) 56 | 57 | // game setup 58 | 59 | entityManager = new YUKA.EntityManager() 60 | time = new YUKA.Time() 61 | 62 | target = new YUKA.Vector3() 63 | 64 | // leader 65 | 66 | const leader = new YUKA.Vehicle() 67 | leader.setRenderComponent(leaderMesh, sync) 68 | 69 | const seekBehavior = new YUKA.SeekBehavior(target) 70 | leader.steering.add(seekBehavior) 71 | 72 | entityManager.add(leader) 73 | 74 | // follower 75 | 76 | const offsets = [ 77 | new YUKA.Vector3(0.5, 0, -0.5), 78 | new YUKA.Vector3(-0.5, 0, -0.5), 79 | new YUKA.Vector3(1.5, 0, -1.5), 80 | new YUKA.Vector3(-1.5, 0, -1.5), 81 | ] 82 | 83 | for (let i = 0; i < 4; i++) { 84 | const followerMesh = followerMeshTemplate.clone() 85 | followerMesh.isVisible = true 86 | 87 | const follower = new YUKA.Vehicle() 88 | follower.maxSpeed = 2 89 | follower.position.copy(offsets[i]) // initial position 90 | follower.scale.set(0.5, 0.5, 0.5) // make the followers a bit smaller 91 | follower.setRenderComponent(followerMesh, sync) 92 | 93 | const offsetPursuitBehavior = new YUKA.OffsetPursuitBehavior(leader, offsets[i]) 94 | follower.steering.add(offsetPursuitBehavior) 95 | 96 | entityManager.add(follower) 97 | } 98 | } 99 | 100 | function onWindowResize() { 101 | engine.resize() 102 | } 103 | 104 | function animate() { 105 | requestAnimationFrame(animate) 106 | 107 | time.update() 108 | 109 | const deltaTime = time.getDelta() 110 | const elapsedTime = time.getElapsed() 111 | 112 | target.z = Math.cos(elapsedTime * 0.2) * 5 113 | target.y = Math.cos(elapsedTime * 0.4) * 3 114 | target.x = Math.sin(elapsedTime * 0.2) * 5 115 | 116 | entityManager.update(deltaTime) 117 | 118 | scene.render() 119 | } 120 | 121 | function sync(entity, renderComponent) { 122 | entity.worldMatrix.toArray(entityMatrix.m) 123 | entityMatrix.markAsUpdated() 124 | 125 | const matrix = renderComponent.getWorldMatrix() 126 | matrix.copyFrom(entityMatrix) 127 | } 128 | -------------------------------------------------------------------------------- /examples/js/steering/pursuit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Pursuit 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

"Pursuit" is useful when an agent is required to intercept a moving agent.

12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/js/steering/pursuit/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js' 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | let entityManager, time, pursuer, evader, target 12 | 13 | const entityMatrix = new BABYLON.Matrix() 14 | 15 | init() 16 | animate() 17 | 18 | function init() { 19 | const canvas = document.getElementById('renderCanvas') 20 | engine = new BABYLON.Engine(canvas, true, {}, true) 21 | 22 | scene = new BABYLON.Scene(engine) 23 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 24 | scene.useRightHandedSystem = true 25 | // scene.debugLayer.show(); 26 | 27 | const camera = new BABYLON.ArcRotateCamera( 28 | 'camera', 29 | BABYLON.Tools.ToRadians(30), 30 | BABYLON.Tools.ToRadians(40), 31 | 15, 32 | BABYLON.Vector3.Zero(), 33 | scene 34 | ) 35 | 36 | camera.target = new BABYLON.Vector3(0, 0, 0) 37 | camera.attachControl(canvas, true) 38 | 39 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 40 | 41 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 10, height: 10 }, scene) 42 | ground.position.y = -1 43 | ground.material = new BABYLON.GridMaterial('grid', scene) 44 | ground.visibility = 0.5 45 | ground.material.backFaceCulling = false 46 | 47 | // 48 | 49 | const pursuerMesh = createVehicle(scene) 50 | 51 | const evaderMesh = BABYLON.MeshBuilder.CreateBox('box', { size: 0.2 }, scene) 52 | 53 | const evaderMaterial = new BABYLON.StandardMaterial('evaderMaterial', scene) 54 | evaderMaterial.disableLighting = true 55 | evaderMaterial.emissiveColor = new BABYLON.Color3(1, 0, 0) 56 | 57 | evaderMesh.material = evaderMaterial 58 | 59 | /* 60 | const grid = new THREE.GridHelper( 10, 25 ); 61 | scene.add( grid ); 62 | */ 63 | // 64 | 65 | // 66 | 67 | window.addEventListener('resize', onWindowResize, false) 68 | 69 | // game setup 70 | 71 | entityManager = new YUKA.EntityManager() 72 | time = new YUKA.Time() 73 | 74 | target = new YUKA.Vector3() 75 | 76 | evader = new YUKA.Vehicle() 77 | evader.maxSpeed = 3 78 | evader.setRenderComponent(evaderMesh, sync) 79 | 80 | pursuer = new YUKA.Vehicle() 81 | pursuer.maxSpeed = 3 82 | pursuer.position.z = -5 83 | pursuer.setRenderComponent(pursuerMesh, sync) 84 | 85 | const pursuitBehavior = new YUKA.PursuitBehavior(evader, 2) 86 | pursuer.steering.add(pursuitBehavior) 87 | 88 | const seekBehavior = new YUKA.SeekBehavior(target) 89 | evader.steering.add(seekBehavior) 90 | 91 | entityManager.add(evader) 92 | entityManager.add(pursuer) 93 | 94 | // dat.gui 95 | 96 | const gui = new DAT.GUI({ width: 300 }) 97 | 98 | gui.add(pursuitBehavior, 'predictionFactor', 0, 5).name('prediction factor') 99 | 100 | gui.open() 101 | } 102 | 103 | function onWindowResize() { 104 | engine.resize() 105 | } 106 | 107 | function animate() { 108 | requestAnimationFrame(animate) 109 | 110 | const deltaTime = time.update().getDelta() 111 | const elapsedTime = time.getElapsed() 112 | 113 | target.x = Math.cos(elapsedTime) * Math.sin(elapsedTime * 0.2) * 6 114 | target.y = Math.cos(elapsedTime) * Math.sin(elapsedTime * 0.2) * 12 + 2 115 | target.z = Math.sin(elapsedTime * 0.8) * 6 116 | 117 | // console.log(target.x) 118 | 119 | entityManager.update(deltaTime) 120 | 121 | scene.render() 122 | } 123 | 124 | function sync(entity, renderComponent) { 125 | entity.worldMatrix.toArray(entityMatrix.m) 126 | entityMatrix.markAsUpdated() 127 | 128 | const matrix = renderComponent.getWorldMatrix() 129 | matrix.copyFrom(entityMatrix) 130 | } 131 | -------------------------------------------------------------------------------- /examples/js/steering/seek/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Seek 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

This steering behavior produces a force that directs an agent toward a target position.

12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/js/steering/seek/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | import 'https://preview.babylonjs.com/babylon.js' 3 | import { createVehicle } from '../../creator.js' 4 | 5 | let engine, scene 6 | let entityManager, time, vehicle, target 7 | 8 | const entityMatrix = new BABYLON.Matrix() 9 | 10 | init() 11 | animate() 12 | 13 | function init() { 14 | const canvas = document.getElementById('renderCanvas') 15 | engine = new BABYLON.Engine(canvas, true, {}, true) 16 | 17 | scene = new BABYLON.Scene(engine) 18 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 19 | scene.useRightHandedSystem = true 20 | 21 | const camera = new BABYLON.ArcRotateCamera( 22 | 'camera', 23 | BABYLON.Tools.ToRadians(30), 24 | BABYLON.Tools.ToRadians(40), 25 | 8, 26 | BABYLON.Vector3.Zero(), 27 | scene 28 | ) 29 | 30 | camera.target = new BABYLON.Vector3(0, 0, 0) 31 | camera.attachControl() 32 | 33 | const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 34 | light.diffuse = BABYLON.Color3.Green() 35 | // 36 | 37 | const vehicleMesh = createVehicle(scene, { size: 0.5 }) 38 | 39 | const sphere = BABYLON.MeshBuilder.CreateSphere('sphere', { diameter: 5, segments: 16 }) 40 | sphere.material = new BABYLON.StandardMaterial('sphereMaterial', scene) 41 | sphere.material.disableLighting = true 42 | sphere.material.emissiveColor = new BABYLON.Color3(0.8, 0.8, 0.8) 43 | sphere.material.alpha = 0.2 44 | sphere.material.wireframe = true 45 | 46 | const targetMesh = BABYLON.MeshBuilder.CreateSphere('target', { diameter: 0.1, segments: 16 }) 47 | targetMesh.material = new BABYLON.StandardMaterial('targetMaterial', scene) 48 | targetMesh.material.disableLighting = true 49 | targetMesh.material.emissiveColor = new BABYLON.Color3(1, 0, 0) 50 | 51 | // 52 | 53 | window.addEventListener('resize', onWindowResize, false) 54 | 55 | // game setup 56 | 57 | entityManager = new YUKA.EntityManager() 58 | time = new YUKA.Time() 59 | 60 | target = new YUKA.GameEntity() 61 | target.setRenderComponent(targetMesh, sync) 62 | 63 | vehicle = new YUKA.Vehicle() 64 | vehicle.setRenderComponent(vehicleMesh, sync) 65 | 66 | const seekBehavior = new YUKA.SeekBehavior(target.position) 67 | vehicle.steering.add(seekBehavior) 68 | 69 | entityManager.add(target) 70 | entityManager.add(vehicle) 71 | 72 | generateTarget() 73 | } 74 | 75 | function onWindowResize() { 76 | engine.resize() 77 | } 78 | 79 | function animate() { 80 | requestAnimationFrame(animate) 81 | 82 | const delta = time.update().getDelta() 83 | entityManager.update(delta) 84 | 85 | scene.render() 86 | } 87 | 88 | function sync(entity, renderComponent) { 89 | entity.worldMatrix.toArray(entityMatrix.m) 90 | entityMatrix.markAsUpdated() 91 | 92 | const matrix = renderComponent.getWorldMatrix() 93 | matrix.copyFrom(entityMatrix) 94 | } 95 | 96 | function generateTarget() { 97 | // generate a random point on a sphere 98 | 99 | const radius = 2 100 | const phi = Math.acos(2 * Math.random() - 1) 101 | const theta = Math.random() * Math.PI * 2 102 | 103 | target.position.fromSpherical(radius, phi, theta) 104 | 105 | setTimeout(generateTarget, 3000) 106 | } 107 | -------------------------------------------------------------------------------- /examples/js/steering/wander/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | Steering Behaviors | Wander 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

12 | "Wander" produces a steering force that will give the impression of a random walk through the agent’s 13 | environment. 14 |

15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/js/steering/wander/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | // import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js'; 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | import { createVehicle } from '../../creator.js' 7 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 8 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 9 | 10 | let engine, scene 11 | let entityManager, time 12 | 13 | const entityMatrix = new BABYLON.Matrix() 14 | 15 | init() 16 | animate() 17 | 18 | function init() { 19 | const canvas = document.getElementById('renderCanvas') 20 | engine = new BABYLON.Engine(canvas, true, {}, true) 21 | 22 | scene = new BABYLON.Scene(engine) 23 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 24 | scene.useRightHandedSystem = true 25 | // scene.debugLayer.show(); 26 | 27 | const camera = new BABYLON.ArcRotateCamera( 28 | 'camera', 29 | BABYLON.Tools.ToRadians(120), 30 | BABYLON.Tools.ToRadians(40), 31 | 25, 32 | BABYLON.Vector3.Zero(), 33 | scene 34 | ) 35 | 36 | camera.target = new BABYLON.Vector3(0, 0, 0) 37 | camera.attachControl(canvas, true) 38 | 39 | const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 40 | light.diffuse = BABYLON.Color3.Teal() 41 | 42 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 30, height: 20 }, scene) 43 | ground.position.y = -1 44 | ground.material = new BABYLON.GridMaterial('grid', scene) 45 | ground.material.backFaceCulling = false 46 | ground.visibility = 0.4 47 | // game setup 48 | 49 | entityManager = new YUKA.EntityManager() 50 | time = new YUKA.Time() 51 | 52 | // 53 | 54 | const vehicleMeshPrefab = createVehicle(scene, { size: 0.8 }) 55 | vehicleMeshPrefab.setEnabled(false) 56 | 57 | for (let i = 0; i < 50; i++) { 58 | const vehicleMesh = vehicleMeshPrefab.clone('vehicle') 59 | vehicleMesh.setEnabled(true) 60 | 61 | vehicleMesh.bakeCurrentTransformIntoVertices() 62 | 63 | const vehicle = new YUKA.Vehicle() 64 | vehicle.rotation.fromEuler(0, 2 * Math.PI * Math.random(), 0) 65 | vehicle.position.x = 2.5 - Math.random() * 5 66 | vehicle.position.z = 2.5 - Math.random() * 5 67 | vehicle.setRenderComponent(vehicleMesh, sync) 68 | 69 | const wanderBehavior = new YUKA.WanderBehavior() 70 | vehicle.steering.add(wanderBehavior) 71 | 72 | entityManager.add(vehicle) 73 | } 74 | 75 | // 76 | 77 | // 78 | 79 | window.addEventListener('resize', onWindowResize, false) 80 | } 81 | 82 | function onWindowResize() { 83 | engine.resize() 84 | } 85 | 86 | function animate() { 87 | requestAnimationFrame(animate) 88 | 89 | const delta = time.update().getDelta() 90 | 91 | entityManager.update(delta) 92 | 93 | scene.render() 94 | } 95 | 96 | function sync(entity, renderComponent) { 97 | entity.worldMatrix.toArray(entityMatrix.m) 98 | entityMatrix.markAsUpdated() 99 | 100 | const matrix = renderComponent.getWorldMatrix() 101 | matrix.copyFrom(entityMatrix) 102 | } 103 | -------------------------------------------------------------------------------- /examples/js/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Yuka | TODO-Title 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |

TODO-Description

12 |
13 | canvas> 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/js/templates/index.js: -------------------------------------------------------------------------------- 1 | import * as YUKA from '../../../../lib/yuka.module.js' 2 | // import * as DAT from 'https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.module.js'; 3 | 4 | import 'https://preview.babylonjs.com/babylon.js' 5 | import 'https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js' 6 | // import 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js' 7 | // import 'https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js' 8 | 9 | let engine, scene 10 | let entityManager, time, vehicle, target 11 | 12 | const entityMatrix = new BABYLON.Matrix() 13 | 14 | init() 15 | animate() 16 | 17 | function init() { 18 | const canvas = document.getElementById('renderCanvas') 19 | engine = new BABYLON.Engine(canvas, true, {}, true) 20 | 21 | scene = new BABYLON.Scene(engine) 22 | scene.clearColor = new BABYLON.Color4(0, 0, 0, 1) 23 | scene.useRightHandedSystem = true 24 | 25 | scene.debugLayer.show() 26 | 27 | const camera = new BABYLON.ArcRotateCamera( 28 | 'camera', 29 | BABYLON.Tools.ToRadians(90), 30 | BABYLON.Tools.ToRadians(0), 31 | 30, 32 | BABYLON.Vector3.Zero(), 33 | scene 34 | ) 35 | 36 | camera.target = new BABYLON.Vector3(0, 0, 0) 37 | camera.attachControl(canvas, true) 38 | 39 | new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 0)) 40 | 41 | const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 40, height: 20 }, scene) 42 | ground.position.y = -1 43 | ground.material = new BABYLON.GridMaterial('grid', scene) 44 | 45 | const vehicleMesh = BABYLON.MeshBuilder.CreateCylinder( 46 | 'cone', 47 | { height: 2, diameterTop: 0, diameterBottom: 1 }, 48 | scene 49 | ) 50 | vehicleMesh.rotation.x = Math.PI * 0.5 51 | vehicleMesh.bakeCurrentTransformIntoVertices() 52 | 53 | scene.onPointerMove = () => { 54 | var pickResult = scene.pick(scene.pointerX, scene.pointerY) 55 | if (pickResult?.pickedPoint) { 56 | target.x = pickResult.pickedPoint.x 57 | target.y = pickResult.pickedPoint.y 58 | target.z = pickResult.pickedPoint.z 59 | } 60 | } 61 | 62 | window.addEventListener('resize', onWindowResize, false) 63 | 64 | // YUKA specific 65 | target = new YUKA.Vector3() 66 | 67 | entityManager = new YUKA.EntityManager() 68 | time = new YUKA.Time() 69 | vehicle = new YUKA.Vehicle() 70 | vehicle.setRenderComponent(vehicleMesh, sync) 71 | 72 | // 73 | entityManager.add(vehicle) 74 | } 75 | 76 | function onWindowResize() { 77 | engine.resize() 78 | } 79 | 80 | function animate() { 81 | requestAnimationFrame(animate) 82 | 83 | const delta = time.update().getDelta() 84 | entityManager.update(delta) 85 | 86 | scene.render() 87 | } 88 | 89 | function sync(entity, renderComponent) { 90 | entity.worldMatrix.toArray(entityMatrix.m) 91 | entityMatrix.markAsUpdated() 92 | 93 | const matrix = renderComponent.getWorldMatrix() 94 | matrix.copyFrom(entityMatrix) 95 | } 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yuka-babylon-examples", 3 | "version": "1.0.0", 4 | "description": "YUKA BabylonJS examples", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/eldinor/yuka-babylon-examples.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/eldinor/yuka-babylon-examples/issues" 17 | }, 18 | "homepage": "https://github.com/eldinor/yuka-babylon-examples#readme", 19 | "devDependencies": { 20 | "prettier": "2.5.1" 21 | }, 22 | "dependencies": { 23 | "@types/yuka": "^0.7.1", 24 | "babylonjs": "^5.0.0-beta.5", 25 | "babylonjs-inspector": "^5.0.0-beta.5", 26 | "babylonjs-loaders": "^5.0.0-beta.5", 27 | "babylonjs-materials": "^5.0.0-beta.5", 28 | "yuka": "^0.7.7" 29 | } 30 | } 31 | --------------------------------------------------------------------------------