2013年8月2日金曜日

LeapMotionとThree.jsでブラウザシューティングゲーム

先日の記事(Box2dWeb)に引き続きLeapMotionネタです。
シューティングゲームを作ってみます。

webGLのAPIを使いこなすことなど不可能です。
なので Three.js を使います。
Three.js はWebGLを使いやすくしてくれるライブラリで、初めて使いましたがすごーく使いやすいです。

デモはこちら

ソースコードはこちら

参考サイト

ということで要点だけ

カメラ

カメラは原点においてz+を向かせます。

camera.position.z = 0;
camera.position.x = 0;
camera.lookAt( new THREE.Vector3( 0, 0, 1 ) );

的の生成

0.5秒おきに的を生成します。
的の初期位置は z:1000。画面奥にいます。

// 的生成
setInterval(function(){
 self._createCube();
}, 500);

createCube = functioin(){
 var r = sl.randWithRange;
 var color = r(0, Math.pow(16, 6));
 var geometry = new THREE.CubeGeometry(r(1,30), r(1,30), r(1,30));
 var material = new THREE.MeshLambertMaterial({color: color});
 var mesh     = new THREE.Mesh(geometry, material);
 mesh.position.z = 1000;
 mesh.position.x = r(-100, 200);
 mesh.position.y = r(-100, 200);
 scene.add(mesh);
};

弾丸の生成

LeapMotionのコールバックから弾丸を作ります。
divNumで感覚を調整します。
なんと、指の向き情報が角度ではなく、x軸,y軸,z軸それぞれの方向に分解した大きさで入ってくる。使いやすい。
そう、LeapMotionならね。
frame.hands[].fingers[].finger.direction

// leapコールバック
Leap.loop({enableGestures: false}, function(frame){
 self._leapLoop(frame);
});

_leapLoop = function(frame){
 var divNum = 10; // TODO threeと同期
 this.frameCount++;
 if(this.frameCount % divNum != 0){
  return;
 }
 this.frameCount = 0;
 var hands = frame.hands;

 var leftFingers = hands[0].fingers;

 // 1hand
 for(var i=0,max=leftFingers.length;i<max;i++){
  if(i == 1) break; // 1本まで
  var finger = leftFingers[i];
  var point = finger.tipPosition;
  var direction = finger.direction;
  var speedRate = {x:-direction[0], y:direction[1], z:-direction[2]};
  this._createBullet(-point[0], point[1] - 150, ThreeApp.Bullet.TYPE.LEFT, speedRate);
 }

 if(hands.length < 2) return;

 var rightFingers = hands[1].fingers;

 // 2hand
 for(var i=0,max=rightFingers.length;i<max;i++){
  if(i == 1) break; // 1本まで
  var finger = rightFingers[i];
  var point = finger.tipPosition;
  var direction = finger.direction;
  var speedRate = {x:-direction[0], y:direction[1], z:-direction[2]};
  this._createBullet(-point[0], point[1] - 150, ThreeApp.Bullet.TYPE.RIGHT, speedRate);
 }
}

_createBullet = function(x, y, type, speedRate){
 var INITIAL_SPEED = 10;
 var speed = {
  x: INITIAL_SPEED*speedRate.x
  , y: INITIAL_SPEED*speedRate.y
  , z: INITIAL_SPEED*speedRate.z
 };
 this.speed = speed;

 var r = sl.randWithRange;
 var color = r(0, Math.pow(16, 6));
 var geometry = new THREE.CubeGeometry(r(1,30), r(1,30), r(1,30));
 var material = new THREE.MeshLambertMaterial({color: color});
 var mesh     = new THREE.Mesh(geometry, material);
 mesh.position.z = 1000;
 mesh.position.x = r(-100, 200);
 mesh.position.y = r(-100, 200);
 scene.add(mesh);
}

動かす

的は単純にこっち側に近づいてくるだけ。

var mesh = this.mesh;
mesh.position.z -= this.speed / 10;
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
mesh.rotation.z += 0.01;

if(mesh.position.z < 0){
 // ぶつかった!
 // ダメージエフェクト
}

弾丸

LeapMotionの指情報から作ったスピードで動かす
yは何となく落とす

var mesh = this.mesh;
var speed = this.speed;
mesh.position.z += speed.z;
mesh.position.y += speed.y;
mesh.position.x += speed.x;

speed.y -= (0.98 * 0.002);

if(mesh.position.z > 1000){
 // 十分向こうに言ったので消す
}

衝突判定

パフォーマンスを考慮した3次元の衝突判定は無理なので、簡易で行きます。
参考:Collision Detection

/**
 * TODOOOOOOO 
 * ここの最適化
 * できません、だれか教えて下さい、お願いします。
 */
ThreeApp.prototype._tryCollision = function(){
 var bulletMeshList = ThreeApp.Bullet.getMeshList();
 var cubeMeshList = ThreeApp.Cube.getMeshList();
 for(var i=0,max=cubeMeshList.length;i<max;i++){
  var cubeMesh = cubeMeshList[i];
  if(this._tryBan(cubeMesh, bulletMeshList)){
   cubeMesh.cube.ban();
  }
 }
}

ThreeApp.prototype._tryBan = function(cubeMesh, bulletMeshList){
 var originPoint = cubeMesh.position.clone();
 for (var vertexIndex = 0; vertexIndex < cubeMesh.geometry.vertices.length; vertexIndex++)
 {  
  var localVertex = cubeMesh.geometry.vertices[vertexIndex].clone();
  var globalVertex = localVertex.applyMatrix4( cubeMesh.matrix );
  var directionVector = globalVertex.sub( cubeMesh.position );
  
  var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
  var collisionResults = ray.intersectObjects( bulletMeshList );
  if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) {
   return true;
  }
 }

 return false;
};

まとめ

Three.jsが素敵すぎる。jsでここまで簡単にかけるなんて。
3次元や、ゲームプログラミングをやったことなくても何となくで書けました。
ただ、パフォーマンス(主に衝突判定)を考慮するとなるとちゃんとしたプログラマの方にお願いせねばなりません。

シューティングのインタフェースとしてLeapMotionは正しいと思う。
指の向きを取れるので撃ってる感がある。
あとは、掴んでる感とか押してる感とかをうまく使えるような表現があれば、もっと面白いかも。

0 件のコメント:

コメントを投稿