new GameObject()から作ってみよう ― UnityのComponentはどう組み合わさるのか

広告

UnityでGameObjectを扱っていると、画面に見えているオブジェクトが最初から「完成したひとつの部品」であるように感じることがあります。

たとえば、ゲームの中にある四角い物体には、

  • 画面に表示される
  • 当たり判定がある
  • 重力で落下する

といった機能があります。

一見すると、これらすべてが「四角い物体」というひとつの部品に入っているように見えます。

しかしUnityでは、これらの機能をComponentとして分け、それらを組み合わせてGameObjectを作るという考え方が基本です。

今回は、あえて

new GameObject()

から始めて、必要なComponentをひとつずつ追加してみます。


1. 今回使うSpriteを用意する

今回は2Dの四角いオブジェクトを作ります。

まず、四角形のSprite画像を1枚用意して、Unityのプロジェクトに入れておきます。

たとえば次のように配置します。

フォルダ構成
Assets
└─ Sprites
   └─ Box.png

この記事の目的はSprite画像の作り方ではなく、

GameObjectにComponentを追加して、機能を組み合わせていく仕組みを理解すること

です。

そのため、今回はSprite画像はあらかじめ用意してあるものとして進めます。


2. まずは空のGameObjectを作る

最初に、次のスクリプトを作ります。

using UnityEngine;

public class ObjectMaker : MonoBehaviour
{
    void Start()
    {
        GameObject obj = new GameObject("FallingObject");
    }
}

new GameObject()によって、新しいGameObjectが作られます。

このスクリプトを適当なGameObjectに付けて実行してみましょう。

Hierarchyを見ると、

Hierarchy
FallingObject

というGameObjectが作られていることが確認できます。

ところが、Gameビューには何も表示されません。

なぜでしょうか。

作ったGameObjectをInspectorで見ると、最初から次のComponentが付いています。

GameObject の構成
GameObject
└─ Transform

new GameObject()でGameObjectを作ると、Transformは自動的に付いています。

しかし、まだ画面に表示するための機能はありません。

つまり、

GameObjectは存在しているけれど、見た目がない

という状態です。


3. SpriteRendererを追加する

2D画像を画面に表示するためには、SpriteRendererが必要です。

そこで、GameObjectにSpriteRendererを追加してみます。

GameObject obj = new GameObject("FallingObject");

obj.AddComponent<SpriteRenderer>();

これでGameObjectの構成は次のようになります。

GameObject の構成
GameObject
├─ Transform
└─ SpriteRenderer

では、これで四角形が表示されるでしょうか。

実行してみると、まだ何も表示されません。


4. SpriteRendererを付けただけでは表示されない

ここが重要です。

SpriteRendererは、

Spriteを画面に描画するためのComponent

です。

しかし今の状態では、

何を描画するのか

がまだ決まっていません。

たとえるなら、

イメージ
SpriteRenderer
= 絵を表示する係

は用意できたけれど、

イメージ
Sprite
= 実際に表示する絵

が渡されていない状態です。

Componentを追加したからといって、それだけですべての機能が完成するとは限りません。


5. Spriteを設定する

そこで、先ほど用意したBox.pngをSpriteRendererに設定します。

まず、スクリプトに次のフィールドを追加します。

[SerializeField]
private Sprite boxSprite;

次に、SpriteRendererを取得し、そのspriteに設定します。

SpriteRenderer spriteRenderer = obj.AddComponent<SpriteRenderer>();
spriteRenderer.sprite = boxSprite;

ここまでのコードは次のようになります。

using UnityEngine;

public class ObjectMaker : MonoBehaviour
{
    [SerializeField]
    private Sprite boxSprite;

    void Start()
    {
        GameObject obj = new GameObject("FallingObject");

        SpriteRenderer spriteRenderer = obj.AddComponent<SpriteRenderer>();
        spriteRenderer.sprite = boxSprite;
    }
}

Unityに戻ると、InspectorにBox Spriteという欄が表示されます。

そこへ、用意したBox.pngを設定します。

イメージとしては次のようになります。

Inspector
ObjectMaker
└─ Box Sprite : Box.png

実行すると、今度は四角形が画面に表示されます。

GameObjectの構成は次のようになりました。

GameObject の構成
GameObject
├─ Transform
└─ SpriteRenderer
   └─ Sprite

ここで、

GameObjectそのものに見た目があるわけではない

という点に注目してください。

SpriteRendererというComponentが、設定されたSpriteを描画しています。


6. 当たり判定を追加する

次に、この四角形に当たり判定を付けます。

2Dの四角形なので、BoxCollider2Dを追加します。

obj.AddComponent<BoxCollider2D>();

構成は次のようになります。

GameObject の構成
GameObject
├─ Transform
├─ SpriteRenderer
└─ BoxCollider2D

これで、このGameObjectは四角形の当たり判定を持つようになりました。

しかし、まだ重力では落ちません。

なぜでしょうか。

BoxCollider2Dの役割は、あくまで当たり判定を作ることだからです。

当たり判定と、物理的に動くことは別の機能です。


7. Rigidbody2Dを追加する

重力の影響を受けて動くようにするには、Rigidbody2Dを追加します。

obj.AddComponent<Rigidbody2D>();

さらに、最初の位置を少し上にしておきます。

obj.transform.position = new Vector3(0, 3, 0);

完成したコードは次のようになります。

using UnityEngine;

public class ObjectMaker : MonoBehaviour
{
    [SerializeField]
    private Sprite boxSprite;

    void Start()
    {
        GameObject obj = new GameObject("FallingObject");

        SpriteRenderer spriteRenderer = obj.AddComponent<SpriteRenderer>();
        spriteRenderer.sprite = boxSprite;

        obj.AddComponent<BoxCollider2D>();
        obj.AddComponent<Rigidbody2D>();

        obj.transform.position = new Vector3(0, 3, 0);
    }
}

実行すると、四角形が上から落下します。

最終的なGameObjectの構成は次のようになります。

GameObject の構成
GameObject
├─ Transform
├─ SpriteRenderer
├─ BoxCollider2D
└─ Rigidbody2D
Component を足していくと GameObject の機能が増えるGameObject+ Transform?見えない+ SpriteRenderer見た目表示される+ BoxCollider2D当たり判定ぶつかれる+ Rigidbody2D物理運動落下する
GameObject は土台。Component を足していくたびに、できることが増える

8. それぞれのComponentには役割がある

今回使ったComponentを整理してみましょう。

Component役割
Transform位置・回転・大きさを管理する
SpriteRendererSpriteを画面に表示する
BoxCollider2D四角形の当たり判定を作る
Rigidbody2D重力や物理運動を扱う

大切なのは、

ひとつのComponentですべてを行っているわけではない

ということです。

たとえば、

SpriteRenderer

だけなら表示はできますが、重力では落ちません。

Rigidbody2D

だけなら物理運動はできますが、見た目はありません。

BoxCollider2D

だけなら当たり判定はありますが、自動的に落下するわけではありません。

それぞれが別々の役割を担当しています。


9. 「落下する四角形」というクラスを作ったわけではない

今回作ったものを見ると、

FallingObjectという「落下する四角形」を作った

と考えたくなるかもしれません。

しかし実際には、

イメージ
表示する機能
+
当たり判定
+
物理運動

を組み合わせただけです。

つまり、

イメージ
GameObject
   + SpriteRenderer
   + BoxCollider2D
   + Rigidbody2D

によって、

イメージ
落下する四角形

になったわけです。

この考え方は、Unityを理解するうえで非常に重要です。


10. new GameObject()は完成品を作っているわけではない

C#を学んでいると、

new GameObject()

というコードを見て、

GameObjectという完成したオブジェクトを作っている

ように感じるかもしれません。

しかしUnityでは、

GameObject obj = new GameObject();

は、

Componentを取り付けるための土台を作った

と考えると分かりやすくなります。

最初は、

GameObject の構成
GameObject
└─ Transform

だけです。

そこへ、

obj.AddComponent<SpriteRenderer>();
obj.AddComponent<BoxCollider2D>();
obj.AddComponent<Rigidbody2D>();

と機能を追加していきます。

つまり、

流れ
土台
  ↓
必要な機能を追加
  ↓
ゲームの中で使えるオブジェクトになる

という流れです。


11. WinFormsのButtonと比べてみる

ここで、WinFormsと比較してみます。

WinFormsでは、ボタンを作るときに次のように書けます。

Button button = new Button();

Buttonというクラスそのものが、ボタンとして必要な多くの機能を持っています。

イメージとしては、

流れ
new Button()
    ↓
ボタンという完成した部品

です。

一方、Unityで

GameObject obj = new GameObject();

とした場合は、まだ土台に近い状態です。

流れ
new GameObject()
       ↓
      土台
       ↓
+ SpriteRenderer
+ BoxCollider2D
+ Rigidbody2D
       ↓
必要な機能を持ったオブジェクト

Unityでは、完成した部品をひとつ作るというより、必要な機能を組み合わせて作るという考え方が強く使われています。

詳しくは、WinFormsのButtonからUnityのGameObjectへ ― newとComponentの違いを比べてみようで解説しています。

WinForms の new Button と Unity の new GameObject の違いWinFormsnew Button();ボタンという完成した部品Unitynew GameObject();= 土台(Transform)+ SpriteRenderer+ BoxCollider2D+ Rigidbody2D必要な機能を足して、使えるオブジェクトにする
完成した部品を作る WinForms と、土台に機能を足していく Unity

12. なぜComponentを組み合わせるのか

では、なぜUnityはこのような仕組みなのでしょうか。

たとえば、

FallingBox

という「落下する四角形専用クラス」を作ることも考えられます。

しかしゲームを作っていると、必要な機能の組み合わせはさまざまです。

たとえば、

  • 表示はするが、当たり判定はいらない
  • 当たり判定はあるが、重力はいらない
  • 重力は必要だが、四角形ではなく別のSpriteを使いたい
  • 音を鳴らしたい
  • アニメーションさせたい
  • 独自のスクリプトを動かしたい

といった違いがあります。

もし組み合わせごとに専用クラスを作っていくと、クラスの種類がどんどん増えてしまいます。

そこでUnityでは、

イメージ
表示
当たり判定
物理運動
音
アニメーション
ゲーム独自の処理

といった機能をComponentとして分けておき、必要なものを組み合わせられるようになっています。


13. 足し算で考えると分かりやすい

UnityのGameObjectは、次のように「足し算」で考えると分かりやすくなります。

イメージ
GameObject
+ SpriteRenderer
= 見えるオブジェクト

さらに、

イメージ
見えるオブジェクト
+ BoxCollider2D
= 当たり判定を持つオブジェクト

さらに、

イメージ
当たり判定を持つオブジェクト
+ Rigidbody2D
= 物理運動するオブジェクト

つまりUnityでは、

必要な機能を後から足していく

という考え方がよく使われます。

これは、継承だけでクラスを作り分ける方法とは違う考え方で、コンポジション(組み合わせ)と呼ばれます。詳しくは、継承の次に学びたい「コンポジション」― 継承より組み合わせを考えるで解説しています。


14. まとめ

今回は、

GameObject obj = new GameObject();

という空に近いGameObjectから始めました。

最初は、

GameObject の構成
GameObject
└─ Transform

だけでした。

そこへ、

obj.AddComponent<SpriteRenderer>();
obj.AddComponent<BoxCollider2D>();
obj.AddComponent<Rigidbody2D>();

とComponentを追加しました。

最終的には、

GameObject の構成
GameObject
├─ Transform
├─ SpriteRenderer
├─ BoxCollider2D
└─ Rigidbody2D

となりました。

そして、それぞれのComponentが、

役割
Transform
    → 位置・回転・大きさ

SpriteRenderer
    → 見た目

BoxCollider2D
    → 当たり判定

Rigidbody2D
    → 物理運動

という別々の役割を担当しています。

Unityでは、

完成した巨大なクラスをひとつ作るのではなく、GameObjectに必要なComponentを組み合わせて機能を作っていく

という考え方が基本になっています。

これからnew GameObject()を見るときは、

完成したオブジェクトができた

ではなく、

Componentを追加するための土台ができた

と考えてみてください。

UnityのGameObjectとComponentの関係が、少し見えやすくなるはずです。


関連記事

訪問数 14 回, 今日の訪問数 14回

広告