johnnyGameStudio’s blog

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

【UE4】ランライムに生成されるActorComponent内のMesh情報をエディタ上で表示する

はじめに

開発していると「このMeshは動的に入れ替わるからエディタ上ではまだ生成されていないし、デフォルトで余計なMeshは表示させたくない」といったクラス設計にすることが多々あると思います
しかしまだ動的に生成されるオブジェクトはそのままだとエディタ上で確認できず「このMeshはどれくらい大きいのか?比較できない」といった状況になります
その際の対処法をまとめておきます

結論:OnRegisterを使おう

実装

今回のサンプルの構造としてはUTestComponentを持つATestActorを継承したBPクラスを作成し、ATestCharactorをB;ueprintエディタ上で設定します

ATestActorとATestCharactorは特筆することはないのでてきとーに用意してください

ざっくり以下のような図になります
image.png

確認

エディタで確認すると表示しているのはATestActorを継承したBlueprintですが、設定してATestCharacotrが表示されていることがわかるとおもいます(椅子のMeshがATestCharacotr)

image.png
image.png

Sample Code

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/SceneComponent.h"
#include "TestComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SANDBOX_API UTestComponent : public USceneComponent
{
    GENERATED_BODY()

public: 
    // Sets default values for this component's properties
    UTestComponent();

protected:
    // Called when the game starts
    virtual void BeginPlay() override;
    virtual void OnRegister() override;
    virtual void OnUnregister() override;

public: 
    // Called every frame
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

    void CreateCharactor();
    void DestroyCharactor();

private:
    UPROPERTY(EditAnywhere)
    TSubclassOf<class ATestCharactor> charactorClass;
    UPROPERTY()
    class ATestCharactor* meshActor = nullptr;
    UPROPERTY()
    bool bCreatedCharactor = false;
        
};
// Fill out your copyright notice in the Description page of Project Settings.


#include "TestComponent.h"
#include "Engine/World.h"
#include "TestCharactor.h"

// Sets default values for this component's properties
UTestComponent::UTestComponent()
{
    // Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
    // off to improve performance if you don't need them.
    PrimaryComponentTick.bCanEverTick = true;

    // ...
}
void UTestComponent::OnRegister()
{
    Super::OnRegister();
    CreateCharactor();
}
void UTestComponent::OnUnregister()
{
    DestroyCharactor();
    Super::OnUnregister();
}
void UTestComponent::CreateCharactor()
{
    if (charactorClass != nullptr)
    {
        DestroyCharactor();

        FActorSpawnParameters spawnParameters;
        spawnParameters.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
        meshActor = GetWorld()->SpawnActor<ATestCharactor>(charactorClass,FTransform(), spawnParameters);
        //自身の子オブジェクトとしてぶら下げる
        meshActor->AttachToComponent(this,FAttachmentTransformRules::KeepRelativeTransform);
    }
}
void UTestComponent::DestroyCharactor()
{
    if (meshActor != nullptr)
    {
        meshActor->Destroy();
        meshActor = nullptr;
    }
}
...

注意点

OnUnregisterを忘れないように

OnRegisterはPlay時に呼ばれたり、PlayInから停止して非実行状態に戻ってきたときにも再度呼ばれたりするので生成するクラスが増えてしまったりするのでOnUnregisterで生成しておいたMeshはDestroyしておくのを忘れないようにしておきましょう

【UE4】LevelSequenceでActorComponentを継承したComponentのプロパティを扱いたい

はじめに

C++で作成したActorに自分で追加してあるActorComponentのメンバ変数をLevelSequenceで扱いたい場合の手順がまとまっている情報がなかったので備忘録を兼ねてまとめておきます

環境

UE4.24.2
VisualStudioCommunity2017
Windows10

HowTo

ActorのコンストラクタでActorComponentを追加する

たとえ、メンバ変数にActorComponentを持っていたとしてもコンストラクタでCreateDefaultSubobjectで追加しておかないとLevelSequenceで認識してくれません
検証用に以下のサンプルでは二つのActorComponentを

という二つの方法でやってみます

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestActor.generated.h"

UCLASS()
class TESTPRJ_API ATestActor : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    ATestActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

private:
    class UTestComponent* testComp;//コンストラクタで生成

    class UTestComponent* testComp2;//BeginPlayで生成
};

// Fill out your copyright notice in the Description page of Project Settings.


#include "TestActor.h"
#include "TestComponent.h"

// Sets default values
ATestActor::ATestActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
    testComp = CreateDefaultSubobject<UTestComponent>(TEXT("testComp"));
}

// Called when the game starts or when spawned
void ATestActor::BeginPlay()
{
    Super::BeginPlay();
    testComp2 = NewObject<UTestComponent>(this);
    testComp2->RegisterComponent();
}

LevelSequenceで扱いたいメンバ変数にUPROPERTY(Interp)を付与する

LevelSequenceでメンバ変数を直接変更させる場合、UPROPERTY(Interp)をつけてあげる必要があります
なので今回はてきとーにBoolの変数をActorComponentに追加しました

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "TestComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TESTPRJ_API UTestComponent : public UActorComponent
{
    GENERATED_BODY()

public: 
    // Sets default values for this component's properties
    UTestComponent();

protected:
    // Called when the game starts
    virtual void BeginPlay() override;

public: 
    // Called every frame
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

private:
    UPROPERTY(Interp)//追加
    bool bFlag;
        
};

確認

てきとーなLevelSequenceを作成し、TestActorのトラックを追加してみるとコンストラクタで追加した「testComp」だけが表示されて「testComp2」は表示されないことがわかります

image.png

また、TestComponentのメンバ変数がLevelSequenceのトラックとして追加できるようになっていることが確認できます

image.png

なぜコンストラクタで追加しないとLevelSequenceで扱えないのか

コンストラクタ内にブレイクポイントを仕込むとわかるのですが、UObjectを継承しているクラスのコンストラクタは以下の二つのタイミングで実行されます

  • ゲームが実行されたとき
  • エディタを起動するとき

つまり、エディタを起動する際にUObjectのリフレクション情報を生成しておいて、その情報を以ってLevelSequenceで扱えるようにしているわけです
C++そのものにリフレクションはないので考えてみれば当たり前ですね

【UE4】BPに公開している関数の引数に使う構造体にデフォルト引数を使いたい【C++】

はじめに

開発をしていてBPに公開されている関数に構造体を追加する必要が出たときにこの現象にぶつかりました
色々調べましたがエラー内容で検索してもドキュメントにも原因が明記されているところは見当たらなかったので解決方法をまとめておきます

デフォルト引数が使えない

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestActor.generated.h"

USTRUCT(BlueprintType)
struct FActorInfo
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "hoge")
    int32 num;

};


UCLASS()
class SANDBOX_API ATestActor : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    ATestActor();

    UFUNCTION(BlueprintCallable, Category = "TestActor")
    void TestFunc(const FActorInfo& info = FActorInfo());//コンパイル時にエラーが出る

};

上記のように書くと以下のようなコンパイルエラーが発生します

重大度レベル   コード   説明  プロジェクト  ファイル    行 抑制状態
エラー       C++ Default parameter not parsed: info "FActorInfo()"   Sandbox C:\UE4_Prj\Sandbox\Source\Sandbox\TestActor.h   30  

原因の推察

C++のルール的には記述の問題ではない(UFUNCTIONコメントアウトするとコンパイルが通る)ことから考えてもUE4ならではの状況なのは間違いないです
ということは,BPとして公開することに原因がありそうです
BPのデフォルト引数はmetaデータとして埋め込むことが可能であることを考えると構造体のデフォルト値をコンパイル時に取得できていない?

解決方法

metaデータにデフォルト引数を持たせることで解決できます
関数の定義を以下のように書き換えましょう
デフォルト引数が設定されているのでBPでピンを繋げていなくてもエラーは出ません

UFUNCTION(BlueprintCallable, Category = "TestActor", meta = (AutoCreateRefTerm = "info"))
void TestFunc(const FActorInfo& info);

image.png

試しに以下のようにmetaデータを削除してみるとエラーがでることが確認できる

UFUNCTION(BlueprintCallable, Category = "TestActor")
void TestFunc(const FActorInfo& info);

image.png

所感

根本の理由はちゃんと追っていないので、詳細な理由がわかる人がいたら教えてください

【UE4】LevelSequenceでTrackとして追加されているActorのインスタンスを取得してイベントを呼び出す【UnrealEngine4】

はじめに

開発をしていてLevelSequence経由で発生させたいイベントがあり、LevelSequenceにBindされているActorの関数を呼び出そうと思ったら、SequenceDirector内でどうやってインスタンスを取得するのか調べてもわかりませんでした
Additional Event Receiversを使いたいのに、どこのサイトも「実際にレベルに配置する」のが前提で書かれており、Additional Event Receiversはレベルに配置しないと設定できないように思えたので動的に再生するLSを切り替えるケースではどうすればいいのかわかりませんでした
色々試していたらできたのでその手順をまとめておきます
もっといいスマートな方法があったら教えてください

環境

UE4.23.1
VisualStudioCommunity2017
Windows10

事前準備するもの

LevelSequenceを用意する
呼び出したい自作のBP公開されている関数を持つC++クラスを継承したBPクラス
今回用意したクラスはこちら

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class IMGUI_PRJ_API AMyActor : public AActor
{
    GENERATED_BODY()
    
public: 
    // Sets default values for this actor's properties
    AMyActor();
public:
    UFUNCTION(BlueprintCallable, Category = "Test Actor")
    void TestFunction();

    UFUNCTION(BlueprintCallable, Category = "Test Actor")
    void BoolFunction(bool hoge);

};

#include "MyActor.h"
void AMyActor::TestFunction()
{
    GLog->Log("TestFunction");
}

void AMyActor::BoolFunction(bool hoge)
{
    GLog->Log("BoolFunction");
}

手順

1. LevelSequenceを開く

image.png ※レベルに配置されてないのでDetailウィンドウにはなにも表示されません

2. 「Track > Actor to Sequence」からBP_MyActorを追加する

sequence1.png

3. BP_MyActorの下にEventトラックを追加する

sequence2.png

4. 追加したEventトラックを右クリックしてプロパティからBP_MyActorをEvent Receiversに登録する

sequence3.png

5. キーを作成し、キーを右クリックからCreateQuickBindingからBP公開されているイベントを呼ぶ

sequence4.png

5-2. BP_MyActorの下につけてないEventトラックではEvent Receiversに登録してもCreateQuickBindingが出てこないので注意

sequence5.png

6. 追加すると確認LevelSequenceDirectorが開き、BP_MyActorのインスタンスを引数で持ってこれているのが確認できる(完)

image.png

6-2. LevelSequenceから呼ぶイベントにtarget以外の引数を増やしたらイベントそのものが無効になるので注意

image.png sequence6.png

さいごに

ざっくりまとめました
これややこしくない?これ以外にスマートな方法がある気がしてならないんですが、誰か教えてくれませんか?
気軽にコメントよろです

【UE4】「え?それで取得できるの?」と思ったAssetやクラスのGet周りのまとめ【C++】

この記事はUnreal Engine 4(UE4) Advent Calendar 2019の4日目の投稿記事です

qiita.com

はじめに

元々UE4C++でのゲームAIについて書いていましたが、諸事情により完成しませんでした・・・
なので大したネタじゃないです、すみません
変わりにUE4C++で開発していて「え?これでインスタンス取得できるの?」と思ったGet系をまとめてみました
初心者向けでよろしくお願いします
公式ドキュメントのどこかに書かれているかもしれないですけど、まぁご容赦ください

Asset系取得

FObjectIterator

Module include
CoreUObject #include "UObject/UObjectIterator.h"

ディレクトリにあるAssetを全検索して名前で引っ張ってこれるので「大丈夫なのかこれ?正しいのか?」と不安になるコード

void ASampleActor::BeginPlay()
{
    UBlackboardData* bbData;
    for (FObjectIterator It(UBlackboardData::StaticClass()); It; ++It)
    {
        UBlackboardData* obj = (UBlackboardData*)*It;
        if (obj->GetFName().IsEqual("Sample_BB"))
        {
            bbData = obj;
            break;
        }
    }
}

クラスの取得・生成

MakeShareable

ぶっちゃけ公式ドキュメントに普通に書かれているんですけど、全然存在に気づかなかったです
MakeShareableを覚えてからはこっちのほうが楽なので「ふ~ん、便利じゃん」となりました

void ASampleActor::BeginPlay()
{
    Super::BeginPlay();
    TSharedPtr<OriginalClass> hoge = TSharedPtr<OriginalClass>(new OriginalClass());//これと同じ
    TSharedPtr<OriginalClass> hoge = MakeShareable(new OriginalClass());
}

GetComponentsByClass

そりゃこういうAPIはあるよねって感じだけど、複数形の名前じゃないからてっきり1個しか取得できないと思っていたのにさりげなく複数取得できたのでちょっと混乱した
空目していただけで元々複数形だった。俺の目は節穴か

void ASampleActor::BeginPlay()
{
    Super::BeginPlay();
    TArray<UActorComponent*> compArray = GetComponentsByClass(USampleComponent::StaticClass());//複数
    UActorComponent* comp = GetComponentsByClass(USampleComponent::StaticClass());//先頭1個
}

CastChecked< T >

UObjectを継承しているdynamic_cast相当の型変換はCastを使用しますが、CastCheckedはもしnullptrが入る場合にはエラーログを吐いたうえで停止します
「こいつがCastできないわけないが、もしnullptrが返ってきてたらわかるようにしておきたい」という場合に便利だなぁと思いました

void ASampleActor::Hoge(AActor* actor)
{
    AFooActor* fooActor = CastChecked<AFooActor>(actor);
}

スマホ向けゲームアプリにおける基本的なBattle(インゲーム)設計

はじめに

スマホゲーム業界ではデッキ編成やガチャなどのバトルではない部分を「アウトゲーム」Battleなどのゲーム性がある部分を「インゲーム」などと呼ぶ
※明確な定義はない
※この記事では単語を明確にするためBattleをインゲームと呼称する
どんなジャンルのゲームだろうと共通の抑えておくべき要点がある
ゲーム業界では仕様変更がかなり頻繁に行われる傾向にあり、それの影響か大人数で開発を行っているとクラスが肥大化しまくっていたり明らかに関係のないクラスや関数内に予想外の処理が行われてしまっているソースを読むことになるのは珍しくない
スマホゲームでは長期間の運営が想定された設計を行う必要があり「運営〇年目」となっても変更に耐えうる形にするには最初の設計が最も重要である
ここに書いてあることは人によっては「そんなこと・・・?」と思ってしまうものであるが、ここ最近スマホゲームを作りなれていない人にとっては当たり前ではないということがわかってきた
一人でも多くの人にインゲームの設計について最低限の必要な要点を理解してもらい皆幸せになってもらうために記しておく
要点は以下の通り

インゲーム設計で抑えるべき要点

  1. インゲームだけで起動できるようにする(アウトゲームとの疎結合
  2. ゲームデザインレベルデザインイテレーションを速く回せるようにする
  3. なんでもやるクラスは作らない(singletonは一つまで)

1-1.インゲームだけで起動できるようにする(アウトゲームとの疎結合

インゲームはアウトゲームとは完全に独立させておくのが最も重要である
理由としては

  • 開発時のテストが楽
  • ゲームデザイン班が確認するのが楽
  • スマホゲームおなじみのバトル再開機能の実装が楽になる

などが挙げられる
逆にこれができていないとこれから紹介する要点がすべて無理になるといっても過言ではない

Unityを使用しているなら、インゲームのSceneからPlayして直接最低限の通信のみで起動できるようにしておくべきであり、UE4を使用しているなら同じようにインゲームのLevelから再生すればバトルを実行できるようにするべき
「タイトル画面を通ってマスターデータを取得し、Topページへ遷移後クエスト選択画面で特定のデバッグエストを選択」などという狂ったことを毎度繰り返さなくてはインゲームの動作確認ができないなんてことは断じてあってはならないのである

1-2.任意のステージ(クエスト)を任意のキャラクター編成で実行できるようにする

ただ実行できるだけでは意味がないのでUnityやUE4で実行していない状態でエディタ上乃至外部エディタで任意のステージ構成できるようにしたうえで実行することが重要である
これがないと新規のキャラクターや新規のスキルを実装しようとした際に都度開発サーバ上のマスターデータを弄らなくてはならなくなる(そんな面倒なことやってられないでしょ?)
具体的にどんな方法で読み込むかは人それぞれであるが、一般的なのはローカルディレクトリに「インゲーム開始のためのなんらかのデータ」を置いておき、SceneやLevelの実行時に読み込むことでサーバとの通信で持ってきたマスターデータと読み込んだデータでバトルを開始する手法ではないだろうか
UnityならJsonUE4ならCSV辺りがベター
※もちろんこれらの処理を行うためのコードはすべてリリース時には含まれないようにすること

2-1.ゲームデザインレベルデザインイテレーションを速く回せるようにする

ゲーム開発の難しいところは一度完成したと思っても容赦なくちゃぶ台がひっくり返るところである
「作ってみたけど思っていたほど面白くない」
「もっと要素を追加したい」
「面白いこと思いついちゃった!」
など日常茶飯事である
ゲームは面白くなくては意味がなく、「面白さ」などという不確かなモノのクオリティを担保する上でゲームデザイナーのトライアンドエラーイテレーションを速くすることがかなり重要であることは言うまでもない
また、エンジニア的視点でみてもゲームデザイナー側で出来ることを増やすことでエンジニアに過度なタスクが降ってこないようにすることができる(メンテナンスは必須になるが)

2-2.ゲームステージの変更をエディタ上で行えるようにする(可能ならばホットリロード)

1-2で述べた通り、任意のステージを任意の編成で行えるようにしたところで、非エンジニア職に「さあJsonを編集するのだ」などと言ってもそれを実行できるのは極わずかである
JsonCSVを直接触るのはバグの元なので、理想はそれらの編集エディタを用意してあげるべきだろう
可能であれば、ゲーム実行中にローカルのマスターデータを編集して動作を変更させたりする環境を構築すると楽(仕様変更がたくさんくるであろう開発序盤でこれを実装するのは時期尚早だと思うが考慮はしておいたほうが良い)
以上を踏まえたバトルの開始時の簡単なシーケンス図(慣れてないのでUML的に正しい書き方かは保証しない)
BattleStartFunc.png

2-2-2.Nodeベースのビジュアルスクリプトを使うことを選択肢に入れよう

エンジニアにありがちなのだが「すべての処理が同じソリューション内のコードで見ることができればそれが一番わかりやすい」といった幻想に取りつかれがちである
エンジニアだけで完結するタスクならばそれは正しいが、ゲームデザイナーやレベルデザイナーと密に連携を取らなくてはならない場合には誤りである

Unityには標準ではないが有名な有料AssetでPlaymakerやArbor、UE4には標準でBluePrintやBehaviorTreeが用意されている
これらはうまく活用すれば非常に強力なToolであり、ゲームを実行しながら値を変更したりNodeを繋ぎ変えたりすることが可能である
そうすることでレベルデザイン班などが自由にエディタ上で試すことが可能になり、イテレーションが高速になる(もちろん学習コストはかかるが)
その際注意しなくてはならないのは、非エンジニアが触るビジュアルスクリプト部分はゲーム処理の高レイヤー部分に限定するように注意して設計する必要がある

例えば、AIのターゲッティング処理にビジュアルスクリプトを採用する場合、「敵AIのターゲット判定の処理」のみを弄ることができるようにPlaymakerなりBehaviorTreeなりを使用しておくべきでターゲッティングを行った次の処理などとは蜜結合にならないようにするべきである(あくまで例でありターゲッティングはビジュアルスクリプトを用いるべきという話ではない)
つまりは非エンジニアがうっかり間違った編集をしてもゲームが止まったり致命的なバグが発生しないような状態にしておく

※便利になる代わりにエンジニアの知らないところで想定していたより複雑且つ大規模なNodeが組まれていたりすることになるので注意
※これらはゲーム性により使うべきかそうでないかは大きく異なってくるので必ず使うべきという話ではない

2-2-3.演出部分はプログラマーではなくても編集して動作確認できるようにする

演出部分は必ず予想外の変更や微調整が入る
プログラマーが演出部分まで担当していた場合によくあるケースだと、当初はアニメーションシーケンスを流すだけだったのが時間がたつにつれて、やれQTE要素入れたいだの特定の条件下では一部のエフェクトを変えたいだのやっぱりやめただのアニメーションの開始時と終了時に特別処理を特定のタイミングの時だけ行ってほしいだの仕様変更が入りコードの保守がとても難しくなる

プログラマが都度対応する必要がある設計になっていると手がいくらあっても足りなくなってしまうのでアーティスト側だけで「編集→動作確認」が完結するように設計する必要がある
UnityならTimeLineなどを使って細かな編集ができるようにしておくべきだし、同様にシーケンサを用いるべきである
また、PlaymakerやBlueprintなどのビジュアルスクリプトと併用して使うことでより広い範囲をアーティスト側だけで実装することが可能になる
演出部分に関してはエンジニア側が行うことは細かな実装ではなく、ビジュアルスクリプトで使う便利な機能をコマンド(Node)単位で作成していく程度にとどめておくのがちょうどよいだろう
以下は演出部分だけをビジュアルスクリプトにした場合のざっくりとしたスクリプト処理イメージ
UnityならPlaymakerやArbor、UE4ならBlueprintなどで脳内保管してほしい
Untitled.png

プログラマが実装するべき青いコマンドをゲームデザイナー側で処理させてしまうとNode全体が煩雑化してしまってバグの元なので注意。モックアップで仮に実装してしまうのもありだが必ずC#乃至C++で書き直すことをお勧めする
このような形にしておけば、ゲームデザイン側でコマンドに渡す引数と条件式の作り方次第で色々なことが試すことができる

3-1.なんでもやるクラスは作らない(singletonは一つまで)

これは別にインゲームに限った話ではないが、ゲーム内のあらゆる処理をなんでも行ってしまうクラスがあるとコードの保守が一気に苦しくなる
そのクラスがsingletonでさらに他にもsingletonがある場合にはもう悲惨である
インゲームは他のシーンと比較して複雑化しやすい傾向にあるため一番最初の設計が特に重要である

3-2.クラスの名前をできる限り具体的にする

そのようなクラスの誕生を防ぐ簡単な方法としてはクラスの名前を具体的にすることである
保守期間がそんなに長くないのにも関わらずスパゲッティ化したコードの多くは曖昧な名前のインゲーム統括クラスが存在するケースが多い
例えば以下のような名前のクラスファイルを見たことはないだろうか

  • Battle.cs(cpp)
  • Game.cs(cpp)
  • BattleManager.cs(cpp)
  • GameManager.cs(cpp)
  • RPGManager.cs(cpp)
  • ...

最初に名言しておくとこれらのクラスの存在意義を否定しているわけではない
おそらくは開発初期ではManagerクラスの上位クラスとして全体の管理や橋渡しだけを行うクラスを想定していたのだと考えられる
しかし、開発も進み関わる人員が増加したり納期に追われたりしていくにつれて知らず知らずのうちにこれらのクラスに具体的な処理が追加されていき、気がついた時には何でもこなす便利クラスが爆誕していたのだろう
例えば「BattleGeneralManager」といった名前にすれば初見で「managerクラスの上位クラス」と明示することができるのでスパゲッティ化する可能性は低くなる(それでもなるときはなるものではある。結局は運用体制とメンバー次第なところは否めない)
同じようにBattleGeneralManagerのメンバで保持する子Managerクラスも具体的な名前をつけていくとでいくらか保守性は高まる
例えばコマンド選択式RPGゲームの場合には具体的には以下のようなイメージである

Class Diagram.png

どれくらいの粒度で具体的に名前をつけるかとかそもそも「~Manager」といったクラス名はつけるなという意見もあるとは思うが、とりあえずは〇〇Managerのような名前では責任が曖昧になりがちなので、もう一単語増やして「〇〇△△Manager」といった名前にするように心がけるだけでだいぶ事故は減るはずである

あとがき

こういう記事を書くと色々なところからマサカリが飛んできそうなので書くべきか悩んだけど書いてみた
同じジャンルのゲームを設計しても人によって設計手法は違うしなにが正解なのかという答えはないものだと思うので、マサカリを投げたくなった人は
「こういうことを気にしている人がいる」
程度でとらえてほしい
でも個人的にはこれらは絶対に抑えておいてほしいしそのほうがみんな幸せになると思っている
そうそうふいつ

【UE4】Unityで慣れたゲームPGがUE4で覚えるべきTips〜その2〜

はじめに

前回の記事(Unityで慣れたゲームPGがUE4で覚えるべきTips)を書いてからだいぶ時間がたったので個人的にまとめておきたいとおもったことをつらつらと書いていく
また、UnityからUE4に移住してきた人やUE4が初めてのゲームエンジンという人も参考になると思います(多分)
間違っていたらご指摘お願いします

本記事の対象になる人

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

headerファイルで無邪気にIncludeするな

前回の記事で「Includeとはなにかということ」という項目を書きました
開発をしていると実感できますが、むやみにheaderファイルでIncludeしまくってるheaderがあったときにそのheaderをイジってしまうと
「1/325...」「2/325...」「3/325...」
といった具合にVisualStudioのビルドを無心で眺めるという虚無時間が頻発します

前方宣言を使おう

前回の記事で書いたようにIncludeとはコンパイル時にそのheaderをその場所にコピペされることで記述したファイル内で参照できるようにするものです
しかし、header内では基本的には関数や変数の宣言のみで関数の中身はcppファイル内に記述されます
ということはheaderでは外部のクラス型は参照する必要がありますが関数や変数は参照できなくてお構わないということになりますので、その場合は前方宣言を使うべきです
具体的には下記のような感じ

#include "CoreMinimal.h"
//#include "Hoge.h" ←Hoge.hをincludeする必要はないのでコメント化
class Hoge;//クラス宣言だけ必要な場合はこれだけで良い

class Foo
{
public:
    Foo();
    ~Foo();

    Hoge *hoge;//クラス型として認識される
};

どういう仕組かというとC++コンパイラコンパイル時にファイル内にひとまず「これはクラスである」という宣言さえあれば実際にリンクさせるのは最後に同名のクラスを探してリンクさせてくれるので前方宣言さえやっていればコンパイルが通り、さらにheaderをincludeしていないのでそのheaderファイルで差分が発生しても変更差分として対象にならないのでコンパイルの虚無時間をへらすことに繋がります
これはとても基本的なことである一方でチームで開発しているとincludeしておけば楽なのでついつい知らずしらずのうちに無駄なincludeが増えてしまうことが多いので注意が必要です

Includeの""と<>

includeするときに下記のようにかかれているのを見ることがよくあると思うのですが、この違いを意識していますでしょうか

#include <stdio.h>//これと
#include "Hoge.h"//これの違い

これらの違いはincludeするファイルのディレクトリの検索するスコープの違いです
<>で囲まれたファイルはコンパイラが認識しているディレクトリ(システムで設定されているディレクトリや環境変数で指定されているディレクトリなど)のみから探し出してincludeしますが、""で囲まれたファイルは記述したファイルのカレントディレクトリを検索した後に<>と同じdディレクトリを検索します
知らなくても絶対マズイというものでもない気がしますが、C++ではheaderのinclude順によってエラーがでることがあるので知識として知っておきましょう
検証してないので知りませんが<>で足りるものを""で書いているとビルド速度に影響でるかも(ほんまか?)

Engine.hをIncludeするのをやめろ

色々な記事でEngine.hをincludeしているところが多いのですが、これってビルド時間が長くなる原因になるのでやめましょう
Engine.hをincludeするとEngineが持つ全てのheaderをincludeすることになるのでたしかに書いておけばEngineのクラスや関数やマクロを呼び出すことができてしまうので楽なのですが、これを色々なファイル内で書いているとコンパイル時に毎回巨大なheaderを読み込むことになるので虚無時間が増えます
「とはいってもこのAPIはなにをincludeすればいいのかわからない!」 という人はUE4C++の公式リファレンスの該当するクラスのページを見てもらえばどうincludeすればいいか書いてあります
公式リファレンス
個人的な肌感ですが、たいていの場合はEngine/Engine.hをincludeしていれば事足りるケースが多いです

Includeの順番(IWYUの記述ルール)

C++ではincludeする順番が大事です
むちゃくちゃにincludeしているとふとした時に不可解なエラーが発生します
可読性という観点から見てもめちゃくちゃにincludeしているよりも一定のルールによってincludeされていることが望ましいです
UE4の場合はIWYUの記述ルールに沿うのが最も良いと考えられます

IWYUリファレンス

UnityBuildの場合の無名名前空間の罠

UE4にはオプションとしてUnityBuildというものがある
断じて競合ゲームエンジンとは関係ない

UnityBuildについてはこちら-株式会社ヒストリアさまより-

かんたんに言ってしまえばUnityBuildとはビルド時に全てのファイルを一つのファイルにまとめてしまってからビルドしてしまえば早いでしょ?という仕組みです
その場合に問題になってしまうのはグローバルスコープにある変数や関数が同名な場合エラーが起きることです

無名名前空間は用法用量を守って正しく扱いましょう

前回の記事で「無名名前空間」を紹介したがこれを多用しているとこの問題にぶつかる
無名名前空間はそのファイル内のグローバルスコープに宣言されてしまうため、UnityBuildを使うと全てのファイルと同一スコープになってしまうためうっかり同名の関数や変数があるとガンガンエラーが出てしまうので無名名前空間は外部から完全に隠蔽したい変数や関数を扱いたい場合には便利ですが、むやみに使うと問題を引き起こしてしまいます
使う場合は本当に無名名前空間で扱うべきものなのか?を考えてから使いましょう

C++にはリフレクションがない

題名の通りです。ありません。終了。

Unityでエディタ拡張をしようとした場合、エディタ拡張で様々なことがかんたんにできます
しかしそれはC#にリフレクションがあるから機能することです
UE4でもそこらへんはカバーされてはいるのですが、それはUCLASSやUFUNCTIONなどのマクロで宣言されているもののみがリフレクションのように取得できるだけであり、例えば独自に作ったローカルのマスターデータクラスを参照して拡張したエディタから操作したいといったケースではリフレクションがないのでかなり大変です
UE4.22以前はエディタ拡張をしようとするとプラグインで作る必要がありました
確認していないのでわかりませんが、今はEditor Utility Widgetがあるので多少作りやすくなっているかもしれません

あとがき?

三連休で暇だったので書きました
C++で書いているとC#ではあんなにかんたんにできたことがなぜできないんだ意味わかんねえやはり人類にC++は早すぎた」という気持ちに支配されます全てを支配できた喜びに打ちひしがれるので大変楽しいです
とはいっても純粋なC++よりもUE4C++ではかなり書きやすくなっているようで今後はより扱いやすくなっていくのではないでしょうか
そこらへんよろしくお願いしますepic games