Advertisement
yskang

threejs-minecraft-32

Apr 25th, 2020
1,124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Point lock navigation tool
  3.  * Ref: https://ithelp.ithome.com.tw/articles/10208322
  4.  */
  5.  
  6. class NavigationTool {
  7.   constructor( camera ) {
  8.     this.camera = camera;
  9.     this.controls = null;
  10.     this.raycaster = null;
  11.     this.moveForward = false;
  12.     this.moveBackward = false;
  13.     this.moveLeft = false;
  14.     this.moveRight = false;
  15.     this.canJump = false;
  16.     this.prevTime = Date.now(); // 初始時間
  17.     this.velocity = new THREE.Vector3(); // 移動速度向量
  18.     this.direction = new THREE.Vector3(); // 移動方向向量
  19.  
  20.     this.onControlLockedHandler = ( event ) => this.onControlLocked( event );
  21.     this.onControlUnlockedHandler = ( event ) => this.onControlUnlocked( event );
  22.     this.onInstructionsClickedHandler = ( event ) => this.onInstructionsClicked( event );
  23.     this.onKeyDownHandler = ( event ) => this.onKeyDown( event );
  24.     this.onKeyUpHandler = ( event ) => this.onKeyUp( event );
  25.  
  26.     this.initialize();
  27.     this.bindEvents();
  28.   }
  29.  
  30.   createUI() {
  31.     const blocker = document.createElement( 'div' );
  32.     blocker.id = 'blocker';
  33.     document.body.appendChild( blocker );
  34.  
  35.     const instructions = document.createElement( 'div' );
  36.     instructions.id = 'instructions';
  37.     blocker.appendChild( instructions );
  38.  
  39.     const spanTitle = document.createElement( 'span' );
  40.     spanTitle.style.fontSize = '40px';
  41.     spanTitle.innerText = '點擊開始';
  42.     instructions.appendChild( spanTitle );
  43.  
  44.     const breakLine = document.createElement( 'br' );
  45.     instructions.appendChild( breakLine );
  46.  
  47.     const spanControl = document.createElement( 'span' );
  48.     spanControl.innerText = '(W, A, S, D = 控制方向, SPACE = 跳躍, MOUSE = 轉動視角)';
  49.     instructions.appendChild( spanControl );
  50.   }
  51.  
  52.   initialize() {
  53.     this.createUI();
  54.  
  55.     const controls = new THREE.PointerLockControls( this.camera );
  56.     controls.getObject().position.set( 10, 0, 60 );
  57.     this.controls = controls;
  58.  
  59.     // 使用 Raycaster 實現簡單碰撞偵測
  60.     this.raycaster = new THREE.Raycaster(
  61.       new THREE.Vector3(),
  62.       new THREE.Vector3( 0, -1, 0 ),
  63.       0,
  64.       10
  65.     );
  66.   }
  67.  
  68.   uninitialize() {
  69.     this.clearEvents();
  70.   }
  71.  
  72.   attach( scene ) {
  73.     if( !(scene instanceof THREE.Scene) ) return;
  74.  
  75.     scene.add( this.controls.getObject() );
  76.   }
  77.  
  78.   detach( scene ) {
  79.     if( !(scene instanceof THREE.Scene) ) return;
  80.  
  81.     scene.remove( this.controls.getObject() );
  82.   }
  83.  
  84.   bindEvents() {
  85.     const instructions = document.getElementById( 'instructions' );
  86.  
  87.     const havePointerLock = 'pointerLockElement' in document ||
  88.                             'mozPointerLockElement' in document ||
  89.                             'webkitPointerLockElement' in document;
  90.  
  91.     if( havePointerLock ) {
  92.       instructions.addEventListener(
  93.         'click',
  94.         this.onInstructionsClickedHandler,
  95.         false
  96.       );
  97.  
  98.       this.controls.addEventListener(
  99.         'lock',
  100.         this.onControlLockedHandler
  101.       );
  102.  
  103.       this.controls.addEventListener(
  104.         'unlock',
  105.         this.onControlUnlockedHandler
  106.       );
  107.     } else {
  108.       instructions.innerHTML = '你的瀏覽器似乎不支援 Pointer Lock API,建議使用電腦版 Google Chrome 取得最佳體驗!';
  109.     }
  110.    
  111.     document.addEventListener(
  112.       'keydown',
  113.       this.onKeyDownHandler,
  114.       false
  115.     );
  116.  
  117.     document.addEventListener(
  118.       'keyup',
  119.       this.onKeyUpHandler,
  120.       false
  121.     );
  122.   }
  123.  
  124.   clearEvents() {
  125.     const instructions = document.getElementById( 'instructions' );
  126.  
  127.     instructions.removeEventListener(
  128.       'click',
  129.       this.onInstructionsClickedHandler,
  130.       false
  131.     );
  132.  
  133.     this.controls.removeEventListener(
  134.       'lock',
  135.       this.onControlLockedHandler
  136.     );
  137.  
  138.     this.controls.removeEventListener(
  139.       'unlock',
  140.       this.onControlUnlockedHandler
  141.     );
  142.  
  143.     document.removeEventListener(
  144.       'keydown',
  145.       this.onKeyDownHandler,
  146.       false
  147.     );
  148.  
  149.     document.removeEventListener(
  150.       'keyup',
  151.       this.onKeyUpHandler,
  152.       false
  153.     );
  154.   }
  155.  
  156.   update( scene ) {
  157.     if( !(scene instanceof THREE.Scene) || this.controls.isLocked !== true ) return;
  158.  
  159.     // 使用 Raycaster 判斷腳下是否與場景中物體相交
  160.     this.raycaster.ray.origin.copy( this.controls.getObject().position ); // 複製控制器的位置
  161.     const intersections = this.raycaster.intersectObjects( scene.children, true ); // 判斷是否在任何物體上
  162.     const onObject = intersections.length > 0;
  163.  
  164.     // 計算時間差
  165.     const time = Date.now();
  166.     const delta = ( time - this.prevTime ) / 1000; // 大約為 0.016
  167.  
  168.     // 設定初始速度變化
  169.     this.velocity.x -= this.velocity.x * 10.0 * delta;
  170.     this.velocity.z -= this.velocity.z * 10.0 * delta;
  171.     this.velocity.y -= 9.8 * 100.0 * delta; // 預設墜落速度
  172.  
  173.     // 判斷按鍵朝什麼方向移動,並設定對應方向速度變化
  174.     this.direction.z = Number( this.moveForward ) - Number( this.moveBackward );
  175.     this.direction.x = Number( this.moveLeft ) - Number( this.moveRight );
  176.     // direction.normalize() // 向量正規化(長度為 1),確保每個方向保持一定移動量
  177.     if( this.moveForward || this.moveBackward )
  178.       this.velocity.z -= this.direction.z * 400.0 * delta;
  179.     if( this.moveLeft || this.moveRight )
  180.       this.velocity.x -= this.direction.x * 400.0 * delta;
  181.  
  182.     // 處理跳躍對應 y 軸方向速度變化
  183.     if( onObject === true ) {
  184.       this.velocity.y = Math.max( 0, this.velocity.y );
  185.       this.canJump = true;
  186.     }
  187.  
  188.     // 根據速度值移動控制器位置
  189.     this.controls.getObject().translateX( this.velocity.x * delta );
  190.     this.controls.getObject().translateY( this.velocity.y * delta );
  191.     this.controls.getObject().translateZ( this.velocity.z * delta );
  192.  
  193.     // 控制器下墜超過 -2000 則重置位置
  194.     if( this.controls.getObject().position.y < -2000 ) {
  195.       this.velocity.y = 0;
  196.       this.controls.getObject().position.set( 10, 100, 60 );
  197.       this.canJump = true;
  198.     }
  199.  
  200.     this.prevTime = time;
  201.   }
  202.  
  203.   onInstructionsClicked() {
  204.     this.controls.lock();
  205.   }
  206.  
  207.   onControlLocked() {
  208.     const blocker = document.getElementById( 'blocker' );
  209.     const instructions = document.getElementById( 'instructions' );
  210.  
  211.     instructions.style.display = 'none';
  212.     blocker.style.display = 'none';
  213.   }
  214.  
  215.   onControlUnlocked() {
  216.     const blocker = document.getElementById( 'blocker' );
  217.     const instructions = document.getElementById( 'instructions' );
  218.  
  219.     blocker.style.display = 'block';
  220.     instructions.style.display = '';
  221.   }
  222.  
  223.   onKeyUp( event ) {
  224.     switch( event.keyCode ) {
  225.       case 38: // up
  226.       case 87: // w
  227.         this.moveForward = false;
  228.         break;
  229.       case 37: // left
  230.       case 65: // a
  231.         this.moveLeft = false;
  232.         break;
  233.       case 40: // down
  234.       case 83: // s
  235.         this.moveBackward = false;
  236.         break;
  237.       case 39: // right
  238.       case 68: // d
  239.         this.moveRight = false;
  240.         break;
  241.     }
  242.   }
  243.  
  244.   onKeyDown( event ) {
  245.     switch( event.keyCode ) {
  246.       case 38: // up
  247.       case 87: // w
  248.         this.moveForward = true;
  249.         break;
  250.       case 37: // left
  251.       case 65: // a
  252.         this.moveLeft = true;
  253.         break;
  254.       case 40: // down
  255.       case 83: // s
  256.         this.moveBackward = true;
  257.         break
  258.       case 39: // right
  259.       case 68: // d
  260.         this.moveRight = true
  261.         break;
  262.       case 32: // space
  263.         if( this.canJump === true )
  264.           this.velocity.y += 350; // 跳躍高度
  265.  
  266.         this.canJump = false;
  267.         break;
  268.     }
  269.   }
  270. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement