カテゴリー別アーカイブ: JavaScript

Facebook Messenger Platform でメッセージbot作成(v2.7)

【Facebook Messenger Platform beta + Facebook SDK v2.7】

前回のブログにFacebook SDKでメッセージ送信する記事を書きましたが、モバイルで使える方法はないのかなと調べていたら、Facebook Messenger Platformなるものを発見。

これでモバイルでもメッセージ送信できるんじゃないのかと調べてみたら、基本的にアプリやFacebookページ内でのメッセージ送信(チャット)に関するAPIのようです。

FacebookアプリやFacebookページを作って、そこにメッセージが送信されたときにコールバックを受け取る処理を組み込んでおいて反応させるもののようです。

JSで使用するにはNode.jsが必要


他のAPIと同じように、JavaScriptでもPHPでも書けるようですが、JavaScriptの場合はNode.jsが必要になるようです。Node.jsをインストール済の外部サーバがないので、今回はPHPでテストしてみました。

PHPはSSL配下で動作させる必要がある


今回はレンタルサーバ(さくらのレンタルサーバ)についている共有SSL機能を使いました。

大まかな手順は、上のGetting Started – Messenger Platformに書いてありますが、実際にテストしてみた手順をメモしておきます。

1. Create a Facebook App and Page(FacebookアプリとFacebookページを作成)


Quick Starts – 開発者向けFacebookを開いて、Facebookアプリを作成します。ここでは「testMessenger2」という名前で作成しました。アプリ名を入力して、「新しいFacebookアプリIDを作成」ボタンを押します。
bot01

開かれたポップアップで、メールアドレスを入力してカテゴリを選択します。ここでは「コミュニケーション」を選択しました。
bot02

アプリの設定画面に切り替わります。ページの内容は無視して、右上の「Skip Quick Start」ボタンを押します。
bot03

作成したFacebookアプリの管理画面が表示されます。
bot04

次にFacebookページを作成ページを開いて、Facebookページを作成します。ここでは会社・団体カテゴリからこのサイト名と同じ「As blind side」の名前で作成しました。
bot05

Facebookページの設定画面が表示されます。今回は全てスキップを選択します。
bot06

Facebookページが表示されます。
bot07

2. Setup Webhook(Webhookを設定)


Webhookを設定します。Webhookとはざっくりいうと、イベントに対するコールバックのことです。詳しくは下記を。

【参考】
Webhookとは? | ブログ | SendGrid
アプリのおしらせ作業を楽にするプッシュ通知の3つのポイント | BACKEND AS A SERVICE mbaas BLOG

ここでは「FacebookのMessengerにメッセージが送信されたときのコールバック」を指す、と考えていいと思います。

Facebookアプリのコールバック用PHPを作成します(ここではcallback2.phpという名前で作成)。「$access_token」には任意のトークンを設定します(ここでは”testmesenger”)。このPHPとトークンは、Facebookにコールバックphpを認証させるためのものです。

<?php
$hub_verify_token = "testmesenger"; // 任意のトークンを自分で作成
if($_GET['hub_verify_token'] == $hub_verify_token) {
    echo $_GET["hub_challenge"];
} else {
    echo 'error';
}

次にコールバック用PHPを外部サーバにアップします。アップするサーバはSSLでなければなりません。

まずFacebookアプリの管理画面から、プロダクト>製品を追加を選び、表示された中から「Messenger」のところにある「スタート」ボタンを押します。
bot08

Messengerの設定画面で「Set Webhooks」ボタンを押すと、下記のポップアップが表示されます。フォロー入力欄の全ての項目にチェックを入れ、「コールバックURL」に先程サーバに上げたPHPのURL、「トークンを確認」にPHPで設定したトークン(ここでは”testmesenger”)を入力して「確認して保存」ボタンを押します。
bot11

コールバック用PHPが正しく確認されると、Facebookアプリ管理画面のWebhooks欄に完了のマークが表示されます。
bot12

3. Get a Page Access Token(ページアクセストークンの取得)


Facebookアプリ(callback2.php)がFacebookページ(As blind side)にアクセスするためのトークンを取得します。

Messengerの設定画面で「トークン生成」の「Facebookページ」から先程作成したFacebookページ(As blind side)を選択して、ページアクセストークンを生成します。
bot10

ページアクセストークン欄に表示された文字列をコピーします。

4. Subscribe the App to the Page(Facebookページにアプリを登録する)


ターミナルから以下のコマンドを入力してBOTを起動します。Facebookページとアプリは、ページアクセストークンで紐付けされます。

curl -X POST “https://graph.facebook.com/v2.7/me/subscribed_apps?access_token=**********”

※**********には、ページアクセストークンを設定する。

正しく起動されると {“success”: true} と表示されます。
bot13

Botを使ってみる


これでBotの準備が出来ました。

Facebookアプリのコールバック用PHP(callback2.php)を書き換えて、受け取ったメッセージに対して返信するようにします。「$access_token」には上でコピーしたページアクセストークンを設定します。

<?php
$access_token = "*******************";
$json_string = file_get_contents('php://input');
$json_object = json_decode($json_string);
$messaging = $json_object->entry{0}->messaging{0};

if(isset($messaging->message)) {
    $id = $messaging->sender->id;
    $rcv_msg = $messaging->message->text;

    $message = '「';
    $message .=$rcv_msg;
    $message .= '」ってなんですか?';

    $post = <<< EOM
    {
        "recipient":{
            "id":"{$id}"
        },
        "message":{
            "text":"{$message}"
        }
    }
EOM;

    api_send_request($access_token, $post);
}

function api_send_request($access_token, $post) {
    error_log("api_get_message_content_request start");
    $url = "https://graph.facebook.com/v2.6/me/messages?access_token={$access_token}";
    $headers = array(
            "Content-Type: application/json"
    );

    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($curl, CURLOPT_POSTFIELDS, $post);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    $output = curl_exec($curl);
}
?>

コード内容については、この辺を見るといいかも。
Webhook Reference – Messenger Platform

書き換えたコールバック用PHPをアップロードして上書きします。

Botのテストをしてみます。Fcebookページ(As Blind side)からメッセージ送信すると、こんなかんじになります。
bot14

モバイルのMessengerアプリからメッセージ送信すると、こんな感じ。
bot15

これで完成です。いろいろ処理を書き込めば、ある程度メッセージは自動応答化できるのかなと思います。

【補足】
これはあくまでテスト用に作ったページとアプリなので、開発モードになっているため開発者権限でしか動作しません(作った本人しか使えない)。実際に稼働させるには、Facebookアプリ管理画面のアプリレビューで、「アイテムを審査に送信」を行って、公開設定する必要があります。
bot16

【参考】
Facebook Messenger Platform + さくらのレンタルサーバ + RapidSSL + PHP で BOT作成 – Qiita
Facebook、Messengerをプラットフォーム化、APIを公開 – iPhone Mania
Heroku+Railsで動かすFacebook Messengerのオウム返しBot – Qiita
Messengerで簡単なBotつくる(Facebook Messenger Platform from F8) – Qiita
FacebookGraphAPIを使った投稿パターンと見え方まとめ

Facebook SDKでメッセージ送信(v2.6)

【Facebook グラフAPI Version v2.6】

Facebook SDKでのメッセージ送信について調べてみました。やりたいことは下記の2つ。

  • 自分の友達を調べる。
  • その友達にメッセージを送る。

久しぶりに調べてみたら、2014.4に出たv2.0以降でかなり仕様が変わったようです。割と自由に引き出せた情報が、v2.0以降はかなり制限されたり、個別に権限が必要になったようです。

※前提としてFacebookでページをアプリ登録します。登録方法についてはこちら

Facebookアプリに権限を付与


Facebook for Developerのアクセス許可のリファレンスを見ると、かなり細かく権限が分かれていて、多くの権限はFacebookのレビューを受けないと使用できないようです。

Facebookのレビューなしに使用できる権限は、以下の3つです。

但しデフォルトで使用できるのは「public_profile」のみで、他の2つはログイン時に使用を宣言する必要があります。

1.Facebookのログインボタンを使用する場合

<fb:login-button scope="public_profile,email,user_friends" onlogin="checkLoginState();">
</fb:login-button>

2.JavaScriptでログインする場合

  function login(){
    FB.login(function(response) {
      if (response.authResponse) {
        alert('ログインも連携認証もされた');
      } else {
        alert('ログインもしくは連携認証がキャンセルされた');
      }
    },{scope:'public_profile,email,user_friends'});
  }

どちらの場合も、「scope」で使用する権限を付与します。

FB SDKを非同期にロード


	//FB SDKを非同期にロード
	(function(d, s, id) {
		var js, fjs = d.getElementsByTagName(s)[0];
		if (d.getElementById(id)) return;
		js = d.createElement(s); js.id = id;
		js.src = "//connect.facebook.net/ja_JP/sdk.js";
		fjs.parentNode.insertBefore(js, fjs);
	}(document, 'script', 'facebook-jssdk'));

FB SDKのロード後に実行される処理を書く


「window.fbAsyncInit」で指定した内容が、SDKロード後に実行されます。ここでは2つの処理を行います。

	  //FB SDKのロード後に実行される
	  window.fbAsyncInit = function() {
		  FB.init({
		    appId      : '***************',
		    cookie     : true,  //セッション用にクッキーを有効化
		    xfbml      : true,  // このページのパース許可
		    version    : 'v2.6' // use version 2.6
		  });

		//FBのログインチェック
		FB.getLoginStatus(function(response) {
			statusChangeCallback(response);
		});
	
	};

※appIdの ‘***************’ には、Facebookアプリ登録時に発行されたidを指定する。

FB ログインチェック後の処理


「FB.getLoginStatus()」でログインチェックが行われると、戻り値(response)を受け取り、「statusChangeCallback()」が処理を振り分けます。

	  //FB.getLoginStatus()のコールバック
	  function statusChangeCallback(response) {
	    if (response.status === 'connected') {
			// Facebookにログイン済で、このアプリを認証済み
			testAPI();
			
			userID = response.authResponse.userID;
			console.log("userID >> "+userID);
	    } else if (response.status === 'not_authorized') {
			//Facebookにログイン済で、このアプリを認証していない
			document.getElementById('status').innerHTML = '右上のボタンでこのアプリを認証してください。';
	    } else {
			//Facebookにログインしていない
			document.getElementById('status').innerHTML = '右上のボタンでFacebookにログインしてください。';
	    }
	  }

ログイン状態に応じて、次のように処理を振り分けます。

not_authorizedFacebookにログイン済、かつこのFacebookアプリを認証していないFacebookアプリの認証を促すメッセージを表示する。

ログイン状態(response.status) 意味 実行する処理
connected Facebookにログイン済、かつこのFacebookアプリを認証済み 次の処理へ(testAPI())
その他(unknown) Facebookにログインしていない(未ログインのため、Facebookアプリの認証は不明) Facebookのログインを促すメッセージを表示する。

・Facebookアプリを認証していない場合のページ表示例
page02_ninsho

・Facebookにログインしていない場合のページ表示例
page03_fb

ユーザー情報を表示する


Facebookにログイン済、かつこのFacebookアプリを認証済みの場合は、ユーザー情報を表示します。

	// FBにログイン済且つアプリを認証済みの場合、ユーザー情報を表示する
	function testAPI() {
		FB.api('/me', function(response) {
		  document.getElementById('status').innerHTML =
		    'ようこそ ' + response.name + ' さん!';
		});
	}

・ユーザー情報のページ表示例
page04_logined

自分の友達を調べる


FBにログイン済且つアプリを認証済みの場合は、「友達を探す」ボタンをクリックしたタイミングで「getFriendlist()」を呼び出し、友人リストを取得します。

・友人リストを取得

	//---友人リスト取得
	function getFriendlist() {
		console.log('Welcome!  Fetching your information.... ');
		FB.api(
		'/me/friends',
		'GET',
		{},
		    function (response) {
		      if (response && !response.error) {
			        //友人リスト表示
			        editPostData(response.data);
			        
		      } else {
			        for (var item in response.error){
				        console.log(item+":"+response.error[item]);
			        }
		      }
		    }
		);    
	}

但し、ここで取得できる友達リストは、以下の条件に合致するもののみになります。

  • ログインしたユーザーの友達であること。
  • このFacebookアプリを認証している友人であること。

つまりユーザーの友人全てのリストを取得することはできません。この辺はAPIのver.2以降で制限がかかったようです。

【参考】Facebook API v2.0で、フレンド数やフレンド一覧を取得する方法 | Sunday In The Park

そして取得したリストから、友人一覧をページに表示します。

	//----投稿データ編集
	function editPostData(data){
		
		//件数関連パラメタ:保存
		var resultCount = data["length"];
		var resultArray = data;
		
		var resultTitle = "";
		var html = "";
		$("#mainContent .resultSec").empty();
		
		var matchCount = 0;
		
		if (resultCount == 0){
				resultTitle = ("検索結果:該当する友達がいません。");
		} else {
			resultTitle = ("検索結果:"+resultCount+"件");
			for (var i=0;i<resultArray.length;i++){
			
					//友達を表示
					html += '<li class="resultArea">';	
					html += '<div class="result">';	
					html += '<span class="resultInfo">';	
					html += '<span class="songName">'+resultArray[i]["name"]+'</span>';	
					html += '<span class="artistName">'+resultArray[i]["id"]+'</span>';						
					html += '</div>';	
					
					html += '<ul class="resultBtn">';
					html += '<li>';
					html += '<div class="btn" id='+resultArray[i]["id"]+'>';
					html += '<span>メッセージ送信</span>';
					html += '</div>';
					html += '</li>';
					html += '</ul>';
					
					html += '</li>';			
					
					matchCount++;		
			}
			
			if (html != ""){
				$("#mainContent .resultSec").append(html);
				resultTitle = ("検索結果:"+matchCount+"件");
			} else {
				resultTitle = ("検索結果:このアプリを使っている友人がいません。");
			}
		}
	
		var html_title = "";
		html_title += '<li class="titleArea">'
		html_title += '<div class="icon">'
		html_title += '<img src="img/search/ico_search.png" width="40" height="40" />'
		html_title += '</div>'
		html_title += '<div class="txt">'+resultTitle+'</div>'
		html_title += '</li>'
		$("#mainContent .resultSec").prepend(html_title);
		
	
		$('#mainContent .resultSec .resultBtn .btn').each(function() {
			$(this).unbind('click');
			$(this).bind('click',clickSendMessageFunc);
		});
		
		//---メッセージ送信ボタン:クリック処理
		function clickSendMessageFunc(event){
			var targetID = event.currentTarget.id;
			sendMessage(targetID);
		}	
		
	}

・友人リストのページ表示例

この例では2人の友人が表示されています。
この例では2人の友人が表示されています。

友人にメッセージを送る


友人一覧に設定したボタンをクリックすると、「sendMessage()」が呼び出され、メッセージが送信されます。

・送信ボタンクリック

		//---メッセージ送信ボタン:クリック処理
		function clickSendMessageFunc(event){
			var targetID = event.currentTarget.id;
			sendMessage(targetID);
		}	

・メッセージ送信

	  //FBメッセージ送信(モバイルは不可)
	  function sendMessage(sendId){
	      FB.ui(
	      { 
	          method: 'send', 
	          to: sendId,
	          link: "http://www.c-geru.com"
	      }, 
	      function(param){
	        //callback
	      }
	      );
	}

FB.ui送信ダイアログを使って、メッセージ送信するのですが、モバイル機器には対応していません

モバイルのブラウザでメッセージ送信ボタンを押すと、モバイルに対応していない旨のメッセージが表示される。
モバイルのブラウザでメッセージ送信ボタンを押すと、モバイルに対応していない旨のメッセージが表示される。

ホントはPC、モバイルの両方で使えるようにしたかったんですが・・・。残念。

最後にjsを含むHTMLのサンプルコードを書いておきます。Facebookアプリ登録が正しく行われていれば、説明通りに動くはずです。参考までに。

<!DOCTYPE html>
<html>
<head>
<title>Facebookログインテスト</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/style.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
</head>
<body>
<script>
	//FacebookのuserID保存用
	var userID;
	
	  //FB.getLoginStatus()のコールバック
	  function statusChangeCallback(response) {
	    console.log('statusChangeCallback');
	    console.log(response);
	    if (response.status === 'connected') {
			// Facebookにログイン済で、このアプリを認証済み
			testAPI();
			
			userID = response.authResponse.userID;
			console.log("userID >> "+userID);
	      
	      
	    } else if (response.status === 'not_authorized') {
			//Facebookにログイン済で、このアプリを認証していない
			document.getElementById('status').innerHTML = '右上のボタンでこのアプリを認証してください。';
	    } else {
			//Facebookにログインしていない
			document.getElementById('status').innerHTML = '右上のボタンでFacebookにログインしてください。';
	    }
	  }
	
	  // This function is called when someone finishes with the Login
	  // Button.  See the onlogin handler attached to it in the sample
	  // code below.
	  function checkLoginState() {
	    FB.getLoginStatus(function(response) {
	      statusChangeCallback(response);
	    });
	  }
	  
	  //FB SDKのロード後に実行される
	  window.fbAsyncInit = function() {
		  FB.init({
		    appId      : '*****************',
		    cookie     : true,  //セッション用にクッキーを有効化
		    xfbml      : true,  // このページのパース許可
		    version    : 'v2.6' // use version 2.6
		  });

		//FBのログインチェック
		FB.getLoginStatus(function(response) {
			statusChangeCallback(response);
		});
	
	};
	
	//FB SDKを非同期にロード
	(function(d, s, id) {
		var js, fjs = d.getElementsByTagName(s)[0];
		if (d.getElementById(id)) return;
		js = d.createElement(s); js.id = id;
		js.src = "//connect.facebook.net/ja_JP/sdk.js";
		fjs.parentNode.insertBefore(js, fjs);
	}(document, 'script', 'facebook-jssdk'));
	
	// FBにログイン済且つアプリを認証済みの場合、ユーザー情報を表示する
	function testAPI() {
		console.log('Welcome!  Fetching your information.... ');
		FB.api('/me', function(response) {
		  console.log('Successful login for: ' + response.name);
		  document.getElementById('status').innerHTML =
		    'ようこそ ' + response.name + ' さん!';
		    
		 	//---友人を探すボタン:表示処理
		// 		showSerachBtnFunc();
		});
	}
	  
	//---友人リスト取得
	function getFriendlist() {
		console.log('Welcome!  Fetching your information.... ');
		FB.api(
		'/me/friends',
		'GET',
		{},
		    function (response) {
		      if (response && !response.error) {
			        //友人リスト表示
			        editPostData(response.data);
			        
		      } else {
			      console.log("---error:"+response.error);
			        for (var item in response.error){
				        console.log(item+":"+response.error[item]);
			        }
		      }
		    }
		);    
	}
  
	  //FBメッセージ送信(モバイルは不可)
	  function sendMessage(sendId){
	      FB.ui(
	      { 
	          method: 'send', 
	          to: sendId,
	          link: "http://www.c-geru.com"
	      }, 
	      function(param){
	        //callback
	      }
	      );
	}
	//----友人リスト表示
	function editPostData(data){
		
		//件数関連パラメタ:保存
		var resultCount = data["length"];
		var resultArray = data;
		
		var resultTitle = "";
		var html = "";
		$("#mainContent .resultSec").empty();
		
		var matchCount = 0;
		
		if (resultCount == 0){
				resultTitle = ("検索結果:該当する友達がいません。");
		} else {
			resultTitle = ("検索結果:"+resultCount+"件");
			for (var i=0;i<resultArray.length;i++){
			
					//友達を表示
					html += '<li class="resultArea">';	
					html += '<div class="result">';	
					html += '<span class="resultInfo">';	
					html += '<span class="songName">'+resultArray[i]["name"]+'</span>';	
					html += '<span class="artistName">'+resultArray[i]["id"]+'</span>';						
					html += '</div>';	
					
					html += '<ul class="resultBtn">';
					html += '<li>';
					html += '<div class="btn" id='+resultArray[i]["id"]+'>';
					html += '<span>メッセージ送信</span>';
					html += '</div>';
					html += '</li>';
					html += '</ul>';
					
					html += '</li>';			
					
					matchCount++;		
			}
			
			if (html != ""){
				$("#mainContent .resultSec").append(html);
				resultTitle = ("検索結果:"+matchCount+"件");
			} else {
				resultTitle = ("検索結果:このアプリを使っている友人がいません。");
			}
		}
	
		var html_title = "";
		html_title += '<li class="titleArea">'
		html_title += '<div class="icon">'
		html_title += '<img src="img/search/ico_search.png" width="40" height="40" />'
		html_title += '</div>'
		html_title += '<div class="txt">'+resultTitle+'</div>'
		html_title += '</li>'
		$("#mainContent .resultSec").prepend(html_title);
		
	
		$('#mainContent .resultSec .resultBtn .btn').each(function() {
			$(this).unbind('click');
			$(this).bind('click',clickSendMessageFunc);
		});
		
		//---メッセージ送信ボタン:クリック処理
		function clickSendMessageFunc(event){
			var targetID = event.currentTarget.id;
			sendMessage(targetID);
		}	
		
		//---友人を探すボタン:表示処理
		function showSerachBtnFunc(){
		    $("#mainContent .check .btnCheck").css({
			    "display":block
		    });
		}	
		
	}


	jQuery.event.add(window, "load", function(){
	
		/* 友達を検索;クリック処理  */
		$("#mainContent .check .btnCheck").bind('click',getFriendlist);
		
		//---aタグにロールオーバー/ロールアウトを設定
		$('.btnCheck').mouseover(function() {
		    $(this).css({
			    "opacity":0.8
		    });
		});
		    
		$('.btnCheck').mouseout(function() {
		    $(this).css({
			    "opacity":1
		    });
		});
	
	});

</script>

	<header>
		<div class="headerin">
				<div class="bar clearfix">
					<div class="barL">Facebookログインテスト</div>
					<div class="barR">
						<fb:login-button scope="public_profile,email,user_friends" onlogin="checkLoginState();">
						</fb:login-button>
					</div>
				</div>
				<div class="title">
					<div id="status" class="copy">右上のボタンでログインしてください。</div>
				</div>
		</div>
	</header>

	<div id="mainContent">
		<div class="check">
			<div class="btnCheck">
				<span>友達を探す</span>
			</div>
		</div>
		
		<ul class="resultSec" id="result"></ul>
	</div>
</body>
</html>

【その他参考】
リファレンス – Facebookログイン
Facebookアプリで重要な連携と権限(FacebookAPI その5) | CODE COPILOT
FacebookSDKを使ってMessageを送信する方法 – Qiita
facebook認証状態を調べるFB.getLoginStatusでできるグロースハック3つ – Qiita

Unityを使ってみた(1)3D編

【MacOSX 10.11.5 + Unity 5.3.5f1 + JavaScript】

先日来、Nintendo Developer Portalや、Unityによる3DS、Wii Uのサポートなど、いろいろ話題が聞こえてくるので、遅まきながらUnityを使ってみました。

まずはネットのチュートリアルからと思って、下記サイトを参考に触ってみました。

【第4回】ブロック崩しを作ろう (Unity入門) | OpenBook

※前段として、上記サイトの第2回第3回に基礎的なことが書かれているので、ご一読をオススメします。

出来上がりはこんな感じ。

Unityで作ってみたブロック崩しもどき。
Unityで作ってみたブロック崩しもどき。

全体説明は、上のリンクをみてもらうとして、作りながら引っかかったり、気になった点を書いてみます。

古いAPIはUnityが補正してくれる。


今回参考にしたサイトのように、バージョンによって書き方が変わったスクリプトをUnityが補正してくれます。
例えば、MonoDevelop(Unity付属のコードエディタ)で下記のスクリプトを書いて保存します。

#pragma strict
var Speed : float = 15.0;
 
function Update () {
    if (Input.GetButtonUp("Jump") && rigidbody.velocity == Vector3(0, 0, 0)){
        rigidbody.AddForce((transform.forward + transform.right) * Speed, ForceMode.VelocityChange);
    }
}

そしてMonoDevelopからUnityに戻ると、下記のようなダイアログが表示されます。

APIのアップデートが必要な旨を通知するポップアップ。「I Made a Backup,Go Ahead!」を押すと、スクリプトが更新される。
APIのアップデートが必要な旨を通知するポップアップ。「I Made a Backup,Go Ahead!」を押すと、スクリプトが更新される。

ここで「I Made a Backup,Go Ahead!」を押してMonoDevelopに戻ると、下記のようにスクリプトが自動更新されます(Rigidbodyへの参照方法が更新されている)。

#pragma strict
var Speed : float = 15.0;

function Update () 
{
    if (Input.GetButtonUp("Jump") && GetComponent.<Rigidbody>().velocity == Vector3(0, 0, 0)){
        GetComponent.<Rigidbody>().AddForce((transform.forward + transform.right) * Speed, ForceMode.VelocityChange);
    }
}

これは変更されたAPIに対応するようにスクリプトを更新するための Script Updater というプログラムの機能だそうです。適用される範囲に限界はあるようですが、便利な機能ですね。ちなみに「No Thanks」を選んだ場合でも、あとから「Assets>Run API Updater…」で呼び出せます。

Assets > Run API Updater... でAPI Updaterを呼び出せる。
Assets > Run API Updater… で Script Updater を呼び出せる。

【参考】【Unity】Unity4で書いたコードをUnity5向けに変換する「Script Updater」 – テラシュールブログ

他のGameObjectに適用されたスクリプトを参照する


基本的にUnityでは、Hierarchyパネルで作成したGameObjectに対して、ProjectパネルのAssets内に作成したスクリプトをドロップして適用する、という形になるようです(Directorにおけるビヘイビアみたい、といっても理解されないか…(笑))。

この作例では、空のGameObject「SceneObject」を作成し、そこに「SceneScript」というスクリプトを作成、適用して、得点や残機数などを管理しています。

・SceneScript.js

#pragma strict

import UnityEngine.SceneManagement;

//残存玉数、スコア、ブロック総数
var life :int = 3;
var score :int = 0;
var blockCt :int = 35;


//シーン移動時にこのスクリプトを削除しないようにする
function Start () {
    DontDestroyOnLoad(this);
}

function Update () {
	//ゲームクリア監視
    if (blockCt == 0){
	//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
        SceneManager.LoadScene ("GameClear");
//        Application.LoadLevel("GameClear");
    }
}

で、残機数や得点を適宜更新する必要がありますが、このスクリプトを参照するには、該当するGameObject >> その中のComponentという順で参照します。

・ParamScript.js

#pragma strict

var style :GUIStyle;
var obj :GameObject;
var script :SceneScript;
 
function OnGUI() {
    obj = GameObject.Find("SceneObject");
    script = obj.GetComponent(SceneScript);
 
    GUI.Label(Rect(10, 10, 200, 40), "点数", style);
    GUI.Label(Rect(100, 10, 200, 40), "" + script.score, style);
    GUI.Label(Rect(10, 120, 200, 40), "残機数", style);
    GUI.Label(Rect(100, 120, 200, 40), "" + script.life, style);
}

・「obj = GameObject.Find(“SceneObject”);」 >> 該当オブジェクト(SceneObject)を参照
・「script = obj.GetComponent(SceneScript);」 >> 該当オブジェクトに適用されているスクリプトをコンポーネントとして参照。
・各変数は、参照したスクリプト(script)からドットシンタックス(例:script.score)で参照。

という感じです。

※参考サイトでは「GameObject.Find(“SceneScript”)」とスクリプト名を指定していますが、GameObject.Find()はGameObjectを参照するメソッドなので、これでは参照出来ません。たぶん誤植だと思います。

シーンの移動は、SceneManager.LoadScene()を使う


このゲームは・・・

・GameStart
・Main(実際のプレイ画面)
・GameOver
・GameClear

の4つのシーンで構成されています。参考サイトでは「Application.LoadLevel()」を使っていますが、これだと下記のワーニングが表示されます。

Assets/SceneScript.js(21,21): BCW0012: WARNING: ‘UnityEngine.Application.LoadLevel(String)’ is obsolete. Use SceneManager.LoadScene

ver5.3以降は「SceneManager.LoadScene()」を使うようになったようです。実際の例は上にある「SceneScript.js」を参照して下さい。SceneManager.LoadScene()を使う際は「import UnityEngine.SceneManagement;」が必須です。

【参考】Unity 5.3 で Application.LoadLevel が Obsolete になりました – NinaLabo

※上記の参考サイトを含め、SceneManager.LoadScene()について書かれている記事では、

using UnityEngine.SceneManagement;

と書かれていますが、本記事のスクリプトはJavaScriptで書いているので「using」ではなく「import」になります(usingはC#の文法)。

平面のUI定義について


Unityの画面で、得点や残機数など平面上に表示したいものは、カメラオブジェクト(通常はMain Camera)にスクリプトを適用します(上に書いたParamScript.jsを参照)。

ここでは、GUI.Labelを動的に生成してテキストを表示しています。

・ParamScript.js(抜粋)

 
    GUI.Label(Rect(10, 10, 200, 40), "点数", style);
    GUI.Label(Rect(100, 10, 200, 40), "" + script.score, style);
    GUI.Label(Rect(10, 120, 200, 40), "残機数", style);
    GUI.Label(Rect(100, 120, 200, 40), "" + script.life, style);
}

この方法だと、例えばゲームタイトルなど常にセンターに表示する場合に、書き出す画面サイズが変わる度に数値を書き換えないといけないので面倒です。で、調べてみたところ、直接GUIオブジェクトを作って配置すればいいらしい。

1.空のGameObjectを作って(Create empty)、Inspectorパネルの「Add Component」ボタンで「GUI Text」を追加します。

空のGameObjectにGUI Text を追加。
空のGameObjectにGUI Text を追加。

2.TransformやGUI Textの値を調整して、表示するテキストや位置などを調整する。

表示位置や内容などはInspectorパネルで調整する。
表示位置や内容などはInspectorパネルで調整する。

【参考】【プログラム】UnityでGUI Textを画面の中央にする方法 – 英姿颯爽

残機数と設定値を合わせる


このサンプルをそのまま作って実行すると、残機数(残玉数)が合わないと思います。「SceneScript.js」で残機数(life)を3にしているのに、実際は4つボールをなくしたところでゲームオーバーになります。

関連する2つのスクリプトを見比べてみます。

・SceneScript.js

#pragma strict

import UnityEngine.SceneManagement;

//残存玉数、スコア、ブロック総数
var life :int = 3;
var score :int = 0;
var blockCt :int = 35;


//シーン移動時にこのスクリプトを削除しないようにする
function Start () {
    DontDestroyOnLoad(this);
}

function Update () {
	//ゲームクリア監視
    if (blockCt == 0){
		//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
        SceneManager.LoadScene ("GameClear");
//        Application.LoadLevel("GameClear");
    }
}

・BottomWallScript.js

#pragma strict

import UnityEngine.SceneManagement;

//得点用
var obj :GameObject;
var script :SceneScript;
 
obj = GameObject.Find("SceneObject");
script = obj.GetComponent(SceneScript);
var ball :Transform;

function OnCollisionEnter (collision : Collision) {
    //衝突した相手を消す(相手>collision.gameObject,自分>gameObject)
    Destroy(collision.gameObject);

    //ボールの残数チェック
    if (script.life > 0){
	    Instantiate(ball);

	    script.life--;
    } else if (script.life == 0){
	//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
    	SceneManager.LoadScene ("GameOver");
//        Application.LoadLevel("GameOver");
    }
}

手順通りに作成すると、最初のボールはシーン上に配置されています。しかし、ボールがロストした時の処理は、Player後ろの壁に当たったとき(BottomWallScript.js)に行われるため、最初のボールがステージされる際に減算処理が実行されません。その為に残機数(life)での設定値より1つ余計にプレイできてしまいます。

対処方法はいろいろありますが、今回は次のようにしました。

1.Mainシーンに配置してある「Ball」を削除する(HierarchyパネルでBallを選択>右クリックでDeleteを選択)。

2.初期起動時のBall配置を BottomWallScript.js に組み込む。

・BottomWallScript.js

#pragma strict

import UnityEngine.SceneManagement;

//得点用
var obj :GameObject;
var script :SceneScript;
 
obj = GameObject.Find("SceneObject");
script = obj.GetComponent(SceneScript);
var ball :Transform;

function Start(){
    //ボールの残数チェック
     checkBallRemains();
}

function OnCollisionEnter (collision : Collision) {
    //衝突した相手を消す(相手>collision.gameObject,自分>gameObject)
    Destroy(collision.gameObject);

    //ボールの残数チェック
     checkBallRemains();
}


function checkBallRemains(){
    if (script.life > 0){
        Instantiate(ball);

        script.life--;
    } else if (script.life == 0){
	//Unity5.3からは、SceneManager.LoadSceneを使用すること(冒頭のimport必須)
    	SceneManager.LoadScene ("GameOver");
//        Application.LoadLevel("GameOver");
    }
}

BottomWallScript.js の変更は、下記の2点です。

・function Start() を追加して、ボールの残数チェックを行う。
・ボールの残数チェックは、function Start() と function OnCollisionEnter() で同じになるため、function checkBallRemains() で関数化して、どちらでもこの関数を呼び出すようにした。

こんな感じで一応動くゲームができました。ファイル一式をこちらに上げましたので、よければ参考にしてください。

c-geru/BlockGame: Unityで作ったブロック崩しゲームサンプル

【追記】2016.8.4
ダウンロードしたプロジェクトを開くには、UnityでFile >> Open Project… を選択して、表示されたProjectsウィンドウの右上にある「Open」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。次回以降は、プロジェクトが Projectsウィンドウに登録されます。

右上の「Open」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。
右上の「Open」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。

【CreateJS】Canvasのアニメーションを動的に切り替える

Canvasでアニメーションだけなら、JavaScript書かずにFlash CC(Animate CC)で書き出せばいいよね。Flashまだまだ使えるよ!なんですが、実際運用するとなると、ある時期でアニメーション切替、追加、削除等の変更が出てきます。AS3.0ならloadMovieでswf切り替えればよかったんですが、「CanvasにはloadMovieとかないし、どうするの?」と思ったら、意外とカンタンな話だったのでまとめておきます。

まずはアニメを作ります。
1つ目。Flash CC(今回はFlash CC 2015を使用)で、新規から「HTML5 Canvas」を選択してクラッシックトゥイーンでアニメーションして書き出しただけ。完成品はこちら

アニメその1

 

2つ目。作り方は同じ。完成品はこちら

anime2

 

で、2つ目のアニメを例に、Flash CCが書き出したHTMLとJavaScriptを見てみると・・・

・HTML抜粋(anime2.html)

<script>
var canvas, stage, exportRoot;

function init() {
	canvas = document.getElementById("canvas");
	exportRoot = new lib.anime2();

	stage = new createjs.Stage(canvas);
	stage.addChild(exportRoot);
	stage.update();

	createjs.Ticker.setFPS(lib.properties.fps);
	createjs.Ticker.addEventListener("tick", stage);
}
</script>

・JavaScript抜粋(anime2.js)

// stage content:
(lib.anime2 = function(mode,startPosition,loop) {
	this.initialize(mode,startPosition,loop,{});

	// timeline functions:
	this.frame_119 = function() {
		//this.stop();
	}

	// actions tween:
	this.timeline.addTween(cjs.Tween.get(this).wait(119).call(this.frame_119).wait(1));

	// レイヤー 2
	this.instance = new lib.mc_girl();
	this.instance.setTransform(698.1,205,3,3,0,0,0,41.1,143.8);

	this.timeline.addTween(cjs.Tween.get(this.instance).to({x:283.1,y:463},59).to({scaleX:4,scaleY:4,x:-154.8,y:479},60).wait(1));

	// レイヤー 3
	this.shape = new cjs.Shape();
	this.shape.graphics.f("#FF9900").s().p("Egq9AfPMAAAg+eMBV6AAAMAAAA+eg");
	this.shape.setTransform(275,200);

	this.timeline.addTween(cjs.Tween.get(this.shape).wait(120));

}).prototype = p = new cjs.MovieClip();

 

JSファイルでは、アニメーション全体がlib.anime2として定義されていて、それをHTML側に書いたJavaScript(init)でstageにaddChildしているという構造です。

なので、アニメーションの再生フレームを監視して、再生し終わったらremoveChildで削除して、次のアニメーションをaddChildすれば言いわけです。で、この2つのアニメーションを切替再生するためにJavaScript部分を書き直したHTMLがこちら。

・HTML(anime_switch.html)

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>anime_switch</title>

<script src="http://code.createjs.com/easeljs-0.8.1.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.6.1.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.8.1.min.js"></script>
<script src="anime1.js"></script>
<script src="anime2.js"></script>

<script>
var canvas, stage, exportRoot;
var animeArray = ["anime1","anime2"];
var count_anime;

function init() {
	canvas = document.getElementById("canvas");
// 	exportRoot = new lib.anime2();

	count_anime = 0;
	exportRoot = new lib[animeArray[count_anime]]();

	stage = new createjs.Stage(canvas);
	stage.addChild(exportRoot);
	stage.update();

	createjs.Ticker.setFPS(lib.properties.fps);
	createjs.Ticker.addEventListener("tick", stage);
	createjs.Ticker.addEventListener("tick",onCheckAnimationEnd);
}

function onCheckAnimationEnd(event){
	if (exportRoot.timeline.position >= (exportRoot.timeline.duration-1)){

		count_anime++;
		count_anime = count_anime > (animeArray.length-1)  ? 0 : count_anime;

		stage.removeChild(exportRoot);

		exportRoot = new lib[animeArray[count_anime]]();
		stage.addChild(exportRoot);
	}
}

</script>
</head>

<body onload="init();" style="background-color:#D4D4D4">
	<canvas id="canvas" width="550" height="400" style="background-color:#FFFFFF"></canvas>
</body>
</html>

※完成サンプルはこちら

要点は以下の通り。

  • 両方のアニメーションのjsを読み込む(9-10行目)
  • アニメーションを切り替えるためのライブラリ名は配列化(15行目)
  • 再生順を管理するためのカウンタ変数を追加(16行目)
  • アニメーション再生監視用に関数を追加(31,34-45行目)

今回のCreateJSでのASとの相違部分は以下です。

処理 AS JavaScript
現在のフレーム数 mc.currentFrame mc.timeline.position
総フレーム数 mc.totalFrames mc.timeline.duration

 

【追記】
2つのアニメーションを1つのflaファイルに、それぞれMovieClipとして作成した場合は、JSファイル上は・・・

  • lib.anime1
  • lib.anime2

のように定義されるので、この名前でaddChildできます。よって、必ずしもアニメーションを別ファイルで作成する必要はありません。

※今回のようにアニメーション全体を切り替える場合、実際には別ファイルと管理する方が楽だと思います。

上記の説明は、私の環境のFlash CCで書き出される下記のバージョンのCreateJSを前提としています。

<script src="http://code.createjs.com/easeljs-0.8.1.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.6.1.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.8.1.min.js"></script>

【参考】
今回はtwitterでのやりとりが元になりました。gyoh_kさん野中さんに感謝。

gyoh_kさんのツイート
野中 文雄さんのツイート1
野中 文雄さんのツイート2

【追記】2016.6.9
野中さんさんから、「MovieClip.currentFrameプロパティは前からあり、EaselJS 0.8.1 でMovieClip.totalFramesプロパティが加えられました。」とのご指摘を受けました。CreateJSとASの対応表を下記のように修正します。

処理 AS JavaScript
現在のフレーム数 mc.currentFrame mc.timeline.position,mc.currentFrame
総フレーム数 mc.totalFrames mc.timeline.duration,mc.totalFrames

 

currentFrame,totalFramesを使ったサンプルはこちら

参考
FN1506001 | EaselJS 0.8.1: MovieClipオブジェクトの再生の長さ・フレーム数を調べる | HTML5 : テクニカルノート
EaselJS v0.8.2 API Documentation : MovieClip – currentFrame
EaselJS v0.8.2 API Documentation : MovieClip – totalFrames

【Cordova】Cordova を使ってみよう(2)Androidでスプラッシュスクリーンを表示する

【OSX 10.11.3 + Node.js v4.2.6 + nam 3.5.3 + Cordova 6.0.0 + Xcode 7.2 + Android Stuido 1.3】

※ターミナルのコマンドについては、$を省略しています。理由はコピペの時に必要ないからです。

Cordovaの続きです。前回に引き続き、下記サイトを参考にAndroidでスプラッシュスクリーン表示をテストしてみました。

Cordova: アプリの設定 – Build Insider

これが意外にハマりました。原因は、参考サイトではCordovaのバージョンが3.0.0なのに対して、現在は6.0.0なので仕様が変わっているようです。以下、手順です。

1.Splashscreen プラグインのインストール

Cordovaのバージョン4.0.0以降、スプラッシュ・スクリーンの機能がプラグイン化されたようです。ターミナルから以下のコマンドでインストールします。

cordova plugin add cordova-plugin-splashscreen

【参考】Apache Cordova のスプラッシュ・スクリーン表示がややこしい | まさくらのブログ
※本サイトでエラーが出ているので、暫定的にキャッシュをリンクしました。

2.config.xmlの設定

config.xmlに設定を書き込みますが、これはプロジェクトディレクトリの直下(今回の場合は、hello/config.xml)にあるファイルです。hello/platforms/android/res/xml/にも同名のファイルがあるので注意。

基本的に設定は、プロジェクトディレクトリ直下のconfig.xmlを変更して、プラットフォーム別のプロジェクトに反映するようです。

Androidのスプラッシュスクリーンは、以下の2つを設定します。
・タグの中に解像度別の画像リンク。
・widgetタグの中にスプラッシュスクリーンの表示時間を指定したタグ(preference name=”SplashScreenDelay” value=”10000″)。

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.hello" version="1.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <name>HelloWorld</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <plugin name="cordova-plugin-whitelist" spec="1" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <platform name="android">
        <allow-intent href="market:*" />
        <splash src="platforms/android/res/drawable-port-ldpi/splash.9.png" density="port-ldpi" /><!--200x320-->
        <splash src="platforms/android/res/drawable-port-mdpi/splash.9.png" density="port-mdpi"/><!--320x480-->
        <splash src="platforms/android/res/drawable-port-hdpi/splash.9.png" density="port-hdpi"/><!--480x800-->
        <splash src="platforms/android/res/drawable-port-xhdpi/splash.9.png" density="port-xhdpi"/><!--720x1280-->
        <splash src="platforms/android/res/drawable-land-ldpi/splash.9.png" density="land-ldpi" />><!--320x200-->
        <splash src="platforms/android/res/drawable-land-mdpi/splash.9.png" density="land-mdpi"/><!--480x320-->
        <splash src="platforms/android/res/drawable-land-hdpi/splash.9.png" density="land-hdpi"/><!--800x480-->
        <splash src="platforms/android/res/drawable-land-xhdpi/splash.9.png" density="land-xhdpi"/><!--1280x720-->
    </platform>
    <platform name="ios">
        <allow-intent href="itms:*" />
        <allow-intent href="itms-apps:*" />
    </platform>
    <preference name="SplashScreenDelay" value="10000" />
</widget>
3.config.xmlの設定を各プラットフォームに反映する

・ターミナルから以下のコマンドを入力します。

cordova prepare -d

ここでAndroidディレクトリにあるconfig.xml(hello/platforms/android/res/xml/config.xml)を開いてみると、スプラッシュスクリーンの情報が追加されています。

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.hello" version="1.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    <preference name="loglevel" value="DEBUG" />
    <feature name="Whitelist">
        <param name="android-package" value="org.apache.cordova.whitelist.WhitelistPlugin" />
        <param name="onload" value="true" />
    </feature>
    <feature name="SplashScreen">
        <param name="android-package" value="org.apache.cordova.splashscreen.SplashScreen" />
        <param name="onload" value="true" />
    </feature>
    <allow-intent href="market:*" />
    <splash density="port-ldpi" src="platforms/android/res/drawable-port-ldpi/splash.9.png" />
    <splash density="port-mdpi" src="platforms/android/res/drawable-port-mdpi/splash.9.png" />
    <splash density="port-hdpi" src="platforms/android/res/drawable-port-hdpi/splash.9.png" />
    <splash density="port-xhdpi" src="platforms/android/res/drawable-port-xhdpi/splash.9.png" />
    <splash density="land-ldpi" src="platforms/android/res/drawable-land-ldpi/splash.9.png" />
    <splash density="land-mdpi" src="platforms/android/res/drawable-land-mdpi/splash.9.png" />
    <splash density="land-hdpi" src="platforms/android/res/drawable-land-hdpi/splash.9.png" />
    <splash density="land-xhdpi" src="platforms/android/res/drawable-land-xhdpi/splash.9.png" />
    <name>HelloWorld</name>
    <description>
        A sample Apache Cordova application that responds to the deviceready event.
    </description>
    <author email="dev@cordova.apache.org" href="http://cordova.io">
        Apache Cordova Team
    </author>
    <content src="index.html" />
    <access origin="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <preference name="SplashScreenDelay" value="10000" />
</widget>
4.Android実機又はエミュレータでアプリを起動する

。ターミナルから以下のコマンドを入力します(ココでは実機で起動します)。

cordova run android

これでスプラッシュスクリーンが表示されました。Cordovaの情報は多いですが、バージョンによる違いもあるので、必ず公式のドキュメントに当たった方がよさそうです。

Cordovaサイトの右上にどのバージョンの情報かが表示されているのでチェック。
Cordovaサイトの右上にどのバージョンの情報かが表示されているのでチェック。

【参考】
アイコンとスプラッシュ画面 – Apache Cordova(6.0.0)
Cordovaめも: スプラッシュスクリーン表示

【Cordova】Cordova を使ってみよう(1)環境設定編

先日のCreateJS勉強会で話題に上ったCordova。とりあえず使ってみようということで、環境設定して、コンパイルできるとことまでやってみたので、その作業メモです。ターミナルを使い慣れていないので、主にその周辺についてです。

Cordovaとは何か?

Adobeの Phone Gap という、HTML5+CSS3で作ったコンテンツをパッケージ化してiOS/Android用アプリにするという技術が、オープンソース化されて Cordova と呼ばれています。詳しくは下記で。

【参考】HTML5ハイブリッドアプリ開発を支えるOSS「Cordova」はなぜアツいのか?PhoneGapとの違いは何か? – ふろしき.js

作業環境

・OSX 10.11.3
・Node.js v4.2.6
・nam 3.5.3
・Cordova 6.0.0
・Xcode 7.2
・Android Stuido 1.3

環境設定

下記のサイトを参考にしました。
【参考】Cordovaを用いた開発環境を構築する – Build Insider

以下、環境構築する際に引っかかった点などをまとめておきます。

【Node.jsのインストール】

私の場合は導入済みでした。その時の手順などはこちらを参照。

【cordovaのコマンドラインツールをインストール】

このインストールがなかなか終わらなくて、npmのバージョンが古いのかとアップデートかけたら、npm自体が消えてしまいました。原因はよくわからず・・・。調べてみたら同じ現象について書いてある記事を見つけて、この通りにnpmの再インストールをして復活しました。
【参考】[node.js]npm自体のアップデートしようとしたらなぜかコマンドが消えた… – もじれつとがめん

その後、cordovaのインストールは無事終了。

【Android SDKに含まれているディレクトリにパスを通す】

「以下のように環境に応じてパスを設定してください」と書いてあるけど、どうすればいいかわからず(笑)。
で、次の手順で設定します。

1.Android Stuidoを起動し、「Android Stuido」>「Preferences…」を選択。
2.Preferencesウィンドウの右側で「Appearance & Behavior」>「System Settings」>「Android SDK」を選択すると、「Android SDK Location」蘭にパスが表示されます。

ココでのパスは「」
この環境でのパスは「/Users/c_geru/Library/Android/sdk」

Android SDKのパスをFinderで確認すると、SDKの中に「tools」「platform-tools」のディレクトリが確認出来ます。

ああ
選択されているのが「tools」「platform-tools」ライブラリは不可視ファイルなので、Finderで確認するには不可視ファイルの可視化が必要。

3.パスがわかったので、2つのディレクトリにパスを通します。ターミナルから下記のコマンドを1つずつ入力してreturnキーを押します。「echo 」以降がコマンドです。正しく受け付けられると、入力可能(c-gerus-MBP2011:/ c_geru$ が表示され、カーソルが点滅している)な状態になります。

※「c-gerus-MBP2011:/ c_geru$」の部分は、当然ながら環境によって異なります。

c-gerus-MBP2011:/ c_geru$ echo "export PATH=\$PATH:/Users/c_geru/Library/Android/sdk/tools" >> ~/.bash_profile
c-gerus-MBP2011:/ c_geru$ echo "export PATH=\$PATH:/Users/c_geru/Library/Android/sdk/platform-tools" >> ~/.bash_profile

パスの設定について詳しくは下記を参照。今回行ったのは.bash_profileというファイルにコマンドライン上からファイルにテキストを挿入する方法です。
【参考】[Mac, Linux] PATHを通す方法 | Memo on the Web

【プロジェクトを作成する】

参考サイト通りにコマンド打ったら、次のようなエラーが出ました。

c-gerus-MBP2011:/ c_geru$ cordova create hello com.example.hello HelloWorld -d
Creating a new cordova project.
Copying assets."
Error: EACCES: permission denied, mkdir '/hello'
    at Error (native)
    at Object.fs.mkdirSync (fs.js:794:18)
    at /usr/local/lib/node_modules/cordova/node_modules/cordova-lib/src/cordova/create.js:284:16
    at _fulfilled (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:787:54)
    at self.promiseDispatch.done (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:816:30)
    at Promise.promise.promiseDispatch (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:749:13)
    at /usr/local/lib/node_modules/cordova/node_modules/q/q.js:557:44
    at flush (/usr/local/lib/node_modules/cordova/node_modules/q/q.js:108:17)
    at nextTickCallbackWith0Args (node.js:419:9)
    at process._tickCallback (node.js:348:13)

ターミナルがデフォルトの状態では、ルート(通常なら Macintosh HD 直下)に位置しているため、ディレクトリを作成する権限がないためです。この場合は、スーパーユーザーを示す「sudo」を冒頭につけて、下記のように実行します。

c-gerus-MBP2011:/ c_geru$ sudo cordova create hello com.example.hello HelloWorld -d

※この際パスワード入力を求められます。ログイン時のパスワードを使用します。
【参考】必ず使える!Macのターミナルで使う基本UNIXコマンド15選 | NEZU.log

大体は作業用にディレクトリを作って、そこにまとめることが多いと思うので、今回はユーザディレクトリ配下の特定のディレクトリを指定して(/Users/c_geru/works/_2tone_dev/_cordova/hello)そこにプロジェクトを作成することにしました。

c-gerus-MBP2011:/ c_geru$ cordova create /Users/c_geru/works/_2tone_dev/_cordova/hello com.example.hello HelloWorld -d
プロジェクトの対象プラットフォームを確認する

参考サイトとは若干表示が違いました。この辺はバージョンによる差違かも。

c-gerus-MBP2011:hello c_geru$ cordova platform ls
Installed platforms: android 5.1.0, ios 4.0.1
Available platforms: amazon-fireos, blackberry10, browser, firefoxos, osx, webos
プロジェクトにAndroidとiOS用のファイルを追加する

cordovaコマンドは作ったプロジェクトのルートで行う必要があるので、追加する前にディレクトリを変更します。

c-gerus-MBP2011:/ c_geru$ cd /Users/c_geru/works/_2tone_dev/_cordova/hello
Androidエミュレータを利用する

問題なく起動しましたが、すごく時間かかりました。この辺はマシンスペックの問題かも。
(MacBook Pro (15-inch, Early 2011) 2GHz intel Core i7 メモリ 8GB)

iOSシミュレータを利用する

参考サイトでは、ios-simをインストールするように指示されていますが、直接エミュレータを起動して問題なかったです。Xcodeがインストール済であれば必要ないのかな?

ブラウザで確認する

「パソコンのブラウザで表示されたアドレスにアクセスすると、指定したプラットフォーム向けのWebアプリを見ることができます。」と書かれているけど、実際に「http://localhost:8000/」にアクセスするとメニュー画面が表示され、そこから各プラットフォームを選択することになるようです。

http://localhost:8000/ にアクセスした様子。対応しているプラットフォームのみリンクがついている。
http://localhost:8000/ にアクセスした様子。対応しているプラットフォームのみリンクがついている。
プラットフォームのリンクをクリックするとコンテンツが表示される。
プラットフォームのリンクをクリックするとコンテンツが表示される。iOSのリンクをクリックした様子。

これで環境設定と、エミュレータ、実機、ブラウザでの確認ができました。次は具体的な中身を作ってみてから。

【CreateJS】CreateJS勉強会(第7回)私的まとめ

2016/1/22(金)CreateJS勉強会(第7回)に行ってきました。その個人的まとめです。

※以下、敬称略です。

セッション内容

【セッション1】Single Page ApplicationにおけるPreloadJSの活用事例

(リクルートマーケティングパートナーズ 山田 直樹

・「キャッシュ済のファイルは読み込まないようにする」について、「ブラウザのメモリ上限に対するキャッシュの扱い(古いものから消す等)」について質問。「現状ではコンテンツ数が少ないので考慮していない」とのこと。
・PreloadJS は 0.6.1 を使用。最新版(0.6.2)だと、queue.getResult().srcから抜き出したBlobオブジェクトのパスが使えない(画像が読み込めない)ため。
【セッション2】事例から見るCreateJSの使いみち

(世路庵 沖 良矢
※レジュメなし(クライアント情報を含むため非公表)

・CreateJSのメリット
 各要素(動画除く)を1つの APIで制御できる。
 ブラウザ間の挙動を吸収してくれる
・CreateJSのデメリット
 画面描画は、Flashは部分更新なのに対し、CreateJSは全画面更新になる為、負荷が高い。
【セッション3】EaselJS 0.8.2の改訂項目とgskinner labの作例「Arc Rainbow」

野中文雄

※「Arc Rainbow」についての、野中さんによる更なる解説記事
 ・ FN1512002 | Graphicsコマンドでトゥイーンアニメーションを描く | HTML5 : テクニカルノートFN1512001 | 設定のオン・オフを切り替える - トグルボタンの論理組み立て | HTML5 : テクニカルノートFN1601001 | 条件分岐を考える | HTML5 : テクニカルノート
【セッション4】CreateJSで開発するクロスプラットフォームなアプリ開発〜ElectronとPhoneGapで広がる可能性〜

(ICS 鹿野 壮野原 のぞみ
・レジュメ(前編後編

・Webコンテンツアプリ化のメリット
 開発リソースの軽減
 アプリ化はカンタン(デスクトップ >> Electron, モバイル >> Cordova
・Webコンテンツアプリ化のデメリット
ネイティブ機能はプラグインで実装。プラグインが公開されているか、または自作。この辺は AIR for iOS/Androidに 対する ANE(Adobe Native Extension)と同じ印象。

関連リンク

CreateJS勉強会 (第7回) まとめ – Togetterまとめ

【Node.js】Node.jsを試してみた(基礎編)

【OSX 10.11.2 + Node.js v.4.2.4】
今更ながらですが、Node.js使ってみました。ターミナルやサーバ周りはあんまり得意ではないので、使ってみて「あれ?」となったところをまとめてみます。

参考にしたのはコチラのサイト。
Tech Blog | リクルートライフスタイル開発者ブログ Tag : ‘Node.js’

このサイトの記事を参考にテストしてみて、つまずいた点をメモとして以下に記します。

Node.jsで5行Webサーバを書いてみよう〈Node.jsシリーズ vol.1〉

手順通り公式サイトからパッケージをダウンロードしてインストールします。今回ダウンロードした時点でのバージョンは、v.4.2.4でした。で、インストールが終了すると、この画面が表示されます。

インストール完了時の画面。ここにインストール先のパスが書かれています。
インストール完了時の画面。ここにNode.js,namのインストール先とパスが書かれています。

記事には説明がないのですが、/usr/local/bin があんたのパスだよと書いてあるので、ここにファイルを作っていくことにします。つまり「たった5行でWebサーバーが書ける」にあるサンプルスクリプトは、 webserver.js という名前で、/usr/local/bin/ に保存するということです。

※ルートパスで書かれているので、実際は Macintosh HD/usr/local/bin にあります。それからディレクトリ「usr」は不可視なので、この辺を参考に不可視ファイルを表示するように設定します。

で、「nodeコマンドでサーバーを起動してみましょう」って書いてあるんだけど、ターミナル使ったことないとココでハマります(笑)。

通常、ターミナルを起動すると、こんな感じになってるはずです。

c-gerus-MBP2011:/ c_geru$

※「c_geru」はユーザ名

つまり、起動時点ではユーザ直下にいるので、そのままnodeコマンドを叩くと・・・

c-gerus-MBP2011:/ c_geru$ $ node webserver.js
-bash: $: command not found

とコマンド(webserver.js)ないといわれてしまいます。

c-gerus-MBP2011:/ c_geru$ node /usr/local/bin/webserver.js

とルートパス指定で起動するか、

c-gerus-MBP2011:/ c_geru$ cd /usr/local/bin/
c-gerus-MBP2011:bin c_geru$ node webserver.js

と、ディレクトリを /usr/local/bin/ に切り替えた上で(1行目)、起動することになります。

ちなみにnodeコマンドのあとに1行空行で改行された状態が、サーバが起動している(nodeコマンドが受け付けられた)状態です。

さて、この最初に起動した webserver.js をどうやって終わらせるのか?「Control+C」で停止します(ブレークを送信/中止・キャンセル)。

c-gerus-MBP2011:bin c_geru$ node webserver.js
^C
c-gerus-MBP2011:bin c_geru$

ターミナルが起動前の状態になってカーソル点滅になり、入力可能状態に戻ります。

この辺りのターミナル用コマンドは、いくつも解説サイトがありますが、例えばこの辺を参考に。
[ Mac ] ターミナルコマンドのまとめ ( 多分 Linux でも使えるはず )

この辺りはターミナルに馴れていれば自明のことなんでしょうが、知らないと結構ハマります。

npm実践! モジュールをインストールしQRコードを生成しよう〈Node.jsシリーズ vol.2〉

「npmコマンドでのモジュールインストール」で、qrcode-console モジュールをローカルインストール/グローバルインストールのどちらでインストールするか書いてないので、ちょっとハマりました。

・ローカルインストール
コマンドを実行するパス直下のnode_modulesディレクトリにインストールします。つまりインストール先は「/usr/local/bin/node_modules」になります。

・グローバルインストール
同一環境内にあるすべてのコードからモジュールを使えるようにインストールします。この場合のインストール先は「/usr/local/lib/node_modules」になります。

私はグローバルインストールしたので、qrcode_sample.jsのサンプルコードがそのままでは動きません。グローバルにあるモジュールを参照するので、1行目のrequire文のパスを書き換えます。

var qrcode = require( '../lib/node_modules/qrcode-console' );

この記事の冒頭に「node_modulesというディレクトリ配下におかれたモジュールは、コアモジュールと同じようにパス記号を付けず呼び出すことができます。」と書いてあったので、グローバルインストールすればパスなしで使えるのかと思ったんですが、「ローカルインストールでnode_modulesというディレクトリ配下におかれたモジュールは、コアモジュールと同じようにパス記号を付けず呼び出すことができます。」ということのようです。

チャットを作りながら学ぶSocket.IO〈Node.jsシリーズ vol.4〉

「Socket.IOを使ってWebサーバーを立ち上げると、/socket.io/socket.io.jsというパスに、そのライブラリが自動的に生成されます。」と書かれていますが、起動してみてもファイルができません。バージョン変わって仕様が変更になったのかも?とファイルを探してみると、下記にありました。

・ローカルインストール
/usr/local/bin/node_modules/socket.io/node_modules/socket.io-client/socket.io.js

・グローバルインストール
/usr/local/lib/node_modules/socket.io/node_modules/socket.io-client/socket.io.js

で、このファイルをindex.htmlに指定してみたのですが、socket.io.js自体がエラー(Uncaught SyntaxError: Unexpected token “<“…)で動きません。。。

Socket.IOのダウンロードページをみたらサーバ上の参照パスが書かれていたので、これを使うことにして無事起動しました。

<script src="https://cdn.socket.io/socket.io-1.4.3.js"></script>

この時期では同一PC上で複数ブラウザを立ち上げているのですが、同一ローカルネットワーク上の別端末からアクセスする場合は、index.htmlに書かれているjsの「localhost」の部分を

var ioSocket = io.connect( "http://192.168.11.6:3000" );

のように、サーバを起動しているPCのIPアドレスを指定することでアクセス可能になります。

ちょっとした小技で効率UP——アプリケーションをデーモン化〈Node.jsシリーズ vol.7〉

foreverを試そうとしてインストールするも、何度やってもエラーでインストールできず。。。

いろいろ試したところ、npm configを下記のように書き換えることでやっとインストールができました。

npm config set registry="http://registry.npmjs.org/"

【参考】【NODE.JS】NPMでFOREVERのインストールに失敗してしまう。。。

これでざっとNode.jsのテストが出来ました。今ひとつわかってない気がするので、間違い等ありましたら、ご指摘下さい。

【その他参考サイト】
・第1回 Node.jsとは:基礎から学ぶNode.js|gihyo.jp … 技術評論社
・node.jsのrequire相対パスうざい問題 – Quita
・SwiftでSocket.io (nodes利用) – Quita
・Socket.IO入門 (全10回) – プログラミングならドットインストール
・How do I check the version of socket.io and update it – Stack Overflow
・Node.jsとSocket.IOによるPCとスマホブラウザのペアリングデモ – ICS MEDIA
・5分くらいで出来るnode.js(0.6) + socket.io(0.8x)のサンプルプログラム – 大人になったら肺呼吸
・Mac OS X から Node.js をアンインストールする方法
・【Node.js】足りないモジュールをたったの一行でインストールするコマンド – Quita

【jQuery】jQueryRotate.jsがIE8で動かなかった件(解決)

某ページで画像を回転させる必要があって、見つけたのがコレ。

jQueryRotate – rotate image in browser by any angle

【参考】
いろんな画像をクルックルクルックル回すイケイケjQueryプラグイン 「jQueryRotate」 | うずまきブログ
アナログ時計をJavaScriptで作ってみた

IE6以降対応ってことで楽勝だと思っていたら、IE8で動かなかった。。。厳密には1回だけ回ってくれるけど、それ以降はまったく無反応。具体的にはこんな関数をクリックイベントに割り当ててました。

【JavaScript】

var rotateImg = 0;

//画像回転処理(右)
function clickTiltRightFunc(){
	rotateImg += 90;
	rotateImg = (rotateImg >= 360) ? 360 - rotateImg : rotateImg;
	
	$("#thumbImg img").rotate(rotateImg);
	$('input[name="rotateImg"]').val(rotateImg);
}

【html】

<div class="photo" id="thumbImg">
 <img src="xxxx.jpg">
</div>

公式サイトを改めてIE8で見たら「動いてないじゃん!」となったんだけど、「公式サイトのjQueryが2.xだから」と某所から指摘を受けて納得。

【参考】
jQuery 2.0がついに正式リリース。IE8以前はサポートせず、より軽量で高速に - Publickey
jQueryのバージョン1.xと2.xの違いは対応ブラウザ。Win XPつまりIE8以下・6以上のサポートが必要なら1系を使い,モダン・スマホサイトは2系を使う – プログラミングとIT技術をコツコツ勉強するブログ

で、いろいろ試してみて気づきました。IE8ではimgタグを直接指定しないとダメらしい。つまり $(“#thumbImg img”) のようなサイン省参照ではなく、直接imgタグにidをつけてそのidに対してrotateするということです。

改定版がこちら。

【JavaScript】

var rotateImg = 0;

//画像回転処理(右)
function clickTiltRightFunc(){
	rotateImg += 90;
	rotateImg = (rotateImg >= 360) ? 360 - rotateImg : rotateImg;
	
	$("#thumbImg").rotate(rotateImg);
	$('input[name="rotateImg"]').val(rotateImg);
}

【html】

<div class="photo">
 <img src="xxxx.jpg" id="thumbImg">
</div>

たったコレだけなんですけど、意外とハマったのでメモしておきます。

Twitterで特定期間のツイートを検索する

先日、Twitterについて調べていたときに、こんな記事を見つけました。

・Twitterで期間を限定して検索する方法&ヘルプに載ってない裏技テク – 聴く耳を持たない(片方しか)
・Twitterの新機能! 過去のツイートを検索する方法 | iPhoneひとすじ! かみあぷ速報

試してみると、コレ、公式サイトの検索窓を使わずにブラウザで直接URL叩いても動くんですね。Twitterにログインしてなくても表示されます。例えば、こんな感じ

で、フォローしている鍵アカも検索できるように、twitterアカウントでOAuth認証を通すようにして、ひとネタページを作ってみました(下の画像からリンクしてます)。

thum_fb_2tones
【twitter】○○年の今頃何してた? | 2tones-dev.net

やってることは基本的に、入力したアカウントと選択した年と現在の日付から前後一日を取得して、JavascriptでURL生成してwindow.open()してるだけです(笑)。あとアカウントのデフォルトは、OAuth認証で返ってくる情報から自分のアカウントを自動入力しています。

まあ検索するなら公式サイトの検索窓使うかURL直打ちすればいいんですが、いちいちキーワード入力するのも面倒だし(ライトユーザーはそんなことしなさそうだし)、ふと「去年の今頃何してたっけ?」みたいなことを思ったりすることもたまにあるので、ネタとしてはいいかなと思いまして(笑)。

ちなみに検索に前後一日ずつの幅を持たせているのは、なるべく検索にヒットするようにです。みんながみんな、毎日ツイートしてるわけじゃないと思うので。

APIでも過去検索できればアプリに組み込んだりできるんですが、そこはまあ負荷の問題なんですかね。制限があるのは。

ちなみにOAuth認証はこの辺を参考にPHPで書いています。

・【PHP】2015春版!TwitterOAuthでログイン機能を実装する – Qiita
・PHP と Twitter API V1.1 で OAuth 認証を行う 「タイムライン取得」「呟き(つぶやき)投稿」「ログイン」(API V 1.1)

twitterでリンクにサムネイル設定するには「Twitter Cards」というのを使うんですね。この辺も普段あまり自分で書かないのでしりませんでした。

・すぐにできるTwitter Cards設定 – E-riverstyle Vanguard

FacebookのOPG用画像をチェックするのに、こんなサイトがありました。フルサイズでも小さい表示でもうまく表示されるよう確認するのに便利です。

・OGP画像シミュレータ | og:image Simulator

Twitterに限らずAPI周りの情報はたくさんありますが、古いAPIの情報だったり、Developersページのデザインが変わってたりするので、その辺が注意点かと。

【補足】
・当初iPhoneのSafariでURLを叩いてみたら、Safariで未ログインだと検索結果が表示されませんでした。iPhoneだとモバイル用のページが開くので、その辺の仕様に違いがあるのかなと。また最初は別ウィンドウで表示するようにしていたのですが、iPhoneだと別タブで公式サイトを開いていると、そちらに結果が表示されてしまいました。

・上記の現象があったのでtwitterのOAuth認証を組み込んだのですが、このブログ書くために再度テストしたら未ログインでも結果表示されていました。キャッシュの問題なのか、再現しないのでちょっとわからないですが。。。