マウスを極力さわりたくないプログラマの為のUnity入門

これは、「テキストベースの非アクションゲーム」をUnityで構成する為のメモです。普通のUnity的ゲームやアクションゲームを作りたい人は他の文書を探してください。

  • 「テキストベースの非アクションゲーム」とは、大体ステッパーズ・ストップあたりを想定しています。

筆者はmac版のunityで操作を行っている。windows版だとボタンの名前が違ったりする可能性あり。

「テキストベースの非アクションゲーム」のスケルトンプロジェクト構築手順

前準備

  1. Unityインストール
    • 適当にインストールして起動するところまでやっとく。メールアドレス登録あたりがかなり分かりにくいが、ぐぐって調べればどうにかなる範囲
  2. 早速起動する
  3. 設定を行う
    • とりあえずメニュー項目より「Unity」→「Preferences」→「External Tools」→「External Script Editor」を、普段自分が使ってるvimなりemacsなりに設定変更する事。これであなたは生きのびる事ができる。

プロジェクトの開始

  1. 既に高度なサンプルプロジェクトが開かれている。画面上部の再生ボタンを押して遊んでみてもよい。
  2. サンプルプロジェクトを閉じ、自分のプロジェクトを開始する。起動メニュー項目より「File」→「New Project」を選択し、項目を埋めればよい。
    • ディレクトリ名には、マルチバイト文字や空白文字を含まない方がいい気がする
    • 「Import」のチェックボックスは全て空でok
    • 「3D」「2D」の選択肢があるが、どちらでも問題ない(デフォルト値をどっちにするかという項目)
  3. プロジェクトの保存形式をバイナリベースではなくテキストベースに変更し、git等で扱いやすくする。
    • 上メニューより「Edit」→「Project Settings」→「Editor」を選択する
      • 「Version Control」→「Mode」を「Visible Meta Files」にする。
        • git保存用の設定。これをしないと隠しファイルとして*.metaファイルが作成されてしまう。
      • 「Asset Serialization」→「Mode」を「Force Text」にする。
        • これでyaml形式でデータが保存される。yaml形式といっても直編集は絶望的だが、mergeとかが一応可能になる。
    • metaファイルの扱い等については、 http://terasur.blog.fc2.com/blog-entry-821.html を読んでおくとよい。

引き続き、「タイトル画面」→「ゲーム開始」→「タイトル画面」というシーン遷移の最小構成を作る。OPデモ、エンディング、ゲームオーバー等の追加は後回し。

タイトル画面の作成

  1. いきなり空の3D空間が表示されていてビビるが、それは見なかった事にすればok(実際、この文書では全く使わない)。メニュー項目より「GameObject」→「Create Empty」を選ぶ。これで空のゲームオブジェクトが生成される。
  2. この今生成したオブジェクトが、画面左のメイン領域のHierarchy欄に出現する。
  3. オブジェクトをクリックし、選択状態にする。すると、右側のペイン領域に、このゲームオブジェクトの詳細が表示される。
    • 左側ペイン右側ペインどちらからでも、このオブジェクトの名前を変更する事ができる。「Title」のような名前をつけておくとよい。将来にゲームオブジェクトが増えた際に、それぞれを区別するのに役立つ。しかし今回は一個だけなので、名前をつけなくても問題ない。
  4. 右側ペインにある「Add Component」ボタンを押し、「New Script」を選択する。
    1. 「Name」としてファイル名を入力する。とりあえずここでは「Title」とする。
      • スクリプト名はクラス名を兼ねる為、大文字はじまり推奨。
    2. 「Language」は、「JavaScript / C# / Boo(Python風)」のいずれかより、自分が一番好きな(もしくは「この三つの中では一番マシ」な)言語を選ぶ。
    3. 「Create and Add」ボタンを押す。
      • これにより、画面下部ペイン領域の「Project」→「Assets」の中に、新しいファイルが作られる。
      • この「Assets」はアイコンの見た目通り、実際のディレクトリを示している。つまりプロジェクトディレクトリが仮に “/home/username/proj1” だったとすると、ここまでの操作によって “/home/username/proj1/Assets/Title.js” が作られた事になる。
  5. (optional) 上記手順以外に、既存のスクリプトを直にAssetsに投入する事もできる。しかしその場合は上記のゲームオブジェクトにリンク(関連付け)されていないので、自分でリンクを行わなくてはならない。
    • スクリプトファイルのアイコンを、右側ペイン領域内にドラッグ&ドロップすればok。既に追加されてるかどうかは、既に項目があるかどうかで判別できる。右クリックより「Remove Component」を選択する事で、リンク解除もできる。
  6. ファイルをダブルクリックし、エディタで開き、編集する!とりあえず以下をコピペする。

    #pragma strict
    
    var logoRect:Rect;
    var logoStyle:GUIStyle;
    var buttonRect:Rect;
    
    function Start () {
      var w:int = 256;
      var h:int = 64;
      var x:int = (Screen.width - w) / 2;
      var y:int = Screen.height * 0.25;
      logoRect = Rect(x, y, w, h);
    
      logoStyle = GUIStyle.none;
      logoStyle.fontSize = 32;
      logoStyle.alignment = TextAnchor.MiddleCenter;
    
      w = 64;
      h = 32;
      x = (Screen.width - w) / 2;
      y = Screen.height * 0.75;
      buttonRect = Rect(x, y, w, h);
    }
    
    function Update () {
    }
    
    function OnGUI () {
      GUI.Label(logoRect, "ゲームタイトル", logoStyle);
      if (GUI.Button(buttonRect, "スタート")) {
        Application.LoadLevel("Game");
      }
    }
    • 簡単に解説すると、「#pragma strict」によって厳密な型指定有効なモードにし、Start()関数は最初の一回だけ呼ばれ、Update()関数は毎フレーム呼ばれ、OnGUI()関数も毎フレーム呼ばれる。今回利用するラベルやボタン類はOnGUI()の内部で描画するもの、という事になっているようだ。
    • また後で、GUI スクリプティング ガイドを見ながら、画像を設定したり、細かいデザイン等を調整したりするとよい。
    • エラー類は画面下部ペイン領域の「Console」および最下段のステータス行に表示される。「Project」タブと共によく使う部分となるようだ。
  7. ソースファイルを変更した後は、Assetsを右クリックし、「Refresh」を実行した方がよい。これをしないとunity側に変更内容が反映されない場合がある。
    • これの詳細については後述の「#判明している問題点」を参照
    • キーボードショートカットあり(macだと「command+R」)。おぼえとくと便利
  8. 画面上部中央の再生ボタンを押し、タイトル文字とボタンが表示される事を確認する。
    • 今のところ、タイトル画面内の「スタート」ボタンを押してもエラーになる。遷移先のシーンをまだ作ってないからだ。
  9. 「Title」シーンとして保存する。
    • メニューより「File」→「Save Scene as」にて「Title」として保存する(Assets内に保存される)。
  10. メニューより「File」→「Build Settings」を選択し、「Add Current」ボタンを押してビルド対象として登録し、ダイアログを閉じる

ゲーム本体の作成

  1. メニューより「File」→「New Scene」を選択する。
  2. タイトル画面同様、空のゲームオブジェクトを作成し、対応するスクリプトを生成する。オブジェクトおよびスクリプト名は「Game」とする。
    • 前述のタイトルのコード内の「Application.LoadLevel("Game")」のところに対応しているので、ここを変更した場合はタイトル側も変更する事。
  3. タイトル画面同様、以下のサンプルソースを反映して動作確認し、「Game」シーンとして保存し、「Build Setting」にて登録を行う
#pragma strict

function Start () {
}

function Update () {
}

function OnGUI () {
  GUI.Label(Rect(32, 32, 256, 32), "TODO: ゲーム内容を実装");
  if (GUI.Button(Rect(32, 128, 128, 32), "タイトルに戻る")) {
    Application.LoadLevel("Title");
  }
}

シーン遷移の設定およびビルド

  1. メニューより「File」→「Build Settings」を選択
  2. 「Scenes In Build」の項目の一番上(0番)がタイトルになっている事を確認する(これが最初に実行される)。
  3. 「Platform」を自分が望むものに変更し、「Switch Platform」ボタンを押す。ここでは「Web Player」を選択した。
  4. (optional) 「Player Settings」ボタンを押すとプレイヤー(操作キャラの事ではなく、再生環境つまりWeb Player等の事)側設定が行える。あとで本格的にデプロイする前ぐらいに、解像度やアイコンを設定しておくとよい。
  5. 「Build And Run」ボタンを押すと保存ダイアログが出るので、適切な名前をつけて保存する。
    • 「Web Player」の場合は、指定した名前のディレクトリが作成され、中に*.html*.unity3dが生成された。
  6. 動作確認をして問題ない事を確認する。
    • 「unityのIDE内では動くのにWeb Playerだと動かない」とかが結構あるので、まだ開発中でもたまにデプロイして確認すると良いかもしれない

そしてゲーム作成へ

  • スクリプトリファレンスを見ながら、画像や効果音を追加したり、ゲーム本体を実装したり、ゲームオーバーやエンディングを実装したりする。
    • やろうと思えばユニティちゃん等の3Dオブジェクトを、背景として表示したり動かしたりもできるだろう。Unity的にはそっちこそが本業なのだし。
  • とにかくGUI スクリプティング ガイドは全部目を通すべき。

更なる先へ

以下を検討する。

  1. このままUnity標準GUIモジュールを使い続ける
    • Unity4.6にて、uGUIという奴に進化するらしい
  2. NGUIへ移行する
  3. 普通に3D/2Dの機能の使い方を調べて、そっちでゲーム本体を作る
  4. Mono/.NET(でコードを動かすUnity)に見切りをつけて、他のもっと良い環境を求め、探求の旅に出る

作った

とりあえずUnity標準GUIをメインに使ったゲームを作ってみた。

作ってみて分かった事

  • 「Unity標準GUIはdraw callが増えて重くなる」という記事をよく見ていたが、この程度では全然問題ないようだ。60fpsを確保できている(左上に「(毎秒のGC回数):(現メモリ使用量):(fps)」形式で表示している)。
    • おそらくだが、3D表示を行い、かつ、その上に大量のGUIパーツを表示するぐらいでないと影響が出ないのでは?
  • ストーリー部分(右上に「SKIP」ボタンが出ているパート)相当、つまりノベルゲーエンジン的なものであれば、機能的に全く問題ない。

  • Unity標準GUIには「GUIレイヤの上には、他のGUIレイヤ以外のものを表示できない」という致命的な問題がある。
    • これが具体的に問題になるのは、パーティクルエンジンを使ったエフェクトを前面に表示できない、という点。ダメージエフェクト等は絶望的。降雪等の表現も不自然になりがち。
    • 「こうすれば回避できる」という記事もあったが、自分が試した限りではどうやっても駄目だった。自分の設定に何か漏れがあるのか、それとも元記事の方が間違っているのか。
    • この問題を回避するには、以下の選択肢がある。
      1. パーティクルエンジン等の奥に表示したいもののみ、GUIで扱うのは諦めて、普通に2D/3D空間内に描画する
      2. 自前でGUIを使ってパーティクルエンジンっぽいものを再実装する
      3. Unity4.6のuGUIで対応している事を祈り、待つ
      4. Unity標準GUIをやめて、NGUIに移行する
      5. https://github.com/keijiro/unity-fxongui を試す(自分は未確認)

その他のメモ

JSとC#とBoo、どの言語を選ぶべきか

  • 上記での例ではJSを選んではいるものの、UnityのJavaScriptは標準的なECMAScriptではなく独自の方言になっており、これを選択する場合は、通常のJSよりも苦労する事を理解しておく事。
  • この三つの中ではBooが一番言語仕様が真っ当に出来ているので、「Python大嫌い!」とか「C系言語大好き!」とかでなければBooを選ぶとよい。
    • この文書のタイトルを見て読み進めるようなプログラマならBooを知らなくても、 http://www.atsuhiro-me.net/unity/dev/boo-basichttp://www.asahi-net.or.jp/~sy7a-ht/diary/tut_boo.html を読めば、問題ない程度にはBooを書けるだろう。
      • Boo言語自体の公式サイトはhttp://boo.codehaus.org/にある。ここの左メニューからある程度のドキュメントを見れる。
    • 公式リファレンスにはJSとC#のコードサンプルはあるが、Booのコードサンプルは無い事がある。が、これも上記のようなプログラマならば問題なくJS/C#のコードサンプルから読み替えができるだろう。
  • なお、Boo固有の機能を利用すると、ビルド後のバイナリにその機能に対応するdllも含まれるようになる為、バイナリサイズが増加するデメリットがある。
    • とりあえずBoo.Lang.*系を使うとそれに応じて増える。
    • これについては、JSでもeval等の、C#にない機能を使うと増えていると思う。

How to

  • ゲームデータを保存するには
    • PlayerPrefsを使う。
      • ブラウザ版には最大保存サイズ1Mまでの制約があるので、1Mを越える可能性がある場合は別の手段も考える事(サーバにPOSTする等)。
    • http://www.previewlabs.com/writing-playerprefs-fast/に、改善版がPublic Domainで置いてある(詳細は未確認)。
    • これらのPlayerPrefs系は数値と文字列しか保存できないので、保存したいデータをまとめて一つのハッシュテーブルに収め、BinaryFormatter→圧縮→base64、と加工した上でPlayerPrefsに保存するのがよいと思う。
      • BinaryFormatterはネストしたハッシュテーブル等も問題なくシリアライズできている。
      • BinaryFormatterはiOSでは正常に動かないらしい(未確認)。しかしWeb Playerではちゃんと動くのを確認した。
      • 圧縮はLZMAがベストだと思う。ライセンスはpublic domain。
        • 標準付属のDeflateStreamが動けばベストだったが、IDE上では動くものの、Web Playerではどうもクラス内でセキュリティサンドボックスの何かに引っかかって動かなかった。
        • 他にもunityで動く圧縮ライブラリを大量に検討したが、どれもライセンス面で問題があった(GPLだったり条文表示義務があったり。通常のアプリなら問題ないが、ブラゲ用では面倒すぎる)。
        • LZMAの特性は「高圧縮率」「展開速度高速」「圧縮速度低速」であり、最後の圧縮速度が問題になるケースがある。これは圧縮レベルを変更すれば十分高速になる。しかし上記配布のLZMAtools経由では圧縮レベルを変更できないので、直にLZMAを叩く必要がある(これは面倒ではあるが難しい箇所はない)。
      • base64はConvert.FromBase64StringConvert.ToBase64Stringで扱える。
  • 画像を表示するには
    1. テクスチャ画像ファイルをAssets/Resouces/に追加する。pngが無難
    2. Resources.LoadTexture2D型としてロードする
      • pathはAssets/Resources/からの相対path指定。また元ファイルについていた拡張子は消して指定する事(unityが勝手に内部でファイルをコンバートする為)
        • 要はAssets/Resources/hoge.pngを置いたら、Resources.Load("hoge")でロードできる
    3. LabelButtonの引数として渡す
      • 拡大縮小はLabelやButtonに渡す引数のRectに応じて、自動的に行われる(アスペクト比は維持されるようだ)。ただしGUIStyleによっては拡大縮小が行われない事があるっぽい。その場合は別のGUIStyleに変更したりしてみる。
      • 回転などはGUIでは無理っぽい。普通に3D/2Dとして扱う必要がある…
  • BGM/効果音を鳴らすには
    1. 音源ファイルをAssets/Resouces/に追加する。oggが無難
    2. Resources.LoadAudioClip型としてロードする
      • 上記テクスチャロード時の注意点も参照
    3. シーン内にAudioSourceを確保しておく。guiから作成したものを参照するか、AddComponentで新たに追加生成する。
    4. BGMは上記AudioSourceのclipにロードしたAudioClip設定してからPlayを実行、SEなら直にPlayOneShotの引数に渡して再生できる
      • 細かい再生パラメータはAudioSourceを参照
    5. 再生自体は上記で可能だが、このままではシーンを変更するとBGM/SEがすぐに終了してしまう。シーンが破棄される際に一緒にAudioSource類も破棄されてしまうからだ。これが嫌な場合は、現在のシーンとは別に、管理用のGameObjectを生成し、DontDestroyOnLoadを設定した上で、AddComponentAudioSourceを追加し、そっちで再生すればよい。これは各シーンから独立したオブジェクトになるので、ファイルも分けた方がよい。
      • つまり http://zyyxlabo.blogspot.jp/2013/03/unitysoundmanager-ver.html みたいなのを作る事になる。「これでよい」と思う人であれば、これをそのまま使ってもよいと思う。
      • 自分の作ったコード。以下を SE.boo として保存し、別のスクリプトから SE.Play("hoge") を実行するだけで、勝手に“SE”という名前のGameObjectを作って保持しつつ“Assets/Resources/hoge.ogg”を一回だけ鳴らす。連打可能。鳴っている間にシーン変更があっても途切れない。
import UnityEngine

class SE:
  static private go as GameObject
  static private clips = {}
  static public MasterVol as single = 0.5

  static private def setup ():
    if not go:
      go = GameObject("SE")
      Object.DontDestroyOnLoad(go)
      go.AddComponent('AudioSource')

  static public def Load (path as string) as AudioClip:
    setup()
    clip as AudioClip = clips[path]
    if not clip:
      clip = Resources.Load(path, typeof(AudioClip))
      clips[path] = clip
    return clip

  static public def Play (path as string, vol as single):
    clip as AudioClip = Load(path)
    if not clip:
      Debug.LogWarning("'$path' not found", go)
    else:
      vol *= MasterVol
      go.audio.PlayOneShot(clip, vol)

  static public def Play (path as string):
    Play(path, 1.0)
  • モーダルダイアログ的操作を行うには
    • GUI.ModalWindowを使う。ダイアログ内にボタン等の他のパーツを含めたい場合は、引数のfunc:WindowFunction内で描画するようにする。
  • 重なったGUIオブジェクト同士での描画順序
    • とりあえず上記モーダルダイアログは常に最前面。
    • 通常は、実行した順に、奥から手前へと描画される。
    • GUI.Windowは、複数のWindow同士の奥/手前設定を手軽に行えるようにする為に、GUI.BringWindowToBackGUI.BringWindowToFrontが用意されている。
    • OnGUI()を持つ複数のオブジェクトが存在する場合は、GUI.depthを設定する事で、OnGUI()単位での描画順を設定できる。
  • 背景色を変更するには
    • Camera.main.backgroundColorに好きな色を設定する
    • もしくはUnityのIDEより、シーン内に最初から入ってるカメラの設定をいじってもよい
  • ゲーム画面領域のリサイズイベントの検知方法
    • 標準では提供されていないので、毎フレーム、Screen.widthScreen.heightが変化してないかどうかをチェックする事で、自前で実装する。
  • リリース版と開発版で処理を分ける
    • 単に「UnityのIDE上での動作確認」かどうかを判定したいだけなら、Application.isEditorを見るのが手軽。
      • ただしUnityEditor名前空間内クラスにアクセスしたい等の場合は、 http://docs-jp.unity3d.com/Documentation/Manual/PlatformDependentCompilation.html を見てマクロ化しないとビルドが通らなくなってしまう(UnityEditor名前空間内クラスはUnityのIDE上でのみアクセス可能な為)。またApplication.isEditorと比べると判定処理をしなくてすむので軽量である。代わりにめんどい。
      • どちらにするにしても、これを使って「IDE上で動作確認するときだけキャラを強くする」みたいにしておくと開発中の確認が楽になる。
    • 「他の人にテストプレイしてもらう為に、リリース版ビルドではないデバッグ版ビルドを作り、この時だけ詳細ログを出したい」等の場合は、Debug.isDebugBuild で分岐させるのがよい。この変数は「Build Setting」ダイアログ内の「Development Build」項目と連動している。
      • このチェックボックスを入れると、画面右下に「Development Build」と常時表示されるようになるのですぐ分かる
  • プロジェクトに対する詳細設定項目一覧
  • 外部URLを開きたい / tweet機能を付けたい
    • web player以外の場合は、普通に Application.OpenURL を使えばよい
    • web playerの場合は非常に困難。
      • 上記のApplication.OpenURLを普通に使うと、元ページからの遷移になってしまいゲーム設置ページが閉じてしまう。
      • Application.ExternalCallもしくはApplication.ExternalEvalからwindow.open等を使うと、ポップアップブロックが作動してしまう。
        • これは要は「JavaScriptから(ポップアップブロックに引っかからずに)新しいタブでリンクを開くにはどうすればいいか」という問題。少し調べれば分かるが「無理」という結論。
      • 抜け道として「タイトル画面にのみtweetボタンを付け、tweetパラメータとしてoriginal_refererを指定した上でApplication.OpenURLで開き、tweet後はリンクを辿って元ページに戻ってきてもらう」というのを考えた。
        • historyに戻り履歴が残ってしまう欠点はあるものの、今のところはこれしかないと思う。
        • 通常のページには応用できない。

Unity上でLispを動かすには

何種類かの選択肢がある。

  • Mono/.NET系のLisp処理系を動かす
  • Lisp->JavaScriptコンパイラ/トランスレータを動かす
    • 上記の通り、UnityのJSは「UnityScript」という方言なので、既存のLisp->JavaScriptコンパイラ/トランスレータはそのままではまず動かないと思う。自分で改修するか、一から作るかする必要がある。
    • もし一から作る場合、コンパイル結果はJavaScriptにするのではなく、C#かBooのソースに変換する方が楽だと思う。

判明している問題点

  • gitに保存しづらい
    • 丸ごとgitにつっこむ事自体は可能だが、追加アセットが無駄に容量食う
      • ユニティちゃんフルセットで追加すると150Mとか増える
      • .gitignore等で追加アセットを除外する事は可能だが、当然、他のところでgit cloneする際に問題が出る(clone後に手でアセット追加しないといけない)
    • #プロジェクトの開始にある通り、プロジェクトデータをyamlで保存するようにすればmerge等はやりやすくなる。が追加アセットのサイズ問題の解決にはならない
  • unityのIDEの自動ファイル更新チェックのタイミングについて
    • http://d.hatena.ne.jp/nakamura001/20121115/1353002126 より引用
      • 『Unity の場合は「ファイルに変更が発生 → その後、 Unity のエディタにフォーカスが戻ってきた」という瞬間にビルドが行われます。』
    • よって、端末上のエディタを使ってる等、unityのIDEのフォーカス状態が変わらないような編集手段を採用している場合、明示的にrefreshを実行する必要がある、という事になるようだ。

ディレクトリ構成について

  • 基本的に、プロジェクトディレクトリ内のAssets/ディレクトリ内に全てを入れる。
    • Assets/ディレクトリ内に自ゲーム名のディレクトリを作り、そのゲーム固有のファイルはその中に入れる。
      • 外部アセットや共有ライブラリ的なものも全てAssets/内に入るので、その区別の為。
    • Resources/ディレクトリは複数あったり、ディレクトリの奥深くにある場合でも、unity開発環境側は全部認識してくれる。path指定はどこのResources/であっても、Resources/からの指定で認識される(ビルド時に一つにまとめているようだ)。
      • この性質を利用して、「このスクリプトとこのテクスチャはセットで使うものとして配布する」ような事をしたい場合に、これ専用のディレクトリを掘った中にスクリプトを入れると同時に更にResources/ディレクトリを掘り、そのResources/の中にテクスチャを入れれば、このディレクトリだけを他プロジェクトにコピーして流用できる。実際、アセットストアで配布されているものは大体この形式になっているようだ。
        • この使い方をする場合は、名前の衝突を防ぐ為、上記で掘ったResources/の中に更にモジュール名でディレクトリを掘った方がいいっぽい。
    • 外部からもらってきたソース等、自分が書いたコードよりも先にコンパイルしておいてほしいソースはPlugins/ディレクトリに入れておくと、先にコンパイルされる。

TODO

その他の参考リンク


provided by 技情研ネット.