Photon Fusion for Unityでタレットやダメージエリアなど設置攻撃を実装する

![タレットデモ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/8973d90c-4c6d-757b-4972-3d240c2fbe56.gif)

# はじめに
Photon Fusionアンバサダーのニム式です。

以前の記事では公式のProjectilesサンプルを元に、色々な弾の同期方法の解説と、弾の種類を増やす改造の基本と応用例を紹介しました。

https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-projectiles-advanced

改造例として、弾の一つ一つがゲームオブジェクトとなっている実弾系と、飛翔体を使わず壁に反射可能なレーザーを取り上げました

https://qiita.com/nimushiki/items/8f51d5e20038b2329b26

https://qiita.com/nimushiki/items/06e6c223ae71e2a6dda4

https://qiita.com/nimushiki/items/a0230d6db7984b7d8971

今回はさらなる弾（攻撃手段）のバリエーションとして、プレイヤーの手元を離れても存在し続ける弾、具体的にはタレットやダメージエリアの追加方法を紹介します。

### 前提記事

Photon Fusionの基本的な解説は以下の記事で行っていますので、そちらを参照下さい。

https://qiita.com/nimushiki/items/d6fe4f0cbe0437677fa8

### 動作確認環境

Windows 11 Home 22H2

Unity 2022.3.2f1

Fusion SDK 1.1.8 F Build 725

# 弾の同期方式

Photon Fusionで弾を扱う時、弾の種類によっていくつか同期方式が考えられます。

公式サンプルの解説が詳しいですが、最も汎用的な方式と本記事で扱う方式を比較してみます。

なお本記事のコードと解説は全て公式サンプル「プロジェクタイル - アドバンス」をベースにしています。

https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-projectiles-essentials

https://doc.photonengine.com/ja-jp/fusion/current/technical-samples/fusion-projectiles-advanced

## 汎用的な同期方式

「Photon Fusion for UnityのProjectilesサンプルを改造して弾のバリエーションを増やす」で扱った同期方式の場合、弾そのものは見た目のみであり独立した同期オブジェクトではありません。

https://qiita.com/nimushiki/items/06e6c223ae71e2a6dda4

発射したプレイヤーがもつ弾管理コンポーネント（AgentプレハブにアタッチされているProjectileManagerコンポーネント）により、位置や向きなどのデータを同期しています。

![ProjectileManager](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/9e4e6f59-edec-dffa-48af-952ad40c7e01.png)

この方式は同期コストの低さや適用範囲の広さがあります。

しかし管理するプレイヤーがデスポーンした場合、弾も全て同期できなくなります。ゲーム進行上のデスポーン以外にも、オンラインゲームでは回線を理由にデスポーンすることがありえます。

１対１であればその時点でゲームを終了しても問題ありませんが、復帰を待つ仕組みを入れたい場合があるでしょう。また複数人の場合はゲームを継続したい事の方が多いと思います。

こういった一度プレイヤー（≒管理）オブジェクトがデスポーンした状況でも弾が存在し続ける必要がある場合には、別の方式を使う必要があります。

## 本記事での同期方式

前項の理由により、プレイヤーのデスポーンに関係なく弾が存在し続ける必要がある場合は専用の同期方式を実装する必要があります。

それにはサンプルのDataStandaloneProjectileコンポーネントが参考になります。弾オブジェクトの一つであるAssets/Prefabs/Projectiles/RicochetStandaloneProjectile.prefabにアタッチされています。

これは他のProjectileコンポーネントと違い、ProjectileManagerコンポーネントへ登録されず一つの同期オブジェクトとして生成され、さらに自身が弾を生成して管理する仕組みになっています。

そのため発射したプレイヤーに依らず弾が生存し続けることができます。

![DataStandaloneProjectile](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/475afcef-cc4e-5bea-ddcc-55b5288c12bc.png)


なおこの手法はインスタンスの生成コストなどのデメリットが大きいため多用は非推奨とされています。適しているのは、大陸間弾道弾のような１試合に数発程度かつプレイヤーより遥かに長寿命、といった特徴のオブジェクトのみのようです。

# 実装方法

## 改造の方針

ポイントは前述の通りDataStandaloneProjectileコンポーネントがアタッチされているRicochetStandaloneProjectileプレハブです。

オリジナルでは、同期する弾としてゴムボールのように跳ねるAdvancedKinematicProjectileを生成します。

着弾時に爆発エフェクトを表示させますが、今回は代わりにタレットを有効化するようにします。

## 改造の手順

### 弾の作成

RicochetStandaloneProjectileプレハブをコピーし、それをベースに作業します。

NetworkObjectをコピーした場合はPrefab SourceがNullとなっており、ビルド（Fusion>Rebuild Prefab Table）が必要なことに注意してください。

![Prefab SourceがNull](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/00d5bf8a-50d5-7e27-a54b-aed580e01a2e.png)

次に設置するオブジェクトを子にコピーします。どちらでも同じように動作しますが今回のサンプルではSimpleTurretプレハブの方を使用します。

Assets/Prefabs/GameplayObjects/DamageArea.prefab

Assets/Prefabs/GameplayObjects/SimpleTurret.prefab

![SimpleTurretのコピー先](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/bba50d68-1eb8-21d5-6314-7f3f06c6d34f.png)


### コードの追記

DataStandaloneProjectileコンポーネントからAdvancedKinematicProjectileコンポーネントを使った弾を生成しますが、着弾時に爆発エフェクトではなくSimpleTurretを有効化するようにするための処理を追記します。

```AdvancedKinematicProjectile.cs
//追加
[SerializeField] private bool _canDeploy = false; //設置タイプの武器かどうかを設定する
[SerializeField] private GameObject _deployObject; //設置されるオブジェクトを設定する

//略

if (doBounce == true)
{
	ProcessBounce(context, ref data, hit, direction, distance);
}
else
{
	HitUtility.ProcessHit(context.InputAuthority, direction, hit, _damage, _hitType, context.TeamIndex);
	SpawnImpact(context, ref data, hit.Point, (hit.Normal + -direction) * 0.5f);

	//追加
	//設置タイプに設定した場合、設置オブジェクトを有効化（＆移動、向きの変更）する処理
	if (_canDeploy && _deployObject != null)
	{
		if (_deployObject.activeSelf) return;
		_deployObject.SetActive(true);
		_deployObject.transform.position = hit.Point;
		var forward = Vector3.ProjectOnPlane(direction, hit.Normal).normalized;
		_deployObject.transform.rotation = Quaternion.LookRotation(forward, hit.Normal);
	}
	else
	{
		data.IsFinished = true;
	}
}
```

### 武器への組み込み

任意の武器で使えるようにするには、WeaponBarrelコンポーネントのStandalone Projectile Prefabに設定します。

今回は以前の記事で追加したCustomLasergunプレハブのSecondaryActionに追加しました。SecondaryActionのベースはRifleプレハブのSecondaryActionです。

![武器へ組み込んだ状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/f81e20a4-a709-5ce2-cebd-7d5bd68da419.png)


# デモ動画

以上の改造を終え動かしてみると、冒頭にもある動画のような動作になります。

最初にもう一人のプレイヤーが打ち出し、着弾した場所の面に沿ってタレットが設置されます。

その後そのプレイヤーは切断されていなくなりますが、タレットはその場に残ったままになっているのがわかると思います。

![タレットデモ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2576800/8973d90c-4c6d-757b-4972-3d240c2fbe56.gif)