Edited at

UE4 でプレイヤー以外の Tick と Physics を止める方法の検討 (第8回ぷちコン)

More than 1 year has passed since last update.


はじめに

第8回ぷちコンにて実装した、 UE4 で自分のプレイヤー以外の時間(Tick,Physics)を止める方法を検討したときの覚書き。

参考までに応募したやつ ↓



http://www.youtube.com/watch?v=TMK2cYWaAyw


検討1: SetGamePaused → 不採用

SetGamePaused.png

これでは自分を含めて全部止まってしまう。

そもそもポーズ画面でこれを使うつもりだったので、不採用。


検討2: CustomTimeDilation + SetSimulatePhysics → 不採用

CustomTimeDilation.png

確かに、これで個別に Tick と物理を止めることは可能。

が、メッシュに対しては、個別に SetSimulatePhysics を呼ぶ必要がある。めんどくさい。

(事前に MeshComponent 検索して設定する機能をつけた BP を継承すれば良い話ではあるが)

あと、時間を止めるトリガーが来た瞬間に、ステージ内の自分以外の全アクターに対してこの処理が走ることになる。

うーん、もっとスマートな方法はないものか・・・。


検討3: UWorld::bPlayersOnly, UWorld::bShouldSimulatePhysics → 今回採用

実は、UWorld 内のプロパティをいじると、特定のアクター以外の時間を止めることができる


UWorld::bPlayersOnly

これを true にすると、ローカルでコントローラーに Possess されている APawn(もしくはその継承クラス, ACharactorとか) のみ Tick を走らせる ようになる。

もっと厳密にいうと、ワールドの TickType が強制的に ELevelTick::LEVELTICK_ViewportsOnly になる。

Actor が Tick による更新指示を受ける部分のコードを読めばわかるのだが、


Actor.cpp

void AActor::TickActor( float DeltaSeconds, ELevelTick TickType, FActorTickFunction& ThisTickFunction )

{
//root of tick hierarchy

// Non-player update.
const bool bShouldTick = ((TickType!=LEVELTICK_ViewportsOnly) || ShouldTickIfViewportsOnly());
if(bShouldTick)
{
// If an Actor has been Destroyed or its level has been unloaded don't execute any queued ticks
if (!IsPendingKill() && GetWorld())
{
Tick(DeltaSeconds); // perform any tick functions unique to an actor subclass
}
}
}

// ~省略~

/** If true, actor is ticked even if TickType==LEVELTICK_ViewportsOnly */
bool AActor::ShouldTickIfViewportsOnly() const
{
return false;
}


となっている。

つまり、アクターは基本的に ELevelTick::LEVELTICK_ViewportsOnly 時は Tick が走らない

しかし、AActor の継承クラスの一部において ShouldTickIfViewportsOnly() を override しているものがある。


  • ACameraRig_Crane

  • ACameraRig_Rail


  • ACineCameraActor


    • これらの3つはシーケンサ用で必ず true を返すようになっている。




  • APlayerCameraManager



    • APlayerController の参照を持っているときだけ true を返す。


    • APlayerController がスポーンされるときには必ず渡されるものなので、これも基本的には true を返すと見てよい




  • APawn


    • 「ローカルでコントロールされている」かつ 「APlayerController がセットされている」という条件で true を返す



ということで、自分で継承クラスを作って ShouldTickIfViewportsOnly() を override しない限りは、

カメラ関係以外ではローカルでコントローラーに Possess されている APawn かその継承クラスのみ Tick が走ることになる。

なので、これを true にすれば、プレイヤー以外の Tick を止めることができる。


UWorld::bShouldSimulatePhysics

これを false にすると、ワールドのすべての Physics による更新をしなくなる。

ただし、これはあくまで Physics の更新を止めるだけであり、オブジェクト同士の作用(ぶつかったときの力など)はそのまま受け付けている状態になる。

そのため、解除した瞬間に、蓄積された作用が一気に適用される挙動となる。

この挙動を起きないようにするためには、やはり SetSimulatePhyshics を false にしないといけない。

が、今回作ったゲームでは、むしろその挙動の方が面白かったので、あえてそのままにしてある。


実装

UWorld も上記のプロパティもブループリント公開はされてないので C++ で組む。

FunctionLibrary で共用の関数として、以下のようにして実装した。


CommonBlueprintFunctionLibrary.h

#include "Kismet/BlueprintFunctionLibrary.h"

#include "CommonBlueprintFunctionLibrary.generated.h"

UCLASS()
class ONEMINUTE_API UCommonBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:

UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject") , Category="TimeStop")
static void StartTimeStop(UObject* WorldContextObject);

UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject") , Category="TimeStop")
static void FinishTimeStop(UObject* WorldContextObject);
};



CommonBlueprintFunctionLibrary.cpp

void UCommonBlueprintFunctionLibrary::StartTimeStop(UObject* WorldContextObject)

{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
if(World) {
World->bPlayersOnly = true;
World->bShouldSimulatePhysics = false;
}
}

void UCommonBlueprintFunctionLibrary::FinishTimeStop(UObject* WorldContextObject)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
if(World) {
World->bPlayersOnly = false;
World->bShouldSimulatePhysics = true;
}
}


あとは、ブループリントから必要なタイミングで呼び出せばよい。

やってみたときの実験動画 → https://ue4-mstdn.tokyo/@negimochi/1098