2014年8月3日日曜日

【Unity、UniRx】REST APIにスコアをPOSTする


@Baiteen

ReactiveExtensionsがUnityでも使えるってのを今更知ったので、今更ながら使ってみた。その5。

やりたいこと

ReactiveExtensionsを使ってREST APIにスコアをPOSTする。
サーバー立ててAPI用意するのは面倒くさいので、アカウントがあるParse.comで試してみることにする。

やったこと

(UniRxはインポート済み)
1.
Parse.comにログインしてアプリ作ってスコア保存用のクラスを用意。(playerとscoreフィールドも追加)

詳しい方法は割愛。(元気が足りず諦めました)

2.
Parse.comのREST APIの仕様を確認。(見るだけ)
まずはRequestFormatを確認。ここ。

なるほど。
Content-Typeをapplication/jsonにして、X-Parse-Application-IdとX-Parse-REST-API-Keyを指定すればいいのね。
IdとKeyはアプリケーションの設定画面にありました。


それからデータの登録方法を確認。ここ。

なるほど。
URLで登録するクラス指定して、ポストデータで登録したいデータをjsonで渡せばいいのね。

3.
MiniJSONを使えるようにする。
JSONデータを渡すということなので、オブジェクトをJSON文字列にしてくれるMiniJSONを使うことにしました。
https://gist.github.com/darktable/1411710からMiniJSON.csをダウンロード。
UnityのAssetsフォルダ内にPluginsフォルダを作ってそこにドラッグドロップ。
これで使えるようになったはず。

4.
Unityでスクリプト書いてみる。
スコア登録用のScoringクラスを用意して、マウスダウンで登録実行するのをイメージ。
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using MiniJSON;

public class TestScript : ObservableMonoBehaviour
{
 /// スコアを登録してくれるクラス
  public class Scoring
  {
  /// プレイヤー名
    private string Player { get; set; }
  /// REST APIのURL
    private string ApiUrl { get { return "https://api.parse.com/1/classes/Ranking"; } }
  /// リクエストに必要なheader
    private Hashtable Headers {
      get { 
        var headers = new Hashtable ();
        headers ["X-Parse-Application-Id"] = "******";//自分のアプリのIDを入れてね
        headers ["X-Parse-REST-API-Key"] = "******";//自分のアプリのKeyを入れてね
        headers ["Content-Type"] = "application/json";
        return headers;
      }
    }

  /// コンストラクタでプレイヤー名設定
    public Scoring (string player)
    {
      this.Player = player;
    }

  /// スコア登録Observable(3回リトライしてみる)レスポンス文字列をそのまま発行
    public IObservable PushScore (int score)
    {
      var form = new WWWForm();
      form.AddField("player", this.Player);
      form.AddField("score", score);

      return ObservableWWW.Post(this.ApiUrl, form, this.Headers).Retry(3);
    }
  }

  ///  RXのサンプルがAwakeだったので、ここに書きました。 
  public override void Awake ()
  {
    //参考サイト:http://neue.cc/2010/07/28_269.html

  //マウスダウンでスコア登録
    OnMouseDownAsObservable()
      .SelectMany(_ => new Scoring("baiteen").PushScore(100))
   .Subscribe (result => {
        Debug.Log ("finish");
      });

    // If you use ObservableMonoBehaviour, must call base method ← サンプルにこう書いてますので、そのまま。
    base.Awake ();
  }
}
ん?何やらエラー発生してる。
ArgumentException: Key duplication when adding: Content-Type
調べてみると"application/x-www-form-urlencoded"か"multipart/form-data"が最初から設定されてるらしい。
消そうと思ったけど消せない。

もうちょっと調べてみたら
WWWクラスでは、WWWFormじゃなくてbyte[]でポストデータを指定できるみたい。

なぜObservableWWW.Postにはbyte[]が渡せないんだー!!

ってことでbyte[]渡せるようにオーバーロード追加してやってみた。

■追加したコード(UniRx/Scripts/UnityEngineBridge/ObservableWWW.cs)
        public static IObservable Post(string url, byte[] content, Hash headers, IProgress progress = null)
        {
            return Observable.FromCoroutine((observer, cancellation) => FetchText(new WWW(url, content, headers), observer, progress, cancellation));
        }

んで、スクリプトのPushScoreを以下に修正(bloggerに貼るとobjectがobject=""になっちゃう)
  /// スコア登録Observable(3回リトライしてみる)レスポンス文字列をそのまま発行
    public IObservable PushScore (int score)
    {
      var dic = new Dictionary ();
      dic.Add ("player", this.Player);
      dic.Add ("score", score);
      byte[] data = System.Text.Encoding.UTF8.GetBytes (Json.Serialize (dic));

      return ObservableWWW.Post(this.ApiUrl, data, this.Headers).Retry(3);
    }
お、今度はエラー出ない。

5.
Parse.comのDataBrowserで確認。

すばらしい。ちゃんとデータ登録されてる。

例外処理とかは置いといて、とりあえずUniRxでPOSTできることが確認できたので良かった。
良かった。



2014年7月25日金曜日

【Unity、UniRx】オブジェクトをカラータイマーみたいに点滅させる


@Baiteen

ReactiveExtensionsがUnityでも使えるってのを今更知ったので、今更ながら使ってみた。その4。

やりたいこと

ReactiveExtensionsを使ってオブジェクトをカラータイマーみたいに点滅させる。

やったこと

(UniRxはインポート済み)
まずは、点滅させるためのSphereを追加(Sphereじゃなくてもいい)
んで点滅してるのがわかるようにPlaneを追加(Planeじゃなくてもいい)
んでResourcesフォルダ作って、その中に"self-illumin"ってMaterialを作っておく(ShaderをSelf-illuminにしてね)
それからC#Scriptを作成してコードを以下のように変更(今回もTestScriptにしました)
そしてTestScriptをSphereにドラッグ&ドロップしたら出来上がり
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UniRx;

public class TestScript : ObservableMonoBehaviour {

  /// 点滅速度
  public float Speed = 5f;

  /// 点滅時間[sec]
  public float DueTime = 3f;

  ///  RXのサンプルがAwakeだったので、ここに書きました。 
  public override void Awake ()
  {
    //参考サイト:http://neue.cc/2010/07/28_269.html

    //最初につけてあるShader
    var defaultShader = this.gameObject.renderer.material.shader;
    //光らす用のShader(Resourcesフォルダの中に"self-illumin"ってMaterialを作っておく)
    var illuminShader = Resources.Load( "self-illumin" ).shader;

    //マウスダウンされると値が発行される
    OnMouseDownAsObservable()
    //光らす用のShaderに切り替え&ライト追加。追加したライトを後続に渡す。
    //ここの書き方はあんまりよろしくない気がする。。。
    .Select(_ => {
      this.gameObject.renderer.material.shader = illuminShader;
      return this.gameObject.AddComponent();
    })
    //Updateで明るさ値更新してOnGUIのタイミングで明るさ値とライトを値として発行。
    .SelectMany(light => 
      UpdateAsObservable()
      .Select(__ => Mathf.Sin(Time.time * this.Speed))
      //OnGUIのタイミングで値を発行したいのでこれする。
      .CombineLatest(OnGUIAsObservable(), (sin,g) => new {sin, light})
    )
    //時間が来たら値の監視をやめて、Shader元に戻す&ライト削除
    .TakeUntil(
      Observable.Timer(TimeSpan.FromSeconds(this.DueTime))
      //Shaderを切り替える処理がメインスレッドじゃないと出来なかったのでこれした。
      .ObserveOnMainThread()
      .Do(_ => {
        this.gameObject.renderer.material.shader = defaultShader;
        Destroy(light);
      })
    )
    //TakeUntilで監視が終了するので、Repeatで繰り返す
    .Repeat()
    //値が発行されたらライトの明るさ更新
    .Subscribe(o => {o.light.intensity = Mathf.Abs(o.sin)*8f;});

    // If you use ObservableMonoBehaviour, must call base method ← サンプルにこう書いてますので、そのまま。
    base.Awake ();
  }
}


こんな感じ。
Hosted by UnityRoom.com

点滅の仕方がちょっとイビツだけどまぁいいや。
センスがないのはほっといて。
一応やろうと思ってた動きにはなったのでOKってことで。


2014年7月23日水曜日

【Unity】ScriptableObjectってなんぞ?

ScriptableObjectって何??

先日TLでScriptableObjectっていうものを聞いて気になってたので調べてみた。
ツイート引用ごめんなさい。

まー結果から言うとよくわからなかったんだけど色々できそうな予感はする。予感だけ。

とりあえず今日知ったことだけまとめとこう。

参考サイト

利用法

ざっと見た感じ、下記の利用方法でメリットがありそう
  • 特定のGameObjectに属さず、共通で参照されるべきデータを保持する場合
    • 例えば、レベルレイアウト、お店の商品、敵ごとのステータス、などなど
  • リリース時にあらかじめデータの入っているDBとして使う場合
    • これもレベルレイアウトや、商品とか。読み取り専用として扱うもの全般。
  • シーンをまたいで保持したい内容
    • ゲームスコアとか
    • ゲーム中に書き換えることもできるけど毎起動時に初期化される模様(後述)。
    • でもこの利用法ならシングルトンなオブジェクトで十分とも思える。
  • UnityPro限定だけど、AssetBundleを利用する場合
    • レベルレイアウトなどをScriptableObjectを使って.asset化しておき、アセットバンドル機能でダウンロードしたりすれば変換なしで直接利用できるっぽい。
      ってことが冒頭のalwei(@aizen76)さんの呟きですね。
  • 外部データ(Excelとか)をインポートするときに利用
    • そのままレベルデータとして利用したり。これは便利そう。
  • エディタ拡張
    • やったことないし未知
    • 拡張するなら間違いなく使うことになりそう

あと思ったこと

  • ハイスコアや所持アイテムなど、ゲーム終了・再開時のための永続化についてはやっぱりPlayerPrefsを使うのが正解っぽい…?
    (UnityでDBって何使うの?って会話でScriptableObjectの話がでてきたのでRDBの代わりとしてデータ保存目的で使えることを期待してたので残念)
    XMLとかJSONにパースしてPlayerPrefsと組み合わせればできそうだけどなんだかなぁ・・・

とりあえずゲーム内で使ってみた

よくわからんけどどんな動きするか見てみた。


新規Project作成


Editorフォルダを作って椿さんのこのスクリプトを配置
https://gist.github.com/tsubaki/5149402


Resourcesフォルダを作り、下記スクリプトを作成

ClickCount.cs
using UnityEngine;
using System.Collections;

public class ClickCount : ScriptableObject {

    public int Value;

}

Cubeを1つ配置(必要ならDirectionLightも)

下記スクリプトを作成し、Cubeにつける

Cube.cs
using UnityEngine;
using System.Collections;

public class Cube : MonoBehaviour {

    private ClickCount clickCount;

 void Start () {
        this.clickCount = Resources.Load<ClickCount>("ClickCount");
 }

    void OnGUI()
    {
        GUI.Label(new Rect(10, 10, 200, 80), this.clickCount.Value.ToString());
    }

    void OnMouseDown()
    {
        ClickCount.Instantiate(this.clickCount);
        this.clickCount.Value++;
    }
}

Resouces/ClickCount.csを右クリックし、Create ScriptableObjectを押す


ClickCount.assetができてるはず


再生ボタンを押して実行

CubeをクリックするとClickCount.assetのValueがインクリメントされる
Inspectorビューを見てると変化してくのがよくわかる。

しかもこれは再生を止めても保存されてる。
実行中に書き換えたものが.assetに書き込まれて残ってるっぽい。

この動きならハイスコアの保存とかに使えるか?
とも思ったけどWebPlayerとかだと毎回初期化されるみたいね。

↓再生ボタンで実行中に17まで進めて、WebPlayer向けにリリースしたもの。

Hosted by UnityRoom.com

毎回初期化されるのでブラウザをリロードすると17に戻る。


うーん、まだしっくりこないな。
エディタ拡張するようになるとなくてはならない存在になりそうだけど。

今日はここまで。
Related Posts Plugin for WordPress, Blogger...