Battery Collectorで躓かない為のメモ


アドベントカレンダー3日目(裏)になります。
三毛猫まいくです。
初参加になります。そしてゆる~い記事ですがよろしくお願いします。

まえがき

対象者はUE4を使ってC++を勉強し始めようとしている人、UEC++に挑戦してみようと思っている方です。
基本的にYoutubeにある公式チュートリアル「C++ Battery Collector」を受講するにあたっての注意書きです。

動画自体が2015年のもので動画通りに記述していくとエラーが出ますのでそれを未然に防ごうというものです。
(有志の方がコメント欄に修正方法を書いてくれていますのでこちらの記事を参考にしなくても完成はできると思いますがこちらの記事でも必ずエラーが出ないという保証は致しませんので悪しからず)

Verは4.23で調査しました。2015年のものといえどそこまで極端に変わる所はないので構える必要はありません。
問題などがあるセクションだけ注意書きを書いていきます。
BPに関しては基本的に触れません。よろしくお願いします。

※せっかくなのでセクション14をC++で書いてみるおまけを追加してます。

C++ Battery Collector: Building the Base Level | 02

使うメッシュなどを準備するセクションです。
見た目にこだわらなければ飛ばしても大丈夫だったりします。

C++ Battery Collector: Making Your First Pickup Class | 03 |

ここでC++ファイルを作成しますが、動画と違う箇所があります。
・作成したcppファイルにincludeファイルにプロジェクトのヘッダが作成されなくなった
・作成したhファイルにCoreMinimal.hが追加でインクルードされている
変わったからといって特に変わりはありませんのでそのまま言われた内容を記述しましょう。
Coreminimal.hについてはリファレンスガイドに書いてあります。↓
https://docs.unrealengine.com/ja/Programming/UnrealBuildSystem/IWYUReferenceGuide/index.html

恐らくコンパイルをすると以下のエラーが出てしまいます。
Error:BlueprintReadOnly Should not be used on private members
protectedまたはpublicにするかBlueprintReadOnlyを削除して対応しましょう。

C++ Battery Collector: Creating the Spawning Volume | 06 |

以下をインクルードしておきます。

C++ SpawnVolume.cpp
#include "Kismet/KismetMathLibrary.h"
#include "Components/BoxComponent.h"

インクルードのエラーがこれから何度かあります。
動画の通りに記述してるのにエラーがでる場合、大抵これが原因になります。

C++ Battery Collector: Defining What to Spawn | 07 |

以下をインクルードしておきます。

C++ SpawnVolume.cpp
#include "Engine/World.h"

C++ Battery Collector: Extending the Character Class | 09 |

以下をインクルードしておきます

C++ BatteryCollercterCharacter.cpp
#include "Components/SphereComponent.h"

スフィアのアタッチは一番下のやり方です。

CollectionSphere = AttachTo(RootComponent);       //× 動画の解説者のミス
CollectionSphere->AttachTo(RootComponent);        //× 
CollectionSphere->SetupAttachment(RootComponent); //○

C++ Battery Collector: Collecting Pickups | 10 |

バインドの仕方が変わっているため、PlayerInputComponentが記述されている箇所を探して以下のようにします。

InputComponent->BindAction...
PlayerInputComponent->BindAction("Collect", IE_Pressed, this, &AquitaCharacter::CollectPickups);```

↓ Tickが動いていないとパワーが減らないのでもしfalseになっていたらtrueにしましょう。

ABatteryCollectorMode::ABatteryCollectorMode()
{
//...
      PrimaryActorTick.bCanEverTick = true;
}

動画がぼけてみにくい箇所を記述しておきます。
フレームごとにパワーが減る処理です。

void ABatteryCollectorGameMode::Tick()
{
      MyCharacter->UpdatePower(-DeltaTime * DecayRate * (MyCharacter->GetInitialPower()));
}

※もしbCanEverTickをtrueにしてもパワーが減っていない場合は
再起動してみたり、ファイルからプロジェクトを開いている人はVisualStudio側から起動してみると動くかも
(私がそうでした・・・(-_-;))

C++ Battery Collector: Change Character Speed & Material | 14 |

★おまけ:マテリアル操作もC++で操作してみる。(一例)

C++ BatteryCollectorCharacter.h
// ABatteryCollectorCharacter();の下に追加します
virtual void BeginPlay() override;

//...
//UpdatePowerの下ぐらいに追加します。
// マテリアルデータ、TMap型を使用して必要なマテリアルデータを取得できるようにします。
TMap<FName, UMaterialInstanceDynamic*> MatDynamicData;
// 操作するマテリアル名
UPROPERTY(EditAnywhere, Category = "Material")
FName ControlMaterialName = "M_UE4Man_Body";
// 操作するパラメータ名
UPROPERTY(EditAnywhere, Category = "Material")
FName ControlColorValueName = "BodyColor";
// 通常のカラー
UPROPERTY(EditAnywhere, Category = "Material")
FLinearColor NormalColor;
// ピンチになるとなるカラー
UPROPERTY(EditAnywhere, Category = "Material")
FLinearColor WeakColor;
C++ BatteryCollectorCharacter.cpp

// 追加するインクルード
#include "Materials/MaterialInstanceDynamic.h"
#include "Kismet/KismetMathLibrary.h"

// ABatteryCollectorCharacter::ABatteryCollectorCharacter()の定義の下に追加します。
void ABatteryCollectorCharacter::BeginPlay()
{
    // 操作するマテリアルの準備
    if (USkeletalMeshComponent* PlayerMesh = GetMesh())
    {
        TArray< UMaterialInterface*> MaterialArray = PlayerMesh->GetMaterials();
        for (int i = 0; i < MaterialArray.Num(); ++i)
        {
            // String型からNameに変換
            FName MaterialName = FName(*MaterialArray[i]->GetName());
            UMaterialInstanceDynamic* MatInstance = PlayerMesh->CreateDynamicMaterialInstance(i,MaterialArray[i], NAME_None);
            // TMapにキーとなる名前とマテリアルインスタンスを追加します
            MatDynamicData.Add(MaterialName, MatInstance);
        }
    }
}

//...(動画ですでに記述しているところ)
void ABatteryCollectorCharacter::UpdatePower(float PowerChange)
{
    CharacterPower = CharacterPower + PowerChange;
    GetCharacterMovement()->MaxWalkSpeed = BaseSpeed + SpeedFactor * CharacterPower;
    // BPで実装する内容が以下のものになります。
    //PowerChangeEffect();

    constexpr float Min = 0.0f;
    constexpr float Max = 1.0f;
    const float Alpha = FMath::Clamp(GetCurrentPower() / GetInitialPower(), Min, Max);

    FLinearColor CurrentColor = UKismetMathLibrary::LinearColorLerp(NormalColor, WeakColor, Alpha);
    // 指定したマテリアルをTMap変数から取得
    if (UMaterialInstanceDynamic* ControlMaterial = *MatDynamicData.Find(ControlMaterialName))
    {
        ControlMaterial->SetVectorParameterValue(ControlColorValueName, CurrentColor);
    }
}

コンパイルが成功すると、BatteryCollectorCharacterのBPのクラスデフォルトに
追加したName変数とカラーが設定できるようになっています。
このカラーパラメータを設定するとグレイマンの色が変わると思います。

C++ Battery Collector: Enabling UMG | 16 |

次のセクションで修正されますが、このセクションでコンパイルするとエラーをはきます。
GetPowerToWin関数の定義が未定義の状態です。

C++ Battery Collector: Setting Up the Play States | 18 |

// : uint8をつける
UENUM(BlueprintType)
enum class EBatteryPlayState : uint8
{
    EPlaying,
    EGameOver,
    EWon,
    EUnknown
};

C++ Battery Collector: Handling New Play States | 20 |

途中で修正されますが
定義する関数名の正しくは"HandleNewState"です。

Movementの操作でエラーが出る場合は以下のインクルードと、UCharacterMovementComponentを前方宣言して以下のようにすれば通ると思います。

 #include "Components/SkeletalMeshComponent.h"
class UCharacterMovementComponent;
//...
ACharacter* MyCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);
if (MyCharacter)
{
    MyCharacter->GetMesh()->SetSimulatePhysics(true);
    MyCharacter->GetMovementComponent()->MovementState.bCanJump = false;
}

あとがき

BPをそれなりに触ったことある方はノードをダブルクリックすると関数の定義に飛んでくれるので書き方が分からない場合はノードから調べるというのもありかもしれません。
他にもコンポーネントを右クリックするとヘッダに飛ぶ項目があるので中身を見てみるのもおすすめです。

注意書きといえどそれなりにボリュームがでちゃいましたね。
というか家の環境が悪くてコンパイルに時間かかって調べるのにかなり時間がかかってしまいました。
ともあれ!参考になれば幸いです。

以上になります。お疲れさまでした。