Help us understand the problem. What is going on with this article?

【Unity】ゲーム会社でスマホ向けゲームを開発して得た知識 拡張メソッド編

More than 3 years have passed since last update.

2年目のゲームプログラマがゲーム会社でUnityを使用して
スマートフォン向けのゲームを開発して感じたことをまとめておきます

拡張メソッド

拡張メソッドを使用することで既存のクラスに関数を追加することができます
今回関わったゲーム開発では拡張メソッドを使用することでコードを短くわかりやすく記述できたので
定義して良かった拡張メソッドを紹介していきます

SafeGetComponent

指定されたコンポーネントを返します
指定されたコンポーネントがアタッチされていない場合は追加してから返します

public static T SafeGetComponent<T>( this GameObject self ) where T : Component
{
    return self.GetComponent<T>() ?? self.AddComponent<T>();
}

public static T SafeGetComponent<T>( this Component self ) where T : Component
{
    return self.GetComponent<T>() ?? self.gameObject.AddComponent<T>();
}

コンポーネントを取得する時にこの拡張メソッドを使用することで
Unityエディタ上でスクリプトをアタッチし忘れていたとしてもnull参照を防ぐことができます

var player = gameObject.SafeGetComponent<Player>();

GetChildren

すべての子オブジェクトを返します

public static GameObject[] GetChildren( this GameObject self, bool includeInactive = false )
{
    return self
        .GetComponentsInChildren<Transform>( includeInactive )
        .Where( c => c != self.transform )
        .Select( c => c.gameObject )
        .ToArray();
}

public static GameObject[] GetChildren( this Component self, bool includeInactive = false )
{
    return self
        .GetComponentsInChildren<Transform>( includeInactive )
        .Where( c => c != self.transform )
        .Select( c => c.gameObject )
        .ToArray();
}

すべての子オブジェクトを検索して一括で処理したい時に役に立ちます

foreach ( var n in gameObject.GetChildren() )
{
}

GetChildrenWithoutGrandchildren

孫オブジェクトを除くすべての子オブジェクトを返します

public static GameObject[] GetChildrenWithoutGrandchildren( this GameObject self )
{
    var result = new List<GameObject>();
    foreach ( Transform n in self.transform )
    {
        result.Add( n.gameObject );
    }
    return result.ToArray();
}

public static GameObject[] GetChildrenWithoutGrandchildren( this Component self )
{
    var result = new List<GameObject>();
    foreach ( Transform n in self.transform )
    {
        result.Add( n.gameObject );
    }
    return result.ToArray();
}

前述のGetChildrenは孫オブジェクトも取得してしまうため
孫オブジェクトは検索対象に含めたくない場合はこの拡張メソッドを使用しました

foreach ( var n in gameObject.GetChildrenWithoutGrandchild() )
{
}

GetComponentsInChildrenWithoutSelf

自分自身を除くすべての子オブジェクトにアタッチされている指定されたコンポーネントをすべて返します

public static T[] GetComponentsInChildrenWithoutSelf<T>( this GameObject self, bool includeInactive = false ) where T : Component
{
    return self
        .GetComponentsInChildren<T>( includeInactive )
        .Where( c => self != c.gameObject )
        .ToArray();
}

public static T[] GetComponentsInChildrenWithoutSelf<T>( this Component self, bool includeInactive = false ) where T : Component
{
    return self
        .GetComponentsInChildren<T>( includeInactive )
        .Where( c => self.gameObject != c.gameObject )
        .ToArray();
}

標準のGetComponentsInChildrenは自分自身も検索対象にしてしまうため
自分自身は検索対象に含めたくない場合にこの拡張メソッドを使用しました

var uiWidgetList = gameObject.GetComponentsInChildrenWithoutSelf<UIWidget>();

RemoveComponent

指定されたコンポーネントを削除します

public static void RemoveComponent<T>( this GameObject self ) where T : Component
{
    Object.Destroy( self.GetComponent<T>() );
}

public static void RemoveComponent<T>( this Component self ) where T : Component
{
    Object.Destroy( self.GetComponent<T>() );
}

通常、ゲームオブジェクトやコンポーネントを削除するときはObject.Destroyを使用しますが
コンポーネントの削除を直感的に記述したかったのでこの拡張メソッドを用意しました

gameObject.RemoveComponent<Character>();

RemoveComponentImmediate

指定されたコンポーネントを即座に削除します

public static void RemoveComponentImmediate<T>( this GameObject self ) where T : Component
{
    GameObject.DestroyImmediate( self.GetComponent<T>() );
}

public static void RemoveComponentImmediate<T>( this Component self ) where T : Component
{
    GameObject.DestroyImmediate( self.GetComponent<T>() );
}

エディタ拡張で即座にコンポーネントを削除したいときはこちらの拡張メソッドを使用しました

gameObject.RemoveComponentImmediate<Character>();

RemoveComponents

指定されたコンポーネントをすべて削除します

public static void RemoveComponents<T>( this GameObject self ) where T : Component
{
    foreach( var n in self.GetComponents<T>() )
    {
        GameObject.Destroy( n );
    }
}

public static void RemoveComponents<T>( this Component self ) where T : Component
{
    foreach( var n in self.GetComponents<T>() )
    {
        GameObject.Destroy( n );
    }
}

iTweenのように関数を呼び出すたびにコンポーネントがアタッチされるプラグインを使用している時に
一括でそれらのコンポーネントを削除したいことが時々あったのでこの拡張メソッドを作成しました

gameObject.RemoveComponents<iTween>();

HasComponent

指定されたコンポーネントがアタッチされている場合 true を返します

public static bool HasComponent<T>( this GameObject self ) where T : Component
{
    return self.GetComponent<T>() != null;
}

public static bool HasComponent<T>( this Component self ) where T : Component
{
    return self.GetComponent<T>() != null;
}

コンポーネントがアタッチされているかどうかは確認したいけれど
コンポーネント自体は必要ない場合にこの拡張メソッドを使用しました

if ( gameObject.HasComponent<UISprite>() )
{
}

戻り値のnullチェックを省略できるので少しだけコードをわかりやすく記述できます

Find

指定された名前で子オブジェクトを検索します

public static Transform Find( this GameObject self, string name )
{
    return self.transform.Find( name );
}

public static Transform Find( this Component self, string name )
{
    return self.transform.Find( name );
}

通常、ゲームオブジェクトやMonoBehaviourを継承した独自のコンポーネントから
子オブジェクトを検索したい時はtransformプロパティを介してFind関数を呼び出します

var player = gameObject.transform.Find( "Player" );

このように、子オブジェクトを検索する時に毎回transformプロパティを介すことが煩わしく感じたので
この拡張メソッドを作成して次のように書けるようにしました

var player = gameObject.Find( "Player" );

FindGameObject

指定された名前で子オブジェクトを検索して GameObject 型で返します

public static GameObject FindGameObject( this GameObject self, string name )
{
    var result = self.transform.Find( name );
    return result != null ? result.gameObject : null;
}

public static GameObject FindGameObject( this Component self,  string name )
{
    var result = self.transform.Find( name );
    return result != null ? result.gameObject : null;
}

transform.FindTransform型で検索結果を返してくれますが
子オブジェクトを検索した後はtransform.gameObjectにアクセスすることが多かったです

var player = gameObject.transform.Find( "Player" ).gameObject;

なので、GameObject型で検索結果を返してくれるこの拡張メソッドを作成しました
この拡張メソッドを使用することで次のように検索処理を記述できます

var player = gameObject.FindGameObject( "Player" );

FindComponent

指定された名前で子オブジェクトを検索して
その子オブジェクトから指定されたコンポーネントを取得します

public static T FindComponent<T>( this GameObject self, string name ) where T : Component
{
    var t = self.transform.Find( name );
    if ( t == null )
    {
        return null;
    }
    return t.GetComponent<T>();
}

public static T FindComponent<T>( this Component self, string name ) where T : Component
{
    var t = self.transform.Find( name );
    if ( t == null )
    {
        return null;
    }
    return t.GetComponent<T>();
}

子オブジェクトを検索した後はGetComponentを呼び出すことも多かったです

var player = gameObject.transform.Find( "Player" ).GetComponent<Player>();

この拡張メソッドを使用することで次のように少しだけ簡潔に処理を記述できます

var player = gameObject.FindComponent<Player>( "Player" );

FindDeep

指定された名前で子オブジェクトを深い階層まで検索して GameObject 型で返します

public static GameObject FindDeep( this GameObject self, string name, bool includeInactive = false )
{
    var children = self.GetComponentsInChildren<Transform>( includeInactive );
    foreach ( var transform in children )
    {
        if ( transform.name == name )
        {
            return transform.gameObject;
        }
    }
    return null;
}

public static GameObject FindDeep( this Component self, string name, bool includeInactive = false )
{
    var children = self.GetComponentsInChildren<Transform>( includeInactive );
    foreach ( var transform in children )
    {
        if ( transform.name == name )
        {
            return transform.gameObject;
        }
    }
    return null;
}

transform.Findで孫オブジェクトを検索する場合は次のように親子関係も指定する必要があります

var player = transform.Find( "Character/Player" );

この拡張メソッドを使用することで親子関係を意識せずに対象のオブジェクトの名前だけで孫オブジェクトを検索できます

var player = transform.FindDeep( "Player" );

ただし、同じ名前のオブジェクトが2つ以上存在する場合はどちらか片方しか取得されないので注意が必要です

SetParent

親オブジェクトを設定します

public static void SetParent( this GameObject self, Component parent )
{
    self.transform.SetParent( parent.transform );
}

public static void SetParent( this GameObject self, GameObject parent )
{
    self.transform.SetParent( parent.transform );
}

public static void SetParent( this Component self, Component parent )
{
    self.transform.SetParent( parent.transform );
}

public static void SetParent( this Component self, GameObject parent )
{
    self.transform.SetParent( parent.transform );
}

Unity4.6で追加されたSetParentTransform型のインスタンスを引数に取るため
親オブジェクトを設定したいときは毎回transformプロパティを渡す必要がありました

player.SetParent( parent.transform );

その手間を解消するために、GameObject型のインスタンスもComponent型のインスタンスも受け取れる
SetParent関数を拡張メソッドで実現しました

player.SetParent( parent );

HasChild

子オブジェクトが存在するかどうかを返します

public static bool HasChild( this GameObject self )
{
    return 0 < self.transform.childCount;
}

public static bool HasChild( this Component self )
{
    return 0 < self.transform.childCount;
}

再帰処理などで子オブジェクトが存在する場合にのみ何か処理をしたい場合に
毎回transform.childCountを参照して子オブジェクトが存在するかどうかを
判定する手間を省くためにこの拡張メソッドを作成しました

if ( gameObject.HasChild() )

HasParent

親オブジェクトが存在するかどうかを返します

public static bool HasParent( this GameObject self )
{
    return self.transform.parent != null;
}

public static bool HasParent( this Component self )
{
    return self.transform.parent != null;
}

こちらも前述のHasChildと同じように
毎回transform.parentを参照してnullチェックする手間を省くために作成しました

if ( gameObject.HasParent() )

GetChild

指定されたインデックスの子オブジェクトを返します

public static GameObject GetChild( this GameObject self, int index )
{
    var t = self.transform.GetChild( index );
    return t != null ? t.gameObject : null;
}

public static GameObject GetChild( this Component self, int index )
{
    var t = self.transform.GetChild( index );
    return t != null ? t.gameObject : null;
}

transformプロパティを参照しなくても
GetChild関数を呼び出せるようにするためにこの拡張メソッドを作成しました

var child = gameObject.GetChild( 25 );

GetParent

親オブジェクトを返します

public static GameObject GetParent( this GameObject self )
{
    var t = self.transform.parent;
    return t != null ? t.gameObject : null;
}

public static GameObject GetParent( this Component self )
{
    var t = self.transform.parent;
    return t != null ? t.gameObject : null;
}

transformプロパティを参照しなくても
parentプロパティを参照できるようにするためにこの拡張メソッドを作成しました

var parent = gameObject.GetParent();

GetRoot

ルートとなるオブジェクトを返します

public static GameObject GetRoot( this GameObject self )
{
    var root = self.transform.root;
    return root != null ? root.gameObject : null;
}

public static GameObject GetRoot( this Component self )
{
    var root = self.transform.root;
    return root != null ? root.gameObject : null;
}

transformプロパティを参照しなくても
rootプロパティを参照できるようにするためにこの拡張メソッドを作成しました

var root = gameObject.GetRoot();

SetLayer

レイヤー名を使用してレイヤーを設定します

public static void SetLayer( this GameObject self, string layerName )
{
    self.layer = LayerMask.NameToLayer( layerName );
}

public static void SetLayer( this Component self, string layerName )
{
    self.gameObject.layer = LayerMask.NameToLayer( layerName );
}

もしゲームオブジェクトのレイヤーをレイヤー名で指定したい場合に
LayerMask.NameToLayerを呼び出す手間を省くためにこの拡張メソッドを作成しました
この拡張メソッドを使用することでレイヤー名でレイヤーを指定できます

gameObject.SetLayer( "UI" );

SetLayerRecursively

自分自身を含めたすべての子オブジェクトのレイヤーを設定します

public static void SetLayerRecursively( this GameObject self, int layer )
{
    self.layer = layer;

    foreach ( Transform n in self.transform )
    {
        SetLayerRecursively( n.gameObject, layer );
    }
}

public static void SetLayerRecursively( this Component self, int layer )
{
    self.gameObject.layer = layer;

    foreach ( Transform n in self.gameObject.transform )
    {
        SetLayerRecursively( n, layer );
    }
}

public static void SetLayerRecursively( this GameObject self, string layerName )
{
    self.SetLayerRecursively( LayerMask.NameToLayer( layerName ) );
}

public static void SetLayerRecursively( this Component self, string layerName )
{
    self.SetLayerRecursively( LayerMask.NameToLayer( layerName ) );
}

ゲームオブジェクトのレイヤーを変更する場合は
子オブジェクトのレイヤーも一括で変更したいことが多いのでこの拡張メソッドを作成しました
これを使用することで自分自身を含めたすべての子オブジェクトのレイヤーを一括で設定できます

gameObject.SetLayerRecursively( 8 );
gameObject.SetLayerRecursively( "UI" );
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away