先駆者をリスペクトしつつありがたくパクらせて 教材とさせていただく企画第二弾。
今回はアングリーバードを再現します。
日本ではケリ姫スイーツが似たようなシステムですね。あくまでも 似たような システムです。
http://kerihime.gungho.jp/kps/
ゲームシステム
いわゆるパチンコ(スリングショット)でトリを飛ばし、敵に当てて得点とするゲームです。
フラッピーバードといい、外人は鳥が好きなんでしょうか。
プラットフォームはiOS、Android。
なので指で操作することが前提です。
完成後はこんな感じで動きます。
今回もUnityで2Dゲームとして作っていきましょう。
物体をパチンコで飛ばすプログラム
本家から素材を使ったら訴訟で何十億と持って行かれてケツの毛まで抜かれてしまうので、今回も私がベジェ曲線で適当に書いた鳥キャラクタ「ライジングサン」を使います。
よく飛ぶとおもいます。
アングリーバードの操作方法は現実世界のパチンコをなぞらえているようで、
- 飛ばすモノをタップ
- 飛ばす方向とは逆方向にスワイプし、角度や威力を調節
- 指を離すと発射!
と、やや複雑なものになっています。
今回もなるべく少ない量のスクリプトで済ませたいため、ライジングサンに付けるひとつで操作を完結させてしまいましょう。
Update関数に以下の処理を書いていきます。
1. タップを判定し、飛ばす準備をする
単純にするためにシングルタッチのみを許容します。
if (Input.touchCount == 1) // シングルタッチのみ判定 { Touch touch = Input.touches[0];
Input.touchCount を参照することで触れている指の本数がわかるのでこれが1の場合のみ、Input.touches のでタッチ状態を保持しているオブジェクトを取得します。
タッチした瞬間は phase プロパティが TouchPhase.Began になるので、switch文で分岐します。
switch (touch.phase) { case TouchPhase.Began: // タップした瞬間
ライジングサンがタップされたかの判定は
- 画面上のタッチされた位置を、ゲームシーン上の座標に変換
- ゲームシーン上の座標をColliderの関数に読ませ、位置が重なっているか判定
という手順で行います。
// 画面上の位置からゲームシーン内の位置に変換する Vector2 tapWorldPosition = MainCamera.ScreenToWorldPoint(touch.position); // ライジングサンをタップしたか判定 if (GetComponent<Collider2D>().OverlapPoint(touchWorldPosition) ) { touching = true; BasePosition = touchWorldPosition; } break;
touching という bool型の変数をメンバとして定義しておき、この状態で発射準備状態を制御します。 また、Vector2の変数に初回のタップ位置(ワールド座標)を保存しておき、発射ベクトルの計算に使います。
2. スワイプを検出して発射軌道の予測線を描画する
アングリーバード2では、スワイプで発射準備中に飛ぶ軌道が表示されます。
これを実装するには、タップ検出で判定したphase プロパティが TouchPhase.Moved を示すたびに発射ベクトルを計算し、線を描画します。
case TouchPhase.Moved: // 発射ベクトル Vector2 FireVector = (BasePosition - touchWorldPosition) * 5; // 予測線を描画する関数(独自で定義したもの) DrawPredictionLine(transform.position, FireVector); break;
DrawPredictionLine という名前で関数を定義し、関数内で LineRenderer コンポーネントを使って予測線を描画します。(関数で何をやるかは後ほど)
3. 指を離して発射する
phase プロパティが TouchPhase.Ended になったら指が離されたということなので、トリを発射!
case TouchPhase.Ended: // 発射ベクトル Vector2 FinalFireVector = (BasePosition - touchWorldPosition) * 5; // 飛んでる状態を示す変数 flying = true; // Rigidbogyに力を加えて発射 Rigidbody2D MyRigidbody = GetComponent<Rigidbody2D>(); MyRigidbody.bodyType = RigidbodyType2D.Dynamic; MyRigidbody.AddForce(FinalFireVector, ForceMode2D.Impulse);
発射軌道の予測線を描画する
めんどいからソース見てね。
第一引数は発射地点、第二引数は発射ベクトルです。
LineRenderer lineRenderer; List<Vector3> renderLinePoints = new List<Vector3>(); void DrawPredictionLine(Vector2 basePosition, Vector2 fireVector) { // 予測線の最大長 int maxStep = 100; // 予測線の軌道をクリア renderLinePoints.Clear(); // 単位時間あたりにかかる重力 Vector2 gravity = Physics2D.gravity * Time.fixedDeltaTime; // 現在の速度を表す変数 Vector2 currentSpeed = fireVector; Vector2 prevPosition = basePosition; for (int step = 0; step < maxStep; step++) { Vector2 nextPosition = prevPosition + (currentSpeed * Time.fixedDeltaTime); // 現在の速度に重力加速度を足す currentSpeed += gravity; // 線のリストに加える renderLinePoints.Add(nextPosition); prevPosition = nextPosition; } // LineRenderer で描画 lineRenderer.numPositions = renderLinePoints.Count; lineRenderer.SetPositions(renderLinePoints.ToArray()); }
トリが停止したことを検出
発車後はトリの動きが停止したことを検出し、次の発射に備えます。
Rigidbody2D.velocity に現在の動きのベクトルが入っているので、これが0になったら停止したと判定できます。
Update関数の先頭に追加してしまいましょう。
void Update () { if (flying) { if (Mathf.Approximately( GetComponent<Rigidbody2D>().velocity.magnitude, 0)) { // 速度が0になった(止まった) } } else { // タップ状態判定
float値の比較は == でやってはいけません。
誤差がでるので必ず Mathf.Approximately 関数を使いましょう。
はい、非常に雑にシステムを再現できましたね。以上です。