PlayFabのセッション切れ

  • 投稿日:
  • by
  • カテゴリ:

PlayFab APIはログインから24時間経つとセッションが切れて、下記のエラーが起きる。

{
"code": 401,
"status": "Unauthorized",
"error": "NotAuthenticated",
"errorCode": 1074,
"errorMessage": "X-Authentication HTTP header contains invalid ticket"
}

ゲームを中断して、次の日に続きを遊ぶ場合などに起こり得るので、エラーチェックと再ログインを実装しておかないといけない。

Unity + PlayFabで課金処理

  • 投稿日:
  • by
  • カテゴリ:

App Store,Google Play Storeの課金処理はUnity IAPで行って、レシートチェックとアイテム付与をPlayFabAPIで行う。
以下、要所だけ載せます。


//////////////////////////
// Unityの課金システム構築
StandardPurchasingModule module = StandardPurchasingModule.Instance();
ConfigurationBuilder builder = ConfigurationBuilder.Instance(module);
//商品ID登録(ストアに登録したIDとPlayFabのカタログアイテムIDを統一しておくと面倒がない)
builder.AddProduct("MyItemID", ProductType.Consumable);
// 非同期の初期化を開始
UnityPurchasing.Initialize(this, builder);


//////////////////////////
//Unity IAP 初期化完了時
//////////////////////////
void OnInitialized(IStoreController controller, IExtensionProvider extensions){
	//ストアの表示価格は下記のようにして取得
	Product p=controller.products.WithID("MyItemID");
	Debug.Log(p.metadata.localizedPriceString);
}


//////////////////////////
//購入ボタン押した時
//////////////////////////
controller.InitiatePurchase("MyItemID");


//////////////////////////
//購入完了時の呼ばれる関数
//////////////////////////
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e){
	//プロダクト不明のため無視
	if (e.purchasedProduct == null){
		return PurchaseProcessingResult.Complete;
	}
	//レシートがないので無視
	if (string.IsNullOrEmpty(e.purchasedProduct.receipt)){
		return PurchaseProcessingResult.Complete;
	}
	//レシート検証&アイテム付与。iOSとAndroidで分岐
	if (Application.platform == RuntimePlatform.Android){
		ValidateAndroidPurchase(e.purchasedProduct);
	} else {
		ValidateIosPurchase(e.purchasedProduct);
	}
	//PlayFabでレシート検証完了するまでペンディングにする
	return PurchaseProcessingResult.Pending;
}

//////////////////////////
//Androidのレシート検証&アイテム付与
//////////////////////////
private void ValidateAndroidPurchase(Product purchasedProduct) {
	var googleReceipt = GooglePurchase.FromJson(purchasedProduct.receipt);
	PlayFabClientAPI.ValidateGooglePlayPurchase(new ValidateGooglePlayPurchaseRequest()
	{
		CurrencyCode = purchasedProduct.metadata.isoCurrencyCode,
		PurchasePrice = (uint)(purchasedProduct.metadata.localizedPrice * 100),
		ReceiptJson = googleReceipt.PayloadData.json,
		Signature = googleReceipt.PayloadData.signature
	}, result =>
	{
		//購入処理が完了したものとする
		controller.ConfirmPendingPurchase(purchasedProduct);
		//TODO:インベントリ更新するなど
	},
	error =>
	{
		//使用済みレシートが残っていた場合は完了扱いにする
		if (error.ErrorMessage == "Receipt already used"){
			controller.ConfirmPendingPurchase(purchasedProduct);
		}
	}
	);
}

//////////////////////////
//iOSのレシート検証&アイテム付与
private void ValidateIosPurchase(Product purchasedProduct){
	Dictionary receipt=PlayFabSimpleJson.DeserializeObject>(purchasedProduct.receipt);
	var request = new ValidateIOSReceiptRequest
	{
		CurrencyCode = purchasedProduct.metadata.isoCurrencyCode,
		PurchasePrice = (int)(purchasedProduct.metadata.localizedPrice * 100),
		ReceiptData = (string)(receipt["Payload"])
	};
	PlayFabClientAPI.ValidateIOSReceipt(request, result =>
	{
		//購入処理が完了したものとする
		controller.ConfirmPendingPurchase(purchasedProduct);
		//TODO:インベントリ更新するなど
	},
	error =>
	{
		//使用済みレシートが残っていた場合は完了扱いにする
		if (error.ErrorMessage == "Receipt already used"){
			controller.ConfirmPendingPurchase(purchasedProduct);
		}
	}
	);
}

iOS IAP Sandboxアカウントの切り替え

  • 投稿日:
  • by
  • カテゴリ:

「設定> iTunesとApp Store」でログアウトしてるのにSandbox用のアカウントを選べない状況。
購入ボタンを押すと"Confirm Your In-App Purchase"ダイアログが表示されるが アカウント入力欄が無く、パスワードしか尋ねられない。
どうやるのか探し回った結果、
「iTunesとApp Store」をスクロールした下の方にSANDBOXアカウントの項目があった
昔からありました?

PlayFabの使い方 ログを採る

  • 投稿日:
  • by
  • カテゴリ:

ユーザーの操作ログやエラーログをサーバーに送るには下記のような感じで実行するとPlayStreamに記録される。


PlayFabClientAPI.WritePlayerEvent(new WriteClientPlayerEventRequest()
	{
		Body = new Dictionary() {{ "foo", "bar"}},
		EventName = "my_custom_event"
	},
	result => Debug.Log("Logged"),
	error => Debug.LogError(error.GenerateErrorReport())
);


ログイン完了していないとAPI実行できないのでログインエラーのタイミングでは送れない。

PlayFabの使い方 ユーザーデータ周り

  • 投稿日:
  • by
  • カテゴリ:

ゲームアプリのバックエンドとしてPlayFabを触り始めました。一通り機能が揃っていてすごく便利な感じです。
英語ドキュメントやフォーラムを検索すれば大抵の事は解決しますが、日本語情報が少ないのでここにメモしておきます。

まずユーザーに紐づく情報をどう取得または保存するかについて、下記のように使い分けるのが良さそうです。

・プレイヤー表示名...
  PlayerProfileのDisplayNameで取得。UpdateUserTitleDisplayNameで更新。

・プレイヤーの経験値やハイスコアなどの数値...
  Statisticsデータとしてサーバー側のCloudScriptを呼び出して保存する。
  不正対策としてクライアントからは直接書き込めないようにデフォルト設定されている。
  Statisticsとして保存しているデータはLeaderboard(ランキング機能)として扱える。

・ゲーム内通貨の所持数...
  GetPlayerCombinedInfoのUserVirtualCurrencyで取得。
  不正対策としてクライアントからは通貨の加算減算ができないようにデフォルト設定されている。

・武器やアイテムやスキルの所持フラグ...
  マスター情報はCatalogで管理し、ユーザーの所持データはGetUserInventoryで取得。
  販売アイテムじゃない物はprice 0でカタログ登録しておき、サーバー側のGrantItemsToUserで付与するか、クライアント側のPurchaseItemで所持する。

・フレンド情報...
  GetFriendsListAddFriendを使う。
  フレンドというより一方的なフォロー登録なので、相互フォローしたいなら自前で処理を書く。

・そのほか諸々のユーザ情報...
  UserDataとして取得や保存する。
  クライアントから書き換えられたくないデータは、サーバー側でUpdateUserReadOnlyDataを使う。


ログイン時にまとめてデータを取得するには、下記のようにGetPlayerCombinedInfoRequestParamsを指定する。


GetPlayerCombinedInfoRequestParams InfoRequestParams = new GetPlayerCombinedInfoRequestParams()
{
	GetPlayerProfile = true,
	ProfileConstraints = new PlayerProfileViewConstraints()
	{
		ShowDisplayName = true
	},
	GetPlayerStatistics =true,
	GetUserInventory = true,
	GetUserData = true,
	GetUserReadOnlyData = true,
	GetUserVirtualCurrency = true
};

PlayFabClientAPI.LoginWithCustomID(
	new LoginWithCustomIDRequest()
	{
		TitleId = PlayFabSettings.TitleId,
		CustomId = SystemInfo.deviceUniqueIdentifier,
		CreateAccount = true,
		InfoRequestParameters = InfoRequestParams
	}, 
	(result) => {}, 
	(error) => {}
);

Photon Unity NetworkingのPhotonTransformViewで微妙にずれる

  • 投稿日:
  • by
  • カテゴリ:

位置同期をPhotonTransformViewで行う際、
補間処理(Interpolate Option)は「Estimated Speed」を使うのが良さそうなのだが
移動停止した時の座標が完全に同期せずわずかにずれたままになる現象がある。

これを解消するには、PhotonTransformViewPositionControl.csの91〜94行目あたりを下記のようすると良さそう。

float estimatedSpeed = (Vector3.Distance(m_NetworkPosition, GetOldestStoredNetworkPosition()) / m_OldNetworkPositions.Count) * PhotonNetwork.sendRateOnSerialize;
if (estimatedSpeed < 1f)
{
currentPosition = targetPosition;
}
else
{
// move towards the targetPosition (including estimates, if that's active) with the speed calculated from the last updates.
currentPosition = Vector3.MoveTowards(currentPosition, targetPosition, Time.deltaTime * estimatedSpeed);
}

要は、推定速度がほぼ0になってしまうと最後まで移動してくれないので
速度 1f未満なら強制的に目標値ぴったりに移動させている。

Photonを使い始めるにあたっては下記の記事がすごく丁寧で分かりやすい。
【Unity】僕もPhotonを使いたい #01〜#14

[Unity] AndroidのWebViewでHTML5 videoを表示

  • 投稿日:
  • by
  • カテゴリ:

AndroidのWebViewでYoutubeなどvideoタグを使った映像再生をしようとしてはまった点をメモ。
WebViewプラグインはgree/unity-webviewを使用。

1)まず、映像が表示されない現象。(音だけ流れて、画面表示は黒か白のまま)
AndroidManifestのandroid:hardwareAccelerated="true"は正しく記述されているのに発生した。
これについてはUnityのバージョンを上げたら解消。
2017.4.10→NG
2018.1.1 →OK

2)映像再生前にとてもダサい再生ボタンが表示される問題。

これはvideoタグのposter属性がない場合にデフォルトで差し込まれるらしい。
Chromeブラウザでは表示されないのにWebViewで見ると表示される。
消すにはWebViewPlugin.javaを書き換えて.jarをビルドしなおす必要がある。

plugins/Android/src/net/gree/unitywebview/CWebViewPlugin.javaを開き、117行目あたりを下記のように変更。

webView.setWebChromeClient(new WebChromeClient() {
 View videoView;
 //透明の画像を返す処理を追記
 @Override
 public Bitmap getDefaultVideoPoster() {
  return Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
 }

あとはビルドして出来上がったplugins/Android/bin/WebViewPlugin.jarをUnityのAssets/Pluginsに持っていくだけ。
ビルド手順はgree/unity-webviewに書かれているが、JDK10だと下記のエラーが出てビルドが通らない。
Could not determine java version from '10.0.1'
Gradleのバージョンの問題らしいが、Gradleのバージョンを変えてうまくビルドできなかったので、
JDK8をインストールすることで回避。

さくらのクラウドにソーリーサーバーを作る

  • 投稿日:
  • by
  • カテゴリ:

サーバーメンテナンスなどでWebサーバーを1時間ほど止めるとき、まったくレスポンスがないと利用者が困るので
どのページにアクセスしてもメンテナンス告知ページを表示するようにする。
具体的には、あらかじめDNSのTTLを短くしておき、メンテの際にソーリーサーバーのIPに振り向ける。
本番サイトはSSL化していたので、ソーリーサーバーもhttps受け入れる想定。

そのソーリーサーバーを立てた時のメモ:
1)さくらのクラウドで一番安いプランを契約して起動。環境はCentOS6.9、Apache2.2
2)SSHでログインしてhttpdインストール
yum -y install httpd
3)mod_sslをインストール
yum -y install mod_ssl
4)告知ページを作成してドキュメントルートに保存
vi /var/www/html/maintenance.html
5)アパッチの設定(httpはhttpsに飛ばす。どのURLにアクセスしても503ステータスでメンテページに飛ばす、ただし画像は除く)
vi /etc/httpd/conf/httpd.conf

-------------------
ErrorDocument 503 /maintenance.html
NameVirtualHost *:80
NameVirtualHost *:443
<VirtualHost *:80>
ServerName twipla.jp
DocumentRoot /var/www/html
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
</VirtualHost>
<VirtualHost *:443>
ServerName twipla.jp
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /cert.pem
SSLCertificateKeyFile /privkey.pem
SSLCertificateChainFile /chain.pem
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !^(.*)\.(gif|png|jpg|jpeg)$ [NC]
RewriteCond %{REQUEST_URI} !=/maintenance.html
RewriteRule ^.*$ - [R=503,L]
</IfModule>
</VirtualHost>
-------------------

6)ssl.confにも書かないとリダイレクトされないかも。
vi /etc/httpd/conf.d/ssl.conf

-------------------
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !^(.*)\.(gif|png|jpg|jpeg)$ [NC]
RewriteCond %{REQUEST_URI} !=/maintenance.html
RewriteRule ^.*$ - [R=503,L]
</IfModule>
-------------------

7)SSL証明書(cert.pem、privkey.pem、chain.pem)を本番環境からコピーしてきてhttpd.confで指定した場所に配置。
8)httpsでアクセスできるよう443ポートをあける
vi /etc/sysconfig/iptables

-------------------
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
-------------------

9)iptables再起動
/etc/init.d/iptables restart

10)アパッチ起動、自動起動ON
service httpd start
chkconfig httpd on

以上でサーバーの準備完了。
手元のパソコンのhostsファイルを書き換えて、ドメイン名でアクセスしてみて動作確認する。

Let's EncryptでSSL化した時のメモ(CentOS6.9、Apache2.2)

  • 投稿日:
  • by
  • カテゴリ:

導入方法を検索したら古い情報も多くて手間取ったけど、
下記サイトに情報がまとまっている。インストールも簡単だった。
https://letsencrypt.jp/usage/#Install

まずiptablesを変更して443ポートを空けておく
> vi /etc/sysconfig/iptables
> /etc/init.d/iptables restart

インストールは下記だけで済んだ。
> sudo yum install epel-release
> wget https://dl.eff.org/certbot-auto
> chmod a+x certbot-auto
> ./certbot-auto
必要なライブラリやPythonのインストールも自動でやってくれる。
httpからhttpsにリダイレクトするか尋ねられたのでyesにしたら、httpd.confのVirtualHost設定も書き換えてくれたみたい。

certbotのstandaloneプラグインだと証明書発行時にWebサーバの停止が生じる。
稼働中のサイトが止まるのは困るのでwebrootプラグインを使うのが良い。

証明書は/etc/letsencrypt/archive内に保存される。
シンボリックリンクが/etc/letsencrypt/liveに作られるので、httpd.confに記述する証明書ファイルパスはこちらを使う。

証明書取得時に使用したオプションは下記に保存されて、更新時もこの設定が使われる。
/etc/letsencrypt/renewal/****.conf

有効期限の確認方法
> openssl x509 -noout -dates -in /etc/letsencrypt/archive/ドメイン名/cert1.pem

定期的に証明書を更新するためにcronに下記を登録した。
0 4 1,15 * * root /bin/certbot-auto renew --webroot-path /var/www/html --renew-hook "/etc/rc.d/init.d/httpd reload"
毎月1日と15日に実行、/var/www/html/.well-known/acme-challenge内に一時ファイルが作られてそれを認証サーバが読みにきて認証される。
--renew-hookで証明書更新成功時にhttpdの設定を再読み込みするようにした。

蛇足:
稼働中の本番サーバーでいきなり実行するのは怖かったので、実際は下記の手順で試した。
・ちょうどrsyncで同期させたテストサーバ(ドメイン無し)があったのでそちらでmanualプラグインで実行。
> ./certbot-auto certonly --manual -d example.com
進めると、認証ファイル名とそのファイルに記述する認証コードが表示されるので指示通りのURLでアクセスできるよう本番サーバにファイルを置く。
・ENTERを押すとそのファイルにWebアクセスがあり、/etc/letsencrypt/に証明書などが生成される。
・証明書をテストサーバに持ってきてhttpd.confでSSL用のVirtualHostを設定。
・hostsファイルを書き換えてドメイン名でテストサーバにアクセス。正常にSSLが機能していることを確認。
・本番サーバーでwebrootプラグインで実行。

OVRLipSyncが動かない時は

  • 投稿日:
  • by
  • カテゴリ:

リップシンクが全然反応しないことがある。
スクリプト実行順のバグらしいので
OVRLipSync.csに[DefaultExecutionOrder(-1)]を追記して解決。

参考情報:https://twitter.com/youten_redo/status/984598867336282112

また、フォーカスが外れるとマイク停止する処理がOVRLipSyncMicInputに入っているのでコメントアウトしたほうが良い。