C++メインでUE4ぷちコンに参加してみた


概要

ぷちコン参加は今回で5回目。これまで最優秀1回、受賞2回。
BPメイン(C++ 1~3割)で作ってました。

今回はほぼC++で作ってます。


C++で開発して良かったこと

  • BPスパゲッティをみなくてすむ
  • 開発後半は、デバッグしやすくコードの差分が見やすいので助かった
  • AnswerHubなどで見つけた解決策がC++でも大丈夫
  • ソースを読めば解決できることも多い

今回やったこと

Visual Studioのプラグインを使ってみた

特にVisual Assist X はオススメです。
ファイル検索だけでよければ、無料のVSFileNavもけっこう使いやすかったです。

  • Visual Assist X
  • Entrian Source Search
  • VsVim

モジュールを分けてみた

ヒストリアさんの記事などを参考にモジュール分割をしてみました。
http://historia.co.jp/archives/3097/

少し手こずりましたがモジュールの仕組みを知ることは、UE4 C++を触るうえで必須だと感じました。
リンクエラーの原因とかもわかるようになります。

ただ、整理のためにモジュールの移動とかしてるとトラブルも起きやすいので、ぷちコンの期間外で試してみたほうがいいです。
そもそもぷちコンの規模の作品では、モジュール分割してもデメリットが多く、あまり必要ないと思います。

Luaを使ってみた

Luaはスクリプト言語なのでC++のビルドを待たずにすぐ結果が反映されます。
ジャンプ力や回転速度、チェインの挙動など細かく調整したいパラメータをLuaで設定できるようにしました。
最初の設定が手間ですが一度仕組みを作ってしまえば、UPROPERTYを追加する+アルファくらいの感覚でパラメータを追加できます。
BPと比べて、

  • テキストなので一覧性が高い
  • 変更履歴が管理できる
  • 計算させた値を返したりできる

など導入するメリットは大きいです。

下のサイトを見て、実装しました。
モジュール分けとか試したあとだと理解しやすいような気がします。
https://wiki.unrealengine.com/Integrating_Lua

後は、下のような関数を用意して呼び出しました。

/** Luaの関数名を指定してfloatを受け取る */
float ULuaImporter::GetFloat(FString Path, FString FuncName)
{
    lua_State* lua = luaL_newstate();
    luaL_openlibs(lua);
    FString root = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir());
    root += FString::Printf(TEXT("Script/%s.lua"), *Path);
    luaL_loadfile(lua, TCHAR_TO_UTF8(*root));
    lua_pcall(lua, 0, 0, 0);
    lua_getglobal(lua, TCHAR_TO_UTF8(*FuncName)); // 呼び出す関数
    lua_pcall(lua, 0, 1, 0);
    float ret=0;
    if (lua_isnumber(lua, -1))
    {
        ret = lua_tonumber(lua, -1);
    }
    lua_pop(lua, 1);
    return ret;
}

luaで簡単な値を返す例

function spin_time() return 1.0 end
function spin_time_in_air() return 5.0 end
function jump_force() return 1800 end

function combo_factors(arr)
   for i = 1, #arr do
       arr[i] = (i - 1) * 500 > 2000 and 2000 or (i - 1) * 500
   end
   return arr
end

サブレベルのストリーミング

ひとつひとつのステージはサブレベルで配置しています。
ステージ呼び出し後に読み込んだアクターに対して処理をしたかったので、下のようにして通知を受け取るようにしました。
UUIDを変えないと、複数のレベルを同時に読み込むことができませんでした。

 FName LevelToLoad = FName(*name);
 FLatentActionInfo LatentInfo;
 LatentInfo.CallbackTarget = this;
 LatentInfo.ExecutionFunction = FName("NotifyLevelLoaded");
 LatentInfo.Linkage = 1;
 LatentInfo.UUID = ++UUID;
 UGameplayStatics::LoadStreamLevel(this, LevelToLoad, true, true, LatentInfo);

LoadLevelInstanceで呼んだほうが、読み込んだレベルのアクターを直接取得できて便利だったのですが、読み込みが重かったのでやめました。

 bool b;
 auto streaming = ULevelStreamingDynamic::LoadLevelInstance(GetWorld(), LevelToLoad.ToString(), FVector(0, 0, 0), FRotator(0, 0, 0), b);
 CurrentStreaming = streaming;
 GetWorld()->AddStreamingLevel(CurrentStreaming);
 CurrentStreaming->OnLevelLoaded.AddDynamic(this, &AStageLoader::NotifyLevelLoaded);

デバッグラインのカスタム

円弧のデバッグラインが欲しかったので、ULineBatchComponentを真似して円弧のラインを描けるようにしました。
こういうことが簡単にできるのはC++の利点だと思います。

ぷちコンでC++を使うべきか

C++を使うと開発スピードが落ちるので、短期間の開発でC++メインにするのはリスクが高いです。
簡単なゲームでも完成させたことがない人や、デザイナー・プランナー志望ならBPメインで作ったほうがいいと思います。

C++を使う場合でも、使ったことのないライブラリの検証とかは、ぷちコンの期間内ではやらないほうがいいです。
肝心のゲーム開発が進まなくなります。

BPとC++を使う上で、参考になる資料も上がってますので、最後に紹介しておきます。

BPからC++に移行した話
https://www.slideshare.net/EpicGamesJapan/ue4-bp18

BPメインの事例
https://www.slideshare.net/EpicGamesJapan/ue4-137253034