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

# はじめに

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

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

[![](http://img.youtube.com/vi/TMK2cYWaAyw/0.jpg)](http://www.youtube.com/watch?v=TMK2cYWaAyw)
http://www.youtube.com/watch?v=TMK2cYWaAyw

# 検討1: SetGamePaused → 不採用

![SetGamePaused.png](https://qiita-image-store.s3.amazonaws.com/0/138515/3fc38ef7-f8dc-3f9f-7738-c65435b1e96d.png)

これでは自分を含めて全部止まってしまう。
そもそもポーズ画面でこれを使うつもりだったので、不採用。

# 検討2: CustomTimeDilation + SetSimulatePhysics → 不採用

![CustomTimeDilation.png](https://qiita-image-store.s3.amazonaws.com/0/138515/98e05a40-6ee6-5539-f459-58590c9ca9a3.png)

確かに、これで個別に Tick と物理を止めることは可能。
が、メッシュに対しては、個別に `SetSimulatePhysics` を呼ぶ必要がある。めんどくさい。
(事前に `MeshComponent` 検索して設定する機能をつけた BP を継承すれば良い話ではあるが)

あと、時間を止めるトリガーが来た瞬間に、ステージ内の自分以外の全アクターに対してこの処理が走ることになる。
うーん、もっとスマートな方法はないものか・・・。

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

実は、[UWorld](https://docs.unrealengine.com/latest/INT/API/Runtime/Engine/Engine/UWorld/index.html) 内のプロパティをいじると、**特定のアクター以外の時間を止めることができる**。

## UWorld::bPlayersOnly

これを true にすると、**ローカルでコントローラーに Possess されている APawn(もしくはその継承クラス, ACharactorとか) のみ Tick を走らせる** ようになる。
もっと厳密にいうと、ワールドの `TickType` が強制的に `ELevelTick::LEVELTICK_ViewportsOnly` になる。
Actor が Tick による更新指示を受ける部分のコードを読めばわかるのだが、

```cpp: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 で共用の関数として、以下のようにして実装した。

```cpp: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);
};
```
```cpp: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

<iframe src="https://ue4-mstdn.tokyo/@negimochi/1098"></iframe>