Google

2012年9月25日火曜日

Unityでコインゲー開発:アスペクト比

開封の儀以降、iPhone5の調整や開発に時間を取られてばかり。
お陰であまりUnity記事を書いてませんが、今回は画面比率(アスペクト)のお話です。

アスペクトとは簡単に言うと縦と横のドットサイズの比率のこと。
Unityでは実行時の画面サイズを以下のクラスから取得できます。

 Unity Screen

これが4:3だったり16:9だったりするのですが、Unityで何も考えないとこんな問題が出てきます。

 ・見せたくない場所が見えてしまう
 ・見せたい場所が半端に隠れてしまう
 ・画面が縦や横に間延びして見えてしまう

固定サイズのPCやWebならまだなんとかなります。
実機ではそうはいかず、Androidは間違いなく最も比率が混在するため、最も対策を考えなければならない環境というのが現状でしょう。

幸い、コインゲームはiOS系で先行販売方針だったので、以下の2種類で済みました。

 ・iPhone/touch系
 ・iPad系

現在はここにiPhone5の3種類が入ることになります。
もしXCodeネイティブで作成する場合、iPad用の画面は別途準備したりしますが、Unityだとそこまで考えてはくれません。
これにも色んな策があるのですが、今回ご紹介する手段がこちら。

  【カメラ比率固定方法】

UnityのカメラにはrectというRect構造体のメンバがあります。
これは書き込みも可能になっていて、ここを調整するというやり方です。
具体的なコードはこんな感じ。

using UnityEngine;
using System.Collections;
public class AspectUtility : MonoBehaviour {
// 縦横比です。インスペクタから修正します。
public float m_x_aspect = 4.0f;
public float m_y_aspect = 3.0f;
void Awake(){
// カメラを検索します。
Camera camera = GetComponent<Camera>();
// 指定された比率からサイズを出します。
Rect rect = calcAspect(m_x_aspect, m_y_aspect);
// カメラの比率を変更します。
camera.rect = rect;
}
// アスペクト比計算
public Rect calcAspect(float width, float height){
float target_aspect = width / height;
float window_aspect = (float)Screen.width / (float)Screen.height;
float scale_height = window_aspect / target_aspect;
Rect rect = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
if(1.0f > scale_height){
rect.x = 0;
rect.y = (1.0f - scale_height) / 2.0f;
rect.width = 1.0f;
rect.height = scale_height;
}
else{
float scale_width = 1.0f / scale_height;
rect.x = (1.0f - scale_width) / 2.0f;
rect.y = 0.0f;
rect.width = scale_width;
rect.height = 1.0f;
}
return rect;
}
}


マジックナンバーとか入りまくってますが、サンプルなのでご勘弁を。
これはMain Cameraなど、カメラがついているGameObjectにヒモ付して使います。
比率情報はインスペクタから自由に変更して下さい。

起動時にカメラを探し、予め指定されたアスペクト比を元に計算し、現在の画面比率を強制的に変更するという仕組みです。
本来の想定と違う比率で起動した場合、比率から外れるエリアにはカメラが届かないため、真っ黒な領域が表示されることになります。

なお、起動中のUnityEditor画面サイズを変更した場合のケースには対応していません。
修正すれば可能ですが、そこは各自工夫して頑張ってください。

前回ポーズの際に使用したデモにこのスクリプトを追加したものを作成しました。
UnityEditor上ではあまり恩恵が無いとは思います。
実機に落とした際(iOSならiPad)にカメラ範囲の制限がされていることを確認できます。

FreeAspectなのにカメラ比率が変わっていることが確認できるかと。
比率から外れている領域は何もしていないのに真っ暗です。
上記ソースを含んだサンプルコードは以下からどうぞ。

  InstanceManageProject3.zip

次回は画面アスペクトのさらに先、絶対位置の変化に対応する話です。

2012年9月21日金曜日

iPhone5装着の儀

ソフトバンクセレクションの注文が届いたので次は装着。
発売当日に届くという素晴らしい迅速さでした。
直前の記事で書きましたが、店舗で購入する際、ケースとフィルムは現金決済せずに月額支払に含めることが可能なようです。

今回はフィルムと黒のハードケースを購入。
フィルムは2枚入ってます。クロスでゴミを取って、、、
貼りつけ完了。画面部分には無事気泡が入りませんでした。
スピーカとカメラ部分の空き領域がなんか適当です。
Touchと共用でしょうか。
続いてケースを装着。裏のシールも剥がさないと。 
装着完了。黒は横の耐久度が心配なのではめる時はちょっと怖いです。
が、ケースを入れても非常に軽い。なかなかです。
上から。電源ボタンは爪じゃないと押しづらくなってしまいました。
下から。ここは最もむき出しなので今後の対策が必要かも。

また別のケースを買うかも知れませんが、今は選択肢も無いので。
ストラップはiPhone4の際にお世話になったこれをまた検討中です。


 NETSUKE for iPhone5

買ったらまた装着記事を書く予定。

iPhone5開封の儀

早速ゲット。
他ブログでもいっぱいやってるんで珍しくもなんとも有りませんが一応。






今回は時間指定をされていたようで、サーバトラブルはありませんでした。
5専用のケースや保護シートも月賦にまとめ払いできますよーと店員さんに言われたのですが、通販で今日届く予定だったのでパス。
しかしお店にもまだ充電器系の姿は一切見当たらず。
もう少し先じゃないとこないそうです。

軽く使った感じはLTEも速いし本体も薄くて軽くて最高です。
が、開発屋としては16:9の画面構成に合わせた修正検討をしていかないと、、、。

追記:
フィルムとケースの装着の儀の記事も書きました。
あわせてどうぞ。

2012年9月20日木曜日

被災したにゃんこの島:その後

以前書いた田代島という被災した猫島から御礼の手紙が届いていました。
中を開けるとこんな嬉しい品が。

スマホポーチとストラップのセットです
ここが復興して来ているという証でもあるので嬉しい限り。
支援させて頂いた側としては御礼は正直どうでもよくて、島全体と暮らしてるにゃんこが幸せな生活に戻ってくれればそれで良いわけです。

まあ、良い方向に向かっているということでなによりです。

2012年9月18日火曜日

Unityでコインゲー開発:ポーズの具体例

開発中に中盤まですっかり忘れていたのがポーズ機能。
別にポーズボタンをつけてお客さんに使わせる訳じゃありません。
Unityでは望まなくても必要になる場合が殆どじゃないかと思います。

必要になるのはこんなタイミング。
  • アイテム一覧等のGUI専用画面遷移時
  • アプリ内課金等の通信で待ってもらう際
  • その他ゲーム内のエフェクト等で止める必要がある場合
2番目は1番目と被るのかも知れませんが一応。

そもそもUnityにはシーンという概念があります。
シーンはインスタンス丸ごと入れ替えなので、GUIが出たり消えたりする毎にごっそり入れ替えればポーズという概念が不要になります。
現在のシーンから別のシーンへの切り替え方法はとっても簡単。
以下のようにシーン名を指定するだけです。

// Unity公式サイトより抜粋。
// この例ではHiscoreという名前で保存されたシーンに遷移します。
Application.LoadLevel("HighScore");

しかしこの処理は負荷が大きく、一瞬アプリが固まったような印象を与えます。
さらにインスタンス管理とセーブロードの処理がきっちり出来ていないと、GUIから行って戻るだけでコイン位置が全リセット、なんて仕様になったりもします。
なのでUnityをよく知ってる人の場合、シーン分割はせいぜいタイトル位でしょう。

そこで今回はシーン切り替えではなく、実際に使ったポーズ処理をご紹介。
まずUpdate()で毎回動きを設定しているのなら、Update()の先頭にポーズフラグ判定を入れれば完了です。
フラグはboolにして、トグルさせるのが普通でしょう。

しかしコインゲーのような物理系の場合RigidBodyで落下処理をしているので、Updateでは止められません。
落下を止められるのはこれです。


通常この値は1.0になっています。
これは時間の進み具合を表現しており、0.0になるとRigidBodyの動きは完全に止まってしまいます。
RidigBodyの動きは重力の動きなので、めでたしめでたし。

、、、とはなりません。
これが0.0になると影響を受ける項目がこちらです。


他にも少々ありますがこれが大きいんじゃないでしょうか。
実はtimeScaleを0.0にしてもUpdate()関数は呼ばれます。
この時、GUIボタンのアニメーション等をdeltaTimeで判断していると、そっちまで全て止まってしまってあらどうしよう、になってしまいます。

しかしそれ以外の処理やイベントなどは全て使用可能ですので、静止タイプの2D処理を書く分には問題は発生しませんのでご安心を。
ちなみにNGUIにはtimeScaleに関するオプションがあり、ポーズの有無に関係ない処理を行うことが可能です。
まとめると、ポーズを実際に使うにはこうなります。

  • ポーズフラグを作る
  • Updateで処理しているものが止まるべきか判断する
  • RididBodyはタイムスケールでトグルさせる

今回は、コインインスタンス管理にポーズボタンだけを付けたものを準備しました。
先日のプロジェクトを改造しただけのシロモノですが。

これが画面。コイン落下自体は何も変わってません。
前回の違いとしては、勝手にコインがどんどん落ちてくるところ。

コインを落とすボタンは変わって、今回は「ポーズ」ボタンです。
左上に文字も出るようになりましたが、各項目はこうです。

  • DeltaTime:Updateで飛んでくる際のdeltaTimeの値
  • ProgressTime:ゲーム開始後の時間経過の値
本筋に関係ないのでソース解説は割愛。
簡単に言うと、Updateの中で毎回今回のdeltaTimeを保存し、FixedUpdateの中で毎回ゲーム開始からの時間を保存しています。
それらをOnGUIで出しているので、タイムスケールの動きがどうなるのかを勉強するにはいい材料ではないかと思います。

このプロジェクトは以下からどうぞ。
弊社サイトの保管庫に上げてあります。


ポーズの話はここまで。

続きます。

2012年9月16日日曜日

Unityでコインゲー開発:保存その3

似たようなが続いてますが、セーブロード話はこれが最後です。

まずは前回1つだけ書いてなかったサーバとのデータの同期について。
高速で動作するゲームにいきなり情報を更新しようとすれば大問題になるのは当然。
ではどこでやるべきなのか?その可能性があるのはこのへんでしょう。

  1. ローディング完了直後
  2. スタートが押された直後
  3. オプションやアイテムなどの2D画面遷移時

どれをとってもユーザに迷惑となるタイミングです。
遊ぼうと思った瞬間に同期で待たされるのでは萎えてしまいますよね。

特に地下鉄やビルの中に入った瞬間に同期が始まると最悪で、UnityのWWWは全くと言っていいほどタイムアウトしてくれません。
これを防ぐには、通信制御実装クラスにおいてWWWクラスを制御する部分をタイムアウトで諦めるような造りにする必要があります。
できればサンプルで解説したい所ですが、ノウハウが出しづらい内容なのでこのへんで。


さて、コインゲームにおける永続化について今まで色々と解説してきましたが、商品のクオリティとして最も注意しなければいけないのがこの保存です。

どこに保存されるのかとか使える機能は他のブログでよく出ているので割愛。
もしUnity側でセーブを実装するなら考えることも別途発生します。
その際やっておいた方が良いという項目はこのあたり。

  1. バックグランドに遷移した際の暗黙セーブ
  2. バージョンによるデータ構造相違の管理

まずは1.のバックグランド。
MonoBiehviourにはアプリ自身が背後に遷移した際のタイミングを取るイベント関数があります。
これを使うのが便利ではないでしょうか。

 MonoBehaviour.OnApplicationPause

正確にはポーズが掛かったタイミングという意味なのですが、実際には以下に挙げるタイミングで飛んできます。

  • ホームボタンを押されてアプリが背後に回った時
  • UnityEditorのWindowが他のアプリの背後に回った時

前者は非常に嬉しいタイミングです。
ユーザへの負担もなく、安心してセーブ作業ができるのではないでしょうか。
2番目はUnity使っていると分かるのですが、Editorで実行中に別ウィンドウが被ると止まってしまうという仕様、あのタイミングです。
予期しないところで勝手に保存っていうのもテストにはよろしくないので、以下のようにプリプロセッサで閉じてしまうのがよろしいかと。

 #if UNITY_EDITOR
 void OnApplicationPause()
 {
   // ここにセーブ処理を記載する
 }

 #endif


上記を含めたUnityで使える定義などはこちらからどうぞ。

続いて2.のバージョン管理。
これは内部データ構造に変化が発生した場合に重要です。
簡単に言うと、バージョン情報を保存しておいて、データ構造が変わった際にコンバート処理をしてあげるというもの。
一見単純そうですが、これを忘れるとバージョンアップ版配布開始時にデータ不整合で遊べなくなる人が出まくって大変なことになったりします。

そしてこれを防ぐ方法は単純、セーブ情報にバージョン情報をつけること。
全てに入れなくても1個だけあれば後は不正扱いでいいんじゃないかと。
最初にチェックしてバージョンを見て、データ変換などを挟むようにしましょう。


セーブ話はここまで。
ネタも大分付きてきましたがもう少し開発の話は続きます。

2012年9月14日金曜日

iPhone5予約完了

64GBブラック&スレート、無事に予約できました。
ヨドバシの行列に並ぶ気はなかったので近くのソフトバンクショップへ。



ソフトバンクショップには1時間前から人が並んでいた模様。
もしかしたら、あの発券機はリミットが1時間なのかもしれません。
無事に予約できたんでいいんですが、次回は1時間前に並ばなくては。


近くのドンキにも携帯コーナーがあり、iPhone5の予約もやっていたのですが、そちらには全然人がいなくて笑いました。
ドンキの方がリスク高いとみんな思ってるんでしょうか。

しかし問題は今後のiPhoneの画面サイズ。
開発屋としてはワクワクするのと同時にそっちが気になります。
今まではratioで320x480を基準にして1倍か2倍かで済んでました。
iPadは例外措置としてやっていたわけですが、ここにきてiPhoneが縦長で例外になってしまってさらにめんどくさいですね。

新型iPhoneでの実機開発の話は入手できてからまた書きます。



2012/09/15:追記

追加のケーブルはこの記事書いた後で届いたので、ポチリの記事に追記しました。
写真付きです。

2012年9月13日木曜日

Unityでコインゲー開発:保存その2

余計な記事で間があいてしまいましたが。
保存とセキュリティ話の続きです。

前回のUnity記事では時間フラグを書きました。
あれは基本のひとつなので、当然防げないものは幾らでもあります。
(時間を細かく履歴で見る等の応用を掛ければさらに効果的ではありますが)
そもそも時間基準で不正監視という仕組みは、以下を前提としているものです。

 「スマホの時計はその国の標準時に合わせて使うもの」

不正も含めスマホの時計が狂うと、中に入ってるスケジュールとかアラームとか他のアプリに多大な影響を出します。
そのため、基本的に時計をほぼ合わせた状態で使うであろう、、、という暗黙の了解的なものを織り込んでしまっている訳です。
日時履歴を細かく取ったり、サーバ通信を強制にすることで回避できなくもないですが。

じゃあ既に使わなくなったスマホやSIMを入れずに使うゲーム専用機での対策はどうするか?という話はよく出るのですが、結局いつもこうなります。

 「8割のユーザが不便になりすぎないようにする」

、、、ま、話が進まないのもアレなんで、サーバと同期した際の話を少々。

まずアプリ内課金を予定していたので自前サーバはありました。
そのサーバを使って何をするかですが、ざっくり要求はこう。

 ・共通のフォーマットで低コスト処理
 ・初回プレイ時に内部的に認証
 ・不正が発見されたら強制的に初期状態へ

なんていうか、8割のユーザがこれで問題なく遊べるのか?は疑問ですが、まあこういう感じだった訳です。
iOSでアプリ内課金しているので、サーバ連携のフォーマットは自ずと決まりました。
当然JSONです。
UnityでJSONを扱う場合、処理を全ていれこんだクラスが既に存在します。

 JsonObject
 http://wiki.unity3d.com/index.php?title=JSONObject

詳細については上記を御覧ください。
1クラスが入れ子になって階層を表現する仕組みなのでフットプリントは大きくなる傾向にありますが、簡単に生成、パーズが実現できます。

生成時のサンプルはこんな感じ。(上記サイト抜粋)


JSONObject j = new JSONObject(JSONObject.Type.OBJECT);
j.AddField("field1", 0.5);
j.AddField("field2", "sampletext");
JSONObject arr = new JSONObject(JSONObject.Type.ARRAY);
j.AddField("field3", arr);
arr.Add(1);
arr.Add(2);
arr.Add(3);
string encodedString = j.print();
インスタンス生成し、フィールドを入れるだけ。
入れ子になるならそれを繰り返し、子供にするだけです。
パーズも簡単。
string encodedString = "{\"field1\":0.5,\"field2\":\"sampletext\",\"field3\":[1,2,3]}";
JSONObject j = new JSONObject(encodedString);
accessData(j);
//access data (and print it)
void accessData(JSONObject obj){
 switch(obj.type){
  case JSONObject.Type.OBJECT:
   for(int i = 0; i < obj.list.Count; i++){
    string key = (string)obj.keys[i];
    JSONObject j = (JSONObject)obj.list[i];
    Debug.Log(key);
    accessData(j);
   }
   break;
  case JSONObject.Type.ARRAY:
   foreach(JSONObject j in obj.list){
    accessData(j);
   }
   break;
  case JSONObject.Type.STRING:
   Debug.Log(obj.str);
   break;
  case JSONObject.Type.NUMBER:
   Debug.Log(obj.n);
   break;
  case JSONObject.Type.BOOL:
   Debug.Log(obj.b);
   break;
  case JSONObject.Type.NULL:
   Debug.Log("NULL");
   break;
 
 }
}
こちらは先ほどよりは複雑ですが概念としては簡単。
もらったJSONデータをインスタンスに入れると自動的にパーズします。
入れ子があればその多段階層ができるので、それらを再帰的に処理しながら、プロパティの種類を見て取り出していくだけです。
そもそも貰うフォーマットもある程度決め打ちであるはずなので、その階層にそって存在するであろうデータを想定するだけです。
性能も悪くないですが、何度かバージョンアップしていますので古いVerを使わないようにご注意を。
続いてユーザ認証ですが、この為だけにユーザに負担をかけちゃいけません。
今までは自動認証に使えた仕組みがUDID。
これはデバイス識別IDなのですが、AppleはiOS5からもう使うなという指示を出してきました。
代わりに何を使うかといったらUUIDという、どうしようもない物。
要は被らないようにおそるおそる出した乱数なので保証はありません。
困った末に私が取った対策はこれ。
 ・UUIDと一緒に長い乱数を生成
 ・それらからハッシュキーを生成しセーブデータに追加
 ・この2つをもって認証IDとして扱う
 ・ユーザが端末からセーブデータを消したらまた新規別垢扱い
、、、、対策になってないかも知れませんね、はい。
しかしログインを明示的にさせないという大前提を崩さないためにはこうするしかありませんでした。
キモとしてはUUIDと乱数のハッシュで、まかり間違って全iOSユーザがダウンしてくれる超大ヒットアプリwになった場合にIDが被るという事を防ぐ、という点です。
UUIDの長さを見ればまず被らないとは思うんですがw、なんか心配すぎてこうなってしまいました。
続きます。

iPhone5の新型ケーブルを

思わずポチリ。


追加のケーブルが一本欲しいし、今までのケーブルの再利用も必要ですな。
さて、明日は予約に行かないと。



2012/09/15追記:

現物がないのにケーブルだけ先に届いてしまいましたw
確かに出荷予定日は1-3営業日なんですが、まあ、そういうことで。

先にケーブルだけ到着。変換コネクタは10月らしい。
箱の裏はこんなかんじ。
中身はこれ1個。束ね方も相変わらずおしゃれ。
話題のリバーシブルケーブル。縦に長い構造がちと強度面で心配。

実機がないのでここまで。
ケーブルは経験上何本かあったほうがいいので買った訳です。
しかし、某国のIT企業がまたパクっていちゃもん付けてきたりするんでしょうかねw

2012年9月11日火曜日

秋葉ショップで一休み

秋葉にオフィスがあるのに、仕事で他の町に移動しっぱなしだったりして買い物の時間が以外と少ない事に気づきました。
なので今日は仕事と税務の会議をこなし、合間に秋葉で買い物を決行。
仕事の合間の秋葉はたまに行くといいものです。

角肉明太角ダブル味噌(じゃんがら常連用語)&明太ごはん。
戦利品その1。マルチエアコンリモコン。右のは2代目。
ノートPC冷却ボード。DOSパラで売ってました。

上の商品は1200円ぐらいでした。
なんと一番安いものは550円!
しかし喜んで使ってみたら、MacBookProがつるつると滑る滑る(泣
滑り止めのゴム足が商品付いてたけど膝の上では意味なしw
そこで以下を追加購入。

PCの足に付けるやつです
わかり辛いですが、パームレストのそばに貼りました。
こうすると問題なく使えるようになりました。
音は静かだし冷えるしで、秋まで活躍してくれる商品です。
毎年毎年膝上が熱くて地獄だったので、これで少しは緩和されるました。

次回はまたUnityの話を書きます。

2012年9月9日日曜日

Unityでコインゲー開発:保存とセキュリティ

セーブロードの話の続き。

データ永続化って、それに対するセキュリティ対策も考えなくてはなりません。
Unityの難読化とは別に考えておく作業です。
まずコインゲームで一番問題となるのは言うまでもなく残枚数管理。
これが不正操作されれば商売になりません。

私が関わったのは大雑把に以下のケース。

 ・サーバと同期させて処理するタイプ
 ・内部だけで処理するタイプ

まあ、サーバ同期はサンプルソースも一切出せませんし、WWWクラスが隠蔽されすぎてて非常に使いづらいので、個人的にはあまりオススメできません。
どうしてもやるならネイティブコードで通信するほうがいいでしょう。
しかし内部だけでやる場合、まずデータそのものをどうにかしなければなりません。
素のまま保存されていたらジェイルブレイクで即解析ですから。

そこで先日書いたEasySave
このプラグインのクラスにはencryptOnというstatic変数があり、これを真にすると内部で暗号化を行ってくれます。
非常に簡単ですが負荷が掛かるので使う場所とデータ量には注意を。

で、コインゲーのもう一つの穴、時間経過でのコイン付与。
付与そのものの方法ですが、簡単に言うとこうやります。

 ・アプリがバックグラウンドに遷移した瞬間の日時を保存
 ・開始時に現在の日時と保存していた日時の差分秒数を出す
 ・1枚付与する秒数で割って、付与枚数を出す。
 ・無課金付与枚数の限界を超えないようにその枚数を加算する

あ、ズレがでないよう、日時は必ずGMTで保存しましょうね。
んで、無課金で可能な限り遊びたいと思うユーザはこんな操作をします。

 ・コインがなくなったらスマホの時間を戻す
 ・昔に戻した状態でアプリを起動して終わらせる
 ・時間を未来に進めて再度アプリを起動

何も対策してないと、時間が進んだと勘違いしてしまいますね。
不正と言い張ってもそういう動きをしてしまっては説得力もありません。

まあ、プログラムからこれを対策する方法は何種類もあって、実際は複数の手段を入れたりするんですが、今回はその対策手法のひとつをご紹介します。
私は過去フラグ方式と呼んでます。

 ・起動した際、前回の保存日時より過去なら不正フラグを立てて保存
 ・次の起動時、セーブデータに過去フラグがあったら不正とみなす
  →不正なのでコインは一切増やさない
 ・上記に該当しなければ不正フラグを戻して保存

この方法なら何度過去と未来を行き来してもコインは増えません。
また、時計を修正してもアプリを起動しなければ関係のない話でもあります。

しかしこれもビジネス。
色々やり過ぎて一般ユーザの不便さを取るより、ごく一部の違法ユーザは無視するという判断をすることが多いでしょう。


続きます。

2012年9月6日木曜日

Unityでコインゲー開発:セーブロード

今回はデータ永続化の話。

ゲームはユーザのプレイ情報を記録しなければなりません。
コインゲームでは以下のような情報を記録しました。

 ・コイン、アイテム等の残枚数
 ・画面に出ているインスタンスの位置、RididBody情報
 ・最終セーブ日時

保存よみこ処理を簡略化したかったので、使ったプラグインはこれ。


現在バージョン2ですが、私が開発で使ってた頃は1でした。
内容は単純で、Unityの永続化機能をラッパー化し、便利にしただけの代物です。
が、まあこれも実装速度には役立ちました。

具体的には配列のオーバロード関数があるのが非常に便利です。
ループではなく、情報を配列にすることでまとめられる関数があるので、コードは比較的少なめの実装を行えるのが利点ですかね。
あとは情報とキーの組み合わせで一意に決まるので管理も楽です。

このツールのハマり点としてはこんな感じ。

 ・間違っても日本語のキーを指定してはいけない(特にMacOSX)
  →コンソールからしか消せないゴミデータが残ります。
 ・Unity Editor上で謎のエラーが出るが動作はする

Transformまで保存できてしまうというこの便利さを利用して、コインゲームでは位置だけでなく、コインが動いている情報まで保存していました。
だって、横に飛んでる最中のコインが次回まっすぐ落ちたら変ですし。
ということで、次回の動きを再現したいなら以下のパラメータを保存しておきましょう。

 gameObject.transform以下の、、、

  ・position
  ・rotation

 gameObject.rigidbody以下の、、、
  ・verocity
  ・angularVelocity

これらを保存し、ロード時に設定すれば前回終えた瞬間からの再現が可能になります。
インスタンス管理システムと連動すればさらに良い管理になるんではないかと。

次回はゲームにつきもののチート対策(簡単なやつですが)予定です。


2012/09/07 追記

EasySaveで保存する場合、概念としてはn個のファイルにストリーム書き出しするような概念になります。
なので、順序はきっちり守らないといけません。
また、EasySave.fileExists()でファイルが存在するかどうかを行わなわず、実行時に存在しないとエラーとなり、実行が止まってしまうので注意しましょう。

2012年9月4日火曜日

Unityでコインゲー開発:2Dと3Dの分割

何度も忘れては思い出したりするTipsがあるので、また忘れる前に記事化しておきます。
一応コインゲー絡みの話。

元となったのはこちらの記事。

 ■[Unity][Unity3d]マウスカーソルでオブジェクトを選択

実際こういった処理を現場ではC#で書いていたのですが、これらの中でやってることの意味、ハマり、詳細等を少々。
で、今回のお題はこうです。

 「2DGUIとマウスの判定がしたい」

3Dゲームの場合、大抵2Dは別に処理します。
カメラが動いたりオブジェが飛んできたりする仕様なら、2Dのポリゴンは別にしないとカメラと一緒に動いてしまいます。
HPとかオプションなんかは当然固定にしたいものですよね。

コインゲームなら、例えば弾けるコインに2Dが邪魔されたり、GUIがコイン投擲操作の邪魔になったりしてはいけない訳です。

んで、私がやったやり方がこう。

 ・最初からあるカメラは3D空間を担当
 ・2D用に別のカメラを準備し、専用レイヤを担当

今回はプラグインは不要な方法ですのでご安心を。
3Dは最初からあるDefaultレイヤを、2D用にはGUIレイヤを準備しました。
2D用のカメラはGUI Cameraという名前で追加しただけです。

あ、カメラのレイヤーですが、こうやって追加します。

カメラ選択して、LayerからAdd Layer選択
開いてる場所に追加。今回はGUIがそれ。
各カメラが重ならないように、カリングマスクもかけときます。

Culling Maskから必要なものだけを選択。
マスクは構成を以下のようにして、余計なものは表示しないようにします。

  Main Camera : Defaultのみ
  GUI Camera : GUIのみ

さらにGUIは3D空間の歪みを消しつつ、3Dよりも上に出し、さらにオブジェクトがない部分は3Dが出るようにしなければなりません。
GUI Camera側の設定を以下に表示します。

Clear FlagsをDepth Onlyに設定
Orthographoc設定で歪みを消します。
2Dの場合はカメラの表示範囲になるので少なめに
あ、あと、上のSSで下に見えてるDepthですが、2D優先にするため以下のようにします。

  Main Camera : -1
  GUI Camera : 0

これでカメラの準備は整いました。
今回はサンプルとして、3D側にはカプセルを、2D側には1枚のプレーンをおいて、その重ね合わせを検証してみます。
プレーンはわかりやすいように赤色にしてみました。
カプセルがあるので一応ライトもあります。

まずカプセル。3D側なのでLayerはDefaultにしてます。
次にプレーン。2D側なのでLayerがGUIになってる点に注意。
これを互いに重ならないような位置に配置します。
座標は適当ですが、各々のカメラにカプセルと板が出ているのがわかります。
2Dカメラは歪みを設定で消したため、表示範囲が長方形になっているのがポイントです。

個別に表示されてますね。2Dは範囲が四角形になってます。
で、ゲーム画面では以下のように出るわけです。

はい、見事重なりました。
この手法を使うことで2Dと3Dが干渉せずに出せます。
レイヤが違い、かつカリングマスクをかけているので、2dカメラのそばにオブジェクトが紛れ込んでも映り込むことはありません。
まあ、大抵はカメラの位置を離して影響ないようにするんですが。

じゃあこの2DのGUIだけマウスを反応させるにはどうするか?が実は本題。
まず忘れちゃいけないのが、マウスで判定させたいGUI、今回はこのプレーンに対してコライダーをつけること。
これを忘れると冒頭で紹介したブログの判定も動いてくれません。
私もこれを結構忘れてしまってしばらく悩んだりとか馬鹿な事をしたりします。

コライダーのサイズ等は触らないのが吉。判定がずれます。
そしてマウス判定をするクラスのUpdate等の内部で以下のように書きます。

 // GUIカメラをヒモ付
 public Camera m_gui_camera;
 // マウス押下時
 if (Input.GetMouseButtonDown(0))
 {
  // マウス押下位置からビームを飛ばして判定
  Ray ray = m_gui_camera.ScreenPointToRay(Input.mousePosition);
  // GUIレイヤのみをマスク
  int gui = 1 << LayerMask.NameToLayer("GUI");
  // 無限の軌道でヒット判定
  if (Physics.Raycast(ray, Mathf.Infinity, gui))
  {
   // GUIレイヤをクリックした際の処理をここに記述
  }
 }

m_gui_cameraは今回準備したGUI専用のカメラを紐付けしましょう。
FindでもインスペクタからのD&Dでもおkです。
マウスが押されたら、GUI側のカメラに対してマウス押下位置の情報を作成します。
int型のguiという変数はなんか変なことやってるように見えますが、さっきの画像を再掲しますのでまずは以下をご覧あれ。


GUIというレイヤを追加しましたが、これは番号として8になります。
LayerMask.NameToLayer()に存在する名称を投げるとその番号が帰ってくるのですが、これはビット単位でのマスク処理になっているため、実際は以下の様なコードなのです。

   1 << 8

つまり、この場合はビットシフトされて256という値になります。
もしさらに追加してレイヤが9なら512になる訳ですな。
続いてこのマスクを使って、Physics.Raycast()にこれらの情報を入れると、マウスの入力位置が該当するレイヤのオブジェクトにあたったかどうかを真偽で返してくれるのです。

真ならその中でGUIにあたった処理を書けばよろしい訳でして。
複数のGUIがあってさらに座標計算とか面倒ならレイヤを沢山作ってボタン別に分ければ綺麗に処理も可能です。
注意点としては、コライダーを忘れないこと。
あとカメラが2つあると、AufdioListenerが複数あるという警告が実行時にひたすらでてしまいます。
どちらか一方のコンポーネントを消すといいでしょう。

一応コインゲーム開発ではこの原理を実際に使っていました。
3D側にも当然コイン投擲等の判定は必要ですが、先ほどのif文に掛からなければ3D空間をクリックしたということになりますので、そこで対処できると思います。

なお、今回のデモのソースは以下からどうぞ。

  サンプル置き場所(SampleProject_20120904.zip 782,845 byte)

デモをEditor上で実行し、赤いプレーン上でマウスクリックすると”hit”とログに出るだけの簡単なシロモノです。
判定スクリプトはプレーンに紐付けしてあります。
まあ、大したことはしていませんが、皆様の何かのお役に立てれば幸いです。


記事長くなりすぎたので続きはまた次回。

2012年9月3日月曜日

Unityでコインゲー開発;XCodeのエラー

続き。
コインゲーにてプラグインを自前で書いた際にはまった罠の話です。

UnityからiOSのプラグインを呼び出す際の制約はあちこちで書かれているので、それらに準拠すれば問題はあまり出ません。

が、先日のPaymentPluginのような*.a系のファイルや他のFWが重なった際、あまり知られていないエラーに遭遇してしまいました。
細かく書くと特定されるのでアレですが、画面はこんな感じ。


これもまた面倒な話で、組み合わせやXCodeのバージョンによっては不安定な動きとなり、エラーが出たり通ったりします。
あまりに不安定で、どのバージョンとの組みわせでこう、といった検証まではできていませんでした。
幾つか試した環境は以下ですが、全て発生しました。

 MacOSX Lion
 XCode4.2〜4.3系
 Unity3.4.x〜3.5.x系

しかし抽象的すぎる話ではネタにもならないので、回避方法だけを書いておきます。
なにかあった際の手段のひとつとして記憶に留めてください。

下のSSは以前も画面で出したネイティブコード側のソースをXCodeに吐き出した際のものです。
C関数だらけのプラグインはmm形式で記述するのが通例です。


UnityでPlugins/iOSの下にファイルを置くと、XCodeに変換された際Librariesの下にそのまま配置されることになります。
が、これが怪しいコンパイルエラーを引き起こす時があります。
そんな時はまずそのファイルを選択しましょう。
選択時のプロパティ画面が以下。


File TypeがデフォルトでObjective-C++と解釈されています。
ビルドシーケンスが明確になっていないのか、XCodeに割り当てる順番が原因なのか、この判定が問題みたいでした。
解決の操作は以下。

C++ではなくCで、かつ先行コンパイル対象としてあげます。
これだけで先ほどのエラーが綺麗に消えます。
XCode上に配置するファイルの順番はUnityが勝手に決めるので、後はaファイルの位置ぐらいだろうと思うのですが、aファイルもプロジェクトの最上段に設置しないとこれまた怪しい現象が出たりしました。

しかしUnityから吐き出すと当然のごとくこの設定が消えてしまうので、毎回この修正を行ってから実機転送する必要があります。
うーん、コインと全く関係無い話にずれてばっかりですみません。

次回は少し戻って3Dコインゲームでの2D管理の話にします。