Gaming Life

一日24時間、ゲームは10時間

某イベントでUE4を使ったVRタワーディフェンスゲームを作った話

 某ハッカソンイベントでVRゲームを作った。一人で。

 今思うといくらUnity触りたくなくてもせっかくイベントに参加したんだからチームに参加して開発すればよかったかなあと反省している。

 OculusやViveの貸出もあったみたいですが、わざわざ深夜バスでWindows MRを持ってきたのでUE4で無理やりWinMR開発した。といってもWindows Mixed Reality for SteamVR を使って無理やり動かしただけだが……

 作ってる最中は深夜テンションでなかなか良いものが出来たなーと思っていたのだが、落ち着いてプレイし直すと何だこのク○バランスゲームは……と衝撃を受けた。

  • 移動が面倒。VRテンプレートそのままの移動方法を採用したが頻繁に動き回るゲームでこれはひどい

  • UIが雑。もっと丁寧に作るべきだった。

  • 剣の当たり判定がザル。当たったときのエフェクトもないし爽快感も足りない。変な当たり方をして敵を倒すと興が削がれる。

 上げるとキリがない。まあVR開発が実質初めて×一人作業×睡眠時間極短だったことを思うとまあこんなもんかなーとも思う。

 ところでUE4上でWinMRを使用するとサムスティックの入力が入らなかったのだがこれは仕様なのだろうか?


 即興開発ではあったが色々知見は溜まったので、色々為になった知識は後でブログに纏めます。

Siv3DとC++と近況と

 プログラマー志望なのにUE4のBPに頼り切りでほとんどコーディングしないのは如何なものかなーと思い最近時間が空いた時はC++ポケットリファレンスを読むようにしている。で、そろそろ何かC++で作りたいなーでもいきなりDirectX/OpenGL直で触るのはハードル高いなーと考えていた所TwitterでSiv3Dを紹介して頂いた。

play-siv3d.hateblo.jp

 CライクなDXライブラリと違いModern C++で書け、複雑なインタラクションをとても短いコードで書くことが出来るらしい。サンプルプログラムをざっと見た感じProcessingより難しい作りだがゲーム制作に特化されていると印象を受けた。これは今の自分の実力とやりたいことにマッチしていると感じたため、UE4での開発と並行してコレを使ってなんか作ろうと決めた。

 早速リファレンスに従いDL。VS2015にも認識されてサンプルプロブラムをビルドしよう、としたら失敗した。正確に言うとビルドは成功するが実行が出来なかった。「Siv3D Engine」のロゴは表示されるのだがウインドウがすぐに消えてしまう。

 Twitterでライブラリの開発者に質問した所これは珍しいバグでGPUが悪さしているのでは、と教えていただいた。パソコンの製造元にもメールで問い合わせたがこちらもGPUが悪い、ドライバが壊れているんじゃ(他社の製品でのバグは知ったこっちゃねぇと突き放されたが)と返信があった。

 結局Intel GraphicsとNVIDIAのドライバを両方再インストールすることで解決したのだが、これも2度のパソコン自体の初期化やら複数回に渡るドライバのクリーンインストールの末になんか知らない間に解決していたので結局何が悪さしてたのかはわからず仕舞い。とりあえず同じ現象に見舞われた人はドライバの再インストールから始めることをオススメする。

 上記の問題解決に1週間くらいかかった為解決した頃にはモチベが死に絶えていた、更に例のボクセルモデルをBlender上手くでアニメーションさせるアイデアが浮かんだこともあり暫くSiv3Dは触っていなかった。試行錯誤の末ボクセルモデルをアニメーションさせることが出来た(この詳細は来月中にブログにまとめる予定)のでつい三日前くらいから本格的にSiv3Dエンジンを触り始めた。

 今はとりあえずGUI周りを勉強中。こちらの記事 で紹介されているスピンボックスを改造してTextField + GUISliderのクラスを作った。

f:id:ai_gaminglife:20180530145356p:plain

# include <Siv3D.hpp>

//Text + TextField + Slider Class

template<typename T>
class TextSliderGUI
{
public:
    TextSliderGUI(T _value, T _min, T _max)
        :value(_value),min(_min), max(_max) {}

    using TagPtr = std::shared_ptr<s3d::GUIText>;
    using TextFieldPtr = std::shared_ptr<s3d::GUITextField>;
    using SliderPtr = std::shared_ptr<s3d::GUISlider>;

    T value;
    T min, max;
    s3d::String name;
    TagPtr tag;
    TextFieldPtr textField;
    SliderPtr slider;

private:
    //初期化
    void InitWidget()
    {
        {
            textField = std::make_shared<s3d::GUITextField>();
            textField->m_style.width = 56;
            textField->setMaxLength(5);
            textField->setText(s3d::Format(value));
        }
        {
            slider = std::make_shared<s3d::GUISlider>();
            slider->setLeftValue(min);
            slider->setRightValue(max);
            slider->setSliderWidth(max);
            slider->setSliderPosition(value);
        }

    }
    //GUIに登録する

public:

    void LinkGUI(const s3d::String& _name, s3d::GUI& _gui)
    {
        InitWidget();

        const bool linkOK = (textField != nullptr) && (slider != nullptr);
        if (linkOK)
        {
            _gui.add(s3d::GUIText::Create(_name));
            _gui.add(textField);
            _gui.addln(slider);
        }

    }
    //TextFieldの入力を終了したか
    bool InputComplete() const
    {
        return textField->hasChanged && (!textField->getActive());
    }

    void TextUpdate()
    {
        const auto currentVal = s3d::FromStringOpt<T>(textField->getText());
        if (currentVal)
        {
            if(currentVal >= min && currentVal <= max)  {this->value = *currentVal;}
            else                                      { textField->setText(s3d::Format(value)); }
        }
        else
        {
            textField->setText(s3d::Format(value));
        }
    }

    void SliderUpdate()
    {
        this->slider->setSliderPosition(Parse<double>(this->textField->getText()));
    }

    void readText()
    {
        if (textField->getActive() && Input::MouseL.clicked)
        {
            if (!textField->mouseOver)
            {
                TextUpdate();
                return;
            }
        }

        if (InputComplete())
        {
            TextUpdate();
            SliderUpdate();
        }

        return;
    }

    //ウィジェット更新
    void update() {
        assert(textField);
        assert(slider);
        if (slider->hasChanged)
        {
            this->textField->setText(Format(this->slider->_get_sliderPosition()));
        }
        readText();
    }

};

void Main()
{
    GUI gui(GUIStyle::Default);
    gui.setTitle(L"テスト");

    TextSliderGUI<uint32> ts_r(0, 0, 255);
    TextSliderGUI<uint32> ts_g(0, 0, 255);
    TextSliderGUI<uint32> ts_b(0, 0, 255);

    ts_r.LinkGUI(L"R", gui);
    ts_g.LinkGUI(L"G", gui);
    ts_b.LinkGUI(L"B", gui);

    while (System::Update())
    {
        ts_r.update();
        ts_g.update();
        ts_b.update();

    }

}

 templateだのスマートポインタだの使っているが正直な所何もわからず使っている。(だって元のコードで使ってるから……)それに実装にも気に食わない所があって特にmain関数内でupdate()を個別に三回呼ぶ所が気に食わない。std::vectorに格納して範囲for文でぶん回せばいいのかなーと思いつつ、しかし、自作テンプレートクラスをコンテナに格納する方法がわからず投げた。C++ほんとわからん。

 そうこう言いつつこれだけの機能を初学者でもあっさり1日程度(元になるコードを改造したに過ぎないが……)で実装できるのは素晴らしい。暫くはUE4VR開発も含め)とBlenderとSiv3Dをメインで触っていこうと思う。

 そんなことを考えていたら某社のインターン採用通知が届いた。Unityを使うようなので8月までにUnityを最低限使えるようにならなくてはいけなくなった。時間がほしい。

(5/31追記)一応vector使った方法でmain関数書き換えた。こんなものを当てにしてはいけない。

const String RGBTag(int i) 
{
    switch (i) {
    case 0:  return L"R";
    case 1: return L"G";
    case 2: return L"B";
    default: return L"Error";
    }
}
void Main()
{
    using TS_RGB =  TextSliderGUI<uint32>;

    GUI gui(GUIStyle::Default);
    gui.setTitle(L"テスト");

    std::vector<TS_RGB> vec_ts;

    for (int i = 0; i < 3; i++)
    {
        vec_ts.emplace_back(0, 0, 255);
        vec_ts[i].LinkGUI(RGBTag(i), gui);
    }

    while (System::Update())
    {
        for (auto& v : vec_ts)   {v.update();}
    }

}

MagicaVoxelで作成したモデルをBlenderでリギングしたい(個人用メモ)

 前回記事でStatic MeshをUE4に持っていく方法は確立できたがボーンを入れてアニメーションを付けるところまではまだできていない。いろいろ実験してみたので今回はそのメモ。

ai-gaminglife.hatenablog.com

QubicleのOptimizationオプションについて

 ボクセルで制作したモデルをplyで書き出してからBlenderでfbxとして書き出すとUVテクスチャがない、頂点、ポリゴン数が莫大に増えるといった問題がある。

 そこで、Qubicleの有料DLC、「Mesh Module」を購入することで自動でUV展開したテクスチャを得ることができ、更にボクセルモデルを自動で最適化し、ポリゴン数を大幅に減らすことができる。

f:id:ai_gaminglife:20180516175718p:plain

 file -> export -> fbx でexport時のオプションを設定できる。最適化に関わるのは "Optimization" のところ。"None"、"Non-Manifold"、"Manifold"の3つの選択肢がある。

 "None" は目に見えない部分の頂点が削除される。これはBlenderの重複頂点を削除でも出来るのでテクスチャを得る以外の目的で使うことはなさそう。

 "Non-Manifold"は最もポリゴン数を減らすことができる。しかし、このオプションは形によって、"not watertight"、水密にならないのが問題。つまり一部の辺が画像のようにつながらなかったりする。一切形を変更しないStaticMeshであればこれでも問題はないが、ボーンを入れてアニメーションさせるSkeletalMeshとして使用するとつながっていない辺が裂かれてしまい問題。

 "Manifold" は "Non-Manifold" より多くのポリゴンを必要とするが、 "Non-Manifold" で問題であった非水密性が解決できる。

 というわけでMagicaVoxelで作成したStaticMesh用のモデルは基本的に "Non-Manifold" 最適化をかければいい。SkeletalMesh用のモデルは "Manifold" を使う…と言いたいところだが話はそんなに単純ではない。詳細は次に。

参考サイト http://minddesk.com/learn/article.php?id=18 http://minddesk.com/learn/article.php?id=30

各手法で作ったモデルをMixamoでリグ付けしてアニメーションさせてみる

 MagicaVoxelで作ったモデルをBlenderでボーンを入れられるようにするには大きく分けて以下の方法が考えられる。

  1. MagicaVoxelでply形式でexport
  2. Magicavoxelでobj形式としてexport
  3. qb形式で書き出した後"Non-Manifold"最適化をかけてfbx or obj形式でexport
  4. qb形式で書き出した後"Manifold"最適化をかけてfbx or obj形式でexport

 1は色を頂点カラーとして保持しているためゲームエンジンで利用するためにUVテクスチャに頂点カラーをベイクしたい。しかし、標準のBlenderではどうしてもズレてしまうのは過去記事を見ていただけるとわかるはず。

 2はパレットカラーをテクスチャとして持っている。しかしできることならUV展開したテクスチャを持ちたい。

 3は前述の通り水密性が保たれておらず、ボーンを入れ変形させると境目で裂けてしまう。

 で本命の4。これが一番良さそうに見えるのだが、ボクセルらしいアニメーションを得られない。四角形で形成されるボクセルモデルを三角ポリゴンに分けているからだ。しかもボクセルで作ったモデルの関節をボーンで無理やり曲げることで明らかに違和感のあるアニメーションになってしまう。

 こんな感じでどの方法もコレといった解決方法にはなっていないがものは試し。ブラウザ上で気軽にリグ付けできるAdobeのサービス、Mixamoを使ってアニメーションさせてみる。(3は試すまでもなくダメなので省略、1は事情により省略)

「Magicavoxelでobj形式としてexport」した場合

「qb形式で書き出した後"Manifold"最適化をかけてfbx or obj形式でexport」

 ぱっと見、「Magicavoxelでobj形式としてexport」が一番それらしい動きになっている。しかしコレではまだ納得できない……

考えられる解決方法

 Twitterで権田支配人(@GONDAman555)さんに教えてもらった方法がQubicleでモデルを分割するというもの。こうすれば違和感ないアニメーションが作れるかも……?しかしBlenderがワカラナイ()のでまだ実際には出来ていない。

おまけ

 現在Qubicleでパーツごとに分割してAutoRigProを使ってリグ付けしようとしている。AutoRigProはBlendファイルをUE4に書き出すのに優れたアドインの一つらしく、コレでうまくいけばUE4上でのリターゲットも容易にできそう。しかし例によってBlenderワカラナイのでドキュメントや解説動画とにらめっこしながら格闘中……

MagicaVoxelで作成した3DモデルをUE4に持っていく方法・完結編(Qubicle使用)

 とうとうMagicaVoxelで制作したモデルをUE4に上手く書き出す方法がわかったのでまとめ。過去記事は以下の通り。

ai-gaminglife.hatenablog.com

ai-gaminglife.hatenablog.com

ai-gaminglife.hatenablog.com

 今回はSteamで販売されている「Qubicle」(1980円)とその追加DLC、「Qubicle Mesh Module」(2480円)を使用する。Qubicleを通して書き出しすることで頂点を減らし更にUV展開したテクスチャも得る事ができます。神ツール!

MagicaVoxelで作成したモデルをQubicleに持っていき調整

f:id:ai_gaminglife:20180516175650p:plain

 今回はMagicaVoxelでこのモデルをUE4に持っていく。Export形式は「qb」。これはQubicle対応の形式らしい。

 続いてQubicleを起動。先程書き出したqbファイルを選択しロード。

f:id:ai_gaminglife:20180516175701p:plain

 Transformから 「Flip Z」を選択してZ軸を反転させる。モデルの中心がずれているので MagicaVoxelでの中心とQubicleエディタ上の座標原点を揃える。

f:id:ai_gaminglife:20180516175710p:plain

 File -> Exportから FBXを選択。下画像の様な設定画面が出るので画像の様に設定する。「Unit Scale Factor」を変更するとモデルの大きさを変えることが出来る。

 これでFBXファイルとして書き出し完了。

f:id:ai_gaminglife:20180516175718p:plain

(必要な場合は)Blenderで調整する

 ボーンを入れる必要があるときなど調整が必要な場合はBlenderを一度通す必要がある。というわけでBlenderを起動。いつものように最初に配置されている立方体を削除し、先程作成したFBXファイルをインポートする。

 シェーディングをマテリアルに変更し、モデル全体に光が当たるようにライトを配置する。

f:id:ai_gaminglife:20180516175725p:plain

 見ての通りテクスチャがボケてしまっている。これはユーザー設定 → システムのミップマップのチェックボックスを外すことで解決する。

f:id:ai_gaminglife:20180516175736j:plain f:id:ai_gaminglife:20180516175745p:plain

 これで問題なくモデルにボーンを入れられるようになった。この辺りはまたいずれブログにまとめる。

 調整が終わったらFBXとしてexport。これでUE4に持っていくまでの調整は終了。

FBXをUE4にインポートする

 FBXをコンテンツブラウザにドラック&ドロップでインポート。特に設定はいじる必要なし。

 これで全ての作業完了…と言いたいところだがモデルを見てみるとテクスチャがボケボケ&色が一部滲んでいる。

f:id:ai_gaminglife:20180516175823p:plain

 これはUE4のテクスチャのデフォルト設定が問題。というわけでテクスチャファイルを開き、以下のように設定する。

  • Comression -> Compression Settingsを 「UserInterface2D」に変更する

  • Level of Detail -> Texture Groupを 「2D Pixels」に変更する

  • Texture -> Filterを 「Nearest」に変更する

 これでMagicaVoxelでの色そのままにモデルをUE4に持っていくことが出来た。

f:id:ai_gaminglife:20180516175836p:plain

 以上、時間は掛かったがほぼ理想通りにボクセルモデルをUE4に持っていくことが出来た。

 

UE4超初心者向けチュートリアルスライド(2)を公開しました

ai-gaminglife.hatenablog.com

 前回は想像以上の方に見ていただけたようでありがとうございます。サークルの第一回集会も個人的には上手くできたと思っているので、第二弾を作成しました。

 今回は

  • プレイヤーの攻撃の簡易実装
  • 敵キャラ、敵AIの実装
  • 敵のスポーン管理

を取り扱っています。二回分やっていただければ、ざっくりとしたUE4の使い方、そしてUE4の良さがわかってもらえると思います。

また、今回はalweiさんのこちらの記事をかなり参考にさせていただいております。毎度有益な記事を書いていただき本当にありがとうございます!

unrealengine.hatenablog.com    例によりSlideshare版は画質劣化が認められますので見づらい場合はこちらでスライドをダウンロードしておいて下さい。

https://drive.google.com/open?id=1m2l15N1dPxDpPCwRrfQgeM0aOjsg_94X

UE4超初心者向けチュートリアルスライド(1)を公開しました

 大学でゲーム制作サークルを立ち上げた(唐突)。その最初の集会用にUE4超初心者向けチュートリアルスライドを制作しました。そこそこ有用なものだと自負しているのでこちらのブログにも公開します。

 なお、下記のzipファイルをダウンロードする前提で話が進むので、先にこのファイルをDLしてからチュートリアルに取り組んでください。(容量が大きすぎて警告が出るようですが無視して下さい)

 誤字脱字は多めに見て下さい……。

(追記) slideshareに上げたスライドの画像がかなり劣化しているようです。恐らくpptxの容量が大きすぎて劣化が起きているのだと思われます。zipファイルにpptxファイルを含めているので、見づらい方はそちらを見て下さい。

(訂正) スライド中でParagonアセットのことを1200万ドル稼ぎ出したアセット、と紹介していますが、正しくは1200万ドル相当の開発費をかけたアセット、でした。完全にこちらの勘違いです。申し訳ありません。

(追記2 2018/04/25)  一部変更を加えてスライド、zipファイルを再アップしました。どうも画質劣化はSlideshareの仕様のようです。非常に見づらいので、実際にやってみる方はzipファイルの中に含まれているpptxファイルを参照して下さい。

(追記3 2018/04/28) 更に誤字脱字を修正してSlideshare版のみ再アップしました。

https://drive.google.com/open?id=1mgs5w6d9MT3pIcWE4A2OG4PkpQQ1hCWd

Substance Painter2018で塗りつぶしレイヤを使うと色がにじむ問題(検証用モデル配布有り)

(2018/07/06追記) 解決した。

ai-gaminglife.hatenablog.com

 最近はぷちコンが終わりUE4のモチベが下がっているのでC++と3D周りのソフトの勉強をしている。(ぷちコンの感想はいずれ上げます)

 で、Blenderでつくったモデルを下の画像のように適当にシーム付けしてUV展開、それをFBXとして書き出してSubstance Painterでマテリアル作ってUE4にもっていこうとしていた。そこで嫌な問題にぶち当たってしまった。

f:id:ai_gaminglife:20180407165447p:plain

 (モデルの完成度はさておき)塗りつぶしレイヤを用意し、Polygon Fill -> UV Chunk FillでUV島単位でブラックマスク画像を作成し塗り分ける。SPは塗りつぶしレイヤでざっと塗り、細かい汚しなどを3Dペイントしていくという使い方をしていくものだととあるサイトで学んだのでそのようにしたのだがここで問題発生。

f:id:ai_gaminglife:20180407165504p:plain

 一件何の問題もなく塗り分け出来ている用に見えるが、拡大して見てみると非常に分かりづらいのだが、一部が黒くにじんでいる。

f:id:ai_gaminglife:20180407165516p:plain

 流石にわかり辛いのでもう少しわかりやすくにじみが出ているモデルを。

f:id:ai_gaminglife:20180407165529p:plain f:id:ai_gaminglife:20180407165541p:plain

 拡大してみると下の画像のように端の部分でわずかながらオレンジ色に滲んでいる。どうやら隣接するUV島の色が隣に滲んでいる様子。

 ぶっちゃけ注意して見ないと気づかないレベルなので無視して作業を進めても良いのだが一度気づいてしまった以上無視できない性分なので他にやるべきことがあるのに暫くコレばかり調べてしまっている。Twitterで色々な方に話を聞いてみたがSP2018ver固有の問題と言った話も飛び出しぶっちゃけ自分には手に負えない。

 というわけで、ブログ冒頭で例として見せた緑色の大砲のようなモデルを公開します。もし原因がわかったら教えていただけると助かります。

 リンク(https://1drv.ms/f/s!AgAyqT_F2n2a60kx43LPxmCjkwK-)

 自分なりに考えた原因は

 3つ目なら泣く。