johnnyGameStudio’s blog

無能なゲームプログラマのぼやき ぎーくになりたい Twitter: https://twitter.com/JGS_Developer

【UE4】Unityで慣れたゲームPGがUE4に乗り換える際のTips

はじめに

最近Unityから離れてUE4触ってみようとするゲーム系エンジニア増えましたね
そんな人たちに向けて、実際にUnityに慣れたプログラマUE4に移行してみたときに困ったポイント・知っておいたほうがいいポイントをまとめてみました
正直、始めたばっかりなので間違っている箇所もあるかもしれませんがその際はコメントしていただけると助かります

本記事の対象となる人

  • C++なんもわからんという人
  • Unityなら一通り触っていたという人

参照渡しとポインタ渡しと値渡し

なにをいってもこれ
もちろん、C#にもこれらはあるわけですがC++ではより厳格な制御が求められます
例えば、C#でこんな感じで書いた場合だとString型のtmpはhoge関数の引数として渡されてもC#のString型は参照型なのでコピーはされません
(もちらんintなどの値型が引数ならコピーされます)

void hoge(string str)
{
...
}

void foo()
{
    string tmp = "hello";
    hoge(tmp);//参照型なのでコピーは発生しない
}

また、C#で参照渡しを行いたい場合はrefを使います
現場によりけりだと思いますが、refを使うことはUnityだとあまり使用するタイミングは少ない気がします
(使うケースだと関数内で引数の中身をいじることを明示的に示したい時などでしょうか)
何が言いたいかというと、C#の場合は割と雑に書いてもコンパイル通るし問題ないように動きます
しかし、C++はそうはいきません
先ほどのように書くと値渡しとなりコピーが発生します

void FooClass::Hoge(FString str)
{
...
}
void FooClass::hogehoge()
{
    FString tmp = TEXT("Hello");
    Hoge(tmp);//値渡しなのでコピーが発生する
}

これを防ぐにはポインタ渡しが参照渡しを行ってあげる必要がありますので以下のようにする必要があります
また、値渡しでない場合はconstを積極的に使ったほうがよいでしょう
※ポインタ渡しと参照渡しがなにかという説明はほかに色々記事があるのでここでは説明しません

void FooClass::Hoge_pointer(const FString* str)
{
...
}
void FooClass::Hoge_ref(const FString& str)
{
...
}
void FooClass::hogehoge()
{
    FString tmp = TEXT("Hello");
    Hoge_pointer(&tmp);
    Hoge_ref(tmp);
}

列挙型の文字列化

C++では列挙型を文字列化させるように気の利いた機能はない
C#のようにToString()で簡単に文字列化させてくれるなんて素晴らしい機能はない
だが安心してほしい、UnrealC++ではそこらへんはカバーされている。ありがとうUE4

UEnumをStringに変換する方法はこちら
もしくはこっち

ただし、これはUEnumの列挙型だけであり、なんらかの理由によりUEnumが使えず、普通に宣言されているenumは泥臭く実装するしか方法はないです(あったら是非教えてほしい)
つまり、いい感じのマクロを組んだり、Map(C#でいうところのDictionary)を使用して文字列を列挙型の各値と連結させるしかない
ここらへんは下記リンクで世界中のエンジニアが試行錯誤しているのでぜひ参考にしてください
世界一悲しい話をしている場所はこちら

関数の呼び出し履歴

Unityの場合、Debug.Logを使えばその関数でLogが吐き出されるまでの経路をConsoleウィンドウで確認できますよね
僕の場合はよくこれを使ってデバッグとかしていたんですが、UE4ではそれはできません

呼び出し履歴を知りたい場合はブレイクポイントを使うしか方法はありません
UE4のブレイクポイントを使うための方法などはこちらの方がまとめてくださってます

改造UE4使ったときのインテリセンスがやばい

小さいProjectデータや改造UE4を使っていない場合などは言うほど、重くならないのですが、僕の場合改造されたUE4を用いて作業をしているのでVisualStudioが参照しようとするファイル数が尋常ではなく、インテリセンスがほとんど機能しません
ですが、こちらの記事を参考にしてもうらうとかなり軽減されます

For those who suffer from Visual Studio IntelliSense slowness

簡単に解説すると、ソースファイルを変更した際にインテリセンスはすべてのパスを検索して参照を見つけており、UE4のエンジンデータは特に量が多いです。なので、エンジン側を毎回検索せずにすることで高速化しているようです。

余談ですが、個人的に予測IntelliSenseを使いたいので
オプション > テキストエディター > 試験的 > 予測IntelliSenseの有効化をTrueにしてます

image.png

includeとはなにかということ

Includeとは何をしているか知っていますか?
includeを行うとコンパイル時にその宣言された部分に指定されたヘッダープログラムを差し込み、そのプログラムファイル内で外部のクラスなどを扱えるようにしています
なぜこれをちゃんと知っておくことが大事かというとコンパイル時間に差が出てくるからです
UE4C++クラスを作成するとヘッダーファイルにこんな宣言がされているのを確認できると思います

#pragma once//これ

#include "CoreMinimal.h"
...

これは何を意味するかというと、このヘッダーファイルを読み込んだ時に二重に読み込まれることを防ぐ効果があります
たとえば以下のように二度同じヘッダーをincludeしてしまうとコンパイル時に二つ同じ関数や変数が存在することになっていまい、予期せぬエラーの原因になったり、コンパイル時間が増加したりするなど悪影響を及ぼします

#include "Sample.h"
#include "Sample.h"

ですが、このSample.h内で「#pragma once」が宣言されていればそのようなことは防ぐことができるというわけです
なので、新しくクラスを作成した際は必ず#pragma onceを宣言することを忘れないようにしましょう
詳しく知りたいかたはこちら

無名名前空間

↑のコンパイル時間を長くしない、ということに関連して無名名前空間は使ってほうがいいかなと思います
無名名前空間はCPPファイル内で宣言できるためコンパイル時間を長くしなくて済むほか、外部から見えるヘッダーファイルでは見てほしくない関数名や変数名などを隠蔽するための機能です
ヘッダーに宣言しているとアクセスできなくても関数などは見えてしまうため、設計的にそれは好ましくない場合などで使います
無名名前空間を使うときは「そのファイル内で完結する関数・変数」に限ります
具体的には以下のように使います

#include "Test.h"

namespace
{
    void Hoge()
    {

    }
    int foo;
}


// Called when the game starts or when spawned
void ATest::BeginPlay()
{
    Super::BeginPlay();
    Hoge();
    int a = foo;
}

まとめ

以上C++なんもわからんゲームプログラマがUnityから移住してきた際に知っておいたほうがいいTipsでした
ほかにも色々ありますが、長くなるのでいったんこれで締めます
参考になれば幸いです
間違いなどありましたらコメントください