【AndroidでOpenGL ES】3Dワールドを自由に歩けるようにしてみた

はじめに


お久しぶりの更新です。

3Dワールドを更新に更新を重ね、気付いたらFPSになっていました。銃じゃないです。キューブ弾です。

今回はFPS化してしまった3D物理演算プロジェクトの注目すべき部分をピックアップして解説していきます。

同じくGitさんにコミットしておきました。

https://github.com/rashu404/purobana

具体的なアップデートした箇所


具体的なアップデート箇所と言われると、更新し過ぎて以前のソースが跡形もなく消え去っているので、やや困ります。正直、覚えてないです。

ただ、「結論:全部」なんて答えを出すと、それはそれで困るので、目に見える範囲で箇条書きしましょう。

  • 前後左右ボタンを追加
  • レンダラ上をスクロールするとカメラ位置(eye)の周りをlookが回転する
  • 非効率的なプログラムを書き直し
  • VBOを適当な初期化メソッドで生成
  • その他、数学関連

全体的に数学に関するものが多いです。

前後左右ボタンを追加


forward-button

プロジェクトをAndroidで動かしてみると、木箱が中央に現れ、同時に左下にボタンがあるなーと思ったら、それはFPSの始まりです。

前後左右をそれぞれ押すと、文字通りにワールド内を平行移動できます。

実際のコードを見てみましょう。

MyEventクラスのonButtonEventメソッドを参照してください。

public void onButtonEvent(View v, MotionEvent e){
	switch (e.getAction()) {
	case MotionEvent.ACTION_DOWN:

		switch (v.getId()) {
		case R.id.forward_button:

			myRenderer.setForward(true);

			break;

		case R.id.left_button:

			myRenderer.setLeft(true);

			break;

		case R.id.right_button:

			myRenderer.setRight(true);

			break;

		case R.id.back_button:

			myRenderer.setBack(true);

			break;
		}

		break;

	case MotionEvent.ACTION_UP:

		myRenderer.setForward(false);
		myRenderer.setLeft(false);
		myRenderer.setRight(false);
		myRenderer.setBack(false);

		break;
	}
}

myRendererのsetForward~setBackまでをACTION_DOWN時にtrue、ACTION_UP時にfalseをセットしています。

myRenderer内でのboolean forwardがスイッチの役目をしてくれていて、onDrawFrame(GL10 gl)でisForward()をifの条件にすることでtrueになれば進み始めます。

onbutton

指が画面から離れれば、isForward()の戻り値はfalseなので、その場でストップします。

// eye位置の平行移動をする
private void eyeTranslate(){
	if (isForward()) {
		this.forbackTranslate(true);
	}

	if (isLeft()) {
		this.sideTranslate(true);
	}

	if (isRight()) {
		this.sideTranslate(false);
	}

	if (isBack()) {
		this.forbackTranslate(false);
	}
}

では数学コードが書いてあるforbackTranslate()を見てみましょう。

// 前後へ移動するメソッド
private void forbackTranslate(boolean forward){
	// eyeからlookへ向かう単位ベクトルを作成
	forbackVec.sub(look, eye);
	forbackVec.normalize();

	// ベクトルのスケーリングをする
	forbackVec.scale(0.15f);

	if(forward){
		setEye(forbackVec.x, forbackVec.y, 0f);
		setLook(forbackVec.x, forbackVec.y, 0f);
	}else {
		setEye(-forbackVec.x, -forbackVec.y, 0f);
		setLook(-forbackVec.x, -forbackVec.y, 0f);
	}
}

全てベクトル演算です。

「タッチ位置にキューブ弾」の記事で耳にタコができるくらいベクトルをうるさく解説しましたが、今回も基本は同じことで、look(カメラの見る方向)からeye(カメラの位置)を引くことで画面まっすぐにキューブ弾が飛んでいくベクトルができます。

default2

最後にforbackVecを歩くスピードに合わせてスケーリングし、加算演算すれば、前後の移動ができます。

左右への移動をするベクトルsideVecはupとforbackVecの外積を使います。

同様に加算演算で移動。

視点を回転する


レンダラ上をスクロールすると視点を回転させることができます。

FPSでは、これがないと後ろに回り込まれたら、後ろに下がるしかなくなります。

標的がバックするので、当然、敵もバックし、交戦中は常に後ろへ下がり続ける、奇妙な現象が起こります。プレイヤーとしては、どうしてこういうゲームを作ってしまったのかな?という感じ。

これを回避するにはsin cosを使い、eye位置の周囲をlookが回転すればよいです。

default-left

default

default-right

コードを見てみましょう。

public void lookRotation(double degH){

	// 現在の水平の視点の角度を極座標で求める
	double thetaH = Math.atan2(look.x - eye.x , look.y - eye.y);
	thetaH = convertRectRad(thetaH);

	// eyeからlookまでの水平の距離
	double rH = Math.sqrt(Math.pow(look.x - eye.x, 2) + Math.pow(look.y - eye.y, 2));	

	// 角度を加算
	thetaH += Math.toRadians(degH);

	// lookのX座標とY座標を求める
	double x = rH * Math.cos(thetaH) + eye.x;
	double y = rH * Math.sin(thetaH) + eye.y;

	look.x = (float) x;
	look.y = (float) y;
}

流れとしては、Math.atan2で極座標を求め、convertRectRad(double polarRad)で直交座標にした時の角度θをthetaHに代入。

円のグラフを描く際に使う半径rHと、引数を現在の角度thetaHに加算し、三角関数の公式を利用して、

cosθ = x / r

となるからxを求めると、

double x = rH * Math.cos(thetaH)

になります。

ただ、このままだと原点の周りをくるくる回るので、eyeの中心にするため、eye.xを足しています。

コードだけで理解するのは厳しいと思うので、そのうち解説の記事を書きます。

今回は割愛します。

最後に


このアップデートでかなり3Dの本質のところまで迫りました。

 

そろそろキャラクターが登場してもいい頃だと思います。

3D数学の方もちょこちょこ解説をいれつつ、これからPhysXワールドを充実させていきます。