Unity勉強会2024
新入生向けUnity講座 第3回
公開日:
2024/07/13
新入生向けUnity講座 第3回

はじめに

ごきげんよう。第3回です。

今日やること

  • 前回の課題の答え
  • 前回作成したスクリプトの内容解説
  • ブロックの追加
  • ゲームクリア・ゲームオーバー判定の作成

前回の課題の答え

前回の課題1の回答例を載せておきます。

Player.cs:

public class Player : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //速度
        float speed = 3f;

        // 自身のRigidbody2Dを取得してrbに代入
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
        // 速度を0にする
        rb.velocity = new Vector2(0, 0);
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            // もし左矢印キーが押されたら速度ベクトルを(-3, 0)にする
            rb.velocity = new Vector2(-speed, 0);
        }
        else if (Input.GetKey(KeyCode.RightArrow))
        {
            // もし右矢印キーが押されたら速度ベクトルを(3, 0)にする
            rb.velocity = new Vector2(speed, 0);
        }
    }
}

Ball.cs:

public class Ball : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        float speed = 5f;
        GetComponent<Rigidbody2D>().velocity = new Vector2(speed, speed);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

このように数値を変数で置くことで、値を変えたくなったときにすぐに変更できるようになります。

これはあくまで一例です。変数をPlayerクラスやBallクラスのメンバ変数(今回の課題で解説)として置いてももちろんOKです。

前回のおさらい

前回(https://ch-random.net/post/1943/)では、ブロック崩しゲームの基本的な部分を作りました。

プレイヤー、壁、ボールのオブジェクトを作り、プレイヤーの操作とボールの挙動を実装しました。

以下で、前回出たUnity用語をおさらいしておきます。

用語 英語名 意味
コンポーネント Component オブジェクトに機能を追加するもので、RigidbodyやBoxColliderなど様々な種類がある
物理マテリアル Physics Material オブジェクトの摩擦や反発係数を設定するもの。マテリアルとは別物

ブロックの追加・プレハブの作成

まずはブロック(ボールを当てて壊すブロック)を追加していきます。

正方形のオブジェクトを一つ追加して、BoxCollider2Dを追加します(前回と作業が同じなのでやり方は割愛します)。名前はBlockとしておきます。

次に、作ったブロックのプレハブを作っていきます。プレハブとは、作成したオブジェクトをアセットとして登録して、複製を簡単に作れるようにするものです(参考文献[1] p.103)。

後ほど説明しますが、プレハブを作ることで複数のオブジェクトの設定変更を簡単に行うことができます。

まずはプレハブ用のフォルダを作りましょう。Assetsフォルダ内にPrefabsというフォルダを作ります。

Prefabsフォルダを開いた状態で、先程作成したBlockオブジェクトをPrefabsフォルダにドラッグアンドドロップします。

するとBlockオブジェクトのプレハブがPrefabsフォルダに作成されます。ヒエラルキーからBlockオブジェクトの名前が青色になっていればOKです。

ブロックの複製

ブロックをもっと増やします。

Blockオブジェクトをコピーする前に、ブロックのオブジェクトの親オブジェクトとなるものを作成します。

ヒエラルキーで右クリック→「空のオブジェクトを作成」をクリックします。名前はBlocksとでもしておきます。

今作った空のオブジェクトはその名の通り中身がないオブジェクトで、ゲーム内では視認することができません。(コンポーネントとしてTransformのみを持ちます)

Blocksオブジェクトの位置(座標)を原点(0, 0, 0)に設定しておきます。

その後、BlockオブジェクトをBlocksオブジェクトの名前の所に重なるようにドラッグして、BlockをBlocksの子オブジェクトにします。画像のように階層関係ができればOKです。

このようにオブジェクトを空オブジェクトの子にしてまとめておくことで、ヒエラルキーが見やすくなる上に、位置関係の管理などもしやすくなります(親オブジェクトの座標を変えると、子オブジェクトの座標も変わる)。

次はブロックを複製していきます。Blockオブジェクトを選択して、Ctrl+C→Ctrl+Vでコピー&ペーストしましょう。

とりあえず5個に増やします。その後、それぞれのBlockオブジェクトの座標をいい感じに設定します。例を以下に載せておきます。

例の通りに座標を設定するとこんな感じになります。前回の壁の作成のときにやったように、オブジェクトを複数選択すれば座標指定も複数のオブジェクトに対して同時に行うことができます。

それを使って、ブロックをあと10個増やします。

5つのオブジェクトを全て選択した状態でコピペします(このとき、全てのBlockオブジェクトがBlocksオブジェクトの子オブジェクトになっているかを確認しておいてください)。

その後、(5)~(9)のオブジェクトのY座標を3に、(10)~(14)のオブジェクトのY座標を2.25にします。

こんな感じになりました。

ブロック用スクリプトの作成

ブロック用のスクリプトを作成します。作成方法等は割愛します。

スクリプトの名前はBlockとしました。内容は以下のようにします。

public class Block : MonoBehaviour
{
    private void OnCollisionEnter2D(Collision2D collision)
    {
        Destroy(gameObject);
    }
}

最初からあるStartメソッドとUpdateメソッドは現時点では使わないので削除しておきました。

内容としては、コライダーを持った他のオブジェクトと接触した際に自身をシーンから削除するという処理を行っています。

StartやUpdate, OnCollisionEnter2Dに代表される、Unity側から呼び出されるメソッド(メソッドについては下記参照)はこのようにVisual Studioの入力支援機能からサジェスト的な感じで出るので便利です。

作ったスクリプトをブロックにアタッチします。15個全てに一つずつスクリプトをつけるのは時間がかかりますが、プレハブを使えば簡単にできます。

作ったBlockのプレハブを選択して、インスペクターの「コンポーネントを追加」からBlockスクリプトを追加しましょう。

これで、15個のBlockオブジェクト全てにスクリプトが付与されました。

また、インスペクターの右上の南京錠のアイコンをクリックするとプレハブの詳細画面で固定されるので、その状態でスクリプトをドラッグすることでも追加できます。

テストプレイ

テストプレイしてみましょう。

ボールがブロックに当たったときに、ブロックが消えたらOKです。

これで、ゲームの基本的な部分は全て完成しました。

ゲームクリア判定の追加

プレイヤーやボール、ブロックの実装はできましたが、現時点ではゲームとして成立していません。

本来ならボールが下の床に触れたらゲームオーバー、ブロックをすべて消したらゲームクリアとなるので、ここからはゲームの終了判定を簡易的に実装したいと思います。

まずは、プレイヤーのスクリプトを少し編集してゲームクリアを実装しましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Player : MonoBehaviour
{
    public GameObject blocks;

    // Update is called once per frame
    void Update()
    {
        /*
            第2回で追加したプレイヤー移動用のコード
        */

        //ゲームクリア判定
        if(blocks.transform.childCount == 0)
        {
            SceneManager.LoadScene(0);
        }
    }
}

blocksというGameObject型の変数をPlayerクラスに作成して、Updateメソッドにゲームクリアの判定を行う処理を書き足しています。

具体的には、Blocksオブジェクト(Blockオブジェクトの親オブジェクト)の子オブジェクトの数が0になったとき、シーンを読み込み直すという処理を実装しています。

前回までで作成したコードは省略しているので、実際は下の画像のようになります。

クラスの型の変数やpublic修飾子の説明は今回の課題に載せています。

スクリプトを保存したら、次はスクリプトのBlocksオブジェクトの参照元を設定します。

Playerオブジェクトの「Player(スクリプト)」のところを見ると、新しく「Blocks」という項目が追加されています。

ここで、スクリプト内の「blocks」がどのオブジェクトを指すかをを設定できますので、ヒエラルキーからBlocksオブジェクトをドラッグして設定します。

これで、ブロックを全て壊したときにゲームがリセットされるようになります(オブジェクトの指定が正しく行えていない場合はUnassignedReferenceExceptionというエラーが出ます)。

このように、スクリプト内で他のオブジェクトの情報を使ったりする場合は、インスペクターでのオブジェクトの指定(これをアサインといいます)が必要になりますので、この一連の流れは覚えておきましょう。

ゲームオーバー判定の追加

次は、ゲームオーバー判定の追加をします。

新しくFloorスクリプトを作成します。これは床(4つの壁のうち下に位置するもの)用のスクリプトになります。

内容は以下のようにします。

public class Floor : MonoBehaviour
{
    private void OnCollisionEnter2D(Collision2D collision)
    {
        SceneManager.LoadScene(0);
    }
}

Blockスクリプトと同様に、衝突時の判定しか記述しないのでStartメソッドとUpdateメソッドは削除しています。

これを下側の壁(この場合はWall4オブジェクト)にアタッチします。

これで、ボールが下の壁に接触した際にゲームがリセットされるようになります。

今回の作業はここまでです。お疲れ様でした。

(余談)SceneManager.LoadSceneについて

ゲームクリア・ゲームオーバー判定ではシーンの再読み込みに「SceneManager.LoadScene(0);」という処理は使っていますが、これは「現在ロードしているシーンを再読み込みする」という処理ではないので注意してください。

これは、「0番目のシーンを読み込む」という処理で、今はシーンが一つしかないのでこの書き方でも正しく動作します(なので、シーンを今後追加する場合は正しく動作しなくなる可能性があります)。

ちなみに、「現在ロードしているシーンを再読み込みする」という処理を実装する場合は以下のように書くとできます。

SceneManager.LoadScene(SceneManager.GetActiveScene().name);

課題(C#プログラミング入門3)

前回、前々回と同様にC#のプログラミングについての解説をしていきます。

今回はC#を含むオブジェクト指向型プログラミング言語において重要なクラスについて解説します。

この解説はかなり簡易的なものなので、オブジェクト指向について理解を深めたい方は適宜本やWebサイトなどを参考にして勉強してみてください。

クラス

クラスとは、変数やメソッド(後ほど解説します)を持ったグループのようなもので、たいていの場合何か一つの物体を表すように作ります。

以下に、2つの変数(これをメンバ変数といいます)を持ったウマ娘を表すクラスの定義の例を示します。

class Umamusume{
    string name;
    int height;
}

そして、クラスは型(第1回で解説しました)と同様に使うことができ、クラスの型を持った変数を作ることができます。

クラスの型の変数には、クラスから作った実体(これをインスタンスといいます)を代入することができます。クラスは設計図のようなものです。

以下に、クラスの型の変数にインスタンスを代入し、そのメンバ変数の中身を設定する例を示します。

Umamusume kitasan_black = new Umamusume();
kitasan_black.name = "キタサンブラック";
kitasan_black.height = 162;

このように、インスタンスはnew演算子と呼ばれるものを使って作ることができます。また、メンバ変数へのアクセスは「.」(ドット演算子)を使って行います。

以下にクラスに関するサンプルコードを置いておきます。ちなみに、Playerスクリプト自体もPlayerという名前のクラスで構成されています。

public class Player : MonoBehaviour
{
    void Start()
    {
        Umamusume kitasan_black = new Umamusume();
        kitasan_black.name = "キタサンブラック";
        kitasan_black.height = 162;

        Debug.Log("名前:" + kitasan_black.name);
        Debug.Log("身長:" + kitasan_black.height + "cm");
    }

    //中略

}

public class Umamusume{
    public string name;
    public int height;
}

実行結果は以下のようになります。

名前:キタサンブラック
身長:162cm

上記のサンプルコードではUmamusumeクラスとそのメンバ変数の定義にpublicと付けていますが、これはアクセス修飾子と呼ばれるものの一種で、これを付けないとクラスやメンバ変数が外部からアクセスできなくなります。

メソッド

次にメソッドについて説明します。メソッドは以下のように定義することができます。

戻り値の型 メソッド名 (引数の型 引数名){
    // 中身
}

戻り値とは、メソッドが返す値のことです。戻り値を持たないメソッドの場合はvoidと書きます。(Unity側で用意されているStart, Updateメソッドはどちらも戻り値を持ちません)

引数とは、メソッド側に渡す値のことです。引数は無しにすることもできますし、複数用意することもできます。(Start, Updateメソッドは引数を持ちません)

以下に、メソッドに関するサンプルコードを置いておきます。

public class Player : MonoBehaviour
{
    void Start()
    {
        Umamusume kitasan_black = new Umamusume();
        kitasan_black.name = "キタサンブラック";
        kitasan_black.height = 162;

        kitasan_black.Print("うまげの最推し");
        Debug.Log("身長:" + kitasan_black.GetHeightInMeters() + "m");
    }

    //中略

}

public class Umamusume{
    public string name;
    public int height;

    public void Print(string str){
        Debug.Log(str);
        Debug.Log("名前:" + name);
        Debug.Log("身長:" + height + "cm");
    }

    public float GetHeightInMeters(){
        return (float)height / 100;
    }
}

実行結果は以下のようになります。

うまげの最推し
名前:キタサンブラック
身長:162cm
身長:1.62m

string型の引数を持つPrintメソッドでは引数の文字列と名前、身長を出力します。float型の戻り値を持つGetHeightInMetersメソッドでは、身長をメートル単位で返します。

コンストラクタ

最後にコンストラクタについて解説します。コンストラクタは以下のように定義します。

クラス名(引数の方 引数名){
    // 中身
}

コンストラクタはクラスのインスタンスを作成した際に呼ばれます。

public class Player : MonoBehaviour
{
    void Start()
    {
        Umamusume kitasan_black = new Umamusume("キタサンブラック", 162);
        kitasan_black.Print();

        Umamusume satono_crown = new Umamusume("サトノクラウン", 163);
        satono_crown.Print();

        Umamusume cheval_grand = new Umamusume("シュヴァルグラン", 160);
        cheval_grand.Print();
    }

    //中略

}

public class Umamusume{
    public string name;
    public int height;

    public Umamusume(string name, int height){
        this.name = name;
        this.height = height;
    }

    public void Print(){
        Debug.Log("名前:" + name);
        Debug.Log("身長:" + height + "cm");
    }
}

実行結果は以下のようになります。

名前:キタサンブラック
身長:162cm
名前:サトノクラウン
身長:163cm
名前:シュヴァルグラン
身長:160cm

上記のコードでは、コンストラクタで名前(name)と身長(height)のデータを設定することで、クラスの定義およびメンバ変数への値の代入を1行で行えるようにしています。

↓クラスのイメージ

参考文献: [1]「UnityではじめるC# 基礎編」, 大槻有一郎著, エムディエヌコーポレーション, 2016

次の記事

https://ch-random.net/post/1945/

loading...