騒音のない世界 BLOG

コンピュータの話と、音楽の話

iOSエンジニアがUnity使って1ヶ月でアプリ作った話

先日、Unity製のカジュアル2DゲームをiOS/Androidでリリースしました。

iOS開発やcocos2d-xでのゲーム開発の経験はあったのですが、Unityはまったく初めてでした。というわけで、Unity使う中で感じたことをまとめたいと思います。これからUnity入門する方やUnityに興味ある方の参考になれば幸いです。

学習

最初にとりあえずUnityの学習期間を設けました。勉強だけしてたのはだいたい2、3日ぐらいだったと思います。インストールして動かすまではすんなりいけました。

勉強方法

最初に驚いたのが公式サイトが日本語に対応していたことです。全てではありませんがドキュメントの類までかなり対応されていました。少し前にAndroid開発のチュートリアルか何かを読んだ時は最初の数ページだけ日本語で後半ほとんど英語だったのですが、そんなこともなくしっかり対応されていました。すばらしい。

Unity - マニュアル: Unity マニュアル

公式のチュートリアルも充実してます。コースは動画になっており、実際にゲームを作りながら基本的な要素を学んでいく形のものになっています。こちらは日本語字幕があるものとないものがありますが、おそらくネイティブスピーカー以外の人が聞くことも想定しており比較的平易な英語ではっきりと発音してくれているので聞き取りやすいです。僕は英語のリスニングはそこまで得意ではないですが、英語字幕もあるので簡単に進められました。

Unity - Learn - Modules

今回作るゲームは2Dだったので2Dの初級と中級をやりつつ色々試したりしてました。それだけであとは調べながら作れそうという状態になれたので、実際のプロジェクトを進めていくことに。

本も読んでいたのですがそちらはあとから知識の穴を埋めるイメージで隙間時間に読んだりしてました。3Dだったら地形とかマテリアル、ライティング周りなども勉強しないと厳しいかもですね。

大変だったこと

Unity開発の中でハマったこと・手間取ったことなどをざーっと書いてみます。最初からこの辺気をつけられるともっと開発効率上がったかなと思います。

エディタを覚える

まず、縦画面にする方法がわかりませんでした。プロジェクトの初期状態は横画面で、チュートリアルも横画面だったので。「いやこんなもんすぐできる」と思ってあれこれやってたんですが結局自力では見つけられませんでした。まあ調べろって話なんですが、「簡単そうなことでも自力で見つけられない」っていう洗礼をいきない浴びた感じでしたね。当たり前かもですが、調べながら覚えていく感じだな〜と。

よく使う処理のショートカットとかも覚えると便利になりますね。公式チュートリアルでは出てきませんが、Q, W, E, Rで移動とか回転とかのツールを切り替えられるのは最初に知っておくと良いです。

複数の座標系が混在する

ゲームによって違うかと思いますが、複数の座標系について考える必要があり少し混乱しました。説明が難しいのでちょっとわかりにくい話になります。もっと良い方法もあるかもです。

ゲーム内のオブジェクトは基本的にワールド座標系で設定します。これは3次元の座標系ですが、2Dゲームでも同じ座標系を用います。ワールド座標系の1単位の長さは例えばカメラに写っている領域の高さを10とする、みたいな感じで自由に決められます。

さらにUnityにはGUIを作るuGUIという仕組みがあって、こちらはスクリーン座標系っていうんですかね、2次元の座標系で設定します。これはワールド座標系とは別の座標系です。デザインをiPhone5サイズでお願いしていたので、今回は横幅が640になるように設定しました(デバイスの横幅/640が1単位になる)。

さらに広告やWebviewなどのネイティブUIを出す必要がありました。例えば画面下部に出す広告の高さは50dipで固定です。dip密度非依存ピクセルというやつで、Androidなどのネイティブアプリでよく使われる単位です。Unityからデバイスのサイズを取るとピクセル値で取れるので、dipにcontent-scaleを掛けた座標系についても考える必要があります。(content-scaleはUnityからは直接取れなさそう?だったので、DPIから計算しました。)

要は色んな長さの「1」があってどれがどれなんだっけとなるっていう話なんですが、結局どうしたかというとそれぞれの座標系を相互に変換するような関数を作りました。それでデザインで指定された座標をワールド座標に変換して配置、とか、画面下から50dip空けて30ワールド座標の位置に配置、みたいなことやってました。

dip座標系 <-> デバイス座標系 <-> スクリーン座標系 <-> ワールド座標系

という感じです。

画面のレイアウトはめちゃくちゃやらないといけないので、最初に仕組みを作るのに時間をかけて細かい計算は隠蔽し、できるだけシンプルに考えられるようにしました。

ついでにですが、マルチ解像度対応は横方向は横幅に合わせて拡縮、そのスケールで縦方向は画面いっぱいに伸ばすという感じにしました。小さめの画面を基準にしているので基本引き伸ばされて多少ガビっちゃいますが、大きめの端末で見ても問題なさそうだったので許容としてます。

transform.positionとtransform.localPosition

Unityエディタ上でGameObjectに設定したpositionとかrotateみたいなプロパティは、スクリプトから動的にいじれたりします。それで、なんとなくエディタ上の名前とプロパティ名って必ず一致してると思ってました。実際だいたい一致してるんですが、transform.position = new Vector2(x, y)みたいにすると全然思った場所にレイアウトされなくてなんでや!ってなりました。

調べたところ、エディタからだと親GameObject基点の相対positionを指定するんですが、スクリプトのpositionは画面中心基点のグローバルなポジションになってしまうみたいです。なのでしばらく親GameObjectのpositionに足しあわせる感じで相対ポジションを書いてて、めんどくさいなーと思ってました。

しばらくしてドキュメント眺めてて気づいたんですが、transformにはlocalPosiitonというプロパティがあって、エディタ上のpositionはスクリプトではlocalPositionと対応しているみたいです。エディタだと表記がpositionなので結構長いこと勘違いしてました。基本スクリプトでセットする時はlocalPosition使うことが多くなるんじゃないでしょうか。

タッチイベントの取得

オブジェクトをタッチして云々ということがやりたいとき、uGUIを用いる場合はButtonコンポーネントを使えばタッチは簡単にとれますが、そうでない場合は自分で当たり判定を書く必要があります。毎回書くと結構大変なので、使いまわせるようなコンポーネントを作るなど考えたほうが良さそうです。

ArrayListとList (C#)

C#も初めてだったので少し戸惑ったことなのですが、「ListのほうがArrayListより新しい」って名前から初見で気づけなくないですか。。なんとなくJavaに近い感覚で書いてたので最初は名前的にArrayList使ってましたが、型情報が失われるのでキャストが多くなってなんか嫌だなと思っていました。あとからListの存在を知って、Listみたいに型指定できるって知ったのでこれだ。。と。普通の配列への変換もこっちのほうがやりやすいですね。

ビルド設定

Unityはビルド周りの機能が結構弱い印象です。普通のネイティブアプリだとdebug, releaseで分けたりとか、繋ぐサーバの環境ごとにビルド設定を分け、サーバの接続先を変えたり、別アプリにしたり、マクロで処理を分岐したりとかできると思いますが、Unityだと難しいです。

一応developmentビルドというものはありますが、bundle-idやアプリ名を切り替えるなど細やかな設定を簡単に実現する方法は標準では用意されてないという感じでした。(今回は、幸い社内ツールでPlayer設定を保存して切り替えられるようなものがありそちらを利用させてもらってだいたいなんとかなりました。)

ビルド工程についてはスクリプトを書いてある程度自動化することはできるようになっていて、今回で言うと広告のUnity SDKを入れるときにキーをinfo.plistに設定してあれこれみたいな手順があったのですが、毎回やってたらヤバイ量だったのでスクリプトを書いて自動化しました。

リソースの管理

色や文字列などもリソースとして一元管理したかったのですが、いまいちベストな方法を確立できませんでした。

最初はスクリプトで定数として管理し、必ずスクリプトからセットするルールにしていたのですが、Unityはエディタが強力で、エディタで完結させたほうがはるかに速く作れるので「必ずスクリプトからセットする」というルールはスピード感がなく諦めました。エディタでの見え方と実行時の見え方が変わるとややこしかったというのもあります。

結局エディタで直接指定するようにしたので一元管理はできていません。なのでUIの色をいっぺんに変えたりとかはできないです。ローカライズとかも大変なのでは。。ちょろっと調べたんですけど簡単にやるデファクトな方法って無さそう?ですかね。

プラグインの管理

Unityプラグインは普通に入れるとプロジェクトの色んなフォルダにガッとインポートされてファイルが散在してしまうこともあり、どれがどのパッケージなのかわからなくなったりします。

外すためのプラグインとしてPackage Uninstallerというのがサードパーティ製で一応ありましたが、パッケージに含まれるファイル全部消してしまいそうな感じだったので、複数のパッケージが同じファイルに依存していたりすると死ぬんじゃないかな。

https://www.assetstore.unity3d.com/jp/#!/content/35439

実際そういうのがあって、複数のプラグインが全く同じGoogleのなにかのライブラリに依存してました。(プラグインに含まれているバージョンが違っていて、古い方で上書きしちゃうとビルドできなくなるという問題もありました。)

あとプラグイン間でAndroidのActivity奪い合っちゃう問題というのもありました。Activityのライフサイクルきっかけで処理したくてActivityを独自のものに置き換えるプラグインがあるんですけど、Activityのイスって1つしか無いので2つ以上そういうプラグインを入れると動かなくなるんですよね。手動でActivityをマージして置き換えないといけないという。

いろいろ書きましたが、プラグインの管理は特にプロジェクト規模が大きくなっていくと結構たいへんになりそうだなーという印象でした。今回はそこまで問題になりませんでしたが、プラグインごとにフォルダがあって独立に管理できるようにしておくと良さそうという感じ。

設計について

ゲームの設計って前々から悩んでますが、難しいですね。

ゲーム内容にもよりますが、ゲームロジックとビューが密接に絡むことが多いので、そこを完全に分離するのは難しいと思うんですよね。見た目に依存するロジック層と見た目に依存しないロジック層で分けるとかなんですかね?MVVM的なアーキテクチャは可能かもしれませんね。VMには見た目に絡んだロジックを書くイメージ。

あとUnityはComponent-Based Architectureというやつだと思うので、それにメンタルモデルを切り替えて乗っかる必要があるなという感じがしています。たまたま昔書いていた記事を貼っておきます。

ゲーム設計: Component-Based Architecture - Qiita

スクリプトを書く時には小さいコンポーネントに分ける意識で、コンポーネントの再利用性をいかに上げられるかを考えるって感じですかね。

あとグローバルにデータを持つのってバッドノウハウなイメージあったのですがゲームだと結構普通にやるのかなって感じがしてます。Scene間のデータ受け渡しをやろうとするとどうしてもそうなりますし。そもそもUnityのGameObjectってFindとかするとどこからでも到達できてしまうし。。

ツール系よりもゲームのほうが変更の振れ幅が大きくて、堅牢性よりフレキシビリティを重視するんですかね。動かせる状態になってからの調整とか多そうですし。その中で保守性や可読性を担保していくのはなかなか骨が折れそうですね。

おわりに

ちょっとダラダラ書いてしまいましたが今回は以上です。何か参考になれば幸いです。