かつて代官山らへんで働いてたengineerのUnityブログ

サーバサイドやってきたエンジニアがUnityとか触って遊ぶだけのブログ

【Unity】NetworkMigrationManagerを試してみる【UNET】

Unity5.3.1p3より追加されたNetworkMigrationManagerですが、
これなにが出来るのかって言うと、ホストマイグレーションができます。
ホストマイグレーションっていうのはつまり通信中にホストが落ちちゃった場合にクライアントの誰かが新しいホストになりかわって通信を繋ぎ直し、ゲームを継続するというもの。
Photonなんかだと普通に実装されている機能ですが、UNETには今までなかったのでこれでちょっとはPhotonの対抗馬となれるのかなーと思い実際に動かしてみました。

やり方

1) 空のプロジェクトを作成します。

2) 空のオブジェクトを作成し、コンポーネントにNetworkManager HUD、NetworkManager、NetworkMigrationManagerを追加してやります。

3) もう一個空のオブジェクトを作成し、NetworkIdentityを追加します。Local Player Authorityにチェックを入れます。

4) 3で作成したオブジェクトをprefab化します。元のは消していいです。

5) 2で作成したオブジェクトのNetworkManagerのPlayer Prefabに4で作成したprefabを追加します。

6) ホストマイグレーションのテストなので、エディタ以外に最低もう一個クライアントが必要となります。PCアプリとしてプロジェクトをビルドします。

7) 6でビルドしたPCアプリを実行して、エディタも実行します。

8) どっちかでLAN Hostします。もう一方でLAN Clientします。つながりましたね?

9) LAN Hostした方をSTOPします。これでホストが落ちたことになります。

10) LAN Clientした方でPick new Host→Start as Hostします。これでこいつが新しいホストになります。
f:id:chroske:20160715150804p:plain

11) STOPした方でPick new Host→Recconect as Clientします。これでさっきホストだった方がクライアントとしてつながりました。
f:id:chroske:20160715150801p:plain

まとめ

とりあえず動かすだけなら簡単に出来ましたね。
実際これを使って開発するとしたら継承してロジックなどを書き足しながら使ってやることになるのかと思います。
あとこれ現在のところUnity MultiplayerなどのMachMakerを使っている場合には使えません。
マニュアルにも書いてあるんですが一番下に書いてあって気づくのに時間かかった。上に書いとけよお!!

IP指定で直接接続してる場合のみ使えるみたいです。
つまりまだまだPhotonのホストマイグレーションほど便利には使えないみたいですね。今後に期待です。

【Unity】ScrollViewを魔改造してTwitter風ひっぱりリロード【uGUI】

スマホアプリなんかでよくある引っ張ってリロードするScrollViewをuGUIを改造して作ってみました。
twitterクライアントアプリとかに使われてるアレです。iOSとかandroidだとUIRefreshControlなんて言われてるやつですね。

ひっぱりリロードデモgif
↑こんなの

Unity側作業

1. UI→ScrollViewでスクロールビューを生成します。ScrollRectのHorizontalのチェックは外しておきましょう。

2. ScrollView/ViewPortの下に空のオブジェクトを作成します。名前は「OffsetGroup」としました。

3. ViewPort下にあるContentにVertical Layout Groupコンポーネントを設定しOffsetGroup下に移動します。

4. Content下にReloadPanelを作成します。リロードのクルクルが出てくるところです。

5. ReloadPanelにLayoutElementコンポーネントを設定し、PrefrredHeightを150にします。これがReloadPanelの高さになります。

6. ReloadPanel下にUI→ImageからLoadArrowImage(引っ張ると出てくる矢印)とLoadingImage(ロード中クルクマまわる画像)を生成し、テキトーな矢印とクルクルましたくなる画像をそれぞれに設定します。LoadingImageはactiveのチェックを外しておきます。

7. UI→PanelからPanelを生成し色をつけたらScrollView内のReloadPanelの部分がちょうど隠れるように被せます。上のgif画像デモでいうUnityマークがついた青い部分です。

以上でUnityでの作業は終わりです。こんな感じになってますでしょうか。
f:id:chroske:20160525161656p:plain

Script

ScrollViewに貼り付けるスクリプトです。
実は結構面倒なことをやっています。
ScrollView内のContentを下に引っ張った時に、Contentが動いた分と同じだけScrollViewも動かします。
しかしContentはScrollViewの子要素なので、同じだけ動かすとContentは2倍動いてしまう。
そこでOffsetGroupをContentが動いた方向とは逆方向に同じだけ動かすことで相殺し、Contentの動きにScrollViewが追従している感じになります。
多分動かして見たほうがわかると思うので、理解する前に動かすこと推奨。
ReloadPanelが隠していたPanelから出きったところでScrollViewの追従を止め、リロードを行います。
リロード完了したら追従を再開、elasticityで勝手に上に引っ張り戻る。

[SerializeField]
private GameObject content;
[SerializeField]
private GameObject offsetGroup;
[SerializeField]
private GameObject loadIcon;
[SerializeField]
private GameObject loadArrowIcon;

private bool listReloadFlag = true; //リロード可能フラグ
private bool listReloadEndFlag = false; //リロード終了確認用フラグ
private int scrollViewReloadHeight = -50; //ScrollViewがこのポジション以下まで下がったら更新開始 ReloadPanelの高さに準ずる
private int scrollViewDefaultHeight = 0; //ScrollViewのデフォルト位置
private float offsetGroupMoveRate = 0.80f; //戻るはやさに関係する
private float scrollRectDefaultElasticity; //デフォルトのelasticityを入れておく
private float pullBackscrollRectElasticity = 0.01f; //リロード開始までの引っ張って戻る速さに関係する

private ScrollRect scrollViewScrollRect;
private RectTransform scrollViewRectTransform;
private RectTransform contentRectTransform;
private RectTransform offsetGroupRectTransform;

void Start () {
	scrollViewScrollRect = GetComponent<ScrollRect> ();
	scrollViewRectTransform = GetComponent<RectTransform> ();
	contentRectTransform = content.GetComponent<RectTransform> ();
	offsetGroupRectTransform = offsetGroup.GetComponent<RectTransform> ();
	scrollRectDefaultElasticity = scrollViewScrollRect.elasticity;
}

void Update () {
	if (contentRectTransform.anchoredPosition.y < 0.0f && scrollViewRectTransform.anchoredPosition.y > scrollViewReloadHeight) {
		//ScrollViewのポジションが下がっているので戻る速度をあげてすばやく上にひっぱり戻す
		scrollViewScrollRect.elasticity = pullBackscrollRectElasticity;
		//ContentにScrollRectを追従させる
		scrollViewRectTransform.anchoredPosition = new Vector2 (0, contentRectTransform.anchoredPosition.y + scrollViewDefaultHeight);
		//offsetGroupをContentが移動した分だけ反対に移動させて子の追従を打ち消す
		offsetGroupRectTransform.anchoredPosition = new Vector2 (0, -contentRectTransform.anchoredPosition.y * offsetGroupMoveRate);
	} else if(scrollViewRectTransform.anchoredPosition.y <= scrollViewReloadHeight) {
		//reloadしてもいい状態か確認
		if (listReloadFlag) {
			listReloadEndFlag = false;
			listReloadFlag = false;

			//矢印を消してローディングを出す
			loadArrowIcon.SetActive(false);
			loadIcon.SetActive(true);

			//ScrollViewのポジションが下がらなくなるので戻る速度をデフォルトに戻す
			scrollViewScrollRect.elasticity = scrollRectDefaultElasticity;
			//ズレるので決め打ちで値を入れておく
			scrollViewRectTransform.anchoredPosition = new Vector2 (0, scrollViewReloadHeight);
			//リスト取得通信(デモなので実際の通信はしないよ)
			StartCoroutine (PullBackScrollView ());
		} else {
			//reload通信が終了していれば引き戻す処理
			if(listReloadEndFlag){
				scrollViewScrollRect.elasticity = pullBackscrollRectElasticity;
				scrollViewRectTransform.anchoredPosition = new Vector2 (0, contentRectTransform.anchoredPosition.y + scrollViewDefaultHeight);
				offsetGroupRectTransform.anchoredPosition = new Vector2 (0, -contentRectTransform.anchoredPosition.y * offsetGroupMoveRate);
			}
		}
	}
	//reloadした状態で上まで戻ったら再度reloadが行えるフラグをtrueにする
	if(!listReloadFlag){
		if(scrollViewRectTransform.anchoredPosition.y >=  scrollViewDefaultHeight-1){
			listReloadFlag = true;
		}
	}
	//loadIconを監視してactiveなら回す
	if(loadIcon.activeSelf){
		loadIcon.transform.eulerAngles += new Vector3 (0f, 0f, -8f);
	}
}

IEnumerator PullBackScrollView(){
	//とりあえず2秒クルクルさせる
	yield return new WaitForSeconds(2);

	/* ここらへんで通信してデータ取って来てリストに追加する */

	//ローディングから矢印に切り替え
	loadArrowIcon.SetActive(true);
	loadIcon.SetActive(false);
	listReloadEndFlag = true;

	yield break;
}

以上をスクリプトをScrollViewに貼り付けて各子オブジェクトとのひも付けを設定してやってください。

まとめ

今回はなるべくコードでアニメーションを制御せず、uGUIの動き利用しようということでこんな方法になりました。
もしかしたらScrollViewのmovement typeをUnrestrintedにして全部自前で制御した方が今後のメンテナンスを考えるといいのかもしれませんが、パパっと作るならこんなもんでもいいのかなーと
パパっとできてる感もあんまりないけど...
気が向いたらAssetStoreにでもサンプルを出してみようと思います。
需要あんのかなー

【Unity2D】角度と距離からベクトルを作ってAddForceする【Vector2】

AddForceは引数にベクトルを渡し、そのベクトルにしたがってObjectに力を加えるメソッドですが
ベクトルってなんぞ...って人ももしかしたらいるのかなーと思うので角度と距離からベクトル(Vector2)を作ってAddForceする方法のご紹介です

float angle = 70; //角度70度 基準は↑
float distance = 1500 //距離1500

float rad = angle * Mathf.Deg2Rad; //角度をラジアン角に変換

//rad(ラジアン角)から発射用ベクトルを作成
double addforceX = Math.Sin((rad) * distance;
double addforceY = Math.Cos((rad) * distance;
Vector2 shotVector = new Vector2((float)addforceX, (float)addforceY);

//Rigidbody2Dを取得してから
Rigidbody2D rigidbody2d = transform.GetComponent<Rigidbody2D> ();
//発射ァ!!
rigidbody2d.AddForce(shotVector);


これにRigidbody2Dまわりの摩擦係数やら空気抵抗やらを調整してやると
モンスト風gif
なんとか〜ストライクみたいのができました
アァ!バックベアード様!

【Unity】ComponentをdisabledにしてもCollider関連のイベントには反応するらしい【OnTrigger】

インスペクターからdisabledにしているはずのComponentでエラーが出るのでおっかしいな〜と思ったら
どうもdisabledにして動かないのはStartやUpdateメソッドのみで
Collider関連のイベントであるOnTriggerEnterOnCollisionEnterなどの呼び出しには応じてしまうらしい

なのでスクリプト内で分岐させるなり、
Destroy(GetComponent<スクリプト名>())
コンポーネント自体を取り外さなければならないという
一番スマートなのはそのComponent専用のオブジェクトを作って非アクティブにしてしまう方法かもしれない

この仕様いらない気がするんですけど.....どうなんだろう(現在Unity5.3.3)

【Unity】Unity4環境 & PBXProjectでiOSビルド時に自動でXcodeの設定をする【iOS】

chroske.hatenablog.com
前回の記事でUnity4じゃPBXProject使えないみたいなこと言ってしまいましたが


qiita.com

こちらの記事によると、どうやらUnity4でも出来るらしい...
Unity本家がPBXProjectだけ切り出して公開してくれてるみたいですね

なので前の記事でやったことをそのままUnity4環境&PBXProject使用でやってみたいと思います

やること

Unity4環境でありながらUnity5の機能であるPBXProjectを使ってiOSビルド時に自動で自前のフレームワークを追加したりファイルごとのARCの設定を行ったりする

準備

https://bitbucket.org/Unity-Technologies/xcodeapi/downloadsからリポジトリをDLしてきてEditorフォルダに配置する
・DLしてきたフォルダ内にあるコードの「Utils.」を「PBX.Utils.」に置き換える(20個ぐらいある)


PBXProjectクラスを使ってファイル取り込みとパラメータ設定をする

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;


public class AddMyFrameworkPostProcessBuild : MonoBehaviour {
	/* [PostProcessBuild]をつけるとビルド時に勝手に実行される */
	[PostProcessBuild]
	public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
	{
		if (buildTarget != BuildTarget.iPhone) return;
		
		string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";
		
		PBXProject proj = new PBXProject();
		proj.ReadFromFile(projPath);
		
		string target = proj.TargetGuidByName("Unity-iPhone");


		/* Xcodeに読み込ませるフォルダのpathからファイル一覧を再帰的に取得してaddする */
		string[] path_array = Directory.GetFiles( path + "/Libraries/TestFramework" , "*.*", SearchOption.AllDirectories);
		foreach (string foler_path in path_array) {

			string after_path = foler_path.Replace(path+"/", "");

			string guid = proj.AddFile (after_path, after_path, PBXSourceTree.Source);

			proj.AddFileToBuild(target, guid);
		}

		/* 読み込ませたファイルに-fobjc-arc(ARC有効フラグ)をセットする */
		List<string> flags = new List<string>() {"-fobjc-arc"};

		/* フラグセットするファイル一覧 */
		List<string> setArcFiles = new List<string>(){
			"TestFramework/TestFrameworkViewController.m",
			"TestFramework/TestFrameworkController.m",
			"TestFramework/TestFrameworkVerificationViewController.m",
			"TestFramework/TestFrameworkAgreementManager.m"
		};

		/* SetCompileFlagsForFileを使ってフラグセット */
		foreach (string fileName in setArcFiles) {
			string fileGuid = proj.FindFileGuidByProjectPath("Libraries/"+fileName);
			if (fileGuid == null) {
				Debug.LogError("Cannot find " + "Libraries/"+fileName);
			}
			proj.SetCompileFlagsForFile(target, fileGuid, flags);
		}
		
		proj.WriteToFile(projPath);
	}
}


フォルダをXcodeのGroupとして取り込むAPIが用意されていなかったので、Directory.GetFilesでフォルダ内ファイルをすべて取得してループで一個ずつaddしております
mod-pbxprojにはあったのに...超めんどくさい...
これをしないと追加したファイルをXcodeがCompile Sourcesとして認識してくれないんですよね

原因見つけて解決するのに結構時間かかってしまった

【Unity】PostprocessBuildPlayer + mod_pbxprojでiOSビルド時に自動でframeworkを追加したりする【iOS】

UnityからiOSにビルド後にframeworkの追加が必要になったりした場合
Unity5からはPBXProjectというAPIを使えば済む話なのですが、Unity4以前だとまだ実装されていないため、PostProcessBuildmod_pbxprojなどのどっかのすごい人達が作った外部のツールを利用必要があります。

今回は残念なことにUnity4環境でframeworkの追加やファイル個別のARC設定の自動化が必要になったため、やったことのメモです。

前提

・Unity4以前の環境
・ビルド後のiOSプロジェクトにframeworkを組み込んで、ファイルごとにARC設定を行う必要がある
・Jenkinsをビルドに使っているなどの理由でframework組み込みと設定も自動化したい
Pythonアレルギーがない(mod-pbxprojはPythonで書かれてるので...)

準備

githubリポジトリからmod-pbxprojを取ってきて、UnityのEditorフォルダ以下に置く
・UnityのEditorフォルダ以下に「PostprocessBuildPlayer」というファイルを拡張子なしで作成する

PostprocessBuildPlayerを書く

UnityのEditorフォルダ以下にPostprocessBuildPlayerというファイルを置いておくだけでビルド後に勝手に実行されます
こいつは基本的にどんな言語でも書けるらしいのですが、今回はmod-pbxprojがPythonで書かれてるのでこいつもPythonです。マジカヨ...

#!/usr/bin/env python

import os, sys
from mod_pbxproj import *

proj = XcodeProject.Load(proj_path + '/Unity-iPhone.xcodeproj/project.pbxproj')
PROJ_ROOT = proj.source_root

#ファイルが存在するかチェック
test_framework_is_readable = os.access(proj_path + '/Libraries/TestFramework', os.R_OK)
if compliance_sdk_is_readable:

    #TestFrameworkをグループとして取り込み
    proj.add_folder(PROJ_ROOT + '/Libraries/TestFramework', parent = lib_group)

    #取り込んだframeworkのファイルに「-fobjc-arc」を付与してARC有効にする
    fileNameList = []
    fileNameList.append('TestFrameworkViewController.m')
    fileNameList.append('TestFrameworkController.m')
    fileNameList.append('TestFrameworkVerificationViewController.m')
    fileNameList.append('TestFrameworkAgreementManager.m')


    for fileName in fileNameList:
        fileId = proj.get_files_by_name(fileName)
        files = proj.get_build_files(fileId[0].id)
        for f in files:
            f.add_compiler_flag('-fobjc-arc')


あとはビルドを実行するだけでこいつが勝手に動いてくれるはず
mod_pbxprojの詳しい使い方はgithubを読むとなんとなくわかるはず

【Unity】僕の考えた最強のエディタ拡張【AdventCalendar】

CYBIRDエンジニア Advent Calendar 2015 8日目書きます。 @stks こと くそすけ です。
昨日はもう残り少ない同期である@asukyの「(このクラウド時代に)自作サーバで重複排除やストレージ階層化を試してみる話」でした。
彼は社内システム管理系の部署に移ってからは同フロアの女性社員さん達と仲良くやってるらしいです。爆発すればいいのにね。

自分もなんだかんだもう4年もここにいるらしいです。やばいですね。色々と。
去年までの3年間はずっとサーバサイドエンジニアとしてPHPばっかり書いていたのですが、
今は色々あって”Unityエンジニア”という肩書で”Unityをほとんど触らず”に”シェルとPHP”を書いています。
この肩書にとらわれない働き方が出来るFreedomさが弊社のいいところだと思います。Unity触りたい。
f:id:chroske:20151207210828p:plain:w300
なんて言っておきながら少しはUnityにも触っております。
主にエディタ拡張を使っての自動化や運用効率化がメインですが
というわけでUnityの中でもちょっとマニアックなエディタ拡張の秘められた真価を解放すべく、
僕が考えた最強にROCKでMANIACでCOOLなエディタ拡張を作ってみたいと思います。
よっしやるぞお~~~~



そもそもエディタ拡張とは?

簡単に言うとUnity自体を改造して日々の開発を効率化して楽しようってものである
詳しくはここを見るとよろしいかと

エディタ拡張を使ってゲーム開発をしながらアニメを見よう!

ゲーム開発は己との戦いである。長期的な開発は否応なしに開発者の精神をすり減らし廃人にする。
またゲーム開発でボロボロになったメンタルを自らケアすることもエンジニアの使命なのだ。
そのケアの手法として今最も注目を集めているのが昨今の日本文化を代表するアニメーションである。
アニメであれば開発の手を止めることなく受動的かつ継続的に開発者のメンタルを癒やすことが可能だ。
これがいわゆる継続的インテグレーションというやつである。


f:id:chroske:20151206231019g:plain:w400
個人的におすすめしたいのが未確認で進行形というアニメだ ましろたん舐めたい

さっそくこの永久機関を現実のものとすべくUnityエディタ上での動画再生APIを探してみたのだがどうもそんなAPIは存在しないらしい。万事休す。
だがまだ諦めるのは早い、僕はどこかでUnityエディタから動画を見た記憶があったのだ。
それはAssetStoreから見れるyoutubeのAssetのサンプル動画だった。
f:id:chroske:20151207103922p:plain:w350
↑こういうやつ

あのAssetStoreはおそらくWebViewのようなものを呼び出して表示しているだけだろう。
つまり動画再生APIはなくともWebを表示させる機能を使えばWebView経由で動画が再生出来るということだ。

public class UnityDougaPlayer : EditorWindow {

	static BindingFlags Flags = BindingFlags.Public | BindingFlags.Static;
	
	[MenuItem("Window/UnityDougaPlayer")]
	static void Open ()
	{
		var type = Types.GetType ("UnityEditor.Web.WebViewEditorWindow", "UnityEditor.dll");
		var methodInfo = type.GetMethod ("Create", Flags);
		methodInfo = methodInfo.MakeGenericMethod (typeof(UnityDougaPlayer));
		methodInfo.Invoke (null, new object[]{
			"UnityDougaPlayer",
			"/Users/kusosuke/unity_projects/hogehoge/Assets/Editor/DougaPlayer.html", //なぜか相対パスが通らない?
			150, 180, 400, 600
		});
	}
}

WebViewを呼び出すだけなのでコードはとてもシンプルです。
実はWebViewを呼び出すAPIもUnityからは公開されていないのだが、AssetStoreで使っている限りどこかにあるはずである。
そこで今回はリフレクションという方法を使って本来触ることができない(Publicクラスではない)未公開のAPIを呼び出しています。

<video controls="true" width="320">
	<source src="tomadoi_recipe.webm">
</video>

WebViewで呼び出すHTMLファイルはこんなものでいいだろう。
HTML5のvideo要素を呼び出してやればいいのだ。
ファイル拡張子によっては再生できないものも結構あるので気をつけていただきたい。

f:id:chroske:20151207000229g:plain
跳ねまわる天使達をご覧いただけただろうか? 夜ノ森さん家の子になりたいよぉ

無事ローカルに保存しておいた未確認で進行形OP「とまどい→レシピ」を再生することが出来ている。
このgif画像からも動画工房と藤原佳幸監督が神であることがおわかりいただけることだろう。
嗚呼、心が洗われてゆく...

エディタ拡張で見たアニメの感想をエディタ拡張からtwitterに投稿しよう!

ゲーム開発の最中、最高のアニメを見たあとにすることといったらなんだろうか。
日々高度な技術を学び続ける意識の高いエンジニア諸兄であれば既におわかりいただけているだろう。
それは心の底より湧き上がる熱いパトスを140字以内にしたためツィートすることに他ならない。

f:id:chroske:20151207203320g:plain:w350
そうだ、次はUnityでツイッターだ!

private IEnumerator Reload (){
	string urlstr = "https://api.twitter.com/1.1/statuses/home_timeline.json";
	Dictionary<string, string> parameters = new Dictionary<string, string>();

	parameters.Add("oauth_version", oauth_version);
	parameters.Add("oauth_nonce", oauth_nonce);
	parameters.Add("oauth_timestamp", GenerateTimeStamp());
	parameters.Add("oauth_signature_method", oauth_signature_method);
	parameters.Add("oauth_consumer_key", oauth_consumer_key);
	parameters.Add("oauth_consumer_secret", oauth_consumer_secret);
	parameters.Add("oauth_token", oauth_token);
	parameters.Add("oauth_token_secret", oauth_token_secret);
	parameters.Add("count", count);

	string oauth_signature = GenerateSignature("GET", urlstr, parameters);

	parameters.Add("oauth_signature", oauth_signature);

	var sortedParameters = from p in parameters
		where OAuthParametersToIncludeInHeader.Contains(p.Key)
			orderby p.Key, UrlEncode(p.Value)
			select p;

	StringBuilder authHeaderBuilder = new StringBuilder("OAuth ");

	int i = 0;
	string kanma = "";
	foreach (var item in sortedParameters)
	{
		if(i != 0){
			kanma = ",";
		}
		authHeaderBuilder.AppendFormat(kanma+"{0}=\"{1}\"", UrlEncode(item.Key), UrlEncode(item.Value));
		i++;
	}
		
	authHeaderBuilder.AppendFormat(",oauth_signature=\"{0}\"", UrlEncode(parameters["oauth_signature"]));

	WWWForm form = new WWWForm();
	Dictionary<string, string> headers = form.headers;
	headers ["Authorization"] = authHeaderBuilder.ToString ();

	string url = urlstr + "?" + "count=" + count;
	if(since_id != "0"){
		parameters.Add("since_id", since_id);
		url += "&since_id=" + since_id;
	}

	WWW www = new WWW(url, null, headers);
	yield return www;
	if (www.error == null) {
		IList TimeLineList = (IList)Json.Deserialize(www.text);
		List<Dictionary<string,string>> HomeTimeLineListBlock = new List<Dictionary<string,string>>();

		int j = 0;
		foreach(IDictionary person in TimeLineList){
			Dictionary<string,string> TimeLineListDatas = new Dictionary<string,string>();
			if(j == 0){
				since_id = person["id"].ToString();
			}

			IDictionary datas = (IDictionary)person["user"];
			if(datas["screen_name"] != null){
				TimeLineListDatas.Add ("user_id",datas["screen_name"].ToString());
			}

			TimeLineListDatas.Add ("tweet_text",person["text"].ToString().Replace("\n"," "));
			HomeTimeLineListBlock.Add (TimeLineListDatas);
			j++;
		}
		HomeTimeLineListBlock.AddRange(HomeTimeLineList);
		HomeTimeLineList = HomeTimeLineListBlock;
	}
}

private static readonly string[] SecretParameters = new[]{
	"oauth_consumer_secret",
	"oauth_token_secret",
	"oauth_signature"
};

private static readonly string[] OAuthParametersToIncludeInHeader = new[]{
	"oauth_version",
	"oauth_nonce",
	"oauth_timestamp",
	"oauth_signature_method",
	"oauth_consumer_key",
	"oauth_token",
	"oauth_verifier",
	"screen_name",
	"count"
};

private static string GenerateSignature(string httpMethod, string url, Dictionary<string, string> parameters){
	var nonSecretParameters = (from p in parameters
		                       where !SecretParameters.Contains(p.Key)
		                       select p);
		
	string signatureBaseString = string.Format(CultureInfo.InvariantCulture,
		                                       "{0}&{1}&{2}",
		                                       httpMethod,
		                                       UrlEncode(NormalizeUrl(new Uri(url))),
		                                      UrlEncode(nonSecretParameters));

	string key = string.Format(CultureInfo.InvariantCulture,
		            "{0}&{1}",
		             UrlEncode(parameters["oauth_consumer_secret"]),
		             parameters.ContainsKey("oauth_token_secret") ? UrlEncode(parameters["oauth_token_secret"]) : string.Empty);
		
	HMACSHA1 hmacsha1 = new HMACSHA1(Encoding.ASCII.GetBytes(key));
	byte[] signatureBytes = hmacsha1.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString));
	return Convert.ToBase64String(signatureBytes);
}

これがタイムライン取得部分。
長ったらしく書いてはいますが、やっていることは送信パラメータから署名(oauth_signature)を作ってWWWメソッドでGET送信。
いわゆるtwitter REST APIのお約束をやっているだけ。
ただUnityはエディタ上でStartContinueでのコルーチンが使えないため、こちらで書かれているクラスを丸々流用させてもらっています(ありがたや〜
ツイート処理の部分は今回省略しますが、リクエストがGETからPOSTになったことと送信パラメータが少し違う以外は基本的に上と同じ処理をしています。
あとは取得したjsonをListやらDictionaryに詰めなおしてからEditorGUIに渡してあれこれしてやると
f:id:chroske:20151207205547g:plain:w350
おお、なんか普通のTwitterクライアントっぽい! やったよましろたん

普通にそれっぽく出来てしまいました。
TwiterとUnityがここまでのハーモニーを奏でるとは、もはや開発した僕の想像を完全に超えてます。実は同じ会社なんじゃないでしょうか。
というかなんだこの空間は! アニメ見れてtwitter出来てゲームも作れてあとはピザさえ注文できればUnityで人生のすべてが完結してしまうこともありえてしまうのでは....Unityとは僕らの想像を遥かに超えた超自然的な存在だったのかもしれない.........終

まとめ

すげえ要約するとUnityエディタでもちょっとがんばれば動画再生したりPOSTやGETが送れたりしますよってことでした。
標準APIで出来ないからって諦めたらそこで試合終了ですよって安西先生も言ってましたね。
ただこちらのエディタ拡張を利用して被ったいかなる被害も当方責任を負いかねますのでなにとぞ...
さすがに弊社も仕事中にアニメ見ながらtwitterしてたら怒られると思う(怒られてすむのか?)
あと弊社ではUnity触れるエンジニアが僕含めて3〜4人くらいしかいません。絶滅危惧種です。
きっと絶賛募集中だと思うので入ってやってください。なにとぞなにとぞ。

CYBIRDエンジニア Advent Calendar 2015 明日は、 @gucchonさん の「スマホ向けWEBフロントエンド開発でお世話になったChromeデベロッパーツールの機能たち」だそうです。
@gucchonさんとは仕事で全く絡んだことがないのですが、一度記事を書けばはてぶトップに躍り出ること間違いなしのスゥパァイケメンエンジニアらしいです。楽しみですね。


f:id:chroske:20151207143647g:plain:w300
未確認で進行形 Blu-ray BOX絶賛予約受け付け中です!
君もこの冬をましろたんと過ごそう!

補足

twitterAPIで一番めんどくさいのは認証部分なのですが、今回はアクセストークンなどをtwitterのdevelopersサイトから手動生成してプログラム内にベタで書いております。
いずれ認証部分もちゃんと書いてtwitter連動ライブラリみたいな感じでAssetStoreなりどこかに公開したいですな。

twitterクライアント作ってる途中で「WebView表示できるならTwitter公式表示すれば終わりじゃね?」とか思ったけど深く考えないことにしました。
でも逆に考えるとブラウザで出来ることは何でもいけそうってことです。夢が広がりんぐですね!