コミュ障だから明日が僕らをよんだって返事もろくにしなかった

何かを創る人に憧れたからブログをはじめたんだと思うよ

Unityで2Dシューティングつくったよの巻 ~前編

Unityで2Dシューティングをつくろう

2Dとは言わずもがな二次元のことである。すなわち二次元のシューティングということである。ここで言うシューティングは、現実社会すなわち三次元に対しての鬱屈した感情・暴力性のはけ口のことを指す。人は誰しも高位の次元に対し尊敬の念を抱き、低位の次元に対しては憎悪の念を抱くものである。つまりは……。



えぇ……、ちょっと何を言っているのかわかりませんね。


そうしたわけでまずは2D シューティングを作ります。

というわけで今回はこちらのチュートリアルを終わらせます
2Dシューティング - Unity

今回はスプライトアニメーションの使い方を知りたいので、イラスト面に茶番は混入されないと信じてます。まずは、素材を取ってきて、スプライトエディタでいじりーの、分割していきます。


f:id:andron:20180525120912p:plainf:id:andron:20180525120917p:plain

画像みたいに赤枠を押さないと分割したやつが展開されるものだと知らずに小一時間詰みました。これは初学者詰みポイントですね。そんでなんやかんややって、スプライト設定をするとこんなアニメーションがさっくりできちゃいます。

f:id:andron:20180525121140g:plain

茶番欲(?)に耐えられず、左下に新規イラストを追加してしまった。やっぱりパイロット的サムシングなやつを入れたいんですよ。というわけでスプライトの使い方も分かったことですし、Prefab化して動かすスクリプトを書いていきますよー。

プレイヤー

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour
{
  // 移動スピード
  public float speed = 5;

  void Update ()
  {
    // 右・左
    float x = Input.GetAxisRaw ("Horizontal");
    // 上・下
    float y = Input.GetAxisRaw ("Vertical");
    // 移動する向きを求める
    Vector2 direction = new Vector2 (x, y).normalized;
    // 移動する向きとスピードを代入する
    GetComponent<Rigidbody2D>().velocity = direction * speed;
  }
}

もはやおなじみとなってしまったプレイヤー操作スクリプトです。GetComponent<Rigidbody2D>()がUnityのバージョンによってはエラーはく要因になるってことを知ったのが最近の知見です。とりあえずおまじないとして覚えればいいんだよ。RigidBodyと組み合わせることで、プレイヤーが動きます。

f:id:andron:20180525121156g:plain
重力の影響を考えないため「Rigidbody 2d ⇒ Gravityt Scale:0」にするとのことですが、そんなもの無視して動かします。


さてさて、動作の確認できましたし次は弾の処理を書いていきます。こいつ弾とか言い出しましたよ。やっぱ、好きなんですねぇ。
プレイヤースクリプト追記_01

  // PlayerBulletプレハブ
  public GameObject bullet;

  // Startメソッドをコルーチンとして呼び出す
  IEnumerator Start ()
  {
    while (true) {
      // 弾をプレイヤーと同じ位置/角度で作成
      Instantiate (bullet, transform.position, transform.rotation);
      // 0.05秒待つ
      yield return new WaitForSeconds (0.05f);
    }
  }

コルーチンを使うとのことですが、書き方のお作法的にはIEnumeratoryieldの組み合わせっぽいです。プレハブ弾を紐づけて、「Sorting Layer」でスプライトの重ね順いじりーので、こんな感じの弾連射ができます。(そろそろ他の要素も消さないと邪魔になってきましたね……。)

f:id:andron:20180525121418g:plain
意外と重力の影響悪くないのでは……?



んで自機弾作ったんで、次は敵弾作っていきます。やっぱり弾は二つ必要ですもんね。とりあえず使いまわしようのコンポーネントを作成するようです。コンポーネントよくわかんないけど、多分ゆるふわオブジェクト指向みたいな設計の仕方でしょう。まあ、今回のは使いまわし用のスクリプトってことで。

使いまわしSpaceship スクリプト

using UnityEngine;

// Rigidbody2Dコンポーネントを必須にする
[RequireComponent(typeof(Rigidbody2D))]
public class Spaceship : MonoBehaviour
{
  // 移動スピード
  public float speed;
  // 弾を撃つ間隔
  public float shotDelay;
  // 弾のPrefab
  public GameObject bullet;

  // 弾の作成
  public void Shot (Transform origin)
  {
    Instantiate (bullet, origin.position, origin.rotation);
  }

  // 機体の移動
  public void Move (Vector2 direction)
  {
    GetComponent<Rigidbody2D>().velocity = direction * speed;
  }
}

この使いまわしによってプレイヤースクリプトに修正が生じます。まあ、やってることはSpaceship呼び出して、使いまわせるところをSpaceshipから使うってことやっているだけです (雑解釈)。んで、これをやることによって再度、スピードや弾の紐づけが必要になりますのでご注意。第二詰みポイント、まあエラー見ればわかることですけどね……。ただ数値とかは普通に0が代入されるから気づきにくい。

プレイヤースクリプト追記_02

using UnityEngine;
using System.Collections;

public class Player : MonoBehaviour
{
  // Spaceshipコンポーネント
  Spaceship spaceship;

  IEnumerator Start ()
  {
    // Spaceshipコンポーネントを取得
    spaceship = GetComponent<Spaceship> ();
    while (true) {
      // 弾をプレイヤーと同じ位置/角度で作成
      spaceship.Shot (transform);
      // shotDelay秒待つ
      yield return new WaitForSeconds (spaceship.shotDelay);
    }
  }

  void Update ()
  {
    // 右・左
    float x = Input.GetAxisRaw ("Horizontal");
    // 上・下
    float y = Input.GetAxisRaw ("Vertical");
    // 移動する向きを求める
    Vector2 direction = new Vector2 (x, y).normalized;
    // 移動
    spaceship.Move (direction);
  }
}


こんなことやってて話がそれてしまいました。敵を作っていきましょう。
敵おきーの、使いまわしスクリプト紐づけーの、敵の移動スクリプト書きーので敵がしたに向かうやつができます。

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
  // Spaceshipコンポーネント
  Spaceship spaceship;

  void Start ()
  {
    // Spaceshipコンポーネントを取得
    spaceship = GetComponent<Spaceship> ();
    // ローカル座標のY軸のマイナス方向に移動する
    spaceship.Move (transform.up * -1);
  }
}

「Rigidbody 2d ⇒ Gravityt Scale:0」を敵に適用してなかったのですごい勢いで下降していくなにかができてしまった……。敵は0にしておくのが正解ですね。

f:id:andron:20180525144740g:plain

敵が下にくる設定のおかげで自動で引き撃ちしてくれる。


そんじゃ、メインイベント敵弾作成やっていきます。今回の敵弾は3ウェイ弾らしいっすよ。
通常弾のほうはいつも通りの流れで、まずは敵弾を作ります。敵弾スプライト乗せて、弾スクリプト紐づけておわりっ。

次に3ウェイ弾準備です。あらかじめ発射角度を設定した空オブジェクトを子に持たせて。弾を呼び出すスクリプト書いて3ウェイ弾ができます。

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
  // Spaceshipコンポーネント
  Spaceship spaceship;

  IEnumerator Start ()
  {
    // Spaceshipコンポーネントを取得
    spaceship = GetComponent<Spaceship> ();

    // ローカル座標のY軸のマイナス方向に移動する
    spaceship.Move (transform.up * -1);

    while (true) {
      // 子要素を全て取得する
      for (int i = 0; i < transform.childCount; i++) {
        Transform shotPosition = transform.GetChild(i);
        // ShotPositionの位置/角度で弾を撃つ
        spaceship.Shot (shotPosition);
      }

      // shotDelay秒待つ
      yield return new WaitForSeconds (spaceship.shotDelay);
    }
  }
}

f:id:andron:20180525144722g:plain
こんなのできるぞ。(3ウェイはこんなやつでしたか……?*1

そんで使いまわしSpaceship スクリプトをいじることで弾を発射しないやつとかも設定できるそうです。
Spaceship スクリプト_追記

public class Spaceship : MonoBehaviour
{
  // 弾を撃つかどうか
  public bool canShot;
}

そんで、boolの判定を拾って、敵弾の発射に関するところで終了処理を追記しておわりっ!!

public class Enemy : MonoBehaviour
{
/* いろいろ省略 */
    // canShotがfalseの場合、ここでコルーチンを終了させる
    if (spaceship.canShot == false) {
      yield break;
    }
}

Bool型使うとInspecterに表示されるのってチェックボックスになるんですね。個人的には、上みたいな3ウェイ改変を大量に作ると想定してショットパターンにしてEnumで管理したほうがいいかもしれないと感じた。
f:id:andron:20180525184637p:plain


あ、チェックオンオフ切り替えるとこんな実際の動作はこんな感じになります。
f:id:andron:20180525184646g:plain


思っていたよりも長くなってしまったので3つにわけることにします。なんか修正の手戻りも多いから一旦区切ったほうがわかりやすいかもしれないと思ったので……、次回は中編になります。


続く

*1:コルーチンのおかげで弾の発動時間を直感的に動かせていいですね