【Unity】Prefab

はじめに

この記事はカヤックUnityアドベントカレンダー2016の5日目の記事になります。
今日の内容はPrefabについてです。基本的な使い方などを中心に説明いたします。

ソーシャルゲーム事業部所属、Unityエンジニアの荒井と申します。
現在は社内で運用と開発が行われているソーシャルゲームアプリぼくらの甲子園!ポケットに携わっています。

Prefabとは

PrefabはGameObjectをAsset化して再利用可能にしたものです。 構成が同じなGameObjectを複数個配置する場合に利用されます。PrefabはProjectビューに保存されます。

Prefabの使い方

あるGameObjectをPrefabにするには、そのオブジェクトをHierarchyビューからProjectビューにドラック&ドロップすることで行えます。

f:id:drknss_kyc:20161205211219g:plain

PrefabはProjectビューに保存しておき、シーンに配置することで 使用することができます。シーンに配置されたものはPrefabのインスタンスと呼ばれ、Prefabのインスタンスを作成することをPrefabのインスタンス化とよく呼ばれます。

Prefabで重要な性質としてPrefabの内容を変更するとインスタンスも同じように変更が反映され、逆にインスタンスの変更をPrefabに反映できることがあげられます。下記はその一例を示しています。

f:id:drknss_kyc:20161205211158g:plain

まず、GameObjectをインスタンス化しています。その後にPrefabにBox Colliderをアタッチしています。 すると、インスタンスにもBox Colliderがアタッチされていることがわかります。 次にPrefab側でBox ColliderのCenterのXの値を200に変更すると、インスタンスのBox ColliderのXの値も200に変更されていることが確認できます。 また、インスタンスでColliderのYの値を300にしてInspectorビュー上部にある【Apply】を選択することで、Prefab側にその値が反映されていることが確認できます。

スクリプトからPrefabをインスタンス化するには

処理を実行中にPrefabのインスタンス化を行うには、Instantiate()関数を使います。
引数にはPrefabのオブジェクトを指定します。 下記の例はMキーが押される度にPrefabのインスタンスを生成するものです。

[SerializeField]
private GameObject _prefab;

// Update is called once per frame
void Update()
{
  if (Input.GetKeyDown(KeyCode.M))
  {
    Instantiate(_prefab);
  }
}

簡単にインスタンスを作成することができることがわかります。 しかし、Prefabのインスタンスを作成するにもある程度コストがかかり、大量に作成するとパフォーマンスが落ちる場合があります。 そこで、コストが大きい場合には作成する数を抑えて、インスタンスを都度作成するのではなく、使いまわすなどの方法を検討する場合もあります。また、Destory()関数を使用することで、作成したインスタンスを削除することができます。

Prefabとインスタンスの関連付けを切るには

Prefabとそのインスタンスには、変更が反映される特徴があることを説明しました。 しかし、この関連付け切りたい場合が出てきます。このような時は別途作成する必要はなく、関連付けを切る機能が備わっています。 関連付けを切るインスタンスを選択し、【GameObjectメニュー】にある、【Break Prefab Instance】を選択することで関連付けを切ることができます。

f:id:drknss_kyc:20161205211119g:plain

また、変更により関連付けが切れてしまう場合があります。インタンスにおいてPrefabにも存在しているGameObjectを削除すると、下記のウィンドウが出てきます。 【Continue】を選択すると【Break Prefab Instance】を選択したときのように、関連付けが切られます。

f:id:drknss_kyc:20161202234135p:plain

インスタンスでのオーバーライド時の挙動に注意!

便利なPrefabですが使用する際に注意しなければならない性質があります。そのひとつがオーバーライド時の挙動についてです。まずはその挙動についてご覧ください。

f:id:drknss_kyc:20161205211140g:plain

上記の例では、はじめにPrefabの方でBox Colliderの値を変更し、Prefabのほうで値が反映されていることを確認しています。 次にインスタンスの方でBox Colliderの値を変更していますが、【Apply】を選択していないのでPrefabには変更は反映されています。 ここで、Prefabのほうで同じ箇所の変更を行った場合どうなるのでしょうか。 Prefabの値が反映されるかと思われますが、反映されないことが上記の例でわかります。

上記のことから、インスタンス側で変更がオーバーライド(上書き)された場合、Prefabでのその値を変更してもその箇所は反映されなくなる性質があります。 そのため、インスタンス側でのオーバーライドを多用すると、Prefabの利点である反映が活かせずに管理も大変になるため、あまり多用しない方がいいと思います。 また、オーバーライドされたプロパティ(上記の例ではCenter)は太文字で表記されるようになり、オーバーライドされている箇所を見分けることができます。

Prefabの中のPrefabを配置する場合は注意!

Prefabの中に別のPrefabを配置し、【Apply】を実行したときの挙動には注意が必要です。その挙動についてご覧ください。

f:id:drknss_kyc:20161205211055g:plain

上記では、まずPrefabAの子にPrefabBをインスタンス化しています。この状態ではPrefabAとPrefabBをPrefab側で変更すると、インスタンスにも変更が反映されていることが確認できます。次に【Apply】を実行しています。これによりPrefabAの子にPrefabBが配置された変更がPrefabに反映されます。ここで先程の同様にPrefabAとPrefabBをPrefab側で変更してみます。値を確認すると、インスタンスのPrefabAには変更が反映されていますが、PrefabAの中にあるPrefabBには反映されているないことがわかります。

このように、Prefabの中にPrefabを配置して【Apply】を実行すると、Prefabの中に配置されたPrefabのインスタンスは元のPrefabとの関連付けが切られてしまいます。 このことを忘れてしまうと【Apply】されても反映されていないことに躓いてしまうので、忘れないでおいた方がいいと思います。 また、このように新しい構成となる場合は別に新しいPrefabを作成することなどを行った方がいい場合もあります。

おわりに

この記事では、Prefabについて作成方法から扱う際の注意点を説明しました。

明日はScriptableObjectについての記事になります。
担当はp_chinになります。 お楽しみに。

HTJS: JavaScriptでHTMLを書くとしたら?

はじめに

※ この記事は Tech KAYAC Advent Calendar 2016 5日目の記事になります

こんにちは。HTMLファイ部の @butchi_y です。

僕の将来の夢は言語を作ることです。

言語って自然言語かよプログラミング言語かよ、と思われるかもしれませんが、両方です。

「日本語とか英語とか、あんな面倒な言語よく使えたものだな…」と思われるぐらいのものを目指しています。

一方、プログラミング言語としては、自分が普段会話で使っているおしゃべりのように コンピューターに話しかけるだけでプログラミングできるような言語を目指しています。

その一環で、言語や数値表現における究極的な記法を考えており、今回の記事を書くに至りました。

目標

JavaScriptの文法を守った上で(JavaScriptで動くコードで)HTMLを命令ベースで記述することを目指します。

それでいて可読性を損なわず、応用もできればいいなと思いながら書きました。

さまざまな言語でのマークアップ

HTML

こんなHTMLがあったとします。

インデントや改行は統一感があまりないですが恣意的です。

<div>
  <div class="test">
    <h1>ふつうのHTML</h1>
    <p class="content">
      これは
      <a href="http://example.com" target="_blank">アンカー</a>
      の
      <span>テスト</span>
      です。
      <br>
      <span style="color: #f00; font-weight: bold; font-size: 20px;">スタイルも効いた!</span>
      <span>うまくいってよかった!</span>
    </p>
  </div>
</div>

以下がレンダリング結果です。

f:id:butchi_y:20161203180927p:plain

jQuery

jQueryでやるとしたら…

$('<div>')
  .append(
    $('<div>').addClass('test')
      .append($('<h1>').text('HTJS(仮)'))
      .append(
        $('<div>').addClass('content')
          .append('これは')
          .append($('<a>').attr({href: 'http://example.com', target: '_blank'}).text('アンカー'))
          .append('の')
          .append('<br>')
          .append(
            $('<span>').append('テ').append('ス').append('ト')
          )
          .append('です。')
      )
      .append(
        $('<span>')
          .css({
            "color": "#f00",
            "font-weight": "bold",
            "font-size": "20px",
          })
          .append('スタイルも効いた!')
      )
      .append(
        $('<span>').append('うまくいってよかった!')
      )
  )

こんな感じですね。(jQueryのテクニック不足のため、もう少しはいい書き方がありそう。)

これを純粋なJavaScriptのDOM APIでもやろうと思ってましたが、さらに骨が折れそうなので諦めました。

innerHTMLや.htmlメソッドを使えば普通にHTMLを書くだけで済む話ではありますが…。

以下に示す提案手法では、さすがにこれよりは読みやすくなります。

HTJS

ここで提案手法です!

div(
  div({class: "test"})(
    h1("HTJS(仮)"),
    p({class: "content"})(
      "これは",
      a({href: "http://example.com", target: "_blank"})("アンカー"),
      "の",
      span("テスト"),
      "です。",
      br()
    ),
    span({
      style: {
        "color": "#f00",
        "font-weight": "bold",
        "font-size": "20px",
      }
    })("スタイルも効いた!"),
    span("うまくいってよかった!")
  )
)

いかがでしょう。

特徴としては

  • 要素名を関数名として、属性の記述を渡して実行し、返り値の関数に内容を渡して実行すると要素が生成される
  • 属性はオブジェクトとして指定
  • 属性のない要素は2回実行せずともダイレクトに内容を入れられる
  • CSSの記述もjQueryのcssメソッドと同じくオブジェクトで指定

といったところでしょうか。

これを実行できるプログラムは書いたのですが、ちょっと恥ずかしいので気になる方は僕のGitからこっそり探していただくか、こっそりDMください。

Slim・Pug

やっぱりSlimやPugが一番簡潔に書けます。

HTJSから括弧をなくし、属性を Emmet 記法で書くようにしたら、

必然としてほとんどSlimのような記法になりました。

div
  div.test
    h1 Slim
    p.content
      | これは
      a[href="http://example.com" target="_blank"] アンカー
      | の,
      span テスト
      | です。
      br
    span[style="color: #f00; font-weight: bold; font-size: 20px;"] スタイルも効いた!
    span うまくいってよかった!

PugはかつてJadeと呼ばれていた言語です。 属性の書き方がHTMLと似ていないところがSlimに劣ると思ってはいますが、これもかなりのすっきり具合です。

div
  .test
    h1 Pug
    .content
      | これは
      a(href="http://example.com", target="_blank") アンカー
      | の
      span
        | テスト
      | です。
      br
      span(style="color: #f00; font-weight: bold; font-size: 20px;")
        | スタイルも効いた!
      span
        | うまくいってよかった!

HTJSの問題点

HTMLとして用意すべきものは、たった以下の記述だけです。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <script src="js/script.js"></script>
  </head>
  <body></body>
</html>

ただ、これが逆に問題なのです。 HTMLに何も情報が書かれていないため、SEOが最悪なのです。 これはAngularJSやReactなどでも問題になっていることだとは思いますが、 SEOだけでなくFacebookにもOGP情報を渡せないので、せめてOGPだけでも設定した方がいいでしょう。

HTJSを応用するなら、動的なDOM更新の用途よりも、Pugのようなコンパイル言語として開発した方が健全に思われます(もちろん両方できればいいのですが)。

記法の改善については、CoffeeScriptと相性がいいかも!?と思いコンバートしてみましたが、思ったようなすっきり感はありませんでした…。

展望

blockやincludeなどのPugの機能をES2015の手助けを借りて実装できたらいいと思ってます。 そしてHTML・CSS・JavaScriptをすべてJavaScriptでやる、となったときに、CSSをどうJavaScriptに落とし込むかも課題です。 また、JavaScriptの体裁から外れてしまうので本末転倒ですが、括弧とインデントの無駄をさらに省いて究極の言語に近づけたいです。


明日はカヤックで唯一な髪型をしている @ryusukefuda さんです。