LotosLabo

プログラミング技術とか気になった情報を載せていきます

【ツール紹介】EMLauncherの導入

EMLauncherとは


KLabさんより開発されたテストアプリ配信ツールです。
スマートフォンのアプリファイル(Androidはapk,iOSipa)をアップロードすることで、
手軽に端末にインストールすることが可能になります。

www.klab.com


導入

EMLauncherは個人でも使えるそうなので、導入してみました。


<準備物>
・メールアドレス
AWSアカウント
Bashとかsshで接続できるシェル

EMLauncherのAMIサンプルからAWSインスタンス作成

AWSにログインしている状態で、以下のアドレスにアクセスします。

https://console.aws.amazon.com/ec2/v2/home?region=ap-northeast-1#Images:visibility=public-images;search=ami-9b295f9a


f:id:lo25131:20151108163333j:plain

表示されている項目を右クリックから[作成]を行います。


f:id:lo25131:20151108163449j:plain


右下の次の手順へ移ります。


f:id:lo25131:20151108163641j:plain

削除保護の有効化だけチェックを入れて、
次の手順に移ります。

ステップ4は特に変更することなく、次の手順へ移ります。


f:id:lo25131:20151108163850j:plain

ステップ5ではインスタンスのタグ付けとして、
名前を入力しておきます。
今回は「EMLauncher」とかでいいと思います。

入力したら次の手順に移ります。


f:id:lo25131:20151108164142j:plain



セキュリティの設定を行います。
ルールの追加より、
「HTTP」と「HTTPS」を追加しておけば
大丈夫だと思います。

次に確認と作成を押します。


f:id:lo25131:20151108164514j:plain


ダイアログが表示されると思いますので、
一番下の「このインスタンのブートボリューム~」を選択して、
次へを押します。


f:id:lo25131:20151108165311j:plain


確認画面が表示されるので、作成を押すと、
秘密鍵を作成しろと言われるので、「新しいキーペアの作成」を選択し、
キーペア名を入力して、ダウンロードしておきます。

最後にインスタンスの作成を押して完了となります。



その後インスタンスの一覧画面へ遷移するので、先ほど作成したEMLauncherのインスタンスの状態が
「running」となれば作成終了です。

f:id:lo25131:20151108164835j:plain


EMLauncherで使用するためのストレージを作成


AWSウェブサービスより、S3を選択します。

f:id:lo25131:20151108170638j:plain


右上のバケットを作成より、新規でバケットを作成します。
バケット名は何でも構いません。

f:id:lo25131:20151108170906j:plain


入力したら作成を押して完了となります。


EMLauncherのインスタンスのIPを固定にする

作成したインスタンスではIPアドレスでは、インスタンスを再起動した時に
IPが変化してしまうので、固定として持ち続けるように設定します。


先ほどのインスタンス画面に戻り、左のメニューより「Elastic IP」を選択します。

新しいアドレスの割当を選択し、アドレスの関連付けを行います。

f:id:lo25131:20151108171337j:plain


先ほど作成したインスタンス名を入力して関連付けます。

AWSのIAMの設定

ユーザ、グループ、アクセス権を設定します。
この項目を設定し忘れるとEMLauncherは正しく動作しないので忘れないで下さい。


AWSのサービスタブより、IAMを選択します。

f:id:lo25131:20151108172138j:plain


IAMの5つのセキュリティステータスを完了すれば設定完了となります。

f:id:lo25131:20151108172151j:plain


ルートアカウントのMFAでは、表示されたQRコードを読み取ります。
QRコードスマートフォンアプリの「Authy」というアプリを使って、
読み取り、表示された認証コードを入力します。

グループの割当では、一番上のポリシーのみ選択すれば大丈夫だと思います。

個々のIAMユーザの作成では、作成したユーザのアクセスキーとシークレットキーを
今後使う必要があるので、これをメモっておきます。


sshの接続


インスタンスの作成時に取得した秘密鍵を使用してsshでサーバにアクセスし、
一部設定を変更します。
Bashからアクセスするのですが、私の場合はGit Bashでアクセスします。


まず秘密鍵に対して、パーミッションを変更しておきます。
chmod 600 名前.pem

続いて、sshでアクセスします。
ssh -i 鍵名.pem ec2-user@インスタンスのパブリックIP

設定ファイルを開きます。
vim /home/ohoflight/emlauncher/config/emlauncher_config.php


ここでは、以下の設定の変更を行います。
Google認証設定
AWSのユーザキー指定
・ストレージ名の指定


Google認証設定

ログイン時にGoogleの認証が必要となっているため、これは特に使わないので、
使用しないように設定を変更します。

変更前
'enable_google_auth' => true
変更後
'enable_google_auth' => false


AWSのユーザキー指定

IAMの個人ユーザを作成した時のキーを入力します。

変更前
'key' => 'xxxxxxxxxxxxxxxxxxxx'
'secret' => 'xxxxxxxxxxxxxxxxxxxx'
変更後
'key' => 'キー'
'secret' => 'パスワード'


・ストレージ名の指定

S3で作成した名前を入力します。

変更前
'bucket_name' => 'emlauncher'
変更後
'bucket_name' => '作成した名前'


接続の確認


インスタンスのパブリックIPを入力し、EMLauncherにアクセスできるか確認します。

http://パプリックIP


f:id:lo25131:20151108175627j:plain


ログイン画面が表示されれば大丈夫です。

メールアドレスの登録


ログインするためにメールアドレスの登録を行います。
EMLauncherでは登録する画面がないので、テーブルに直接MySQLで叩いて、
登録します。

sshで接続した状態で、テーブルを指定してメールアドレスのみ追加します。
mysql -uroot emlauncher -e "INSERT INTO user_pass (mail) VALUES ('登録したいメールアドレス');"


メールアドレスのみテーブルに登録して、ログイン画面「forget password」より、
登録したメールアドレス先にパスワードの変更画面のアクセスURLを送り、
パスワードを登録します。

なお、確認したところ一部のメールアドレスにはメールフィルターか何かにより、
迷惑メールにもいかず、ブロックされてしまいますのでGoogleMailが良いと思われます。


パスワードを登録後、ログイン画面より、メールアドレスとパスワードを入力して、
ログインできれば完了です。

登録したアプリケーションを他のユーザと共有する


登録したアプリケーションを他のユーザと登録するには、
共有したいユーザのメールアドレスをテーブルに追加した後、そのアドレスを
「Preferences」という項目からOwnerにメールアドレスを追加することで、
他のユーザとの共有ができます。


f:id:lo25131:20151108180459j:plain

セキュリティの強化

今の状態ではインターネットに接続している他のユーザにもこのページが見られてしまうため、
アクセス制限、セキュリティ強化をしなくてはなりません。

手っ取り早いのはIPアドレスによる制限ですが、SSLより暗号化したり、
また簡易ですがベーシック認証を導入したりすることが必要です。


まとめ

スマートフォンアプリを簡易的に、他のバージョンもインストールできるようにしたい。
という時にはとても便利なツールです。スマートフォンからEMLauncherにアクセスし、
インストールボタンを押すだけで端末にインストールができるのでいちいちPCに接続して、
しなくても済むので時間の節約にもなります。
是非試してみてください。

UnityとMySQLの連携

はじめに

外部データベースからUnityにデータを送受信する例を紹介します。
今回はデータベースであるMySQLphpMyAdminを通して、PHPから
C#(Unity)へとつなげていきます。

MySQLApacheは既に設定されているものとして進めていきます。
私は開発環境にXamppを使用しているので、Xamppを例としていきます。

今回、サーバーはローカルのものを使っていきますが、ローカルでも外部サーバー、
レンタルサーバーでもIDやパスワードを変更すれば基本同じです。

開発環境の準備

「開発環境」

Windows8
・Xampp ver 3.2.1
PHP ver 5.6.12
MySQL ver 5.6.26


Xamppの導入にはいくつか設定が必要なところがありますので、簡単にですが、
手順を説明します。


①インストール
https://www.apachefriends.org/jp/download.html


②日本語化(したい人のみ)

TeraPad等でUTF-8Nで保存するとよいです。


③Xamppホームに接続

http://localhost/security/index.php

versionが上がったことによって、securityを通さないと設定ページに飛ばないそうです。


MySQLのrootパスワードを変更、ディレクトリにhtaccessを設定

http://localhost/security/xamppsecurity.php


MySQLの日本語の文字化けを防止


phpMyAdminの環境設定を登録する



データベースの用意

取得するデータを予め用意しておきます。

データの作成にはphpMyAdminで行います。作成方法は紹介しませんので、他サイトを参考にしてください。


今回は「ユーザ情報のテーブル」と「ランキングのテーブル」の2つを作成しておきます。

仮にですが以下のように用意しておきました。


●ユーザ情報

ユーザID ユーザ名 性別 ユーザ作成時刻


<テーブル構成>

カラム 型数 Null AI その他
uid int 11 いいえ はい Primary, Unique
name varchar 255 はい いいえ
sex int 1 はい いいえ
create_time varchar 255 はい いいえ


<サンプルデータ>

uid : 入力せず
name: user1
sex : 1
create_time: 関数(UNIX_TIMESTAMP)

uid : 入力せず
name: user2
sex : 2
create_time: 関数(UNIX_TIMESTAMP)


性別の登録は以下の方法が良いとされていますが、今回は仮でもあり、
UIとして男か女のみ選択できる場合であれば、1か2でいいと思います。

作成時刻にはUNIXTIMEを入れておきます。


[性別情報の登録参考サイト]
ISO - DBテーブルに性別カラムを作るなら、男=1, 女=2が標準 - Qiita


●ランキング情報

ユーザID カテゴリ 得点 登録時刻


<テーブル構成>

カラム 型数 Null AI その他
uid int 11 いいえ いいえ
category int 2 はい いいえ
point int 11 はい いいえ
create_time varchar 255 はい いいえ


<サンプルデータ>

uid : 1
category: 1
point : 2500
create_time: 関数(UNIX_TIMESTAMP)

uid : 2
category: 2
point : 500
create_time: 関数(UNIX_TIMESTAMP)

phpからMySQLの接続

MySQLへの接続を行います。
接続を行う際に以下の情報が必要となるので準備が必要です。

MySQL接続ドメイン
MySQLユーザID
MySQLユーザPW

ドメイン名についてはローカルでおこなうので、「localhost:3306」で接続します。

接続するためのPHPファイルを作成します。


MySQLへの接続ですが、以前まではmysql_connectで接続できていたのですが、PHP 5.5.0から非推奨となり、使用できなくなりました。
mysql_connectを使うと、「それは古いからmysqliかPDOを使って」と言われてしまいます。
今回はmysqli形式で書いたものを紹介します。


mysql_connect.php

<?php

// MySQLに接続(ドメイン名、ユーザID、パスワード、データベース名).
$mysqli = @new mysqli( 'localhost:3306','ユーザID','パスワード', 'データベース名' );
if($mysqli->connect_errno) {
  die( 'Connect Error: ' . $mysqli->connect_error() );
}

// データベースのコードを選択.
if(!$mysqli->set_charset( 'utf8' )) {
  die( 'Error loading charaset: ' . $mysqli->connect_error() );
}
?>

このmysql_connect.phpをデータを取得する際に呼び出していきます。


接続テストを行うには、このファイルを
\xampp\security\htdocs\php 等の中にでも入れて、

http://localhost/security/php/mysql_connect.php

で呼び出します。
特にエラーがでなければ成功です。

データを取得する

データベースで登録されてあるデータを取得していきます。
まずPHPからデータを取得していき、Jsonの形で出力し、C#でUnityの画面にDebug.Logで出力します。


・引数なしのデータ取得

PHP側 user_get.php

<?php

// 接続処理.
require_once( dirname(__FILE__). "/mysql_connect.php" );

// 出力形式の設定
header( 'Content-type: application/json; charset=UTF-8' );

$query = "SELECT `uid`, `sex`
          FROM `user_table`";

// クエリ文の実行.
if($result = $mysqli->query( $query )) {

	$user = array();

	// 結果をオブジェク形式で出力.
	while($data = $result->fetch_object()) {
		$user[] = array(
				'uid'=> $data->uid,
				'sex'=> $data->sex,
				);
	}

	// 出力結果が空の時は、nullを出し、JSON形式で変換.
	if(empty($user)) {
		$user = null;
		echo json_encode( $user );
	}else{
		echo json_encode( $user );
	}

	// 結果を解放.
	$result->close();
}

// 接続を切断.
$mysqli->close();
?>

接続テストをしたい場合は、
http://localhost/security/php/user_get.php

にアクセスします。


<Unity側>

今回はJSONObjectを使って取得しますが、UnityではLitJsonを使っても取得できますので、
両方覚えておくと良いです。

JSONObjectを使うには、以下のライブラリをUnityのPluginsディレクトリに入れる必要があります。
http://wiki.unity3d.com/index.php?title=JSONObject#Download

const string LOCALDOMAIN = "ローカルドメイン";
string m_url = "http://" + LOCALDOMAIN + "/security/php/user_get.php";


void Start() {	
 StartCoroutine(GetUser());
}

IEnumerator GetUser() {
 WWW result = new WWW(m_url);
 yield return result;

 if(result.error == null) {
 	JSONObject rdbUserGet = new JSONObject(result.text);
 	for(int i = 0; i < rdbUserGet.Count; ++i) {
 		JSONObject jsonPos = rdbUserGet[i];
 		JSONObject jsonUid = jsonPos.GetField("uid");
 		JSONObject jsonSex = jsonPos.GetField("sex");
 		string uid = jsonUid.int;
 		string sex = jsonSex.int;
 		Debug.Log("ユーザID:" + uid + "性別:" + sex);
 	}
 }
}


[出力結果]
f:id:lo25131:20151017234940j:plain



・引数ありのデータ取得

PHP側 user_get2.php>

引数ありの場合はPHP側では、値を渡してもらうために変数を宣言しておきます。
今回はuidをもらって、そのuidのデータを返してもらうようにします。

●変更点

$uid = $_REQUEST['uid'];

$query = "SELE `name`, `sex`
          FROM `user_table`
          WHERE `uid` = '$uid'";

接続テストをしたい場合は、
http://localhost/security/php/user_get2.php?uid=

にアクセスします。

よくWebのURLみたいに 「?カラム=値」 という形で、
urlの後ろに引数として渡します。 


<Unity側>

Formを作成して、値を送信してからその結果を受取る形になります。

●変更点

WWWForm form = new WWWForm();
form.AddField("uid", uid);
WWW result = new WWW(m_url, form.data);



データを登録する

データベースのテーブルにデータをセットします。


PHP側 ranking_post.php

<?php

// 接続処理.
require_once( dirname(__FILE__). "/mysql_connect.php" );

// 出力形式の設定
header( 'Content-type: application/json; charset=UTF-8' );

$uid = $_REQUEST['uid'];
$category = $_REQUEST['category'];
$point = $_REQUEST['point'];
$createTime = time();

$query = "INSERT INTO `ranking_table`(`uid`, `category`, `point`, `create_time`)	 
          VALUES('$uid', '$category', '$point', '$createTime')";

// クエリ文の実行.
if(!$result = $mysqli->query( $query )) {
	die( 'Error data set: ' . $mysqli->connect_error() );
}

// 接続を切断.
$mysqli->close();
?>


<Unity側>

const string LOCALDOMAIN = "ローカルドメイン";
string m_url = "http://" + LOCALDOMAIN + "/security/php/ranking_post.php";

void Start() {
	StartCoroutine(PostRanking(1, 2, 2555));
}

IEnumerator PostRanking(int uid, int category, int point) {
	WWWForm form = new WWWForm();
	form.AddField("uid", uid);
	form.AddField("category", category);
	form.AddField("point", point);
	WWW result = new WWW(url, form.data);
	yield return result;
	if(result.error == null) {
		Debug.Log("登録完了!");
	}
}

まとめ

Unityでデータを保存する方法はいくつかありますが、今回はMySQLからデータを
送受信する方法を紹介しました。
内部でのデータの保存だと、PlayerPrefs(クラス)、SQLite(データベース)
を使用する方法もあります。
このブログでは紹介しませんが、気になる方はそちらも利用してみると、どん
なときにそのデータの保存方法を使用したらいいか、
使い分けが可能になると思います。

UnixTimeを日本語表記に変換する

UnityでUnixTimeを日本語表記に変換した例
現在時刻を求めるときに使います。

例として、1443627101というUnixTimeを変換

/// <summary>
/// UnixTime.
/// </summary>
private int m_unixTime = 1443627101;

void Start() {
  string unixTime = UnixTimetoDate(m_unixTime);
  Debug.Log(unixTime);
}

/// <summary>
/// UnixTimeを渡して年月日時秒に変換.
/// </summary>
/// <param name="unixTime">UnixTime.</param>
/// <returns>UnixTimeを年月日時秒に変換した文字列.</returns>
private string UnixTimetoDate(int unixTime) {
   var localDate = new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc).AddSeconds(unixTime).ToLocalTime();
   string Str_unixTime = localDate.ToString("yyyy" + "年" + "MM" + "月" + "dd" + "日" + "HH" + "時" + "mm" + "分" + "ss" + "秒");
   return Str_unixTime;
}


出力結果:

2015年10月01日00時31分41秒

localData.ToStringの中の秒や分を除けば、分までの表記というのもできます。


例: 分までの表記

localDate.ToString("yyyy" + "年" + "MM" + "月" + "dd" + "日" + "HH" + "時" + "mm" + "分");

UnityによるProcess出力の日本語文字化け対策の奮闘記録

はじめに


前回紹介した外部プロセスの記事の内容から、Subversionバージョン管理システム)のプロセスをUnityから呼び出す処理を試しみました。


しかし、その記事にも書いていたのですが、Subversionから出力を取る際に、日本語の文章が文字化けしてしまいました。
WindowsMacで両方で試してみたのですが、どうやらMacのみ文字化けしており、その問題点と解決方法をまとめてみました。


問題点


------以下、文字化けした結果-----


WindowsSubversion
f:id:lo25131:20150929231926j:plain                  

WindowsのUnity
f:id:lo25131:20150929232001j:plain


MacSubversion
f:id:lo25131:20150929232220p:plain

MacのUnity
f:id:lo25131:20150929232140p:plain

このようにMacのUnityの出力だけ、文字化けしてしまいました。
「?\227?\129?\138?.......」

Shift-JISかな?UTF8に変換してあげればいいかな。

試行錯誤中


~以下、試行錯誤の記録~


・出力のエンコード設定を変更

StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;

結果:失敗


・取得したデータを一度Unityのオブジェクトに入れる

string data = args.Data;
Debug.Log(data);

結果:失敗


・取得したデータをUTF8に変換

System.Text.Encoding enc = System.Text.Encoding.GetEncoding("Shift_JIS");
System.Text.Encoding utf8 = System.Text.Encoding.UTF8;

byte encByte = enc.GetBytes(data);
byte
encConByte = System.Text.Encoding.Convert(enc,utf8,encByte);
string encText = utf8.GetString(encConByte);
Debug.Log(encText);

結果:失敗

対策の発見へ


Twitterで聞いてみました。

> 参考となるサイトを紹介してもらいました。ありがとうございました。

MacでSubversionを利用する際に発生する文字コード問題の解消方法 - Shuichi’Tec


どうやらSubversionの方に問題があったようで、一度Macに入ってるSubversionをアンインストールして再度インストールしてみました。
私のMacではXcodeのCommadn Line ToolsのSubversionを使っていたため、Xcode自体を一回アンインストールしました。


そして新たに上記のサイトを参考にしてSubversionをインストール。
以下コマンド

sudo port install subversion + unicode_path

そのままコマンドを叩くと、Subversion1.9.2がインストールされました。
見た目ではヘルプ等の文字が日本語化されていました。


次に、UnityでSubversionを起動させてみます。
しかし、svnコマンドは使えないと言われてしまいます。

どうやらターミナルで叩いているsvnコマンドと、Unityから起動するプロセスのsvnコマンドでは起動しているパスが異なっているようです。
↑解決方法がわからないので、御存知の方いらっしゃればご教授よろしくお願いします。


なので直接先ほどMacPortからインストールしたSubversionのパスからSubversion
プロセスを起動します。
Subversionのパスは、ターミナルから取得することができます。

which svn

多分デフォルトは以下の場所にインストールされるかと思います。
> /opt/local/bin/svn

上記の場所を指定してプロセスを立ち上げます。

すると、先ほどの文字化け文字に変化がありました。


以下変化画像
f:id:lo25131:20150930000115p:plain

{U+30C6}{U+30B9}{U+30C8}.....

見覚えのあるコードが出てきました。

30C6 -> テ
30B9 -> ス
30C8 -> ト

[参考サイト]
ASCIIコード変換機


あとはこれをすべて文字に直す作業です。

文字化けコードの変換


変換する流れとしては…

①一行分のログが変換できるかどうか (できる場合は変換、できない場合はそのまま取得)
②文字化けコード全てを取得
③文字化けコードの中のASCIIコードのみ取得
④ASCIIコードを16進数に変換して文字列に変換
⑤元の文字化けコードの場所に変換した文字列を置き換える


これをプログラムにしていきます。

以下プログラム

// 出力された文字列.
string data = args.Data; 

if(!string.IsNullOrEmpty(data)) {
  string encData = EncodeBinaryToString(data);
  Debug.Log(encData);
}

/// <summary>
/// 文字化けした文字列を日本語文字列に変換.
/// </summary>
/// <param name="data">コンソール出力文字.</param>
/// <returns>文字化け文字を日本語変換された文字.</returns>
private string EncodeBinaryToString(string data) {
  string Result = null;
  
  List<string> asciiArray = new List<string>();
  List<string> codeArray = new List<string>();

  // ASCIIコードのみを取得するためのパターン.
  string getAsciiPattern = @"(\{U\+)(?<Result>.+?)(\})";

 // 文字化け文字列全てを取得するためのパターン.
 string getcodePattern = @"(\{.*?\})";

  System.Text.RegularExpressions.Match asciiMatch;
  System.Text.RegularExpressions.Match codeMatch;

  // マッチされない場合はそのまま返す.
  if(!System.Text.RegularExpressions.Regex.IsMatch(data, getcodePattern)) {
   return data;
  }

  // 文字化け文字の取得.
  codeMatch = System.Text.RegularExpressions.Regex.Match(data, getcodePattern);

 // マッチしている間.
 while(codeMatch.Success) {
     codeArray.Add(codeMatch.Value);

     // 次のマッチングへ.
     codeMatch = codeMatch.NextMatch();
  }

  // ASCIIコードのみを取得.
  asciiMatch = System.Text.RegularExpressions.Regex.Match(data, getAsciiPattern);

  while(asciiMatch.Success) {
     asciiArray.Add(asciiMatch.Groups["Result"].Value);
     asciiMatch = asciiMatch.NextMatch();
  }

  List<string> japaneseCharArray = new List<string>();

  // ASCIIコードを日本語文字列に変換する.
  for(int index = 0; index < asciiArray.Count; ++index) {
     // 16進数を基に32bit符号付き変数に変換.
     int intCode16 = Convert.ToInt32(asciiArray[index], 16);

     // char型に変換.
     char conChar = Convert.ToChar(intCode16);

     // string文字列に変換.
     string strChar = conChar.ToString();
     japaneseCharArray.Add(strChar);
  }

  // 元の文字列を日本語文字列に置き換える.
  for(int index = 0; index < japaneseCharArray.Count; ++index) {
     data = data.Replace(codeArray[index], japaneseCharArray[index]);
  }

  encResult = data;
  return encResult;
}


結果

f:id:lo25131:20150930005059p:plain


コードについて


ASCIIコードから文字列に変換するプログラムは以下のQiitaの投稿を参考にしました。


全体的に、もうちょっとスマートにならないかどうか考えてるけど、これで精一杯でした。


まとめ

結果としては日本語に置き換えることができました。


流れとしては...

Subversionのアップデート → 新しいSubversionで起動する → 文字化け文字を変換する

という感じです。


しかし一つ問題として、濁点・半濁点がなくなっています。
考えられる理由としては、Subversionのインストール時にunicode_pathのオプションが
うまく実行されていなかったため、濁点・半濁点が文字化けした時に
ユニコードとして出力されていなかった可能性があります。


例えば本来、

デスト → 30C7 30B9 30C8  のはずが

テスト → 30C6 30B9 30C8  として置き換わっています。


こちらについては原因を特定中ですが、やはりインストールに失敗しているというところが問題っぽいです。


今回の文字化けからの日本語への変換に関して、かなり強引に変換してみました。
もしかしたら設定次第でなんとかなるのかもしれませんが、こんな風にしても変換できるということを紹介させていただきました。

UnityのEditor拡張触ってみた

はじめに

                 Editor拡張とは何か…?



UnityEditorに自分自身で新たに機能を追加すること。
例えばUnity画面のメニューに項目を追加したり、インスペクターの表示を変えたり…

まさに自分の好きなように、便利にするためのものです。

はっきり言って、 楽しいです!!!


ウインドウ表示

Unity画面上部のメインメニューにアイテムを追加して、
追加したアイテムをクリックすると新しいWindowを表示する処理をやってみます。

まずEditor拡張するための準備を行います。


(1)Assets以下にEditorフォルダを作成
(2)Editorフォルダの中にEditor拡張用のプログラムを作成します
(3)例:SampleEditor.cs(C#)ファイルを作成

※EditorフォルダにEditor拡張用のプログラムを置かないと、
Build時にエラーを吐いて止まってしまいます。


以下、SampleEditor.csのコードとなります。

using UnityEngine;
using UnityEditor;
using System.Collections;

public class SampleEditor : EditorWindow {

  /// <summary>
  /// メニューにアイテムを追加し、ウインドウを開く.
  /// </summary>
  [MenuItem("SampleEditor/ShowWindow")]
  private static void ShowWindow()
  {
    // ウインドウの取得.
    var window = EditorWindow.GetWindow(typeof(SampleEditor));

    // ウインドウの表示.
    window.Show();
  }
}


出力結果
f:id:lo25131:20150928002932j:plain


【解説】

UnityEditorの機能を使うため、
using UnityEditor; を追加する。

クラス自体にEditorWindowを継承する。

[MenuItem("タブ名/アイテム名")]
でメインメニューにアイテムを追加し、そのすぐ下のメソッドに、アイテム押下時の機能を書く。
(既存のタブ名を指定するとそのタブにアイテムが追加されます。)

アイテムの機能のメソッドはstaticのみ受け付けます。アイテムが選択できない状態になります。


EditorのUIの表示

UIの表示を行っていきます。
今回は以下のUIを配置していきます。

  • ラベル
  • テキストフィールド
  • ボタン
  • スクロールビュー
  • 色指定
  • スタイルの指定


UIはOnGUIに書いていきます。
以下コードとなります。


【テキスト系UI】

/// <summary>
/// テキストフィールド用変数.
/// </summary>
private string m_textField = string.Empty;

void OnGUI()
{
 // ラベルの作成.
  EditorGUILayout.LabelField("Hello World");

  // 値付きラベルの作成.
  EditorGUILayout.LabelField("Hello World", "Guten Tag");
  
  // スタイルの作成.
  GUIStyle guiStyle = new GUIStyle();

  // テキストカラーを指定.
  guiStyle.normal.textColor = Color.red;

  // スタイル指定ラベル.
  EditorGUILayout.LabelField("Hello World", "Guten Morgen", guiStyle);
  
  // テキストフィールド.
  EditorGUILayout.TextField("Hello World", m_textField);
}


<出力結果>
f:id:lo25131:20150928003021j:plain



【ボタン系UI】

/// <summary>
/// ボタン名1.
/// </summary>
private const string BTN_NAME1 = "button1";

/// <summary>
/// ボタンサイズ1.
/// </summary>
private Vector2 m_btnSize1 = new Vector2(100,20);

void OnGUI()
{
  // ボタン(ボタン名、ボタンサイズ横、ボタンサイズ縦).
  if (GUILayout.Button(BTN_NAME1, GUILayout.Width(m_BtnSize1.x), GUILayout.Height(m_BtnSize1.y)))
  {
  // 以下処理.
    Debug.Log("Hello World");
  }
}

ボタンの作成は少々面倒ですが、if文として作成します。

<出力結果>
f:id:lo25131:20150928003040j:plain



【スクロールビュー】

/// <summary>
/// スクロールビューポジション.
/// </summary>
private Vector2 scrollView_Position;

/// <summary>
/// スクロールビューサイズ.
/// </summary>
private Vector2 scrollView_Size = new Vector2(500, 100);

/// <summary>
/// スクロールビュー内ラベル.
/// </summary>
private string scrollView_Label = string.Empty;

void OnGUI()
{
  // スクロールビューの作成.
  EditorGUILayout.BeginScrollView(scrollView_Position, GUI.skin.box, GUILayout.Width(scrollView_Size.x), GUILayout.Height(scrollView_Size.y));
  
  // スクロールビュー内のラベル.
  GUILayout.Label(scrollView_Label);

  // スクロールビューの終了.
  EditorGUILayout.EndScrollView();
}

void Test()
{
  // スクロールビュー内のラベルに追加.
  scrollView_Label += "\n" + "追加";

  // スクロールバーを最下部まで送る.
  scrollView_Position.y = Mathf.Infinity;
}

スクロールバーを送るには、scrollView_Positionのx、yを調整します。
今回は一番下まで自動で送りたいのでMathf.Infinityという無限を指定しています。


<出力結果>
f:id:lo25131:20150928003058j:plain



【スタイル系】

/// <summary>
/// ボタンアクティブフラグ.
/// </summary>
private bool is_btn_active = false;

/// <summary>
/// タブボタンアクティブフラグ.
/// </summary>
private bool is_tabBtn_active = false;

void OnGUI()
{
   // 横並びにする.
   EditorGUILayout.BeginHorizontal();

      // ラベルの配置.
      EditorGUILayout.LabelField("Hello World1");
      EditorGUILayout.LabelField("Hello World2");
      EditorGUILayout.LabelField("Hello World3");

   // スタイルの終了.
   EditorGUILayout.EndHorizontal();

   // スペースを空ける.
   EditorGUILayout.Space();

   // ボタンの非アクティブ(灰色になり押下不可).
   EditorGUI.BeginDisabledGroup(is_btn_active);

      // ボタン配置.
      if (GUILayout.Button("ボタン1", GUILayout.Width(300), GUILayout.Height(50)))
      {
          Debug.Log("Hello World");
      }

   // グループの終了.
   EditorGUI.EndDisabledGroup();
}

// タブボタンの表示・非表示.
[MenuItem("SampleEditor/Show1", is_tabBtn_active)]
public static void Show1()
{
}

private static void Test()
{
 // タブボタンの表示.
 is_tabBtn_active = true;
}
  

メインメニューのアイテムに追加したタブボタンも表示・非表示の設定が行えます。

<出力結果>
f:id:lo25131:20150928003115j:plain


ボタン押下時にファイル選択ボタンを開く


ボタン押下時にファイルブラウザを開き、そこで選択したファイルのパスをラベルに表示させます。

/// <summary>
/// ファイルパス.
/// </summary>
private string m_filePath = string.Empty;

void OnGUI()
{
   // ファイルブラウザを開く(第1引数:ブラウザ名、第二引数:最初に開くパス、第三引数:選択可能なファイル).
   string filePath = EditorUtility.OpenFilePanel
                         (
                            "Select File",
                            Application.dataPath,
                            "txt"
                         );

  // ファイルが選択されていない時.
 if(filePath.Length <= 0)
  {
    return;
  }

  m_filePath = filePath;
 
 EditorGUILayout.LabelField("Hello World", m_filePath);
}

まとめ

Editor拡張はやればやるほど楽しくなり、それに便利になります。
しかし、Unity内でできることは限られているので、無理にUnity内で済ませようとしないことも大切です。

今回はEditorUtilityクラスを主に用いてEditor拡張を紹介しましたが、インスペクターのUIを変える、
Attributeというものもあります。

例えば普段使っている、[SerializedField]もAttributeです。

それでは、よいEditor拡張ライフを。

UnityのAssetsフォルダ内の選択されているフォルダパスを取得

はじめに

UnityでEditor拡張する際に必要になるかと思いまして、
コードを書いてみました。参考にしていただければと思います。

説明


f:id:lo25131:20150927195544p:plain

例:Scenesまでのパスを取得したい!

パス -> Assets/Scenes


サンプルコード


~パス表示までの流れ~

(1)Assets内にあるフォルダを左クリックで選択する
(2)選択されているフォルダを右クリックでGetFilePathというアイテムを選択
(3)Consoleにそのファイルのパスが表示される


以下コード

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

public class EditorToolsSample: EditorWindow
{
 // 右クリックMenuに追加.
 [MenuItem("Assets/GetFilePath")]
 private static void GetFilePath()
 {
   GetSelectFile();
 }

 /// <summary>
 /// 選択中のファイルパス取得.
 /// </summary>
 private static void GetSelectFile()
 {
   // ファイルが選択されている時.
   if(Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0)
   {
     // ファイルリスト作成.
     List<string> fileList = new List<string>();
     
     // 選択されているパスの取得.
     foreach(var files in Selection.assetGUIDs)
     {
       var path = AssetDatabase.GUIDToAssetPath(files);
       fileList.Add(path);
     }

     // 出力.
    foreach(var list in fileList)
     {
       Debug.Log(list);
     }
   }
 }
}


【実行結果】

[メニューに追加]



[コンソールの出力]
f:id:lo25131:20150927190453j:plain
出力されたパスはAssetsからのパスとなります。



[複数選択の出力]
f:id:lo25131:20150927190521j:plain
複数選択も可能です。


解説

MenuItem~ でメニューにアイテムを追加し、これをクリックすることで
パス取得のメソッドが実行されます。

選択されている情報はSelection.assetGUIDsで取ることができますので、
それをリストに全ていれて出力。



取得したパスへの追加処理

以下は余談ですが、取得したパスへ行う処理をいくつか追加しました。


●Assetsの部分を消す処理

「Assets/Scenes」 というパスが取得できても、「Assets/」の部分がいらない!
という時がありますよね。そういう時に使用します。
先ほど取得したfileListを引数として渡します。

private static void GetSelectFile()
{
 List<string> childPath = GetChildPath(fileList);
 foreach(var list in childPath)
 {
   Debug.Log(list);
 }
}

/// <summary>
/// Assetsを除いたパスを取得.
/// </summary>
/// <param name="path">リスト型のパス.</param>
/// <returns>「Assets」部分が取り除かれたリスト型のパス.</returns>
private static List<string> GetChilePath(List<string> path)
{
 List<string> childPath = new List<string>();
 for(int index = 0; index < path.Count; ++index)
 {
   string temp = path[index].Remove(0, path[index].IndexOf("/") + 1);
   childPath.Add(temp);
 }
 return childPath;
}


出力結果:
f:id:lo25131:20150927193256j:plain




●パスを繋げる処理

パスを繋げたいという時があるかもしれません。そういう時に使用します。

private static void GetSelectFile()
{
 string joinPath = GetChildPath(fileList);
 Debug.Log(joinPath);
}

/// <summary>
/// パスを繋げる.
/// </summary>
/// <param name="path">リスト型のパス.</param>
/// <returns>繋げられたパス.</returns>
private static string GetJoinPath(List<string> path)
{
 string joinPath = string.Join(" ", path.ToArray());
 return joinPath;
}

出力結果:
f:id:lo25131:20150927193251j:plain


まとめ

Editor拡張をするにあたって、選択したファイルパスを取得して何かをするという時があるかもしれません。
今回はフルパスは取得していませんが、実際に外部プロセスを起動して何か作業したいという場合には、
プロセスのオプションとして、以下を設定してあげればいいと思います。

例: process.StartInfo.WorkingDirectory = Application.dataPath;

そうすればAssetsまでのフルパスとして取得でき、その場所からの作業を行うことができます。

Unityによる外部プロセスとの連携

利用方法

Unity上で外部プロセスを起動して、アウトプットを出力する例です。
外部プロセスを起動させて操作をし、その出力も欲しいという時に使います。

プロセス作成側


void ProcessStart()
{
    // プロセス作成.
    System.Diagnostics.Process process = new System.Diagnostics.Process();

    // 起動するプロセス.
    process.StartInfo.FileName = "C:\notepad.exe";
    
    // プロセス起動にシェルを使用するかどうか(defaultはfalse).
    process.StartInfo.UseShellExecute = false;

    // 標準出力を読み取り可.
    process.StartInfo.RedirectStandardOutput = true;

    // 標準出力イベント設定.
    process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(OutputHandler);

    // エラー出力読み取り可.
    process.StartInfo.RedirectStandardError = true;

    // エラー出力イベント設定.
    process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler(ErrorOutputHanlder);

  // 入力を読み取り不可.
    process.StartInfo.RedirectStandardInput = false;

  // 新しいウインドウを作成しない.
    process.StartInfo.CreateNoWindow = true;

    // 引数の指定(開くファイルの指定等).
    process.StartInfo.Arguments = "test.txt";

    // プロセス終了時にExitedイベントを発生.
    process.EnableRaisingEvents = true;

    // プロセス終了時に呼び出されるイベントの設定.
    process.Exited += new System.EventHandler(Process_Exit);

    // プロセスの起動.
    process.Start();

    // プロセス標準出力.
    process.BeginOutputReadLine();

  // プロセスエラー出力.
    process.BeginErrorReadLine();
}


全体の流れ

①プロセスの作成

②起動するプロセスの指定
C:\test.exeのように指定する場合と、
環境変数に登録してあるものが指定できる。(例:adb, svn

③オプションの指定

④イベントの設定

⑤プロセスの起動


※プロセスからの出力をイベント設定せずに出力してしまうと、途中で処理がフリーズしてしまうので、
出力がほしい時はこのように書きます。



プロセスの結果出力側


    // 標準出力時.
    private void OutputHandler(object sender, System.Diagnostics.DataReceivedEventArgs args)
    {
        if (!string.IsNullOrEmpty(args.Data))
        {
            Debug.Log(args.Data);
        }
    }
    
    // エラー出力時.
    private void ErrorOutputHanlder(object sender, System.Diagnostics.DataReceivedEventArgs args)
    {
        if (!string.IsNullOrEmpty(args.Data))
        {
            Debug.Log(args.Data);
        }
    }

    // プロセス終了時.
    private void Process_Exit(object sender, System.EventArgs e)
    {
            System.Diagnostics.Process proc = (System.Diagnostics.Process)sender;

            // プロセスを閉じる.
            proc.Kill();
    }
    


プロセスの使用例(adbを例として)

実際にUnityに拡張として作った「adb」を例に紹介していきます。
adbとはAndroid Debug Bridgeの略で、Androidデバッグをサポートするツールです。

通常使用する場合は、adbを環境変数として登録してcmdでadbコマンドを使用するか、
adb.exeが存在するカレントディレクトリ内でcmdでadbコマンドを実行するかです。

今回はこの動作をC#で、Unityで行っていく例を紹介します。

void ProcessStart()
{
    // 起動するプロセス.
    process.StartInfo.FileName = "C:\android-sdk-windows\platform-tools\adb.exe";

    // 引数の指定(コマンドの指定).
    process.StartInfo.Arguments = "version";

    /****************************
     *
     *          途中省略
     *         
     *****************************/

    // プロセスの起動.
    process.Start();
}


<起動するプロセス>

起動するプロセスに関しては、直接指定するか、環境変数にadbコマンドを指定している場合は、
"adb"のみの指定で起動できます。
エディター拡張として使用するなら、ファイルエクスプローラ開いて直接adbを選択してもらうほうがいいかもです。


<引数の指定>

ここでは adb [コマンド] のコマンドの部分を指定します。
例として、
version(バージョンの確認)
devices(接続しているデバイスの確認)
install [apkファイル名](apkのインストール)
kill-server(adbの停止)
start-server(adbの起動)


【cmdによる出力】

f:id:lo25131:20150927132630j:plain


【Unityによる出力】

f:id:lo25131:20150927132634j:plain



まとめ

Unityで外部プロセスを使用するにあたって、Visual Studioで開発する場合と記述の仕方は特に変わらないかと思います。
ただプロセスを使用するにあたって、起動したあと、エラーがあっても停止させることが重要です。
停止させないでいると、同じプロセスがタスクにたまってたまって、途中でフリーズする原因となってしまいます。

Unityから外部プロセスを起動するなどあまりないかもしれませんが、Editor拡張するときなどに使用するかもしれないので、
是非参考にしていただければと思います。