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で所持する。

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

・そのほか諸々のユーザ情報...
  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をインストールすることで回避。

サーバーメンテナンスなどで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ファイルを書き換えて、ドメイン名でアクセスしてみて動作確認する。
問題なければメンテ時にDNSのIPを書き換える。

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に入っているのでコメントアウトしたほうが良い。

Blenderで作った3DモデルをFaceRigで動かす

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

なかなかハードルが高いので、はまりポイントをメモします。
下記を参考にすればとりあえずFaceRigで単純に3Dモデル表示するところまではいける。
https://steamcommunity.com/sharedfiles/filedetails/?id=523433759

まずモデルは原点に配置、スケールは1にしないとボーンやカメラがずれる。
頭のサイズは高さ2に収まる程度にする。
(モデルの位置回転スケールを調整した後で、ctrl+Aで原点原寸に変換できる)

マテリアルの命名規則を守る。例:smileyと名付けたアバターのfaceマテリアルなら「smiley_sht_metalcloth_face」。
そのfaceマテリアルのテクスチャ画像の名前は「smiley_face_d.tga」とする。テクスチャ画像はなくても動く。
画像はTARGA形式、縦と横のpixelサイズは2の累乗にする。

モデルのUVマップを必ず作る。
(Unwarpは、Edit ModeでAキーで全メッシュ選択してUキー)
一つのメッシュに複数のUVマップがあるとエラーになる。
(モデル選択してPropertiesウィンドウのDataタブ内にUV Maps一覧があるので使わないものは削除する。
複数のUVを使用したいときは、1メッシュ1UVになるようメッシュを分割する。)


最低限必要なボーンは、BipHead , BipLEye , BipREye の3つだけ。
Armatureを作ってWeightマップを作る。
ボーン名にドット( . )を含むとインポートエラーが起きる。
エラーが出る場合は、ボーンを消していき最低限のボーンだけにしてインポート成功するか確認するとよい。

Cameraという名前のボーンも必要らしいが認識されなかった。代わりにScene内にCameraという名前のカメラがあれば大丈夫。
アバターの正面とカメラは、Y軸(緑色の線)で向き合うようにする。(一般的に前後はZ軸だけど、BlenderではY軸が標準の模様)
カメラの位置はサンプルアバターを参考に合わせる。

アバターの動きは、様々なアニメーションを用意することで実現する。
最低限必要なのは「idle1」のみ。DopeSheetのActionEditorでidle1と名付けたアニメーションを作る。
キーフレームを選択してKeyメニュー→Sample keyframe(キーフレームを抽出)を実行して、
全フレームにキーを打たないとFaceRig側で正しくアニメーションされないケースがある。

下記のモーションを用意すればそれなりに動くと思う。
idle1 å¾…æ©Ÿ
Head_LR 首を左右に曲げる 0(左)~15(中央)~30(右)の31フレームで作成。
Head_UD 首を上下に向ける
Head_Twist 首を左右に振る
LeftEyeClosed 左目を閉じる
RightEyeClosed 右目を閉じる

アバターの腕は、Shift+マウスドラッグで動かせる。モーションは下記を用意する。
HandL_closeDown_LR 左腕、下げたまま左から右に振る。0(左)~15(基本ポーズ)~30(右)
HandL_closeMiddle_LR 左腕、胸の位置で左から右に振る。0(左)~30(右)
HandL_closeUp_LR 左腕、頭の位置で左から上に振る。0(左)~30(右)
※HandL_closeUp_LRが反映されない場合はHandL_farUp_LRに割り当ててみる。

口の動きは下記があるとよい。
MouthOpen 口を開ける。0(閉じる)~30(開ける)
MouthOpen_base 口を開けっ放し。0~30まで同じ。
Mouth_pursedLips_LR 唇をすぼめる。0(左ですぼめる)~15(中央で)~30(右で)
MouthClosedLeft_U 左、口角を上げて笑う
MouthClosedRight_U 右、口角を上げて笑う

BlenderからデータをCollada形式で書き出す。
ファイル名に「Geometry.dae」をつける。例:smileyGeometry.dae
アニメーションはそれぞれの名前で書き出す。例:idle1.dae
ファイルパスに、スペースやハイフンがあると読み込めない。
FaceRigのインポーターは下記にあるので起動して、Data Folderを指定してImport実行。
C:\Program Files (x86)\Steam\steamapps\common\FaceRig\Bin\Tools\ImportWizardNew

インポーターから一度書き出したデータは、インポーター上でクリアしても消えないようなので
間違ったアニメが残り続ける場合は、下記にあるアバターデータを消すと良い。
C:\Program Files (x86)\Steam\steamapps\common\FaceRig\Mod\VP\PC_CustomData\Objects

FaceRigのLAUNCHボタンでクラッシュするようになってしまったときは、前述のアバターデータを消して
OPTIONSでReset to Defaultsを押してみると直るかも。
私の環境ではマルチモニター(外部ディスプレイ接続中)でLAUNCHボタン押すとクラッシュする。

Qキー押したときに発動するspecial01などの特殊アクションについては
special01だけでなくベースとなるidle1、Avatar_LR、Avatar_FBの全ボーンにキーフレームを打っておかないとアバターが消える。

参考:
ばけもの屋 -BAKEMONOYA- faceRig編
アニメテンプレート一覧
https://www.youtube.com/watch?v=ecLd4NsmpUw
サンプルアバター「yexample」のダウンロード

UnityでWebSocketやSoicket.io

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

いろいろ試した結果のメモ:

WebSocketなら http://sta.github.io/websocket-sharp/ が良い。
OSXとiOSでws://echo.websocket.orgに接続できることを確認した。
しかし、wssでは接続できなかった。Socket.ioサーバにも接続できなかった。

Socket.ioでの接続は https://github.com/floatinghotpot/socket.io-unity が良い。
OSXとiOSで接続できることを確認した。
ただほかのライブラリと競合して、The imported type `xxx' is defined multiple times みたいなエラーが出る場合は対策が必要。
どちらかのActionをUnityActionに変えるとか。Func<T, TResult>を自分で用意したdelegateに変えるとか。
https://github.com/floatinghotpot/socket.io-unity/issues/1

下記も試したが、バージョンが古いせいか接続できなかった。
https://github.com/NetEase/UnitySocketIO
https://github.com/kaistseo/UnitySocketIO-WebSocketSharp
https://github.com/fpanettieri/unity-socket.io-DEPRECATED

参考:
http://littlewing.hatenablog.com/entry/2015/10/05/144124