代官山らへんで働く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のホストマイグレーションほど便利には使えないみたいですね。今後に期待です。

【JavaScript】はてなブログに貼ったgif画像に再生ボタンをつける【Gifffer】

ゲーム系のブログなんかをやっていると、動きを見せるためにgif画像を使いたい時があると思います。
しかしブログみたいな複数ページが連なっているようなところでgifを貼りまくるとそれぞれが勝手に動くのでなかなかにCPUを食いまくります。
そこで紹介したいのがGiffferというgif画像に再生ボタンをつけるJavaScriptライブラリです。
はてなブログ上でも使えそうだなと思い、試してみたらいけたのでそのやり方を書いていきます。

大天使チタンダエルgif
↑こんな感じになるよ!チタンダエル!

やり方

設定→デザイン→カスタマイズ→フッタを開き、下記のhtml及びjavaScriptを貼り付けます。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="http://krasimir.github.io/gifffer/lib/gifffer.js"></script>
<script type="text/javascript">
     window.onload = function() {
        Gifffer();
     }
</script>

あとはgif画像を貼り付ける時に

<img data-gifffer="http://チタンダエル.gif">

こんな感じに指定してやると再生ボタンが勝手につけられます。超簡単!

捕捉

Gifffer.jsはHTML5Canvasを利用してサムネイルを作ってくれているみたいですね。
読み込みに若干時間はかかるけどなんとなくページスクロールが軽くなったような気がします。
あと上のスクリプトに書いてあるURLはgifffer.js開発元から勝手に引っ張ってきたものなので、あっちで変更があると動かなくなる可能性があります。
下の開発元サイトからダウンロードしてきて自前サーバなりDropBoxなりに置いて、そこから引っ張ってくることをオススメします。
それとはてなブログスマートフォンサイト版だとヘッダとフッタをいじれるのはPro版かららしい、さすがはてな汚い課金しよ...



Gifffer
http://krasimir.github.io/gifffer/

【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にでもサンプルを出してみようと思います。
需要あんのかなー

【Google Play】Developer Consoleでサポート対象端末をまとめて設定するスクリプト【JavaScript】

Google Playにアプリを申請時に非対象端末を設定しておいて、その端末からはアプリを検索&表示させないみたいなことが出来るようなのですが、
Android端末って現在のところ1万機種以上あるらしく、手作業でやるとか死ねるんでなんとかならんかと依頼されて書いたフニャフニャスクリプトがなんだかすごい感謝されてしまい
"是非全世界の人に広めるべき"とのお言葉をいただいたので"全世界に広めるべく"ここに載っけておきます(広がるとは言ってない)

↓配列のデバイスを非対応にしてそれ以外を対応にするスクリプト

//この配列に非対象端末名を入れる
var disableDeviceListStr = ["picasso_m","DA220HQL","HMB4213H","octopus-masu","hoge","hoge2"];

var jq = document.createElement('script');
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);

$('[data-device-id]').each(function(){
    var deviceId = $(this).attr("data-device-id");
    if(jQuery.inArray(deviceId, disableDeviceListStr) != -1){
		$(this).children("checkbox").click();
		console.log("disabled="+deviceId);
	}
});

手順

1. Google Play Developer Consoleからこの画面にいく
f:id:chroske:20160516170633p:plain:w500

2. 上のスクリプトの配列disableDeviceListStrに非対象にしたい端末を羅列する。
端末名は一覧の灰色の文字の方。

3. ブラウザのデベロッパツールのConsoleを開いて「>」マークのとこにコピペしてエンター
f:id:chroske:20160516171549p:plain
Chromeだとここ

4. 完了するのを待ってお終い。端末数によっては結構時間かかる模様。

捕捉

わざわざjQueryを取ってきてるのはどうしてもinArrayが使いたかったからです。二重ループとか書きとうない。
今後GooglePlayがhtmlの構成を変えたりすると動かなくなる可能性があります。というかさすがに本家が機能追加するのでは。

↓配列のデバイスを対応にしてそれ以外を非対応にするスクリプト(上とは逆バージョン)

var enableDeviceListStr = ["picasso_m","DA220HQL","HMB4213H","octopus-masu","hoge","hoge2"];

var jq = document.createElement('script');
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);

$('[data-device-id]').each(function(){
    var deviceId = $(this).attr("data-device-id");
    if(jQuery.inArray(deviceId, enableDeviceListStr) == -1){
		$(this).children("checkbox").click();
	} else {
		console.log("enabled="+deviceId);
	}
});


誰かが救われますように。

【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として認識してくれないんですよね

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