やあ
レベルデザインで、道を作るのにLandscape Spline
を使おうと思ったら、凹んだメッシュでは衝突判定が起きないことが分かったのでsplineアクターでやることにしたよ。
UMG(WidgetBlueprint)
のコンストラクタが呼ばれるタイミングを調べてみたよ。
間違ってたら教えてください。
ウィジェットがViewport
に追加されたときに呼ばれます。
こんな感じのWBP
がある。
呼ばれる例
AddToViewport
したとき。
PanelWidget
(BorderやVerticalBoxなど)にウィジェットをAddChild
したとき。
呼ばれない例
Visibility
やRenderOpacity
などを切り替えるだけでは呼ばれません。
CreateWidget
でも呼ばれません。
PanelWidget
からRemoveChild
したり、RemoveFromViewport
したりしたとき。
親UMGがAddToViewport
されたりAddChild
されないと、子UMGのConstruct
は呼ばれないので注意。
調べ方が悪いんだろうけど、調べてもなにも情報が出てこなかったのでメモしておく。
使ったプロジェクト
github.com
例えば、UserWidget
から継承したクラス、WBP_CustomButton
とWBP_ChoiceUI
があったとする。
このWBP_ChoiceUI
にWBP_CustomButton
をそのまま配置する。
これをこんな風に出したり消したりする。
スタンドアローンで実行してMemreport -full
WBP_ChoiceUI
を表示中のレポート
WBP_CustomButton
を3つしか配置してないのに、表示中のレポートではWBP_CustomButton
のオブジェクトの数が9つになっているのがわかる。
これを回避するには、すべてのWBP_CustomButton
をCreateWidget
すればいい。
WBP_ChoiceUI
を表示中のレポート
直接配置していたのをCreateWidget
で表示するようにしただけでオブジェクトの数が3つになった。
割と重大な情報っぽいのに調べても何も出てこない(多分調べ方が悪い)ので、みんなそんなに気にしてないのかなぁと思いました。
それともCreateWidget
以外に何か回避策があるのかな?
知ってる人は教えてください。
関数の配列ってどうやるんだろーって調べながら書いてみた。
間違ってるところとか、もっといい書き方あれば教えてください。
FuncArray.h
#pragma once #include <vector> class FuncArray { public: FuncArray(); using PtoIntNoParamFunc = int (FuncArray::*) () const; // typedef int (FuncArray::*PtoIntNoParamFunc) (); using PtoVoidOneIntParamFunc = void (FuncArray::*) (const int&); // typedef void (FuncArray::*PtoVoidOneIntParamFunc) (const int&); void ShowSpecs() const; std::vector<PtoVoidOneIntParamFunc> GetAddFuncs() { return AddFuncs; } protected: int A; int GetA() const; void AddA(const int& In); int B; int GetB() const; void AddB(const int& In); int C; int GetC() const; void AddC(const int& In); std::vector<PtoIntNoParamFunc> GetFuncs = { &FuncArray::GetA, &FuncArray::GetB, &FuncArray::GetC }; std::vector<PtoVoidOneIntParamFunc> AddFuncs = { &FuncArray::AddA, &FuncArray::AddB, &FuncArray::AddC }; };
FuncArray.cpp
#include "FuncArray.h" #include <iostream> FuncArray::FuncArray() { A = 10; B = 4; C = 111; } int FuncArray::GetA() const { return A; } int FuncArray::GetB() const { return B; } int FuncArray::GetC() const { return C; } void FuncArray::AddA(const int& In) { A += In; } void FuncArray::AddB(const int& In) { B += In; } void FuncArray::AddC(const int& In) { C += In; } void FuncArray::ShowSpecs() const { for (auto&& Func : GetFuncs) { std::cout << (this->*Func)() << std::endl; } }
Main.cpp
#include "FuncArray.h" int main() { FuncArray* FuncArrayTest = new FuncArray(); int Idx = 0; for (auto&& func : FuncArrayTest->GetAddFuncs()) { (FuncArrayTest->*func)(Idx); ++Idx; } FuncArrayTest->ShowSpecs(); }
Main.cpp
using PtoFuncArrayTest = void(*) (); void FuncA() { std::cout << "FuncA()" << std::endl; } void FuncB() { std::cout << "FuncB()" << std::endl; } void FuncC() { std::cout << "FuncC()" << std::endl; } int main() { PtoFuncArrayTest funcs[] = { FuncA, FuncB, FuncC }; for (auto&& func : funcs) { func(); } return 0; }
GameplayEffectExectuionCalculation
についてのメモ
以下GEEC
と略します。
間違ってるところなどあれば教えてください。
今回はほぼすべてのゲームにある、最大HPをキャプチャしてみる。
まず、キャプチャしたいすべてのAttributeを宣言するための構造体の作成。
GEEC_Test.h
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameplayEffectExecutionCalculation.h" #include "GEEC_Test.generated.h" UCLASS() class TESTPROJECT_API UGEEC_Test : public UGameplayEffectExecutionCalculation { GENERATED_BODY() };
GEEC_Test.cpp
// Copyright(C)write by pto8913. 2020. All Rights Reserved. #include "Abilities/GEEC_Test.h" struct TestStatics { /* キャプチャしたいAttributeの宣言 */ DECLARE_ATTRIBUTE_CAPTUREDEF(MaxHealth); /* コンストラクタ */ TestStatics() { /* @ param1 : UTestAttributeSet : 自分で作ったAttributeSetクラスを指定 @ param2 : MaxHealth : UTestAttributeSet内にあるキャプチャしたいAttributeの名前。上で宣言したものと同じ名前 @ param3 : Source : キャプチャする対象。あとで説明します。 @ param4 : true : スナップショットするかどうか。あとで説明します */ DEFINE_ATTRIBUTE_CAPTUREDEF(UTestAttributeSet, MaxHealth, Source, true); } }; static const TestStatics& Test() { static TestStatics test; return test; }
GEEC
のコンストラクタで、
RelevantAttributesToCapture
にキャプチャするAttribute
を追加することで、内部から値を処理できるようになる。
InvalidScopedModifierAttributes
に追加すると、値のキャプチャはできるけど、エディタから処理もできないし、値も処理できないようになる。
と思ってたのですがどうもちがうっぽい
これを忘れて、値が変わらないよーって泣くことにならないように覚えておく。
GEEC_Test.h
に追加
/* コンストラクタ */
GEEC_Test();
GEEC_Test.cpp
に追加
UGEEC_Test::UGEEC_Test() { /* 宣言したAttributeをエディタに公開する画像.2参照 */ RelevantAttributesToCapture.Add(Test().MaxHealthDef); /* 宣言だけしてエディタに公開しない */ // InvalidScopedModifierAttributes.Add(Test().MaxHealthDef); }
画像.2
RelevantAttributesToCapture
に追加することで画像.2
の赤枠内に追加される。
TestStatics
のコンストラクタのparam3
部分
/* コンストラクタ */ TestStatics() { /* @ param1 : UTestAttributeSet : 自分で作ったAttributeSetクラスを指定 @ param2 : MaxHealth : UTestAttributeSet内にあるキャプチャしたいAttributeの名前。上で宣言したものと同じ名前 @ param3 : Source : キャプチャする対象。 @ param4 : true : スナップショットするかどうか。あとで説明します */ DEFINE_ATTRIBUTE_CAPTUREDEF(UTestAttributeSet, MaxHealth, Source, true); }
このparam3
でキャプチャする対象を選択する。
マクロで隠されているけど、実際はEGameplayEffectAttributeCaptureSource::Source
という列挙体で、
EGameplayEffectAttributeCaptureSource::Source
EGameplayEffectAttributeCaptureSource::Target
の二つがある。
これは、このGEEC
を持つGameplayEffect
の実行者です。
例1.
プレイヤーが攻撃して、敵にダメージを与えるGameplayEffect
の実行者はプレイヤーになる。
画像.3
参照
例2.
ゲーム開始時に、キャラクターのステータスを決定するGameplayEffect
の実行者はそのキャラクター自身になる。
これは、このGEEC
を持つGameplayEffect
を適用するターゲットのことです。
プレイヤーが攻撃して、敵にダメージを与えるGameplayEffect
を適用されるのは、敵なので、ターゲットは敵になる。
画像.3
参照
例2.
ゲーム開始時に、キャラクターのステータスを決定するGameplayEffect
のターゲットはそのキャラクター自身になる。
画像.3
正直スナップショットについては認識があってるかわからないです。間違ってたらごめんなさい
キャプチャするAttribute
TestStatics
のコンストラクタのparam4
部分
/* コンストラクタ */ TestStatics() { /* @ param1 : UTestAttributeSet : 自分で作ったAttributeSetクラスを指定 @ param2 : MaxHealth : UTestAttributeSet内にあるキャプチャしたいAttributeの名前。上で宣言したものと同じ名前 @ param3 : Source : キャプチャする対象。 @ param4 : true : スナップショットするかどうか。 */ DEFINE_ATTRIBUTE_CAPTUREDEF(UTestAttributeSet, MaxHealth, Source, true); }
このparam4
でスナップショットするかどうかを決める。
このGEEC
を持つGameplayEffect
のスペックGameplayEffectSpec
を作成した瞬間に、キャプチャしたAttribute
を保持するかどうか。
例
キャラクターが火の玉の魔法を、敵に向けて発射した場合。
理由 :
火の玉を発射した時に魔法攻撃力が5だったが、火の玉が敵に当たった時には、キャラクターの魔法攻撃力が10になっていた場合に、敵が受けるダメージは5であるはずが、10になるのを防ぐためです。
スナップショットをすると、キャラクターが魔法を使ったときに、魔法攻撃力が5なら、敵に魔法が当たる前に魔法攻撃力が変化しても、敵に魔法が当たった時には、魔法攻撃力が5のまま計算されます。
GEEC_Test.h
に追加
virtual void Execute_Implementation( const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput ) const override;
GEEC_Test.cpp
に追加
void UGEEC_EnemyStatus::Execute_Implementation( const FGameplayEffectCustomExecutionParameters& ExecutionParams, OUT FGameplayEffectCustomExecutionOutput& OutExecutionOutput ) const { const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec(); const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags(); const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags(); FAggregatorEvaluateParameters EvaluationParameters; EvaluationParameters.SourceTags = SourceTags; EvaluationParameters.TargetTags = TargetTags; float BaseValue_MaxHealth = 0.f; /* ModifierMagnitudeを処理する前の値 */ ExecutionParams.AttemptCalculateCapturedAttributeBaseValue(TargetSt().MaxHealthDef, BaseValue_MaxHealth); float BonusMaxHealth = 0.f; /* キャプチャしたAttriuteのModifierMagnitudeの取得 画像.6参照 */ ExecutionParams.AttemptCalculateCapturedAttributeBonusMagnitude(Test().MaxHealthDef, EvaluationParameters, BonusMaxHealth); float MaxHealth = 0.f; /* キャプチャしたAttributeの取得 画像.5参照 */ ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Test().MaxHealthDef, EvaluationParameters, MaxHealth); }
画像.5
画像.6
float BaseValue_MaxHealth = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeBaseValue(TargetSt().MaxHealthDef, BaseValue_MaxHealth);
キャプチャしたAttribute
の元の値。
例
キャラクターのMaxHealth : 100
に20追加するGameplayEffect
の場合、元の値は100になる
float BonusMaxHealth = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeBonusMagnitude(Test().MaxHealthDef, EvaluationParameters, BonusMaxHealth);
キャプチャしたAttribute
に追加されるMagnitudeModifier
の値。
例
キャラクターのMaxHealth : 100
に20追加するGameplayEffect
の場合、値は20になる
float MaxHealth = 0.f; ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(Test().MaxHealthDef, EvaluationParameters, MaxHealth);
キャプチャしたAttribute
の元の値とModifierMagnitude
をModifierOp
で処理した結果の値。
例
キャラクターのMaxHealth : 100
に20追加するGameplayEffect
の場合、値は120になる
GEEC_Test.cpp
に追加
/* @param1 : Test().MaxHealthProperty : 処理結果を適用したいAttribute @param2 : EGameplayModOp::Additive : 処理結果の適用方法 @param3 : MaxHealth : 処理結果の値 */ OutExecutionOutput.AddOutputModifier( FGameplayModifierEvaluatedData( Test().MaxHealthProperty, EGameplayModOp::Additive, MaxHealth ) );
適用する対象などはTestStatics
で宣言したものになるので、一番慎重に決めなければいけないのはTestStatics
部分です。
メモなので期待しないでください。
一部、感想や想像が含まれます
・SingletonやGameInstanceなど、値を永続的に保持する場所で宣言する。
レベル遷移中のアセットの読み込みとかそのキャンセルをするときに、そういった場所にあったほうがバグらなさそう。
Singleton
historia.co.jp
GameInstance
meganeo.blog.shinobi.jp
HogeGameInstance.h
#include "Engine/StreamableManager.h" UCLASS() class HOGEPROJECT_API UHogeGameInstance : public UGameInstance { GENERATED_BODY() public: static UHogeGameInstance* GetGameInstance(); static FStreamableManager StreamableManager; static FStreamableManager& GetStreamableManger(); };
HogeGameInstance.cpp
#include "Engine.h" FStreamableManager UHogeGameInstance::StreamableManager; UHogeGameInstance UHogeGameInstance::GetGameInstance() { UHogeGameInstance* instance = nullptr; if (GEngine != nullptr) { FWorldContext* context = GEngine->GetWorldContextFromGameViewport(GEngine->GameViewport); instance = Cast<UHogeGameInstance>(context->OwningGameInstance); } return instance; } FStreamableManager& UHogeGameInstance::GetStreamableManger() { return StreamableManager; }
これでGameInstanceからStreamableManagerを受け取って読み込みの処理をする。
AssetManagerを使ってるならそこで宣言したほうがまとまってていい気がする。
というか普通、ソフト参照にしたいUIのクラスとかもAssetManagerに突っ込むんですかね?
・TSoftClassPtr
もTSoftObjectPtr
も同じ方法で読み込みができる。
hoge.h
class UUserWidget; UCLASS() class HOGEPROJECT_API AHoge : public ACharacter { GENERATED_BODY(); public: UPROPERTY(EditAnywhere, BlueprintReadOnly) TSoftClassPtr<UUserWidget> InventoryUIClass; UPROPERTY(BlueprintReadOnly) UUserWidget* InvnetoryUI; UFUNCTION(BlueprintCallable) void OpenInventoryUI(); };
void AHoge::OpenInventoryUI()
{
UClass* _InventoryUIClass = InventoryUIClass.LoadSynchronous();
InventoryUI = CreateWidget<UUserWidget>(GetWorld(), _InventoryUIClass);
InventoryUI->AddToViewport();
}
もちろん同期読み込みのため、InventoryUIClass
の読み込みが終わるまで処理が止まります。
なので、たくさんのソフト参照や、サイズの大きいものを同期読み込みしようとすると、
プレイヤーは、このゲーム重いわーとか思ったり思わなかったり。
それを防ぐための非同期読み込み。
hoge.h
class UUserWidget; UCLASS() class HOGEPROJECT_API AHoge : public ACharacter { GENERATED_BODY(); public: UPROPERTY(EditAnywhere, BlueprintReadOnly) TSoftClassPtr<UUserWidget> InventoryUIClass; UPROPERTY(BlueprintReadOnly) UUserWidget* InvnetoryUI; UFUNCTION(BlueprintCallable) void BeginLoadInventoryUI(); UFUNCTION(BlueprintCallable) void FinishedLoadInventoryUI(); TSharedPtr<FStreamableHandle> InventoryUIHandle; };
#include "HogeGameInstance.h" void AHoge::BeginLoadInventoryUI() { FStreamableManager& StreamableManager = UHogeGameInstance::GetStreamableManager(); const FSoftObjectPath ObjectPath = InventoryUIClass.ToSoftObjectPath(); InventoryUIHandle = StreamableManager.RequestAsyncLoad( ObjectPath, FStreamableDelegate::CreateUObject( this, &AHoge::FinishedLoadInventoryUI ); ); } void AHoge::BeginLoadInventoryUI() { if (InventoryUIHandle.IsValid()) { UObject* LoadedObj = InventoryUIHandle.Get()->GetLoadedAsset(); if (LoadedObj) { UClass* _InventoryUIClass = Cast<UClass>(LoadedObj); if (_InventoryUIClass) { InventoryUI = CreateWidget<UUserWidget>(GetWorld(), _InventoryUIClass); InventoryUI->AddToViewport(); } } } }
もちろんデリゲートを使わずに一つの関数のなかでもできます。
デリゲートを使わない例
FStreamableManager& StreamableManager = UHogeGameInstance::GetStreamableManager(); const FSoftObjectPath ObjectPath = InventoryUIClass.ToSoftObjectPath(); InventoryUIHandle = StreamableManager.RequestAsyncLoad( ObjectPath, FStreamableDelegate::CreateUObject( this, &AHoge::FinishedLoadInventoryUI ); ); if (InventoryUIHandle.IsValid()) { UObject* LoadedObj = InventoryUIHandle.Get()->GetLoadedAsset(); if (LoadedObj) { UClass* _InventoryUIClass = Cast<UClass>(LoadedObj); if (_InventoryUIClass) { InventoryUI = CreateWidget<UUserWidget>(GetWorld(), _InventoryUIClass); InventoryUI->AddToViewport(); } } }
なお、ソフト参照のサイズが大きいと読み込みが間に合わずにLoadedObj
がnullptr
になるぞ!
非同期読み込みのタイミングよくわからない。。。誰か教えてください。
Assertion failed: Spec.Ability [File:D:/Build/++UE4/Sync/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp] [Line: 250]
めちゃくちゃどうでもいいことをだらだらと書いてるので原因まで飛んでください。
今日も今日とてゲーム制作を進めていると、とあるGameplayAbilityを実行するとクラッシュすることに気付いた。
そのGameplayAbilityは所有者にGameplayEffectを適用するだけの単純なもの。
エラーの内容は、
Assertion failed: Spec.Ability [File:D:/Build/++UE4/Sync/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp] [Line: 250]
エラーの場所を見てみると、UAbilitySystemComponent::GiveAbility
だった。
GiveAbility
はキャラクターがコントローラーを所有したときに行っているので、GameplayAbilityを実行したときに、ここでエラーが出るのはおかしい。。。
冷静に考えれば、どこかのGrantedAbilityにnullptrを設定している可能性が高いことがわかるのだが、
僕は
「んー?呼び出し方が悪いのかなー?」→違う
「あれー。じゃあ呼び出すタイミングが悪いんだ!」→違う
「えー。じゃあ呼び出し方が(」→違う
「だったら、GameplayAbility(アセット)が!」→違う
を永遠にループして繰り返した。
その結果、まじで一ミリも原因が思いつかなかった僕はAnswerHubに投稿した。
「クラッシュシマース(英語)」
ふぅ、これで誰かしらが案を出してくれるだろう。
ちょっと休憩する前にもう一回、GameplayAbility(アセット)を見てみるかー。
んーなにも問題ないよなぁ。。。
念のためGameplayEffect(アセット)も確認っと。
マウスホイールスイ―ッ。
あれ?GrantedAbilitiesに何か設定してある。
GameplayEffectアセットの一番下にある、GrantedAbilitiesにnullptrを設定していたため起こりました。
この間僅か3分っ!
みんなもnullptrに気を付けよう!