[UE4]Gameplay Dev Notes
keywords: UE4, Gameplay Development Notes
Switch Level
论坛上有不少资料说切换场景时使用GEngine->Browse()
:
if (FWorldContext* Context = GEngine->GetWorldContextFromWorld(GetWorld()))
{
FString Error;
FURL URL(TEXT("/Game/Demo/scecn_test/scecn_test02"));
EBrowseReturnVal::Type RS = GEngine->Browse(*Context, URL, Error);
if (EBrowseReturnVal::Type::Failure == RS)
{
WSI->ChangePlayerState(EPlayerState::EP_LoginFailed);
}
}
GEngine->Browse这种方式第一次打开场景时正常,如果第二次切换场景(比如从别的场景再切回原场景),则会导致程序崩溃。
建议用:UGameplayStatics::OpenLevel(GetWorld(), TEXT("/Game/Map/TestMap"));
How to get UWorld
if(UWorld* World = GEngine->GetCurrentPlayWorld(nullptr))
{
ULevel* Level = World->GetLevel(0);
}
Check if it’s server or client without WorldContext
Quoted from CoreMisc.h
bool IsRunningDedicatedServer();
bool IsRunningGame();
bool IsRunningClientOnly();
Subsystem (Unreal Stylized Singleton)
How to create customized Subsystem?
header
UCLASS()
class MYGAME_API UMySubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
UMySubsystem();
static UMySubsystem* Get(const UObject* WorldContext)
{
UMySubsystem* Ret = nullptr;
if (WorldContext)
{
Ret = WorldContext->GetWorld()->GetSubsystem<UMySubsystem>();
}
return Ret;
}
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
}
cpp
#include "MySubsystem.h"
UMySubsystem::UMySubsystem()
: UWorldSubsystem()
{
}
void UMySubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
}
void UMySubsystem::Deinitialize()
{
Super::Deinitialize();
}
How to disable initialization of GameInstanceSubsystem
on dedicated server?
Quoted from:
Engine\Plugins\Experimental\CommonUI\Source\CommonUI\Private\CommonUISubsystemBase.cpp
bool UCommonUISubsystemBase::ShouldCreateSubsystem(UObject* Outer) const
{
return !CastChecked<UGameInstance>(Outer)->IsDedicatedServerInstance();
}
Save Game / Load Game
void MyGameInstance::LoadSaveGame()
{
UMySaveGame* SaveGame = Cast<UMySaveGame>(
UGameplayStatics::LoadGameFromSlot(SaveSlotName, UserIndex));
if (!SaveGame)
{
if (UMySaveGame* SaveGame = Cast<UMySaveGame>(UGameplayStatics::CreateSaveGameObject(Instance()->SaveGameClass));)
{
UGameplayStatics::SaveGameToSlot(SaveGame, SaveGame->SaveSlotName, SaveGame->UserIndex);
MySaveGame = SaveGame;
}
}
else
{
MySaveGame = SaveGame;
}
}
How to get PlayerStart Point in Level
AActor* AGameModeBase::ChoosePlayerStart(AController* Player);
AActor* AGameModeBase::FindPlayerStart(AController* Player, const FString& IncomingName = TEXT(""));
Event
Level Event
Level changed (switch level) event:
//只有Standalone和Client中触发,Server不触发。
FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &UMySubsystem::OnPreLoadMap);
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UMySubsystem::OnPostLoadMap);
Level destroyed (e.g. exit game) event:
GetWorld()->GetLevel(0)->OnCleanupLevel.AddUObject(this, &UMySubsystem::OnCleanupLevel);
Actor spawned event (spawned at runtime, not the actor placed in level):
FOnActorSpawned::FDelegate Delegate;
Delegate.BindUObject(this, &AMyActor::OnActorAdded);
GetWorld()->AddOnActorSpawnedHandler(Delegate);
or
GEngine->OnLevelActorAdded().AddUObject(this, &AMyActor::OnActorSpawned);
GEngine->OnLevelActorDeleted().AddUObject(this, &AMyActor::OnActorDeleted);
GEngine->OnLevelActorListChanged().AddUObject(this, &AMyActor::OnLevelActorListChanged);
Engine Event
FGameDelegates::Get().GetExitCommandDelegate().AddUObject(this, &UMyGameInstance::OnEnginePreExit);
FGameDelegates::Get().GetExitCommandDelegate().AddUObject(this, &UMyGameInstance::OnEnginePreExit);
APIs
Interface Example
Define: PyWrapperBase.h
#pragma once
#include "PyWrapperBase.generated.h"
UINTERFACE(MinimalApi, BlueprintType)
class UPythonResourceOwner : public UInterface
{
GENERATED_UINTERFACE_BODY()
};
class IPythonResourceOwner
{
GENERATED_IINTERFACE_BODY()
public:
/**
* Release all Python resources owned by this object.
* Called during Python shutdown to free resources from lingering UObject-based instances.
*/
virtual void ReleasePythonResources() = 0;
};
Usage: check if an actor implements an interface.
if(MyActor->GetClass()->ImplementsInterface(UMyInterface::StaticClass()))
{ }
if(IMyInterface* Interface = Cast<IMyInterface>(MyActor))
{
Interface->OnDamage(DamageValue, DamagePosition);
}
Reference: Unreal Interfaces
Async Task
Quoted from UE5\Engine\Plugins\AI\MLAdapter\Source\MLAdapter\Public\MLAdapterAsync.h
// sends InFunction to be called on the GameThread and waits for the result
template<typename RetType>
RetType CallOnGameThread(TFunction<RetType()> InFunction)
{
TOptional<RetType> Result;
TAtomic<bool> bCompleted(false);
AsyncTask(ENamedThreads::GameThread, [&Result, &bCompleted, &InFunction]
{
Result = InFunction();
bCompleted = true;
});
while (!bCompleted);
return MoveTemp(Result.GetValue());
}
// fire (InFunction to be called on the GameThread) and forget
template<>
FORCEINLINE void CallOnGameThread(TFunction<void()> InFunction)
{
AsyncTask(ENamedThreads::GameThread, InFunction);
}
Shared Pointer in Async Task
Quoted from engine source FileSourceControlContextMenu.cpp.
Step 1: inherit class TSharedFromThis
.
class FFileSourceControlContextMenuState : public TSharedFromThis<FFileSourceControlContextMenuState>
{
}
Step 2: create shared pointer using MakeShared()
.
void FFileSourceControlContextMenu::MakeContextMenu()
{
InnerState = MakeShared<FFileSourceControlContextMenuState>();
InnerState->Initialize();
}
Step 3: create a “temporary” TSharedPtr for the lambda function using [this, This = AsShared()]
, this new shared ref (TSharedRef) will release the ref automatically when lambda function execution finished.
void FFileSourceControlContextMenuState::NextAsyncState()
{
if (TrySetAsyncState(EAsyncState::None, EAsyncState::TryCacheCanExecute))
{
Async(EAsyncExecution::ThreadPool, [this, This = AsShared()]()
{
TryCacheCanExecuteVars(SelectedFiles, &PathsWithUnknownState);
// Source control operations have to be started from the main thread
Async(EAsyncExecution::TaskGraphMainThread, [this, This = AsShared()]() { NextAsyncState(); });
});
}
}
This action ensures the current object (this pointer) still be valid if shared ref counter reach to 0 outside the lambda function. Otherwise this
pointer will goes to wild pointer and crashes on executing lambda function.
几时归去,作个闲人。对一张琴、一壶酒、一溪云。----宋·苏轼《行香子》