てんちょーの技術日誌

自分がつまづいたこととかメモ

【UE4】OculusTouchでHapticEffectが使えないとき

PlayHapticEffectが動かない

Touchのコントローラの振動にはPlayHapticEffectを使えとあります。

developer3.oculus.com

要約すると、ここからHaptic用アセットを作って適用できるよと。

f:id:shop_0761:20161023023737p:plain

  • Haptic feedback Effect Buffer 数値指定
  • Haptic feedback Effect Curve カーブで指定
  • Haptic feedback Effect Sound Wave 音声ファイルから生成

と3つあります。

今回は適当にカーブを作りました。

f:id:shop_0761:20161023023959p:plain

でHapticEffectに入れて終わり。あとは好きなところで。

f:id:shop_0761:20161023024214p:plain

これで使えるようになります。

しかし、手を抜きたいあまり、Riftのセンサーを片手で塞ぎながら空いてる手でデバッグすると反応しませんでした。

どうやらTouchを持った時に生じる揺れ(人の手は完全に静止しないので)をTriggerにしてるようです(勘)。

なので両方持つか、ちゃんと被るか、足にでもつけるかしないと振動しません。

【UE4】簡単な音声認識をしてみた

はじめに

Unreal Engine 4 (UE4) Advent Calendar 2016 10日目です。

qiita.com

本記事の主役とも言えるほげたつさんのアセットのレビューがされている こちらの記事も合わせてお読みください(何ももらってませんよ)

pafuhana1213.hatenablog.com

今回はちょーお手軽に音声認識をしてみようと思います。

というのもmikulusを見てるとUnityは標準で音声認識が出来たり、プラグインを探すと大体英語対応のものばかりで辛い目に遭いました。

なので作りました。

現在作成中のVR内で使えるキーボードに音声認識で入力アシストするのに使っています。

イメージ

こちらテスターを募集しているのでOculusTouchがある方はぜひお願いします。@shop_0761にご連絡ください。(圧倒的テスター不足)

方針

色々遠回りしたお話は、あとに書くとして端的に実装方針を説明します。

http通信を用いて実装します。

しかもこれ、ほげたつさんがすでにプラグインを作成して公開しています!!

hogetatu.hatenablog.com

つよい。

ちなみに

らしいので先日のこちらの記事を参照してゆくゆくは移行しましょう。

hogetatu.hatenablog.com

http通信については適当にググるか、頭を空っぽにしてそういうものがあるんだなくらいでも動きます。

で、音声認識側のツールはC#で適当に作れます。

参考サイトを貼っておくので自作しても構いません。先人は偉大!

参考:

neareal.com

www.misuzilla.org

面倒な方は僕がすでに作成したものでも使ってください。実行するだけです。おわり。

www.dropbox.com

ダウンロードするだけで環境が整ってしまいました。

音声認識の精度について

精度的なお話だと、

コマンドプロンプトで UWP の音声認識APIを利用するサンプル。NuGet で UWPDesktop を追加してください。https://blogs.msdn.microsoft.com/lucian/2015/10/23/how-to-call-uwp-apis-from-a-desktop-vbc-app/ · GitHub

こちらのほうがいいかもしれません。ただなぜか手元の環境だと、

await contSpeechRecognizer.ContinuousRecognitionSession.StartAsync();

のところで The speech privacy policy was not accepted prior to attempting a speech recognition. と言われてしまい実行できず…

音声認識のプライバシーポリシーってどこで設定するんだ…となってしまったので詳しい方がいればぜひお教えいただけると嬉しいです。

このように認識は外部のツール依存なので、自作してチューニングすればよりよいものになるかと思います。

実装する

と言っても、大したことはしません。

先程のほげたつさんのプラグインにはサンプルプロジェクトWebApiDemoがあるので、それを起動します。

その中にあるWebApiDemo/WebApi/Api 内にあるBP_DemoApiBaseを開きましょう。

f:id:shop_0761:20161202234346p:plain

そして、継承元の変数を表示するために赤線のところにチェックを入れます。

すると

f:id:shop_0761:20161202234427p:plain

Domain変数が見えます。

今回はアプリのポートの都合上ここを http://127.0.0.1:50001/ に書き換えます。

f:id:shop_0761:20161202234559p:plain

あとは、さっきの音声認識のツールを実行後にUE4側で実行しましょう。

SimpleGetを押せば終了!!

f:id:shop_0761:20161202235109p:plain

音声認識の結果が得られます。

ちなみにこのStringはBP_01_SimpleGet内のResponsebodyに入ってるので後はご自由に。

f:id:shop_0761:20161202235534p:plain

VRKeyboardの方ではTickで適当にポーリングするBPComponentにしてます。

応用例

普通に音声認識してそのまま使ってもいいのですが、ちょっとあちこち手を加えてこんなことをしてみました。

音声認識した結果を使ってLipSync的なことをしています。これをLipSyncさせても面白いかもですね。

色々できるので遊んでみてください。

辛い目にあったお話

これ以降は今回の記事に至るまでに迷子になった余談なので、気が向いたら読んで下さい。

きっかけはこの辺

こんなのあるんだ と思って調べてみたら Pocketsphinxってのがベースらしく

と、以外にも日本語の音声認識プラグインが無いことを知りました。

しばらく経って、先程のwin10で動く音声認識のサンプルがあるのを知ったので、これUDP通信で実装するできそうと思ってました。

よく聞くJuliusとかでプラグインでも作ったほうがよさそうかなと思いましたが、あれはデフォのままだとかなりアレな感じなのでガツガツやるにはいいのかもしれません…(DNNベースでもHMMベースでもできます)

じゃあさっそくH社のUDPのサンプルを読みつつ進めていくと、デシリアライズの方法で引っかかりました。

UDP Socket Sender Receiver From One UE4 Instance To Another - Epic Wiki

こことか見てたんですが、日本語stringの場合文字コード周りで死にたくなりましたね…。C++から文字コードを日本語にする方法が見当たらなかったので、 お友達に聞くとpythonをかますか、http通信にしたほうがいいよと言われたので、今回の方法に落ち着きました。

UnrealC++を読む機会が出来たので、副産物的に前よりは抵抗感がなくなりまし た。

まとめ

そろそろデフォで音声認識ができてもいいんじゃないかなとは思います。

ただVR_keyboardで日本語の変換周りを扱っていると日本語難しいなと思ったので、当分ないだろうなと思ってしまいました。

明日はprince_ue4さんの「エフェクトネタやります」です。

【UE4】水彩風マテリアルを作ってみた

はじめに

Unreal Engine 4 (UE4) Advent Calendar 2016 8日目です。

qiita.com

我らがおぎまふさんやcom04さんなど、いろいろな方がトゥーン表現に挑戦されています。

最近ではMarketPlaceでもトゥーンなアセットが出てきたりしています。

いろいろ参考サイトをまとめていたのですが、com04さんの記事を見たほうが早いですね…

qiita.com

ですがやっぱり自分好みのものを作りたい…と思って探していたら参考になりそうなところを見つけました。

d.hatena.ne.jp

こちらのサイトではsoftImageですが、なんとかそれっぽくなりました。

マテリアルダメダメなので、練習ついでに作ってみました。

できたもの

つくりかた

ここから先は、先程の参考サイトを見た前提でお話を進めていきますね。(さらっとでおkです)

考え方、手順は参考サイトと同じなので それをどうやって再現したか という感じで進めます。

とりあえず今回は

  • BaseColor
  • Specular
  • Shadow

に当たる部分のマスクを愚直に作って色指定、その境目にNoiseを入れてなじませていきます。コンパイルが重いかも。

ついでに、各色をテクスチャではなく直で色指定しています。モデルによっては細かくマテリアルが分けてる 方が相性がいいです。処理負荷は気にしないことにします。

BaseColorのマスクを作る

f:id:shop_0761:20161202014231p:plain

BaseTextureを指定できますが、結局Desaturationしてしまうのでそのままでもいいかも。

Blurをかけます。

Blurノード

float4 output = 0;
float weightSum = 0;
float currentWeight = 1.0;

for (int i = 0; i < 10; ++i) {
    output += Texture2DSample(Material.Texture2D_0, Material.Texture2D_0Sampler, texCoord + i * stride) * currentWeight;
    weightSum += currentWeight;
}

output /= weightSum;

return output;

参考:

qiita.com

そして、ちょっとNoiseをのせたり。 もちろん、すでにあるNoiseノードでもいいかと思いますが今回はstylized RenderingのT_TexturedPaperを使用しています。

Specularのマスクを作る

f:id:shop_0761:20161202014328p:plain

こちらはおぎまふさんを見習ってLightVectorを擬似的に用意して、Specularの計算をします。

f:id:shop_0761:20161202014445p:plain

こうして出来た2つをDesaturationしてそれぞれマスクとしました。

ここまででとりあえず2色分塗ることが出来ました。

ShadowとEdgeのマスクを作る

f:id:shop_0761:20161202014544p:plain

f:id:shop_0761:20161202014646p:plain

ここで影色も塗らなきゃなりませんが、このままでは塗れないのでゴリ押しで解決します。

影色になる部分を一旦求めて、今の絵に黒で塗り直します。(いい方法が知りたい)

こうすると後からaddして色を乗せられます。

f:id:shop_0761:20161202014834p:plain

こちらはおぎまふさんのRimLightの実装をそのまま使ってますので関数の中身は割愛します。

水彩の参考サイトの完成絵を見るとエッジが2段あるように見える(実際は歪めたり、ノイズかけたり)のでRimLight_Rangeにちょっとaddして作ります。

NoiseありとなしのEdgeがあったほうがよかったので、分けて作ってます。

f:id:shop_0761:20161202015259p:plain

f:id:shop_0761:20161202015632p:plain

ついでにさっきのshadowマスクを使って、明るい方のEdgeと暗い方のEdgeを作り分けてます。

ここで影色も作ってます。

まとめる

f:id:shop_0761:20161202015742p:plain

最後にshadow側とBaseColor側をaddして完成です。 お好みでNormalMapも使えるようにswitchをつけたりしました。

こうすることでGraymanの胸のUnrealロゴが出せます。

余談

最初はこのマテリアルをPostProcessで実装していたのですが、マスクできるようになったけど色指定できない…ってなったのでやめました。

その時、最初に作っていた影色を少し淡くするPostProcessが出来たのでついでに載せておきます。

alweiさんのお手軽Toonのマテリアルにちょっと足すだけです。

全体

f:id:shop_0761:20161202021012p:plain

追加した部分

f:id:shop_0761:20161202021032p:plain

CustomDepthでマスクを作った結果を使ってなんかごにょごにょしました。

でこの結果を最後にaddして出力してます。

余談2

blurのコードを書いてるときに思いついたのですが、blurベースでedgeが取れそうだったのでやってみました。

f:id:shop_0761:20161207225818p:plain

このぐらいいけます。

ノードはこれだけ 面倒なSobelフィルタを作らなくても使えるかも。

f:id:shop_0761:20161207225837p:plain

Blurのコード

float3 output = 0;
float ScreenMult_X = GetPostProcessInputSize(0).zw.x;
float ScreenMult_Y = GetPostProcessInputSize(0).zw.y;

output += 1.0 * SceneTextureLookup(TexCoord + float2(ScreenMult_X * BlurAmount * -1.0, 0.0), SceneTextureID, true);
output += 1.0 * SceneTextureLookup(TexCoord + float2(ScreenMult_X * BlurAmount * 1.0, 0.0), SceneTextureID, true);

output += 1.0 * SceneTextureLookup(TexCoord + float2(ScreenMult_X * BlurAmount, 0.0), SceneTextureID, true);
output += 1.0 * SceneTextureLookup(TexCoord + float2(0.0, ScreenMult_Y * BlurAmount), SceneTextureID, true);

output += 1.0 * SceneTextureLookup(TexCoord + float2(0.0, ScreenMult_Y * BlurAmount * -1.0), SceneTextureID, true);
output += 1.0 * SceneTextureLookup(TexCoord + float2(0.0, ScreenMult_Y * BlurAmount * 1.0), SceneTextureID, true);

output /= 6.0;

return output;

SceneTextureIDを引数にすることで、任意のSceneTextureを指定できます。 outputは加算した分だけ割ります。

f:id:shop_0761:20161207230113p:plain

多分この順番通りでIDが振られてると思います 分からなかったらコードを読めばいいんだよ うん。

まとめ

なんとなくMaterialの演算が分かった気がします。もろもろの計算は試行錯誤の結果なので、「なんでこうなるの?」というより「色々やったらこれがよかった」って感じです。

作りたい絵を目指して、実際に手を動かしてみるとこの意味がわかるかと思います。すごい人になれば頭のなかで完成形をイメージしながらノードを組めるかと思いますが()

まずは作りたい絵を探しましょう!!

わからないこととかあれば、可能な限りお答えするのでTwitter(@shop_0761)等でご連絡ください。

明日は Epic社のおかずさん(@pafuhana1213)の「帰ってきたUE4のマーケットプレイスで購入したアセットを片っ端からレビューするマン」です。

鹿のレビューが懐かしい…

頭の悪いHMDのレビューをしてみる

はじめに

Oculus Rift Advent Calendar 2016の2日目です。

qiita.com

昨日は桜花一門さんのAllumetteが素敵な理由でした。

docs.google.com

N回目のVR元年ということで、いろいろHMD(Head Mount Display)が出てきました。

気付いたらうちにもたくさん生えてきました。

ということで、これらのHMDのレビューでもしようかと思います。

と言ってもだいたいのレビューはあちこちですでになされているので、別の観点からします。

題して

一番寝心地のいいHMDはどれだ選手権!!

f:id:shop_0761:20161119060017j:plain

すでに皆さんも一度は被って寝落ちしているかと思いますし、僕自身も手元にあるHMDを被ったまま(意図して)寝たことがあります。 ですが改めて記事になっているものはなかったので、チャンスだと思いました。

注)別に各HMD、コンテンツを批判したいわけではありません。どれもいいものですし、お世話になってます。

事前知識

VRについて全くわからない方はこちらのリンクとかを見てみるといいかもです。一つ目の動画は割りとイメージつかみやすいかも。

www.moguravr.com

www.moguravr.com

www.moguravr.com

www.youtube.com

www.youtube.com

評価

評価方法

実際に被って寝(落ち)ます。メガネかけてます。

Rift以外はヘッドホンを使います。

実験時間は寝てから起きるまで。最近あまり長く寝れないことが多いのでだいたい2-3時間です。

(そもそもほんとに眠いときは準備する気になれない)

評価基準

  1. 起きたときの没入感
  2. 寝返りのうちやすさ
  3. コンテンツの寝やすさ
  4. 被った状態での後頭部へのやさしさ
  5. 総合評価

をそれぞれ5段階で評価します。かなりアレなやつは☆になります。

結果

Rift

Mikulusの場合

mikulusについてはこちら。

www65.atwiki.jp

膝枕をしてもらうかのように調整してみました。 他にもたくさんやっている方がいるので、写真は割愛します。

この辺でも覗いてみてください。

twitter.com

起きたときの没入感 ★★★★

目を開けたら目があった。 びっくりした。

寝返りのうちやすさ ★★★★

ヘッドホンが付いているので便利。 しかも軽いのがいいですね。

あまりゴロゴロするとヘッドホンの部分が壊れてしまいそうなので、おとなしくしてました。

コンテンツの寝やすさ ★★★★★

あの これ 呼吸音と心拍音がいい感じに落ち着く感じ非常に良い…

こう看取られてる気分ですね…(看取られたい

被った状態での後頭部へのやさしさ ★★★★★

Riftの後頭部のつくりは以外にもクッションが効いてて、あまり違和感がありませんでした。

後頭部にもセンサーがあるそうなので、これを使ってなにか作れないかなとか思ったり。

総合評価 ★★★★

最初は寝るのにちょうどいいコンテンツを思いつかなくてVirtualDesktopにでもしようかと思ったらMikulusがよさそうだったので選びました。

参考:

store.steampowered.com

目をつぶってしまえば現段階での呼吸音の違和感もなにもなく快適でした。

というか呼吸音と心拍音が聞こえるだけの音声を聞けばいいのではという意見は受け付けません。

目を開けたらそこにいると思えるほうがいいのです。はい。

Riftだとメガネが厳しいHMDなので、そこさえなんとかなれば(というか被ってしまえば)意外とお布団でも使えることがわかりました。

HTC Vive(Pre)

The lab 丘の場合

The labについてはこちら。

store.steampowered.com

丘で横になったときはこんな感じ。鳥が飛んでます。

f:id:shop_0761:20161130230355p:plain

起きたときの没入感 ★★★

起きて外した時にあれ部屋が狭い…?っと軽く思うくらいには没入していたようです。

まあ曇り空なんですが。

寝返りのうちやすさ ★★

ちょっと重いので頭が持って行かれます。出来なくはないですね。イヤホンにしたらもっといいかも。

コンテンツの寝やすさ ☆

Thelabの丘で寝ると上で鳥が飛んでいます。いい感じに環境音があって快適…かと思いきやタイミングやら位置やらが悪かったのか例のロボット犬が周囲で暴れまくっています。

いやあの、ほんと戯れる分にはいいんですよ… 寝るのにはうるs…(めっちゃギュインギュインしてた

ロボット犬 On/Offできると嬉しい。

被った状態での後頭部へのやさしさ ★★★★

Viveだと後頭部からまっすぐコードが伸びてます。

f:id:shop_0761:20161120151847p:plain

このためコードのことを思うと頭の下にそのままコードがくることになると思います。このためちょっと違和感があるかなという感じでした。

工夫次第でよくなるかもって感じです。

総合評価 ★★★

ぱっと思いついたコンテンツがコレだったのですが、犬が…犬が…という思いで完全に失敗しました。

といっても、他にこういうのが思いつかなかったのでオススメが知りたいです。

Viveは以前VirtualDesktopをしながら寝ていたこともあるのですが、如何せん重い…首が疲れます。

寝ているとそれは多少軽減されるのですが、ちょっとでも動くとぐいっと持っていかれる感じがします。

PSVR

サマーレッスンのひかりちゃんの部屋のベッドの場合

サマーレッスンについてはこちら。

summer-lesson.bn-ent.net

ベッドの上に座った感じ

f:id:shop_0761:20161130225448p:plain

ベッドに寝っ転がった

f:id:shop_0761:20161130225506p:plain

起きたときの没入感 ★

そもそも寝れなかった 頭痛すぎ。

寝返りのうちやすさ ★

プレイエリアの外に出ると目を開けた時に気が散ってしまうので、なかなか動けません。 ヘッドホンをしているとさらに動けないです。イヤホンにしたらいいかも。

しかも全体的に大きめなので壊しそうであまり動けない。

コンテンツの寝やすさ ★★

まあ確かに誰かがいる部屋で寝てる感は出てるっぽいので、ぼちぼち。

どのタイミングで寝るかに迷うかも。(教えてる時、ハンコを押す前、押した後)

今回はハンコを押した後にしましたが、突然話しかけてきたりしてビビります。

教えてるときはBGMがかかってしまうのでやめました。ので、ハンコを押す前がいいかも。

場所を調整するのがかなり難しかったです。 なかなかベッドの位置に移動出来ないし(当たり前)、低すぎると暗転してしまいます。

被った状態での後頭部へのやさしさ ☆

良くない これは頭が痛い 後頭部のパーツが痛すぎる。

他のは薄めのバンドだったのにこれだけやたらしっかりしてるせいで、 寝っ転がった時にこの後頭部のパーツにばかり力がかかって痛い。

そのせいではずした後に頭が痛くなってしまった。

f:id:shop_0761:20161129023239j:plain

総合評価 ★

PSVRは寝るのに挑戦したのは3回ほどあります。一度だけ座椅子にもたれかかって寝ることに成功しましたが、 今回のように布団や床で寝ることを想定すると後頭部の出来のよさが仇になってしまっています。

後1回はAmazonPrimeVideoでアニメを見ていました。結局後頭部の痛みが気になり寝るにねれない状況でした。 よっぽど疲れている、眠い状況であれば寝付けるかもしれませんが、数時間でこのダメージなので ちょっと怖いですね…

番外編 GearVR + GalaxyS6 edge

niconicoVRの場合

info.nicovideo.jp

起きたときの没入感 ★★★

まあ単に動画を見ていただけなので、特にやべえコレ みたいなことにはなりませんでした。

寝返りのうちやすさ ★★★★★

これは超いい ほんとにいい モバイルの良さを思い知らされました。

なにより軽い。余計なコードがない。寝返りがうてる。こんなに違うものかと…

思わずゴロゴロ転がってしまいました。

コンテンツの寝やすさ ★★★

眠くなる動画とか見ればいいのでは 終了。

被った状態での後頭部へのやさしさ ★★★★★

こいつもやわらかバンドなので違和感は無いに等しい。 頭が痛くないのはいいことですね。

総合評価 ★★★

手元にあってすぐ試せるniconicoVRにしてみましたが、割りとパフォーマンスがいいのか 3時間くらい持ちそうです。一回も落ちてないっぽい。2時間半くらいで100%→28%だった。

正直GearVRなので寝ろと言わんばかりに熱くなっておしまいかと思ったら 長持ちしてびっくりしました。

余談ですが、起きてからGear VR Cooler!(物理)と題して、雪を詰めた袋をくっつけるだけの 記事を書こうかと思いましたが5秒でやめました。

参考: itachinx.hatenablog.com

考察

ということで、今回はRiftが一番良さそうという結果になりました。 ヘッドホンよりイヤホンがいいかもと思ったので完全に選択ミスですね。動けない。

一応どのHMDでも(AmazonPrimeVideoのような)映像を見るだけのものが出来ますが、せっかくなら いいところでお昼寝したいです。欲を言えば寝ても覚めても被って生活したいです。

UE4で言えばKiteDemoのマップは寝心地よさそうな気がします。

参考: www.youtube.com

また今回は扱わなかったハコスコなどのモバイル系は一晩も使えないのがネックですね。

ダンボールだと固定できないので、どうしてもという場合のみアロンアルファで顔にくっつけてください。

参考

http://www.hashiyansoft.com/contents/dc/death.html

hacosco.com

これからはVR Nevangelist(寝バンジェリスト)とでも名乗ってみようかな!!

ぜひこの記事を参考に寝心地のいいHMDを購入してみてはいかがでしょうか。

明日はnoshipuさんの「VRアプリ作って思った事とか書きます」です。

おまけ

ちなみにSAOのキリトくんが被っているナーブギア

f:id:shop_0761:20161119054153j:plain

ヘルメット系は詳しくないのですが、これとても首を痛めそうな気がします。

調べてみると、やっぱり何人かの方がヘルメットを被って寝たことがある模様。特に寝心地レビューみたいのが見当たらず残念。

アミュスフィアはこんな感じ

f:id:shop_0761:20161119054433j:plain

HoloLensに近い気がしますが、やはりPSVRの例から後頭部に優しくないのではと思ってしまいます。

なのでメディキュボイド型が最強なのでは!?!?

点滴してもらえそうだし。

f:id:shop_0761:20161119054537p:plain

やっぱりこっちのメガネも捨てきれませんね(もはやVRではない

こちらは確か作中では寝る時に外していたような気もします。

f:id:shop_0761:20161119055104j:plain

おまけ2

ちょっと調べてみるとこんなものがあったのを思い出しました。

www.digimonostation.jp

確かに寝る分にはいいかもしれませんが、VR空間で寝たいのでうーんという思い。

【UE4】SaveとLoad時のUserIndexについて

はじめに

時々SaveとLoadに苦戦するのでよくお世話になっているほげたつさんのサイトです。

hogetatu.hatenablog.com

ここには

セーブデータにはスロット名とユーザーインデックスが指定できます。 スロット名はセーブデータの名前、ユーザーインデックスはその中でのIDみたいなものですね。 ドラクエ冒険の書1とか冒険の書2とかそんなイメージです。

とあります。

もちろんこちらにも。

docs.unrealengine.com

For some platforms, master user index to identify the user doing the saving.

ただ実際に実装してみると、なにやら動作がおかしいので調べてみました。

ソースコード

ということで、Engineのソースコードを追います。UE4.13です。 ctrl+FとかでUserIndexをハイライトしておくと、見やすいかもしれません。

Runtime/Engine/Private/GameplayStatics.cpp

USaveGame* UGameplayStatics::LoadGameFromSlot(const FString& SlotName, const int32 UserIndex)
{
    USaveGame* OutSaveGameObject = NULL;

    ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();
    // If we have a save system and a valid name..
    if(SaveSystem && (SlotName.Len() > 0))
    {
        // Load raw data from slot
        TArray<uint8> ObjectBytes;
        bool bSuccess = SaveSystem->LoadGame(false, *SlotName, UserIndex, ObjectBytes);
        if(bSuccess)
        {
                   (省略)
bool UGameplayStatics::SaveGameToSlot(USaveGame* SaveGameObject, const FString& SlotName, const int32 UserIndex)
{
    ISaveGameSystem* SaveSystem = IPlatformFeaturesModule::Get().GetSaveGameSystem();
    // If we have a system and an object to save and a save name...
    if(SaveSystem && SaveGameObject && (SlotName.Len() > 0))
    {
        (省略)
        // Stuff that data into the save system with the desired file name
        return SaveSystem->SaveGame(false, *SlotName, UserIndex, ObjectBytes);
    }
    return false;
}

とSaveもLoadもUserIndexに関わる処理はSaveGame、LoadGameを呼び出しているのでそちらへ。

Runtime/Engine/Public/SaveGameSystem.h

virtual bool SaveGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, const TArray<uint8>& Data) override
    {
#if PLATFORM_HTML5_BROWSER
        return UE_SaveGame(TCHAR_TO_ANSI(Name),UserIndex,(char*)Data.GetData(),Data.Num());
#elif PLATFORM_HTML5_WIN32
        FILE *fp;
        fp=fopen("c:\\test.sav", "wb");
        fwrite((char*)Data.GetData(), sizeof(char), Data.Num(), fp);
        fclose(fp);
        return true;
#else
        return FFileHelper::SaveArrayToFile(Data, *GetSaveGamePath(Name));
#endif
    }
 virtual bool LoadGame(bool bAttemptToUseUI, const TCHAR* Name, const int32 UserIndex, TArray<uint8>& Data) override
    {
#if PLATFORM_HTML5_BROWSER
        char*  OutData;
        int        Size;
        bool Result = UE_LoadGame(TCHAR_TO_ANSI(Name),UserIndex,&OutData,&Size);
        if (!Result)
            return false; 
        Data.Append((uint8*)OutData,Size);
        ::free (OutData);
        return true;
#elif PLATFORM_HTML5_WIN32
        FILE *fp;
        fp=fopen("c:\\test.sav","rb");
        if (!fp)
            return false;
            // obtain file size:
        fseek (fp, 0 , SEEK_END);
        int size = ftell (fp);
        fseek (fp, 0 , SEEK_SET);
        Data.AddUninitialized(size);
        int result = fread (Data.GetData(),1,size,fp);
        fclose(fp);
        return true;
#else
        return FFileHelper::LoadFileToArray(Data, *GetSaveGamePath(Name));
#endif
    }

えーっとPlatformがHTML5のブラウザだけUserIndexを使っているように思います。

そしてこのUE_SaveGameとUE_LoadGameはこちらに。

Runtime/HTML5/HTML5JS/Private/HTML5JavaScriptFx.js

UE_SaveGame: function (name, userIndex, indata, insize) {
    // user index is not used.
    var _name = Pointer_stringify(name);
    var gamedata = Module.HEAPU8.subarray(indata, indata + insize);
    // local storage only takes strings, we need to convert string to base64 before storing.
    var b64encoded = base64EncArr(gamedata);
    $.jStorage.set(_name, b64encoded);
    return true;
  },

  UE_LoadGame: function (name, userIndex, outdataptr, outsizeptr) {
    var _name = Pointer_stringify(name);
    // local storage only takes strings, we need to convert string to base64 before storing.
    var b64encoded = $.jStorage.get(_name);
    if (b64encoded === null)
      return false;
    var decodedArray = base64DecToArr(b64encoded);
    // copy back the decoded array.
    var outdata = Module._malloc(decodedArray.length);
    // view the allocated data as a HEAP8.
    var dest = Module.HEAPU8.subarray(outdata, outdata + decodedArray.length);
    // copy back.
    for (var i = 0; i < decodedArray.length; ++i) {
      dest[i] = decodedArray[i];
    }
    Module.HEAP32[outsizeptr >> 2] = decodedArray.length;
    Module.HEAP32[outdataptr >> 2] = outdata;
    return true;
  },

いやあのこれ、UserIndex出番なくないですか…(引数として呼ばれているだけで未使用)

まとめ

現状だとUserIndexは出番がないようです。ちゃんと(?)設定していないはずのUserIndexでLoadしてもエラーが出ませんでした。

Forum等も漁りましたが、良さげな解答が見つからなかったので追ってみました。

どこか間違ってたり、新たに何か分かった時にはぜひ@shop_0761までお知らせください(更新します)

【UE4】自作アクションゲームにAutoPlayを仕込んでみた

はじめに

ぷちコンで作ったもろもろ紹介編2です。

元記事

shop-0761.hatenablog.com

今回はCEDECに行った際に聞いたAutoPlayのお話をもとに作ってみました。

講演はこちら。

cedec.cesa.or.jp

資料はCEDiLにあります。

cedil.cesa.or.jp

実際にAutoPlayしたものがこちら。

youtu.be

考え方

講演の内容をもとに(正確な内容はあやふやですが)AutoPlayの実装方法を簡単に説明します。

f:id:shop_0761:20160922233931p:plain

-------事前準備-------

  1. マップにConeを置く

  2. そのConeにはいくつかActionを登録しておく

-------実行時---------

  1. ConeのActionを実行する

  2. 次のConeについたら上へ戻る

これだけです。AIは使わなくても実装できます。

Actionというのは絵に描いてあるような移動だったり、ジャンプだったりと なんでもいいわけです。

管理、保守など調整しやすいように難しいことはしません。

実装

実際に実装してみると、このConeのBPだけで大部分はまかなえてしまいました。

(あとはループ処理のためのConeManagerを作ったりしたくらい)

方針

ポイントを整理しておきます。

  • 今回は予めConeを配置したあとにNextConeとして次のConeを登録しておく

  • 今ActionをしているConeとNextConeのみCollisionを有効にする(例外あり)

くらいだと思います。

一つ目はConeと同じ型の変数を公開しておき、レベル上で設定していきます。

こんな感じ。

f:id:shop_0761:20160922235034p:plain

二つ目はそのほうが安全だよねってくらいです。

そして"例外あり"とは、スタート地点はCollisionを有効にしておかなきゃそもそも動かないのでという意味だったり、不具合対処の意味が大きいです。

この辺はまたあとで触れます。(※)

実際のBPはこちら(1-2日で作ったので一応整理したけど散らかっている感)

ConstractionScript

一応確実にジャンプして欲しい場面(大きな穴とか)があるので

f:id:shop_0761:20160922235517p:plain

全体像

f:id:shop_0761:20160922235538p:plain

BeginPlay

ここでCollisionの設定をしています。

f:id:shop_0761:20160922235553p:plain

BeginOverlap

NextConeのCollisionをEnableにしたあと、Wait秒待ちます。

落ち着いてジャンプしてもらうためです。

f:id:shop_0761:20160922235611p:plain

Debug用にPrintStringしてるだけです。Duration0は便利ですね。

f:id:shop_0761:20160922235629p:plain

到着判定

NextConeにたどり着いたかどうかをチェックします。

今回はVecLengthでやりましたが、2点間の距離でもいいと思います。

そして、先程の※のお話(ポイントの2つ目)がここで出てきます。

後述するRandomJumpを使っていたりすると、偶然Coneをスキップされてしまい、 Gateが開いたまま移動処理が残ったりする場面が多々ありました。

実装方法に問題があるのは分かっていますが、今回は一定距離が離れたりした場合でも到達したとみなして、処理を打ち切ることにしました。

f:id:shop_0761:20160922235644p:plain

Move

一応Axisの向きも内積でなんとなく求めておきます。

このMoveActionはキー入力の移動と同じ処理をするだけです。そのために追加したイベントです。

f:id:shop_0761:20160922235657p:plain

Jump

こちらのJumpは一度だけ確実にジャンプする必要があるときに使います。

JumpActionも先程のMoveActionと同じです。

f:id:shop_0761:20160922235721p:plain

RandomJump

適当なタイミングで適当に長押し(したフリをして)ジャンプします。

f:id:shop_0761:20160923001012p:plain

Reset

今回はループさせたかったので、Reset処理を追加しました。

ループさせる必要がないならDestroyしてしまってもいいかもしれません。

(そうするとさっきの※のお話はなくなるはず)

f:id:shop_0761:20160922235738p:plain

AutoPlayのメリット

最初は なんかすげぇでも作れそう とおもったので勉強がてら作りました。

何がいいかというとテストプレイ(周回系)が楽です。

  • 実装に疲れたから、眺めて確認したい
  • どうせちゃんと動くだろうから、操作したくない
  • Twitterを見たい

などAutoPlayにしておけばこんなに便利です。

あとプレイ動画を撮るのも楽ですね。コミケ等の展示時に流していると面白いかもです。

まとめ

このくらいのスピード感で作ってました。

意外にあっさりできましたね。

ちなみに途中で操作介入できます。(意図的にジャンプさせたり)

なにかわからないことがあれば@shop_0761までお願いします。

【UE4】ParallaxOcclusionMappingで背景アセットを作ったお話【小ネタ】

はじめに

ぷちコンで作ったもろもろ紹介編1です。

元記事

shop-0761.hatenablog.com

HeightMapの描き方が分かりません。

けど、いい感じの背景アセットを手抜きで作りたい。

そんなこんなで出来たのが、これ。

f:id:shop_0761:20160921004208p:plain

f:id:shop_0761:20160921004225p:plain

意外に良くないですか 手抜きの割には。

作り方

Heightmapは描きました。手描き出来ます。

f:id:shop_0761:20160921004449p:plain

f:id:shop_0761:20160921004432p:plain

引っ込ませたい方を黒で塗ればいいんです。おわり。

あとはMaterialですが、ContentExampleにあったものを拝借してきただけです。

f:id:shop_0761:20160921004911p:plain

f:id:shop_0761:20160921004922p:plain

超お手軽ですね!!

深いことを考えなければ、パラメータはSampleそのまま使っていい気がします。

最初はalphaで抜こうか、なんて思ってた時期もありました。

f:id:shop_0761:20160921004420p:plain

ですが、シェーダ複雑度が真っ赤になったのでやめました。

これなら実質箱だけなので、描画負荷はalpha抜きに比べると軽いです。(上手くやれば赤いとこもなくなるはず)

f:id:shop_0761:20160921010135p:plain

試しにalpha抜きしてみました。(見栄えはおいておくとして

f:id:shop_0761:20160921010433p:plain

f:id:shop_0761:20160921010443p:plain

そりゃそうですねって感じです。

まとめ

ぱららっくすおくるーじょんまっぴんぐ ってカッコよかったので使いたかっただけです。

ContentExampleのは出来が良すぎて意味が分からなかったですが こんな手抜きの白黒絵を描いても使えるよ ということが言いたいだけです。

賞味20分くらい?だった気がします。

誰かもうちょっとまともな使い方の記事を書いて欲しいところですね。