2013年11月25日月曜日

【Unity】デバッグ用にクラスのメンバ変数を列挙してみた

Twitterで気になる発言を発見。
上記のブログで紹介されてる関数を使えば
プロパティ名などをInspectorで表示されるような
ちょっと見やすい形に整形できるとのこと。

何かの役に立つかな?とのことなのでちょっと考えてみた。

考えてみた。

プロパティ名を手打ちしてまで使うものじゃないなー

プロパティを自動取得すれば使えるなー

そのまま列挙すればデバッグ時に便利じゃない?

ToString()でそのオブジェクトの内容を表示できるので
ToString2()みたいな関数を作ってより詳しい内容を表示しよう。

書いてみた。

MonoBehaviourExtensionsってクラスを作成
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor;

public static class MonoBehaviourExtensions
{
 
 /// <summary>
 ///  MonoBehaviourを継承したクラスの内容を文字列として返します。
 /// </summary>
 public static string ToString2<T> (this T obj) where T : MonoBehaviour
 {
  Type t = typeof(T);
  
  var txt = new System.Text.StringBuilder ();
  
  //  GameObject名を取得
  txt.Append (((MonoBehaviour)obj).name);
  
  // Get Public Fields
  FieldInfo[] fields = t.GetFields ();
  foreach (FieldInfo f in fields) {
   string fName = ObjectNames.NicifyVariableName (f.Name);
   string fValue = f.GetValue (obj).ToString ();
   txt.Append (string.Format (" [{0}:{1}]", fName, fValue));
  }
  
  return txt.ToString ();
 }
}
こんな感じかな?

ポイント

・各クラスに実装するのが面倒だったので拡張メソッドで定義
 (MonoBehaviourを継承してToStringをOverrideでもいいけど)
・呼び出しクラスのPublicな変数一覧を取得
・変数名と値を取得
・変数名を紹介されてた関数を使って整形
・整形した変数名と値を列挙して返す

使ってみた。

適当なGameObjectにMyScriptという名前でスクリプトを追加
using UnityEngine;

public class MyScript : MonoBehaviour {
 
 public int PublicIntXXX = 10;
 public string PublicStringABC = "ABC";
 public int PublicPropertyIntYYY {get;set;}
 private int myPrivateIntXXX = 3;

 void Start () {
  Debug.Log(this.ToString2());
 }
}
こんな感じで内容を表示したいクラス内でToString2関数を呼ぶだけ。
Debug.Logで使うぐらいしかないけど…。
うまく表示できた。
これは地味に便利かもしれないね。

プロパティやPrivateメンバは省いてる。
PublicFieldメンバだけだと足りないかもしれないから
必要に応じてカスタマイズしよう。

Unity系記事まとめ

2013年11月23日土曜日

【Unity】AudioManagerクラスを作ろう

さて、前の記事でシングルトンを提供するクラスができたので
AudioManagerを仕上げてみました。
【前の記事】シングルトンの実装についてはこちら
【Unity】なんちゃらManagerクラスを作ろう(シングルトン)

イメージ

自分がAudioManagerを作ろうって思った時点で思い描いた仕様はこんな感じ。

  • デザイナ上でBGMやSEを好きなだけセットできる。
  • 好きな場所からいつでも再生、停止できる。
  • 再生する音源は音声ファイル名で指定する。
  • BGMは1つだけ再生。
  • SEは指定した数まで同時再生可能。


実装

できました。
ソースは長いので一番下に載せときました。

使い方

AudioManagerの配置

・空のGameObjectを作成
・名前をAudioManagerとかにしとく
・AddComponentでAudioManagerを追加
追加直後の状態

AudioClipをセット

・mp3ファイルとかをHierarchyビューにドロップ
 (これでAudioClipができる)
・AudioClipの名前は分かりやすい名前にしておく
 (呼び出すときに使うため)
・BGMListにAudioClipを好きなだけ追加
・SEListにAudioClipを好きなだけ追加
・MaxSEはSEの最大同時再生数です。好きな値で。
BGM2つ、SE3つセットしてみた所

テスト用スクリプト書いてみる

・MainCameraのAudioListenerを削除(or 無効化)
・空のGameObjectにこんな感じのコンポーネントを付ける
using UnityEngine;

public class script : MonoBehaviour {
 
 void OnGUI()
 {
  if(GUI.Button(new Rect(10,100,100,50),"BGM1"))
  {
   AudioManager.Instance.PlayBGM("bgm1");
  }
  if(GUI.Button(new Rect(110,100,100,50),"BGM2"))
  {
   AudioManager.Instance.PlayBGM("bgm2");
  }
  if(GUI.Button(new Rect(210,100,100,50),"BGMSTOP"))
  {
   AudioManager.Instance.StopBGM();
  }
  if(GUI.Button(new Rect(10,200,100,50),"SE1"))
  {
   AudioManager.Instance.PlaySE("se1");
  }
  if(GUI.Button(new Rect(110,200,100,50),"SE2"))
  {
   AudioManager.Instance.PlaySE("se2");
  }
  if(GUI.Button(new Rect(210,200,100,50),"SE3"))
  {
   AudioManager.Instance.PlaySE("se3");
  }
  if(GUI.Button(new Rect(310,200,100,50),"SESTOP"))
  {
   AudioManager.Instance.StopSE();
  }

 }
}
実行!

BGMが鳴る!切り替えられる!停止できる!
SEが鳴る!いっぱい鳴る!
完璧っす。

ソース

AudioManager.cs

using UnityEngine;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

public class AudioManager : SingletonMonoBehaviour<AudioManager> {
 
 public List<AudioClip> BGMList;
 public List<AudioClip> SEList;
 public int MaxSE = 10;
 
 private AudioSource bgmSource = null;
 private List<AudioSource> seSources = null;
 private Dictionary<string,AudioClip> bgmDict = null;
 private Dictionary<string,AudioClip> seDict = null;
 
 public void Awake()
 {
  if(this != Instance)
  {
   Destroy(this);
   return;
  }

  DontDestroyOnLoad(this.gameObject);
  
  //create listener
  if(FindObjectsOfType(typeof(AudioListener)).All(o => !((AudioListener)o).enabled))
  {
   this.gameObject.AddComponent<AudioListener>();
  }
  //create audio sources
  this.bgmSource = this.gameObject.AddComponent<AudioSource>();
  this.seSources = new List<AudioSource>();
  
  //create clip dictionaries
  this.bgmDict = new Dictionary<string, AudioClip>();
  this.seDict = new Dictionary<string, AudioClip>();
  
  Action<Dictionary<string,AudioClip>,AudioClip> addClipDict = (dict, c) => {
   if(!dict.ContainsKey(c.name))
   {
    dict.Add(c.name,c); 
   }
  };
  
  this.BGMList.ForEach(bgm => addClipDict(this.bgmDict,bgm));
  this.SEList.ForEach(se => addClipDict(this.seDict,se));
 }
 
 public void PlaySE(string seName)
 {
  if(!this.seDict.ContainsKey(seName)) throw new ArgumentException(seName + " not found","seName");
  
  AudioSource source = this.seSources.FirstOrDefault(s => !s.isPlaying);
  if(source == null)
  {
   if(this.seSources.Count >= this.MaxSE)
   {
    Debug.Log("SE AudioSource is full");
    return;
   }
   
   source = this.gameObject.AddComponent<AudioSource>();
   this.seSources.Add(source);
  }
  
  source.clip = this.seDict[seName];
  source.Play();
 }
 
 public void StopSE()
 {
  this.seSources.ForEach(s => s.Stop());
 }
 
 public void PlayBGM(string bgmName)
 {
  if(!this.bgmDict.ContainsKey(bgmName)) throw new ArgumentException(bgmName + " not found","bgmName");  
  if(this.bgmSource.clip == this.bgmDict[bgmName]) return;
  this.bgmSource.Stop();
  this.bgmSource.clip = this.bgmDict[bgmName];
  this.bgmSource.Play(); 
 }
 
 public void StopBGM()
 {
  this.bgmSource.Stop();
  this.bgmSource.clip = null;
 }
 
 
}

ポイント

・実行にはSingletonMonoBehaviourクラスが必要です。→こちら
・複数Scene跨いでも使えます。
・Scene内にAudioListenerが無い場合は生成します。
・AudioSourceを動的に生成します。


まとめ

やりたいと思ったことは実現できました。
でもListとかDictionaryとかLinqをガンガン使ってるので
ゲーム開発って視点で見ると無駄の多いソースなのかも。

アプリ開発始めたころは省メモリ意識してたんだけど
結構動いてくれるから気にしないで書いちゃうのよね。

ソース載っけてみたので
”もっとこうした方がいいよ。" とか "自分はこんな風にしてるよ。”
っていうのがある方は是非おしえてください!
人のソース読みたい…。

それじゃまた!
Unity系記事まとめ

【Unity】なんちゃらManagerクラスを作ろう(シングルトン)

どこからでも呼び出したいもの

Unityでゲーム作ってるとどこからでも呼び出したいクラスが欲しくなります。
命名するとなんちゃらマネージャーみたいになるやつ。
AudioManagerとか、SettingManagerとか、ParticleManagerとか
どこからでも呼び出せると便利で、でも2つ以上あったら変なもの。

じゃーどうすんの?って調べてると大体シングルトンっていう考え方に行き着くと思う。
シングルトンとは→Wikipedia
ようするにインスタンスを1個しか作らなければいいだけ。
よし使おー。

シングルトンを使ってみよう

ってことで普通にやろうとすると
AudioManagerクラスを作成→シングルトンになるよう変数、関数追加
SettingManagerクラスを作成→シングルトンになるよう変数、関数追加
ParticleManagerクラスを作成→シングルトンになるよう変数、関数追加
・・・

ってなる。
同じようなの作るたびにコピペでめんどくさい。
いや、めんどくさいというより何度も書くのが気持ち悪い。

そんなときこそ、

じぇねりくす

こんなことしなくていいようにC#には便利な機能があって、
ジェネリクスってものを使うと1回書くだけで再利用できるようになります。
ジェネリクスとは→ジェネリックス

よし作ろう!と思ったけど
MonoBehaviourが絡んでくるのでどう書いていいか良くわからない。

絶対先輩方が書いてるでしょってことでめっちゃ調べた。
あるわあるわいっぱいあるわ

[Unity3D]もっと楽なシングルトンの実装 : テラシュールウェア
Unity開発に関する50のTips 〜ベストプラクティス〜(翻訳): No hack, no work
[Unity]Generic Based Singleton for MonoBehaviours完全版(?) : ケットシーウェア

この中でも一番自分のイメージに近かった
50のTipsにあるものを使うことにしました。

SingletonMonoBehaviour

using UnityEngine;

public class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
 private static T instance;
 public static T Instance {
  get {
   if (instance == null) {
    instance = (T)FindObjectOfType(typeof(T));
 
    if (instance == null) {
     Debug.LogError (typeof(T) + "is nothing");
    }
   }
 
   return instance;
  }
 }

}

使い方

using UnityEngine;

public class AudioManager : SingletonMonoBehaviour<AudioManager> {
    
    public void Awake()
    {
        if(this != Instance)
        {
            Destroy(this);
            return;
        }

        DontDestroyOnLoad(this.gameObject);
    }    
    
}
こんな感じ。
SingletonMonoBehaviourを継承するだけでシングルトンパターンが実装できます。
テラシュールウェアさんも書いてましたがDestroyについては
利用側(XXXManager)で実装することにしました。
そしてDontDestroyOnLoadはクラスに応じてって感じですね。
AudioManagerならタイトルとかのSceneで生成して使い回すだろうし書きました。


うんうんいい感じ。
MonoBehaviourよくわかってないから合ってるのかわからんけど使えそう。

次はAudioManager完成させよう。

Unity系記事まとめ

2013年11月22日金曜日

【Unity,Photon】PhotonCloudでオンラインゲームつくっちゃおー

これに参加してきましたー。
Unityで Photon Cloudを使って、オンラインゲームを作っちゃおう【導入編】

今回参加したセミナーはPhotonCloudっていうオンラインゲームを作るために便利ななんやかんやがあって、それの布教活動の一環らしいです。
結構頻繁にやってるらしくもうすぐ累計200名突破らしいー。パチパチ。

作ったもの


1時間半のセミナーの中でこういうの作りました。
Hosted by UnityRoom.com

複数ウィンドウ立ち上げて動かしてみるとマルチプレイが確認できると思います。

実際作ってた時間は30分もないと思う。ていうかコード書いてないし。
キャラクターは気持ち悪いけどすごい。
セミナー会場では全部で10体?の気持ち悪い物体が歩き回ってました。

セミナー内容

ざっくりとメモった内容をば。

Photon Cloudとは

ひとことで言うとオンラインゲームを作るために必要な
ネットワーク系の機能を集めたもの。
オンラインゲームを自分で作ろうと思うとサーバーを用意したりいろいろと大変だけど
photonを使えば何も用意しなくていいって感じ。
機能はいっぱい。メモるのめんどくさくなるぐらいいっぱいあった。
集合ロビーとか、マッチメイキングとかチャットとかほんと色々。
もちろん無料で始められます。

利用事例


トロッ娘

http://www.unitygames.jp/game/ug7000556

TicTacToePlus

Sketch with Friends

http://www.sketchwithfriends.net/

ダンジョンズ&ゴルフ

https://itunes.apple.com/jp/app/danjonzu-gorufu/id571639322

他にも色々。

すごいなー。
トロッ娘とか実質20時間ぐらいらしい。
くるくるさんのTicTacToePlusもCEDECの合間だけで作っちゃったとか!
びっくり。

ここから手を動かして作ってみる。

アカウント取得

http://cloud-jp.exitgames.com/

ここいってメールアドレス入れて新規登録。
そしたらメールがくるのでパスワード設定して終わり。
あら簡単。

Assetインポート

Unity起動しててきとーにプロジェクト作成。
AssetStoreで"Photon Unity Networking Free"ってのをインポート。
ほうほう

Photon Cloudの設定

インポートすると"PUN(Photon Unity Networking) Wizard"が立ち上がる。
※無ければOption+Pで開く
それのSetupからAppIDを入れてリージョンを選んでSave。

AppIDは登録時のメールからPhotonにログインすれば書いてある。

ネットワーク機能実装

ここから設定してく。
・サンプルSceneが開かれてると思うのでNew Sceneを作成
・空のゲームオブジェクト作成(PhotonScriptとか適当な名前にする)
・AddComponent -> Random Matchmaker
 これが勝手にマッチングしてくれるらしい。

ここで実行すると既に気持ち悪いのが出てくる。(落ちてく)

チャット機能実装

・空のゲームオブジェクト作成(Chatとか名前つける)
・AddComponent -> In Room Chat
 これで2つのコンポーネントが追加される。
   ・Photon View
     ↑これがついてるオブジェクトが同期対象となるみたい。
   ・In Room Chat
     チャットするやつ。

ユーザー名つける

RandomMatchmaker.csのStartメソッドを修正
    // Use this for initialization
    void Start()
    {
        PhotonNetwork.ConnectUsingSettings("0.1");
  
  if (string.IsNullOrEmpty(PhotonNetwork.playerName))
  {
   PhotonNetwork.playerName = "guest" + Random.Range(1, 99999);
  }
    }

ふむふむ。

あと諸々調整

・Plane置いて床にする。
・照明追加

実行

うごいた。
想像以上に簡単だった。
お手軽すぎる。(気持ち悪い)

ここまで30分程度。
残り時間はみんなでトロッ娘で遊んでました。

まとめ

チャットだけでも自分で作ろうとすると相当めんどいよね。
コンポーネントつけるだけで動いちゃうとか素敵!!
もう少し実用的なことをしようとすると色々複雑になってくるんだろうけど
「お手軽」に「オンラインゲーム」ってことは十分伝わりました。

いやーおもしろい。
来月応用編あるらしいし行けたらいいな。

おまけ

モバイル環境での実行について

以前どっかでPhotonはモバイルじゃ使えないって聞いたんですよね。
それがとても気になってたので一応質問してみました。
そしたらあっけなく「できますよ」って。

!!!!

マジか!

どうやらUnityFreeでもPhotonの有料アセットを購入すればiOS,Androidで動かすことができるみたいです。

たぶんこれかな。
Photon PUN+

Unity3Dがモバイル無料になったのにPhoton使えないとかダメじゃん!ってことで
がんばってくれたみたいです。

いやーありがたい!夢が広がるね!

参考サイト

公式サイト
http://www.photoncloud.jp/
Photon Unity Network (Asset Store)
http://u3d.as/2ey
Photon Cloud ユーザー助け合い所(FaceBook)
https://www.facebook.com/groups/photoncloudjp/

2013年11月16日土曜日

【Unity,Parse】Parse.comのサーバー日時が取得できないか試してみた。

Parse.comでサーバー日時が取りたくなったので試してみた。

やったこと

・サーバー日時の取得
・取得にかかる時間の計測

サーバー日時の取得

ここ見ながらプロジェクト作成

https://parse.com/apps/quickstart

こんな感じのスクリプトを書いた。

using Parse;
using UnityEngine;

public class testParse : MonoBehaviour
{
 void Start ()
 {
  ParseObject o = new ParseObject ("EVENT");
  o.SaveAsync ().ContinueWith (t => {
   Debug.Log ("TIMESTAMP " + o.UpdatedAt);
  });
 } 
}
ParseObjectを更新するとUpdatedAtってプロパティに
時間が入るようなのでそれを取ればいいみたい。

実行


問題なく取れた。
標準時っぽいね。

取得にかかる時間

スクリプトを変更

using Parse;
using UnityEngine;
using System.Collections;

public class testParse : MonoBehaviour
{
 
 private Queue msgQueue = new Queue ();

 void Start ()
 {
  msgQueue.Enqueue ("Start");
  
  ParseObject o = new ParseObject ("EVENT");
  o.SaveAsync ().ContinueWith (t => {
   msgQueue.Enqueue ("Saved Async Complete");
   msgQueue.Enqueue ("TIMESTAMP " + o.UpdatedAt);
  });
  
  msgQueue.Enqueue ("End");

 }
 
 void Update ()
 {
  if (msgQueue.Count > 0) {
   string msg = string.Format ("{0} {1}", Time.time, msgQueue.Dequeue ());
   Debug.Log (msg); 
  }
 } 
 
}
・別スレッドからTime.time呼べなかったからQueue使った。
・task.Wait()でいいと思うんだけど使うとなんかフリーズした。

実行

1秒ちょい。こんなもんか。

おまけ iPhoneでも計測

これも1秒ちょい。時間はそんな変わんなかった。
非同期で使えばそんなに気にならない時間なのかな〜。

2013年11月11日月曜日

ザッカーバーグもいいねって言ったと思うBaaSのParse.comからUnitySDKが出てたので使ってみた


Parse.comとFacebookの関係が知りたい人はこの記事を読んで。
Facebookの傘の下に入ったParseが初のデベロッパカンファレンスを開催

ってことでどれくらい簡単に使えるのか試しに使ってみました。

やったこと

1.Parse.comに書いてたQuickStart
2.ParseUserを使ってユーザ登録
3.ParseObjectを使ってデータ登録
4.Parse.comのDataBrowserを使ってどんなデータが入ったか確認

1.Parse.comに書いてたQuickStart

まずはParse.comにアクセス
ナビゲーションバーの Help → QuickStart
Unity選んでNewProject選んで・・・ま、書いてある通りやった。
そしたら書いてある通りの結果になった。
つまりQuickStartは簡単だった。

2.ParseUserを使ってユーザ登録

ParseUserっていうクラスが用意されてて、それを使うとユーザアカウント周りが簡単に作れるっていうので簡単に使ってみた。
でも、Unityでユーザ登録とかパスワード入力してログインとかはしたくないから、デバイス情報使って自動ログインさせることにした。
空のGameObject作って↓のスクリプトを追加
using UnityEngine;
using System.Collections;
using Parse;

public class ParseLoginScript : MonoBehaviour {

    // Use this for initialization
    void Start () {
        string deviceId = SystemInfo.deviceUniqueIdentifier;
       
        ParseUser.LogInAsync(deviceId, deviceId).ContinueWith(t=>{
            if(!(t.IsFaulted || t.IsCanceled)){
                Debug.Log ("login success");
            }else{
                Debug.Log ("login failure");
                var user = new ParseUser(){
                    Username = deviceId
                    , Password = deviceId
                };
                user.SignUpAsync().ContinueWith(t2=>{
                    if(!(t2.IsFaulted || t2.IsCanceled)){
                        Debug.Log ("signup success");
                    }else{
                        Debug.Log ("signup failure");
                    }
                });
            }
        });
    }
   
    // Update is called once per frame
    void Update () {
       
    }
}
初回実行すると"signup success"って出て、2回目実行すると"login success"って出た。
だけど起動してからsuccessするまでに1,2秒かかった。そんなもんか。
ユーザはもういいや。

3.ParseObjectを使ってデータ登録

どうやらParseUserと紐付けたデータを作ることができるみたいなので、そんな感じでやってみる。
ParseUser.CurrentUserで今ログインしてるユーザをとってこれるらしい。
↓のスクリプトを適当に呼び出し。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Parse;

public class ParseObjectScript {
    public void SetEvents() {
        var ev = new ParseObject("Event");
        ev["Title"] = "aaa";
        ev["User"] = ParseUser.CurrentUser;
        ev.SaveAsync().ContinueWith(t=>{
            if(!(t.IsFaulted || t.IsCanceled)){
                Debug.Log ("save success");
            }else{
                Debug.Log ("save failure");
            }
        });
    } 

    public IEnumerable GetEvents(){
        IEnumerable events;
       
        ParseObject.GetQuery("Event")
            .WhereEqualTo("User", ParseUser.CurrentUser)
            .FindAsync()
            .ContinueWith(t =>
            {
                Debug.Log ("load success");
            });
        return events;
    }   
一応saveもloadも成功したっぽい。

4.Parse.comのDataBrowserを使ってどんなデータが入ったか確認

QuickStartでも見たと思うけどDashboard見てみる。
テストで追加したユーザと合わせて2ユーザ作られてる。
そしてなぜかすごいAPIリクエスト数になってる??
DataBrowserで実際に保存されてるデータを確認してみる。
ユーザクラスちゃんと作られてる。
objectIdとcreatedAtとupdatedAtは勝手に入るらしい。
イベントクラスも作られてる。
LinqToSqlいいね。

って感じでかなり簡単に使えました。
データの保存以外にもPUSH通知とかJOB作れたりだとか画像処理とかもできるらしいので、気力があるときに使ってみたい。

2013年11月9日土曜日

【Unity,NGUI3.x】UIPanelとDrawCall

NGUI使っててDrawCallが減らせなくて困った。
原因は単純だったけど解決したのでメモ。

環境

・Mac
・Unity 4.2.2.f1
・NGUI 3.0.3g

つまづいたこと

NGUIでUIを大量に配置してたらDrawCallが30超えた。
同じUIAtlas使ってるしDepthも揃えたのになんでだろなーって思ってた。

原因

同じUIAtlasを使っていてもUIPanelを複数配置するとDrawCallが別れるんですね。
以前どこかで「まとめて動かすようなものはPanelに入れる」って読んだ気がしてUIPanel量産してましたw

ダメなパターン

Root(UIRoot)
┗Camera(Camera,UICamera)
 ┗Anchor(UIAnchor)
  ┗Panel(UIPanel)
   ┣Panel1(UIPanel)  ←まとめて動かす用
   ┃ ┗Sprite(UISprite)
   ┗Panel2(UIPanel)  ←まとめて動かす用
     ┗Sprite(UISprite)

2つのSpriteはどちらも同じAtlasを参照してますけどDrawCallは2ですね。

Panel1のInspector見てみてもDrawCallが別れてるのが分かります。

対応

じゃあどうするか?UIPanelを1つだけにすればいいんです。
Panel1,Panel2からUIPanelコンポーネントをむしり取りましょう。

大丈夫なパターン

Root(UIRoot)
┗Camera(Camera,UICamera)
 ┗Anchor(UIAnchor)
  ┗Panel(UIPanel)
   ┣Panel1(空のGameObject)  ←まとめて動かす用
   ┃ ┗Sprite(UISprite)
   ┗Panel2(空のGameObject)  ←まとめて動かす用
     ┗Sprite(UISprite)

これだけでDrawCallは1つになりました。
DrawCallは親Panelに集約されてますね。


Panel1,2は空のGameObjectになってしまったけど何の問題もなさそうです。

まとめ

DrawCallをとにかく減らしたいなら1つのUIPanelに配置する。
でも同時に表示しないUI(ゲーム画面用、ポーズ画面用など)はこだわる必要ないので別々のUIPanelにした方が楽に扱えそうですね。

疑問

DrawCallが増えてでもUIPanelを別けたい場合はあるだろうか?
パーティクル表示を(Atlas、Panelセットで)分離したいときとか?

参考

 ここでは別Panelでも1Callだったって書いてあるけど仕様変わったのかな。
 最初読んだとき良くわからなかったけど今じっくり読んでやっと分かった。やったこと同じだね。


楽しいUnityライフを!

2013年11月7日木曜日

【Unity,C#】RGBではなくHSVによる色指定

今日はこんなの作ってみた話。

ことの発端はNGUIでボタンを作ってて、
統一感のあるカラフルなボタンを作りたくなったこと。
Unityのエディタ上ならなら色選択ダイアログのスライダー動かすだけなんだけど、
スクリプトでやろうとしたらできなくて困った。
これ
ColorクラスはRGBでしか指定できない。
RGBをどう変えたら同じ雰囲気の別の色に変えられるか分からなかった。

調べてみたらHSVっていう方法で色を指定できればいいみたい。

HSV色空間


HSV色空間

RGBは名前の通り赤緑青の比率だけど、
HSVだと以下の3つで色を決定するらしい。
・Hue(色相)
  赤、青、黄色と言った色
・Saturation(彩度)
  色の鮮やかさ
・Value(明度)
  色の明るさ

なのでSとVを固定してHだけを変更すればやりたいことが実現できそう。

UnityEngine.Colorクラス

http://docs.unity3d.com/Documentation/ScriptReference/Color.html
RGBしか扱えないみたい。
当然コンストラクタもRGBしか用意してない。

HSVとRGBを変換できるものが必要。

調べた

EditorGUIUtility.HSVToRGB
  標準でも変換クラスはあるみたい。
HSVの値からRGBを返すユーティリティスクリプト
  作ってる人いた。でもちょっと物足りない。

ちょっと欲しい感じの見つからなかったので作ってみた。
ここに変換式も全部載ってたし。

作ったクラス

ColorHSV.cs

using System;
 
namespace UnityEngine
{
 /// <summary>
 /// HSV色空間を扱うクラス
 /// </summary>
 public static class ColorHSV
 {
  
  
  /// <summary>
  /// HSV色空間による指定でUnityEngine.Colorを作成します。
  /// </summary>
  /// <param name="h">色相(Hue) 0-360</param>
  /// <param name="s">彩度(Saturation) 0-255</param>
  /// <param name="v">明度(Value) 0-255</param>
  /// <returns></returns>
  public static Color FromHsv(int h, int s, int v)
  {
   while (h >= 360) h -= 360;
   while (h < 0) h += 360;
   if (s > 255) s = 255;
   if (s < 0) s = 0;
   if (v > 255) v = 255;
   if (v < 0) v = 0;
   
   return FromHsv((float)h, (float)s / 255.0f, (float)v / 255.0f);
  }
 
   
  
  
  /// <summary>
  /// HSV色空間による指定でUnityEngine.Colorを作成します。
  /// </summary>
  /// <param name="h">色相(Hue) 0.0-360.0</param>
  /// <param name="s">彩度(Saturation) 0.0-1.0</param>
  /// <param name="v">明度(Value) 0.0-1.0</param>
  /// <returns></returns>
  private static Color FromHsv(float h, float s, float v)
  {
   if (h > 360.0) throw new ArgumentOutOfRangeException("h", h, "0~360で指定してください。");
   if (h < 0.0) throw new ArgumentOutOfRangeException("h", h, "0~360で指定してください。");
   if (s > 1.0) throw new ArgumentOutOfRangeException("s", s, "0.0~1.0で指定してください。");
   if (s < 0.0) throw new ArgumentOutOfRangeException("s", s, "0.0~1.0で指定してください。");
   if (v > 1.0) throw new ArgumentOutOfRangeException("v", v, "0.0~1.0で指定してください。");
   if (v < 0.0) throw new ArgumentOutOfRangeException("v", v, "0.0~1.0で指定してください。");
   
   Color resColor = Color.clear;
   
   if (s == 0.0) //Gray
   {
    int rgb = Convert.ToInt16((float)(v * 255));
    resColor = new Color(rgb, rgb, rgb);
   }
   else
   {
    int Hi = (int)(Mathf.Floor(h / 60.0f) % 6.0f);
    float f = (h / 60.0f) - Hi;
    
    float p = v * (1 - s);
    float q = v * (1 - f * s);
    float t = v * (1 - (1 - f) * s);
    
    float r = 0.0f;
    float g = 0.0f;
    float b = 0.0f;
    
    switch (Hi)
    {
     case 0: r = v; g = t; b = p; break;
     case 1: r = q; g = v; b = p; break;
     case 2: r = p; g = v; b = t; break;
     case 3: r = p; g = q; b = v; break;    
     case 4: r = t; g = p; b = v; break;
     case 5: r = v; g = p; b = q; break;   
     default: break;
    }
    
    resColor = new Color(r,g,b);
   }
   
   return resColor;
  } 
 }
  
   
  
  
 /// <summary>
 /// UnityEngine.ColorのHSV色空間への拡張
 /// </summary>
 public static class ColorExtension
 {
  /// <summary>
  /// 色相(Hue)
  /// 0-360
  /// </summary>
  public static int h(this Color c)
  {
   float min = Mathf.Min(new float[]{c.r, c.g, c.b});
   float max = Mathf.Max(new float[]{c.r, c.g, c.b});
   
   if (max == 0) return 0;
   
   float h = 0;
   
   if (max == c.r) h = 60 * (c.g - c.b) / (max - min) + 0;
   else if (max == c.g) h = 60 * (c.b - c.r) / (max - min) + 120;
   else if (max == c.b) h = 60 * (c.r - c.g) / (max - min) + 240;
   
   if (h < 0) h += 360;
   
   return (int)Mathf.Round(h);
  }
  
   
  
  
  /// <summary>
  /// 彩度(Saturation)
  /// 0-255
  /// </summary>
  public static int s(this Color c)
  {
   float min = Mathf.Min(new float[]{c.r, c.g, c.b});
   float max = Mathf.Max(new float[]{c.r, c.g, c.b});
   
   if (max == 0) return 0;
   return (int)(255 * (max - min) / max);
  }
  
  
  /// <summary>
  /// 明度(Value)
  /// 0-255
  /// </summary>
  public static int v(this Color c)
  {
   return (int)(255.0f * Mathf.Max(new float[]{c.r, c.g, c.b}));
  }
  
   
  
  
  /// <summary>
  /// 現在の色を基準にHSV色空間を移動します。
  /// </summary>
  /// <param name="c"></param>
  /// <param name="offsetH">色相(Hue)オフセット値</param>
  /// <param name="offsetS">彩度(Saturation)オフセット値</param>
  /// <param name="offsetV">明度(Value)オフセット値</param>
  /// <returns></returns>
  public static Color Offset(this Color c, int offsetH,int offsetS,int offsetV)
  {
   int newH = (int)(c.h() + offsetH);
   int newS = (int)(c.s() + offsetS);
   int newV = (int)(c.v() + offsetV);

   return ColorHSV.FromHsv(newH,newS,newV);
  }
  
  
  /// <summary>
  /// 現在の色を文字列として返します。
  /// </summary>
  /// <returns></returns>
  public static string ToString2(this Color c)
  {
   return string.Format("R={0}, G={1}, B={2}, H={3}, S={4}, V={5}", 
   new object[]{
   c.r
   ,c.g
   ,c.b
   ,c.h()
   ,c.s()
   ,c.v()});
  }
 }
}

簡単な説明

1ファイル追加するだけで使えるように気をつけた。

ColorHSVクラス

HSV指定でUnityEngine.Colorのインスタンスを作るクラス
Color color = ColorHSV.FromHsv(0,100,255);

 H:0~360で指定
 S:0~255で指定
 V:0~255で指定
 S,Vは0~255ではなく0~1指定が一般的なのかな?
 RGBが0~255だったりするからそうしちゃった。
 色詳しくないし標準は知らない。

ColorExtensionクラス

UnityEngine.Colorクラスへの拡張メソッド群。

 Colorインスタンスから直接H、S、V取得可能に。
Color color = Color.red;
int h = color.h();
int s = color.s();
int v = color.v();

 ColorインスタンスからH、S、V指定でオフセット可能に。
Color color = ColorHSV(0,100,255);
Color color2 = color.Offset(30,0,0);
これ結構気に入ってる。

遊んでみた

NGUIのUISpriteを360枚配置、Hを1度ずつ変えてみた。
きもちわるっ!
まぁでも機能的には実現できたし満足。

Unity系記事まとめ

2013年11月3日日曜日

【Unity,NGUI3.x】Gridを使って等間隔に配置

ステージセレクトとかで1画面に25個とかボタンが並んでることありますよね。
どーやるのか調べてみたらNGUIに便利なクラスがありました。

Gridっていうものを使うらしいです。

試してみたので残しておきます。

作ったサンプル

適当なSpriteを等間隔に配置してみた。

環境

・Mac
・Unity 4.2.2.f1
・NGUI 3.0.3g

作り方

プロジェクトを用意

・NGUI3.0をインポート
  Examplesは使わないので削除
・以下の素材をインポート
  http://www.tasharen.com/ngui/tutorial3assets.unitypackage
・ProjectビューのMain Cameraも使わないので削除

パネルを用意

・NGUI->Create->Panel
 設定は規定のまま。

Spriteを追加

・NGUI->Create->Sprite
適当に画像を選んでサイズを調整。
これを横5列、縦5列に並べてみようと思います。

Gridを配置

・Game Object->Create Empty
・Panel直下に移動
・Transform.Scaleを1,1,1に変更←忘れがち!
 X,Y,Zの左にある[S]を押すだけで1,1,1になります。
・名前をGridに変更
・Gridを選んだ状態で、Component->NGUI->Interaction->Grid
これでUIGridコンポーネントを持ったオブジェクトができました。
こいつが綺麗に並べてくれるらしい。

並べるSpriteの準備

SpriteをGrid直下に移動し、25個コピーします。
当然まだ重なってますね。

Gridの設定

・Arrangement = Horizontal(水平)
・Max Per Line = 5(横5列)
・Cell Width = 65
・Cell Height = 65
ここでReposition Nowを押すと…
Gridの座標から右へ5個、改行されながら計25個。
綺麗に並びました。あー楽ちん。

まとめ

・等間隔に配置するにはUIGridコンポーネントを使う。
・直下に色々置いてRepositionNowするだけ。

Gridの座標から右(下)に向かって伸びてくみたい。
並んだ数の中心にPivotが欲しいけどそこは仕方ないのかな。
あと空のGameObjectを追加するところ、一発でPanel直下に生成できないのかな?
ショートカットとかありそうなものだけど。

Unity系記事まとめ

2013年11月1日金曜日

【Unity,NGUI3.x】NGUI3.0を触ってみた。

先日NGUIが3.0になりましたねー。
2.7も良くわかってないのに3.0が出てしまいました。
全然ついていけてない。

とりあえずチュートリアル動画見ながら触ってみました。

NGUI 3 Tutorial


触ってみた

準備

・新規プロジェクト作成
・NGUI3.0をインポート
・Examplesフォルダをおもむろに削除
・Spritesフォルダ作成
・画像の取り込み
  こんな画像ない。もう着いていけないって思ったけど前回見たチュートリアルと同じサンプル画像使うことにした。
  http://www.tasharen.com/ngui/atlas.zip

[2013/11/3追記]
Youtube動画の説明に素材ダウンロード先書いてありました。
http://www.tasharen.com/ngui/tutorial3assets.unitypackage

テクスチャ

・NGUI->Create->Texture
・適当なテクスチャをD&Dで貼り付け
  テクスチャにズーム。3D Gizmos OFFにしたけどなんだろうね。
  マウスでぐりぐりできる。

テクスチャアトラス

・NGUI->Open->Atlas Maker
  DrawCall減らすのに重要だよー。
  画像を選択するとAtlasMakerに"Add"って出てくる。
  適当にアトラス名付けてCreate
  相変わらず簡単だなー。
  画像を追加したり更新したりする場合は画像を選択してUpdate押すだけ。

スプライト

・最初に追加したテクスチャは用済みなので削除
・NGUI->Create->Sprite
  Atlasとそれに含まれるSpriteを選択
  SpriteType
    ・Simple - ただ表示
    ・Tiled - 名前のまんま
    ・Filled - 名前のまんま
      Fill DirとFill Amountを変えると色々できる
    ・Sliced - 角がボケないように拡大縮小できる
      Inspector->Sprite->Edit->Boarderを変更        

  Pivot
    これを変えると回転中心を変えられる

ラベル

・Spriteを選択した状態でNGUI->Create->Label
   まだフォントがない。
・NGUI->Open->Font Maker
   作れるフォントは2種類。bitmapフォントとdynamicフォント。
   2.7のときdynamicフォントなんてあったっけ?
   動画だとdynamicフォントにarialがビルトインされてるって言ってるけど見つからない。というかFontMakerのUIが違う。
   と思ったらこんなの出てた。
もはやUIFont作る必要ないって。
   ただTrueTypeフォントを参照しなさい。ってマジか。すごい。
ただFont選択を押すだけでArial出てきた。
・Labelを編集
   テキスト入れて、色変えて。
   影つけたりもできるよ。
   このLabelはSpriteの子供だからSpriteのサイズや位置を変更しても追従するよ。
   もし他のコントロールに隠れてしまったら
     ・NGUI->Selection->Bring to front
     ・NGUI->Selection->Push to back
   で調整。

コライダ

・Spriteを選択して、NGUI->Attach->Collider
   これでSpriteにぴったりのColliderができる。
   Spriteのサイズ変更したときとかはもっかいAttach ColliderでOK
・ProjectSettings->Physics->Raycasts Hit Triggers
   これ確認してるけど何のためか分からなかった。
・裏に隠れてるコントロールを選択するには右クリック。
   何これすげーーーーーーーーーー。
・逆に手前にあるものを選択するには左クリック。

Buttonコンポーネント

・Spriteを選択してAddComponent->Button
・TargetにはSprite自身を設定
   これで色設定とかできるようになる。
   標準色/マウスホバー時の色/クリック時の色/無効時の色
   簡単過ぎてやばい。
   色選択のとこにPresetってあるけどこんなのあったっけ。むっちゃ便利やん。
・OnClickってとこにSprite自身を設定
・Sprite->AddComponent->NewScript
   適当なpublicメソッドを作成
・そうするだけでドロップダウンに作ったメソッドが出てくる…
   マジで言ってんのかこれ…。便利になりすぎてやばい。

Layerのお話

・Cubeを追加
   Layerを変更すればSceneビューで表示されるものを変更できるよって言ってる。
   でも自分の環境だとSpriteもLabelも全部Defaultレイヤーなんだよね。
   どこか間違えたのか…。まぁいいや。

[2013/11/03追記]
動画見直したらTexture追加した時点で2DUIレイヤーだった。
自分がやってみたらDefaultレイヤーだったし、そもそも2DUIレイヤーなんてなかった。
予めCreate->Panelで2DUIレイヤーのパネルを作っておくといいかもしれない。

Tweenコンポーネント

・Cubeを選択した状態でAddComponent->TweenPosition
   From,Toを指定。これだけで動く。
・TweenPositionを無効化しておく
・Spriteを選択して、OnClick.NotifyにCubeを指定
・OnClick.MethodにTweenPosition.Playを指定
   するとクリック時にTweenが働く。
・TweenPositio.Toggleを指定すると往復動作になったり。
・一旦OnClick.Notifyをクリアしとく

PlayTweenコンポーネント

さらに細かく指定したい場合はこっちを使う
・Spriteを選択した状態でAddComponent->PlayTween
・TargetにCubeを指定
・TriggerConditionにOnHoverを指定
   マウスオーバーでTweenPlay、アウトで逆。
・CubeのTweenPositionコンポーネントでカーブを変更したり。
・TweenコンポーネントはOnFinishedに別のメソッドを追加したりできる。
   連続するTweenの指定も楽々。

Cameraコンポーネント

・UIRoot(2D)を無効化
・MainCamera->AddComponent->Camera
・Cube->AddComponent->PlayTween
・Sceneビューを見やすく操作
・MainCameraを選択し、GameObject->AlignWithView
   これでカメラがSceneビューと同じ位置にくる。これ便利。
・暗いからDirectionalLight追加
・CubeクリックでTweenPositionが起動することを確認
   UIRootじゃなくてもいいんだなー

ウィンドウの作成

・UIRoot(2D)を有効化
・Panelに適当なSprite追加
・ウィンドウっぽく配置
・Label追加
・ウィンドウに重なるSpriteを追加
・半透明のTiledSpriteにする。
・ボタンとかのDepth調整
あれ、なんで動画ではGameビューでPanelが真正面向いてるんだ…。

Widgetをまとめる

・Panelを選択してAddComponent->UIWidgetContainer
   これでマウスダブルクリックするだけでPanelを選べるのね。
   Container単位で一気に移動できたりするのね。

Align

・適当にSprite追加->AddComponent->Anchor
・Sideを変更すると変更するとCamera表示域に対して移動する
・Containerを指定するとそのコントロールを基準にAlignされる
   なるほどなぁ。こうやって×ボタンとか位置揃えるのか。

DrawCall

・Panelを選択するとUIPanelの中にDrawCall数が表示される。
  この動画だと5つ。
    Atlas->Font->Atlas->Font->Atlas
  Depth順に描画するからこうなるらしい。

・Labelは最前面にくるから2つのラベルのDepthを10にしてみる。
   DrawCallが2つに減った!

Spriteを通り抜けるクリックイベント

・実行するとSpriteをクリックしても後ろのCubeが反応してしまう。
・SpriteにNGUI->Attach->ColliderとするとRayCastがとまるのでCubeは反応しなくなる。
  なるほど。こうやるのか。

Widget Wizard

・NGUI->Open->Widget Wizard
・PanelにScrollBar追加
・PanelにToggle(CheckBox)追加
・Toggleを複数追加し、Groupを同一にするとRadioButtonのような動作にもできる。
・Toggleを選択肢、AddComponent->ToggledObjects
・ActivateにScrolBarを指定
  Toggleが選択されているときだけScrolBarが表示されるようになった。
・PanelにSlider追加
・Sliderの背景、前景サイズ変更
  ここでもAttachCollider
・Thumb削除
  削除しても動くらしい
・Panelにスクリプト追加
  スライダーの値を使ってアルファを変更するやつ
・SliderのValueChangedにPanel.SetAlphaを指定
  これでスライダーに応じてPanelの透明度が変わるようになった。

UILabel

・Overflowを変更することで文字やラベルの自動リサイズの動きを変更できる
  ほんと何から何まで実装してあるな…。
・テキストに[99ff00]とかを含めると文字色を途中で変更できる。
・[-]を入れるとそこで色変更が止まる。

ハンドル

・背景画像などによってNGUIの選択枠が見づらくなったら
・NGUI->Handles->Set to Orageとかで色を変えられる。
   あら便利

まとめ

2.7もちょっと触っただけだけどこれは間違いなくよくなってますわー。
動画見ただけで そんなんできるの! ってのいっぱいだった。
Unityのエディタ拡張ってほんとなんでもできちゃうんだね。

まだチュートリアル触っただけで使いこなすにはほど遠いけど
迷いなく使えるようになったらめっちゃ捗りそうなのは間違いない。

とりあえず使おう!

Unity系記事まとめ
Related Posts Plugin for WordPress, Blogger...