今更ながらPHPでFacebook API でログイン処理を書いてみた(v2.8)

【MacOS X 10.11.6 + Facebook API v2.8 + Facebook SDK for PHP v5.0】
必要があってFacebookのログイン処理をPHPで書いてみたので、そのメモです。

1.Facebookに開発者登録してサイトをアプリとして登録

Facebook APIを使うには、Facebookに開発者登録してサイトをアプリとして登録する必要があります。その辺はここでは割愛するので、ご存じない方は下記を参照のこと。
Facebook開発者登録の方法とアプリ作成方法の手順

※この辺の説明はたくさんありますが、サイトによってはキャプチャ画面が古いものもあるので、適宜読み替えが必要。

2.Facebook SDK for PHP v5.0 のインストール

以下から、手動インストール。
facebook for developers | スタートガイド – ウェブSDK
解凍後、サイトのルートに「php-graph-sdk-5.0.0/src/Facebook」をフォルダごとコピーする。

3.PHPでのログイン処理

下記のサイトを参考にしました。コールバックのURLもパス付きで書いてあって、わかりやすいです。
[Facebook SDK for PHP v5.0]を利用してログイン認証をおこなう[Oauth]

ざっと図解すると、こんな感じ。

FacebookAPIの画面遷移

以下、各phpの概要です。

・config.php

Facebook APIで使用する、アプリID,app secret,APIのバージョンを定義。実際にAPIを使用するphpに、require_onceで読み込ませるために共通化。各設定項目とダッシュボードとの対応は、以下の通り。

app_id:アプリID
app_secret:app secret
default_graph_version:APIバージョン

アプリのダッシュボード画面

・index.php

Facebook APIでアプリにログインさせる。実際にはAPI側で、Facebookのログイン及びアプリの認証の可否を確認する画面が表示されるため、ユーザーの意に反してログインされることはない。

・callback.php

Facebook APIからのコールバックを受け取るphp。ここでアクセストークンを取得し、ログインしたユーザー情報を取得する。ユーザーがログインしない/アプリを許可しない場合、取得できないのでindex.phpへのリンクを表示。取得できた場合は、member.phpへ。

・member.php

ログインしたユーザー情報を表示する。

・logout.php

アプリからログアウトする。クッキーを削除して、セッションを破棄することでログアウトになる。

4.コールバックURLをアプリの管理画面に登録

コールバックURLはアプリの管理画面に登録する。左メニューから「プロダクト」>「+製品を登録」>「Facebookログイン」選ぶ。「クライアントOAuth設定」の「有効なOAuthリダイレクトURI」にコールバックURLをフルパスで指定し、右下の「変更を保存」ボタンを押す。

Facebookログインの画面でコールバックURLを指定して、保存する。

4.アクセストークン取得にはコールバックURLの指定が必要

参考サイトのコードをそのまま使ったら、「URLを読み込めません: このURLのドメインはアプリのドメインに含まれていません。このURLを読み込むには、アプリ設定のアプリドメインにすべてのドメインとサブドメインを追加してください。」というエラーが出た。

以下のサイトを参考に、アクセストークン取得のところにコールバックのURIを指定してエラーを解消。

【PHP】 Facebook APIのOauthのエラー解決「URLを読み込めません」「Can’t Load URL」 | codechord

実際にはこんな感じに書き換える。

//アクセストークンを取得する
$accessToken = $helper->getAccessToken('https://c-geru.sakura.ne.jp/c-geru/fb_test/callback.php');

5.デモ

実際の動作デモは、こちらからどうぞ。

6.各ソース

上記4.で述べたように、アクセストークン取得(callback.php)とログイン(index.php)の2箇所でコールバックURLが必要になるため、このサンプルでは、config.phpにコールバックURLを定義して、各phpではセッションから値を取得するようにしました。

・config.php

<?php 
session_start(); 
require_once("Facebook/autoload.php"); 
//コールバックURLをセッションに保存 
$_SESSION['fb_callback_ulr'] = 'https://c-geru.sakura.ne.jp/c-geru/fb_test/callback.php'; 
$fb = new Facebook\Facebook([
  'app_id' => '○○○○○○○○○',
  'app_secret' => '△△△△△△△△△△△△△',
  'default_graph_version' => 'v2.8',
]);

・index.php

<?php 

session_start(); 
header("Content-type: text/html; charset=utf-8"); 

//設定ファイルを読み込み 
require_once("config.php"); 

//コールバックURLの取得 
$callcak_url = $_SESSION['fb_callback_ulr']; 
$helper = $fb->getRedirectLoginHelper();

//オプションによって認証画面の文言が変わる
//$permissions = ['email', 'user_likes','user_posts']; //あなたの公開プロフィール、メールアドレス、タイムライン投稿、いいね!。
//$permissions = ['email', 'user_likes']; //あなたの公開プロフィール、メールアドレス、いいね!。
//$permissions = ['email', 'user_posts'];//あなたのタイムライン投稿。
//$permissions = ['email','user_friends'];//あなたの公開プロフィール、友達リスト、メールアドレス。
//$permissions = ['email'];//あなたの公開プロフィール、メールアドレス。
$permissions = [];//あなたの公開プロフィール。
$loginUrl = $helper->getLoginUrl($callcak_url, $permissions);

echo '<a href="' . $loginUrl . '">ログインする</a>';

・callback.php

<?php 

session_start(); 

header("Content-type: text/html; charset=utf-8"); 

//設定ファイル 
require_once("config.php"); 

//コールバックURLの取得 
$callcak_url = $_SESSION['fb_callback_ulr']; 

//タイムゾーンの設定 
date_default_timezone_set('asia/tokyo'); 

$helper = $fb->getRedirectLoginHelper();

try {
	if (isset($_SESSION['facebook_access_token'])) {
		$accessToken = $_SESSION['facebook_access_token'];
	} else {
		//アクセストークンを取得する
		$accessToken = $helper->getAccessToken($callcak_url);
	}
} catch(Facebook\Exceptions\FacebookResponseException $e) {
	// When Graph returns an error
	echo 'Graph returned an error: ' . $e->getMessage();
	exit;
} catch(Facebook\Exceptions\FacebookSDKException $e) {
	// When validation fails or other local issues
	echo 'Facebook SDK returned an error: ' . $e->getMessage();
	exit;
}

if (isset($accessToken)) {
	//アクセストークンをセッションに保存
	$_SESSION['facebook_access_token'] = (string) $accessToken;
	
	header('Location: member.php');
	exit();
}else{
	echo "<a href='index.php'>はじめのページへ</a>";
}

・member.php

<?php 

session_start(); 
header("Content-type: text/html; charset=utf-8"); 
//設定ファイル 
require_once("config.php"); 

if (isset($_SESSION['facebook_access_token'])) { 
$accessToken = $_SESSION['facebook_access_token']; 
$fb->setDefaultAccessToken($accessToken);
	
	try {
		//取得するユーザ情報の指定
		$response = $fb->get('/me?fields=id,name,first_name,last_name,email,gender');
		$profile = $response->getGraphUser();
		
		//ユーザ画像取得
		$UserPicture = $fb->get('/me/picture?redirect=false&height=200');
		$picture = $UserPicture->getGraphUser();
		
	} catch(Facebook\Exceptions\FacebookResponseException $e) {
		// When Graph returns an error
		echo 'Graph returned an error: ' . $e->getMessage();
		exit;
	} catch(Facebook\Exceptions\FacebookSDKException $e) {
		// When validation fails or other local issues
		echo 'Facebook SDK returned an error: ' . $e->getMessage();
		exit;
	}
	
	$id=$profile['id'];
	$name=$profile['name'];
	$first_name=(isset($profile['first_name'])) ? $profile['first_name'] : '';
	$last_name=(isset($profile['last_name'])) ? $profile['last_name'] : '';
	$email=$profile['email'];
	$gender=(isset($profile['gender'])) ? $profile['gender'] : '';
	$picture_url = $picture['url'];
    
   
	echo "アクセストークン:".$accessToken."";
	echo "ID:".$id."";
	echo "名前:".$name."";
	echo "性別:".$gender."";
	echo "ファーストネーム:".$first_name."";
	echo "ラストネーム:".$last_name."";
	echo "メール:".$email."";//ユーザが未公開・未設定の場合は表示されない
	echo "<img src=".$picture_url.">";
	echo "<a href='logout.php'>ログアウト</a>";

}else{
	header('Location: index.php');
	exit();
}
?>

<!--#_=_を排除する-->
<script type="text/javascript">
if (window.location.hash && window.location.hash == '#_=_') {
  if (window.history && history.pushState) {
      window.history.pushState("", document.title, window.location.pathname);
  } else {
    // Prevent scrolling by storing the page's current scroll offset
    var scroll = {
        top: document.body.scrollTop,
      left: document.body.scrollLeft
    };
    window.location.hash = '';
    // Restore the scroll offset, should be flicker free
    document.body.scrollTop = scroll.top;
    document.body.scrollLeft = scroll.left;
  }
}
</script>

・logout.php

<?php
session_start();
 
header("Content-type: text/html; charset=utf-8");

//セッション変数を全て解除
$_SESSION = array();
 
//セッションクッキーの削除
if (isset($_COOKIE["PHPSESSID"])) {
    setcookie("PHPSESSID", '', time() - 1800, '/');
}
 
//セッションを破棄する
session_destroy();
 
echo "ログアウトしました。"; 
echo "<a href='index.php'>はじめのページへ</a>";

【Xcode】iOS10で各種ユーザーデータへのアクセス許可を参照

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

前回の記事でアプリの初回起動時以外に、各種ユーザーデータへのアクセス許可をどのように参照すればいいのかを前回対象とした「写真」「マイク」「音声認識」「メディアライブラリ」についてまとめました。

※以下、コードは Objective-C です。

写真


従来の写真アクセスである「ALAssetsLibrary」でコードを書いていると・・・

'ALAssetsLibrary' is deprecated: first deprecated in iOS 9.0 - Use PHPhotoLibrary from the Photos framework instead

というワーニングが出ます。

従来の「ALAssetsLibrary」はiOS9以降非推奨になり、Photos frameworkの「PHPhotoLibrary」を使いなさいということなので、「PHPhotoLibrary」ベースでのチェック方法です(もちろん写真のアクセス自体も「PHPhotoLibrary」ベースである前提です)。

//写真設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitPhotoLibrary
{
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    switch (status) {
        case PHAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            return NO;
            break;
            
        case PHAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            return NO;
            break;
            
        case PHAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            return NO;
            break;
            
        default:
        case PHAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            return YES;
            break;
    }
}

マイク


いくつか方法があるようですが、ここでは「AVAudioSessionRecordPermission」を使ってます。

//---マイク使用許可チェック
-(BOOL)isPermitMic{
    AVAudioSessionRecordPermission status = [AVAudioSession sharedInstance].recordPermission;
    
    switch (status) {
        case AVAudioSessionRecordPermissionUndetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---AVAudioSession:未選択");
            return NO;
            break;
            
        case AVAudioSessionRecordPermissionDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            NSLog(@"---AVAudioSession:拒否");
            return NO;
            break;
            
        default:
        case AVAudioSessionRecordPermissionGranted:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---AVAudioSession:許可");
            return YES;
            break;
    }
}

【参考】
マイクのアクセス許可 – iOS9とXcode7
authorizationStatusForMediaType: – AVCaptureDevice | Apple Developer Documentation
AVAudioSessionRecordPermission – AVFoundation | Apple Developer Documentation
Swift: アルバム・カメラ・マイク・プッシュ通知のアクセス許可判定一覧 | siro:chro

メディアライブラリ


MPMediaLibraryの「authorizationStatus」を使います。Appleのリファレンスにもあんまり詳しいことは書いてないです。何でだ?

//メディアライブラリ設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitMediaLibrary
{
    MPMediaLibraryAuthorizationStatus status = [MPMediaLibrary authorizationStatus];
    switch (status) {
        case MPMediaLibraryAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---MPMediaLibrary:未選択");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            NSLog(@"---MPMediaLibrary:許可を変更できない");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
             NSLog(@"---MPMediaLibrary:拒否");
            return NO;
            break;
            
        default:
        case MPMediaLibraryAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---MPMediaLibrary:許可");
            return YES;
            break;
    }
}

【参考】
authorizationStatus – MPMediaLibrary | Apple Developer Documentation
MPMediaLibraryAuthorizationStatus – MediaPlayer | Apple Developer Documentation

音声認識(Speech.Framework)


SFSpeechRecognizerの「authorizationStatus」を使います。

    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
        dispatch_async(dispatch_get_main_queue(), ^{
            switch (status) {
                case SFSpeechRecognizerAuthorizationStatusAuthorized: {
                    NSLog(@"--許可あり");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusDenied: {
                    NSLog(@"--拒否");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusRestricted: {
                    NSLog(@"--設定により使えない");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusNotDetermined: {
                    NSLog(@"--未認証");
                    break;
                }
            }
        });
        
    }];

【参考】
SFSpeechRecognizer – Speech | Apple Developer Documentation
authorizationStatus – SFSpeechRecognizer | Apple Developer Documentation
SFSpeechRecognizerAuthorizationStatus – Speech | Apple Developer Documentation

おまけ:自分のアプリの設定を呼び出す


各種ユーザーデータへのアクセス許可を参照して、許可がないものは改めて許可してもらいましょう。ということで、設定アプリの中にある自分の設定を呼び出す方法です。

いわゆるURLスキームというやつらしいですが、iOS10で使い方(openURLのメソッド)が変わっているようです。また下記の参考リンクに「URLスキームの内容をinfo.plistに記載する」とありますが、単純に自分のアプリ設定を呼び出すだけなら不要なようです(実機で動作確認済)。

//このアプリの設定を呼び出し
- (void)prefsButtonPushed {
    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url
                                           options:@{}
                                 completionHandler:nil];
        //以下は、iOS9までの書き方
        //[[UIApplication sharedApplication] openURL:url];
    }
}

これを実行すると、自分のアプリの設定が表示されます。

自分のアプリの設定が表示されます。
自分のアプリの設定が表示されます。

【参考】
application:openURL:options: – UIApplicationDelegate | Apple Developer Documentation
UIApplicationOpenSettingsURLString – UIKit | Apple Developer Documentation
[iOS 10] UIApplication の openURL: が Deprecated になりました | Developers.IO
iOS9でカスタムURLスキームの遷移に失敗するときの注意点 – Qiita
【iOS9】特定のURLスキームのみを呼び出し可能にする→.canOpenURL(url)について | MUSHIKAGO APPS MEMO
投稿の編集 ‹ AS blind side — WordPress
標準アプリのURLスキームについて – もう一人のY君

最後に


今回出てきた「Photo.Framework」「Speech.Framwork」については、別途まとめたいですが、とりあえず気になる人は下記を参考に。

・Photo.Framework
Photos vs Assets Library – いまさら始めるPhotos.framework
[iOS 8] PhotoKit 1 – Photos frameworkの概要 | Developers.IO
Photos Framework を使って写真.appと画像をやりとりする – Qiita
Photos frameworkを使ってiPhoneアルバム内の写真を取得・削除する+α – Think Big Act Local
Photos.framework でカメラロールを取得する – ObjecTips
投稿の編集 ‹ AS blind side — WordPress

・Speech.Framwork
[iOS 10] SFSpeechRecognizerで音声認識を試してみた | Developers.IO
[iOS 10] SFSpeechRecognizerの音声認識処理の仕組みを見てみる | Developers.IO
【iOS 10】Speechフレームワークで音声認識 – 対応言語リスト付き – Over&Out その後
新規投稿を追加 ‹ AS blind side — WordPress
【iOS】Speech Frameworkの実装 – Qiita
【iOS 10】Speech Frameworkで音声認識 – あたも開発ブログ
【iOS10】Speech Recognition API(音声認識API)の制約まとめ – Qiita

【Xcode】iOS10以降は各種ユーザーデータへアクセスに許可が必要

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

iPhoneの音楽ライブラリからアートワークを使おうと思って、前に書いた古いコードから必要な部分を移植して動かしてみたらエラーが出た。

[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSAppleMusicUsageDescription key with a string value explaining to the user how the app uses this data.

iOS10以降は、ユーザーデータにアクセするには、Info.plistに該当アクセスに対応したキーと使用目的を示すテキストを記述する必要があるようです。詳しくは下記リンクを。

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです | Developers.IO

具体的には、TARGETSからinfoを選んで、下記のように追加するだけ。

TARGETS > info で必要なアクセス先に対する許可を追加します。
TARGETS > info で必要なアクセス先に対する許可を追加します。ここではメディアライブラリ、マイク、音声解析、写真ライブラリの4つを指定。

Keyの選択肢は、全て「Privacy – 」で始まっているので、そこから必要なものを追加すると、アプリの初回起動時にそれぞれ許可を求めるダイアログが表示されます(keyに指定したString部分が、ダイアログの説明分として表示されます)。

info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。
info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。

ダイアログでの選択(許可するかしないか)は、iPhoneの 設定>アプリ名の中に保存され、ユーザーが切り替え可能です。逆に言うと、初回起動時以降は、ユーザーに設定で許可を変更するよう促す必要がある、ということですね。

アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。
アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。

で、冒頭の音楽ライブラリのアートワークを使うには、メディアライブラリへのアクセス許可を求めればいいようです(今回は音楽データそのものは使っていません)。

【参考】
iOS10ではカメラアクセスなどの目的を明示しないと強制終了する – Qiita
[iOS 10] ユーザーのプライバシー情報にアクセスするときは理由を書こう | Developers.IO
iOS 10 からユーザの許可を求めるアラートへの説明文追加が必須に – 強火で進め

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」ボタンを押して、ダウンロードしたプロジェクトのフォルダを開きます。

A-FRAMEを使ってみた(2)タグ編

A-FRAMEのドキュメントを見ていて、サンプルなどに出てくるタグが見当たらないことがあったのでまとめてみました。ドキュメントは機能や要素毎にまとめられているので、タグから引けないので。

A-FRAME タグ一覧(version 0.2.0 版)

タグ名 ドキュメント 備考
<a-scene> Scene A-FRAMEの global root object。canvasもここに含まれる。
<a-entity> Entity オブジェクトの位置、回転、およびスケールのなどを定義するタグ。
<a-animation> Animations アニメーションを定義するためのタグ
<a-assets> Asset Management System プリロード要素を定義するためのタグ
<a-mixin> Mixins <a-assets>内に定義する、再利用可能なコンポーネント属性を定義するタグ
<a-asset-item> Asset Management System <a-assets>内に定義する3D用のファイル(.dae,.mtl,obj)。
<audio> Asset Management System <a-assets>内に定義する音声ファイル。
<img> Asset Management System <a-assets>内に定義する画像ファイル。
<video> Asset Management System <a-assets>内に定義するビデオファイル。
<a-box> <a-box> 箱状オブジェクト。ver 0.1.0までは、<a-cube>
<a-camera> <a-camera> カメラオブジェクト
<a-collada-model> <a-collada-model> 3D COLLADA オブジェクト(.dae)
<a-cone> <a-cone> 円錐オブジェクト
<a-cursor> <a-cursor> カーソルポインタ
<a-curvedimage> <a-curvedimage> 湾曲した板状オブジェクト
<a-cylinder> <a-cylinder> 円柱オブジェクト
<a-image> <a-image> <a-plane>に画像を適用したオブジェクト
<a-light> <a-light> 照明オブジェクト
<a-obj-model> <a-obj-model> 3Dオブジェクト。.obj、.mtlを指定可能。
<a-plane> <a-plane> 板状オブジェクト
<a-ring> <a-ring> 板状のリングオブジェクト。radius-inner=”0″ (内径=”0″)で円盤状になる。
<a-sky> <a-sky> シーンの背景
<a-sphere> <a-sphere> 球体オブジェクト
<a-torus> <a-torus> 円管形状オブジェクト(arc=360 でドーナツ状)
<a-video> <a-video> <a-plane>にビデオを適用したオブジェクト
<a-videosphere> <a-videosphere> シーンの背景に動画を適用したオブジェクト

上記の説明は、執筆時点でのA-FRAME version 0.2.0 を前提としています。

不備、誤りなどありましたらご指摘下さい。

A-FRAMEを使ってみた(1)360°画像編

Playstation VRの予約が始まったり、諸々盛り上がりを見せつつあるようにみえるVR界隈。興味はあるけど、Oculus Riftもその開発にPC用意するとか考えると結構なお値段だし、基本静観してたのですが、HTMLベースのVR用のフレームワークである「A-FRAME」の記事をみつけたので、試してみました。

「A-Frame」でVR開発入門!HTMLの追記だけでスマホブラウザから好きな場所を360度パノラマ画像体験 – paiza開発日誌

まずは上の記事を参考に、360°パノラマ画像のVRを作ってみました。

1.特別な機材なしで360°パノラマ画像を作る。

通常、360°パノラマ画像を作るにはRICOH THETAなどの対応カメラが必要ですが、iOS/Android用のGoogle ストリートビューアプリで作成が可能です。使い方は、上の参考リンクに詳しいのでをそちらを参照のこと。

複数の画像をつないで全天球型の画像を作成するので、撮影回数が多くなるのが難点ですが、特別な投資なしで作れるのでテストとしては十分だと思います。ちなみにこんな感じの画像が生成されます。

ストリートビューアプリで作成した360°画像。なるべく被写体(人や物)が近くにない方が繋ぎがうまくいく(ズレが目立たない)ようです。
ストリートビューアプリで作成した360°画像。なるべく被写体(人や物)が近くにない方が繋ぎがうまくいく(ズレが目立たない)ようです。

2.HTMLを書く

VRコンテンツとして表示するために、HTMLを書きます。A-FRAMEは基本的にはタグベースなので、A-FRAMEのjsライブラリを読み込んで、必要なタグを書くだけです。以下、サンプルのHTML全文です。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>神保町の路地 | A-Frame</title>
    <meta name="description" content="神保町の路地 | A-Frame">
    <script src="https://aframe.io/releases/0.2.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
        <a-sky src="img/IMG_2277.jpg"></a-sky>
        <a-camera wasd-controls-enabled="true"></a-camera>
    </a-scene>
  </body>
</html>

<a-scene>は、canvasタグに相当します。その中に要素を定義していきます。
<a-sky>は、VR空間の背景です。VR空間が大きな球体として定義されていて、その内側にカメラがあるイメージです。その球体の内側に色や360°パノラマ画像を指定することで背景として表示されます。
<a-camera>は、カメラです。スマホだと端末の傾き、PCならマウスのドラッグ方向で、このカメラの向きが変わります。

※<a-camera>が省略された場合は、デフォルトのカメラが設置されるので、このタグがなくても表示されます。

iPhoneで見た様子。右下のメガネアイコンをタップするとWebVRモードで表示されます。
iPhoneで見た様子。右下のメガネアイコンをタップするとWebVRモードで表示されます。
WebVRモードで表示した様子。PCブラウザなどWebVRがサポートされていない場合は切り替えできない。
WebVRモードで表示した様子。PCブラウザなどWebVRがサポートされていない場合は切り替えできない。

実際のサンプルは、下のリンクからどうぞ。

神保町の路地 | A-Frame

さくらのレンタルサーバでモリサワフォントを使ってみた

さくらのレンタルサーバでモリサワのWebフォントが使えるようになるというプレスリリースを見て、ググってみたらもう使えるようなので試してみました。

モリサワとさくらインターネットが協業し、無料でのWebフォント導入を実現

インストール手順は、さくらのサポートページにありました。
Webフォントプラグイン機能|さくらインターネット公式サポートサイト

私の場合は、WordPressを導入済みなので、「設定手順(追加インストール)」で行いました。以下、気づいた点をメモしておきます。

1.選べるドメインは1つだけ。独自ドメインとそのサブドメインの両方には使えない。

選べるドメインは1つだけとい制限自体はサポートサイトに書いてあるのですが、サブドメインも別ドメインとなるため、例えば下の例だと「c-geru.com」「app.c-geru.com」の両方で使うことは出来ないということです。

独自ドメインとそのサブドメインは別ドメイン扱いとなるため、両方同時にはWebフォントを適用できない。
独自ドメインとそのサブドメインは別ドメイン扱いとなるため、両方同時にはWebフォントを適用できない。
2.プラグインのバージョン

サポートサイトのキャプチャのバージョンは 0.9.0でしたが、インストールされたバージョンは 1.0.1 になっていました。

ブログ執筆時点のプラグインのバージョンは、1.0.1。
ブログ執筆時点のプラグインのバージョンは、1.0.1。
3.フォントの指定は4つのカテゴリ単位

フォントの指定は、見出し、リード、本文、強調(太字)の4つにタグをカテゴライズして行うようになっていて、同時に使用可能なWebフォントは4つまでになります。

TypeSquare Webfonts画面にあるフォント設定クラスの指定。
TypeSquare Webfonts画面にあるフォント設定クラスの指定。

またこの設定は、TypeSquare Webfontsのプラグイン画面にあるため、フォントテーマ毎に変更することができません。見出し、リード、本文、強調(太字)の4つにカテゴライズされるタグは、デフォルトを含む全てのテーマで同じになります。

フォントテーマ設定画面で指定できるのは、各カテゴリに対する適用フォントのみ。
フォントテーマ設定画面で指定できるのは、各カテゴリに対する適用フォントのみ。

※フォントテーマの適用、使用の有無は投稿単位で行えます。

今回は取りあえずのテストだったので、この投稿では用意されているテーマからポップ ( 見出し:新丸ゴ 太ライン / リード:はるひ学園 / 本文:じゅん 201 / 太字:じゅん 501 )を選んでみました。

プラグインのインストールと設定はカンタンなので、さくらのレンタルサーバを使っている方は試してみてください。

8つのテーマがデフォルトで用意されています。
8つのテーマがデフォルトで用意されています。

【追記 2016.7.1】
投稿ページを見ると、フォントテーマがサイドバーにも適用されてしまっています。TOPページではサイドバーには影響しません。これ、ページ毎に設定を持っているんだとしたら、ちょっと微妙ですね。。。

左:TOPページ。右:投稿ページ。
左:TOPページ。右:投稿ページ。

Adobe Capture CCで紙に描いた絵をパスに変換してみた

【Adobe Capture CC 2.0 + Illustrator CC 2015 + iPhone SE + MacOSX 10.11.5】
タイトル通りですが、ちょうどアドビサイトのチュートリアルを見つけたので、試してみました。

ロゴを手軽に! 紙に描いたイラストでロゴをつくろう! | Adobe Illustrator CC チュートリアル

細かい手順は上のリンクを見ていただくとして、ざっくりの流れと気づいた点などを書いておきます。

まず、Adobe Capture CCをダウンロードします。

今回は、先日某アイドルを描いた落書きがあったので、それを使用します。

某アイドルの落書き
某アイドルの落書き

Adobe Capture CCのキャプチャ画面の様子。きちんと線だけを認識してます。カメラを真上に置くことで発生する影には影響されないようです(この写真だと認識(緑の線)が弱いので、キャプチャ前にスライダーで調整)。

IMG_2249

シェイプ編集画面では、消しゴムを使って不要な線を削除。ピンチも出来るけど、線のないところなどを選んでやらないと、消しゴムとご認識されて意図しないところが消えてしまうことも。右下はandoではなくて戻すボタンのようで、押すと編集自体が初期化されました。

IMG_2250

で、Illustratorを立ち上げてみたものの、ライブラリには何も表示されず・・・。いろいろ見たところ、デスクトップのCreate Cloudアプリからファイル>同期を開始を押すことで、ライブラリに表示されるようになりました(表示されるまでタイムラグあり)。

※この辺は初めて使うので、別に方法があったりするのかしらん?

デスクトップのCreate Cloudアプリで、ファイルの同期を開始する。
デスクトップのCreate Cloudアプリで、ファイルの同期を開始する。
Illustratorのライブラリウィンドウに表示された様子(画面右下)
Illustratorのライブラリウィンドウに表示された様子(画面右下)

あとはIllustratorでの作業なので特に問題はありませんが、とりあえず参考ページに従ってテキストなど入れて余分なパスを多少調整したりしてできたのがこちら。

完成品
完成品

パスデータは「線ではなくシェイプ」で作成されるので、この元絵のように手描きでくっついている部分は1つのパスになってしまいます。なので、人物の塗りを分けるためにはさみ/ナイフツールでパスを分割しています。

結構サクッと使えていい感じですが、iPhoneとMac(PC)を往復するのが面倒かなという気も。Adobe Capture CC自体は、写真を撮るだけでなくライブラリの写真も使えるようなので、この機能自体をPhotoshopかIllustratorに組み込んでもらえる(または単機能のアプリ)とより便利かなと思います。

【参考】
[Illustrator]はさみツールの使い方 | バンフートレーニングスクール スタッフ ブログ
[Illustrator]ナイフの使い方 | バンフートレーニングスクール スタッフ ブログ

AS,Objective-C,Javascript,その他諸々の備忘録