VRChatでのパーティクル射撃と残弾システム

# はじめに

パーティクルの設定と撃ち方のメモ。
弾数管理とかいらないんで簡単に撃ちたいんです！って人は↓の購入をオススメします、マジで。

[◤VRChat向け◢ 銃声付きフルオート射撃システム](https://hotelqadesh.booth.pm/items/5108821)

## Particle

メッシュやテクスチャを飛ばすやつ。場所を考えて使いましょう(自戒)。

### Particleの基本

基本的にいつも使うscaleは効果ありません。パラメータを弄って制御します。
以下はパラメータ紹介。弾の発射を例にしてます。

#### メイン

| プロパティ | 機能 |
| ---- | ---- |
|  Duration  |  1回の再生時間  |
|  Looping  |  ループ再生  |
|  Prewarm  |  使わない  |
|  Start Delay  |  指定秒、発射が遅れる  |
|  Start Lifetime  |  生存時間。自然消滅までの時間になる  |
|  Start Speed  |  弾の速度。z軸方向に射出される  |
|  Start Size  |  弾のサイズ  |
|  Start Rotation  |  弾オブジェクトの向き  |
|  Gravity Modifier  |  地面(y軸)基準で重力を受けたような曲がり方をする  |
|  Simulation Space  |  localは発射後も親オブジェクトに追従、worldは発射後は親が動いても追従しない  |
|  Simulation Speed  |  全体速度。マトリックスごっこに使う  |
|  Play on Awake  |  オブジェクトが表示されたら起動。基本オンでいい  |
|  Max Particles  |  弾がこの数を超えると古いものから消滅  |
|  Auto Random Seed  |  シェイプを使ったときにランダム要素が加わる  |

#### Emission

弾の発射量やタイミングの制御です。これがないと弾が出ません。
| プロパティ | 機能 |
| ---- | ---- |
|  Rate over Time  |  Durationごとに発射される数  |
|  Rate over Distance  |  親の移動量に応じて発射される弾の最大数  |
|  Bursts  |  めっちゃめんどい何か。基本使わなくていい。Time間にCount個の弾が発射される。それをInterval秒間隔でCycles回繰り返す。1回につきProbability(1で100%)の確率で弾が出る。  |

#### Shape

弾の発射方向の制御。あまりに多いので色々遊んでみてください。銃の発砲はConeでAngle 10とかでいいと思います。
| プロパティ | 機能 |
| ---- | ---- |
|  Shape  |  発射方向の種類。詳しくは[こちら](https://docs.unity3d.com/ja/2018.4/Manual/PartSysShapeModule.html)  |

#### Force Over Lifetime

弾の発射方向の制御その2。最初は垂直で後から横に飛ぶミサイルを作るとか変なことに使います。
| プロパティ | 機能 |
| ---- | ---- |
|  X, Y, Z  |  Start Speedに対して指定した軸の方向へ指定量の速度が加わる  |
| Space | 上の軸の基準を親オブジェクトにするかワールドにするか |

#### Color Over Lifetime

弾の色制御その2。ビームの残光や煙が徐々に消える演出によく使います。
| プロパティ | 機能 |
| ---- | ---- |
|  Color  |  生存時間経過で左から右に色が変わる  |

#### Color By Speed

弾の色制御その3。速度が落ちると光が弱くなる素敵な演出用です。
| プロパティ | 機能 |
| ---- | ---- |
|  Color  |  Speed Rangeに応じて左から右に色が変わる  |
|  Speed Range  |  Colorを適用する速度の最小値と最大値  |

#### Size Over Lifetime

徐々に広がる煙やニュッと広がるスタンプくらいしか使ったことがないですね...。
| プロパティ | 機能 |
| ---- | ---- |
|  Size  |  Start Sizeを基準に、生存時間経過で左から右にサイズが変わる  |

#### Rotation  Over Lifetime

ランダムにくるくる回ります。薬莢の射出などに。
| プロパティ | 機能 |
| ---- | ---- |
|  Angular Velocity  |  この速度でくるくる回る。  |

#### Collision

着弾判定です。ゲームワールドやSub Emittersに使います。
| プロパティ | 機能 |
| ---- | ---- |
|  Type  |  world一択。  |
|  Dampen  |  跳弾後の速度  |
|  Bounce  |  跳弾の強さ。0で跳ねない。  |
|  Lifetime Loss  |  着弾時に失う生存時間の割合  |
|  Collision Quality  |  High一択  |
|  Collides With  |  Everything一択  |
|  Max Collision Shapes  |  基本256  |
|  Send Collision Messages  |  ワールドへの判定。オンでゾンビワールドなどで敵がしばけるようになる  |

#### Triggers

コライダーが消えたら発射などちょっと変わった条件で起動できます。

#### Sub Emitters

パーティクル芸の真骨頂。このパーティクルの生存時間中に条件を満たしたら別のパーティクルが起動します。花火や手りゅう弾の爆発やミサイルの噴射煙、銃の弾痕などに使います。
Sub EmittersはSimulation Spaceをworldが推奨です。
| プロパティ | 機能 |
| ---- | ---- |
|  Birth  |  パーティクル発射と同時  |
|  Collision  |  コライダー着弾時。Collisionがオンの必要がある  |
|  Death  |  生存時間超過などによる消滅時。Collision項目のLifetime Loss = 1(つまり着弾で生存時間全て喪失)で↑Collisionのような挙動になる  |
|  Trigger  |  Trigger項目と連携する  |
|  Manual  |  使えない  |
|  Emit Probability  |  Sub Emitters起動確率  |

#### Lights

パーティクルが光源になります。ライターの火やマズルフラッシュ、ビームや爆発の演出など。使いすぎると重くなるので注意。

#### Trails

このパーティクルが尾を引くようになります。ビームや曳光弾に。

#### Renderer

飛ばすものの設定。
| プロパティ | 機能 |
| ---- | ---- |
|  Render Mode  |  Billboardはカメラ目線な画像、Horizontal Billboardは床に広がる画像、Meshはメッシュとマテリアルが設定できる  |
|  Trail Material  |  Trails項目が有効なときにマテリアルが設定できる  |
|  Render Alignment  |  Billboard時の向き。Viewはカメラ目線、Worldは発射時のワールド基準、Localは親依存  |

## 残弾システム

**銃の発砲に弾数の要素を！**

お手持ちの銃にレートに応じた弾の発射はもちろん、マガジンを撃ち切ったら発射不能になったり、チャンバーへの装填が必要だったりと(面倒な)要素を追加します。
中身はRose([@AmnesiaRose](https://twitter.com/AmnesiaRose))氏考案のパーティクル発射・レート制御機構に私が構築した弾数・音声管理アニメーションを組み合わせたFXレイヤー群です。

※ここでの解説はフルオートのみです。セミはオンオフ変えるだけだから......。

### システム構築

#### 前準備
#### PB関連

##### 引っ張るギミック実装

チャーハンやマガジンが引っ張れるようにします。

#### アニメーション関連

この先の各レイヤーで使うものを先に準備しておきます。パラメータは同期のためにレイヤーとAvatar Parameterの両方に追加してください。

##### パラメータ

###### 必須

| 名前 | 型 | 役割 |
| ---- | ---- | ---- |
| mag_ammo | int |  マガジン弾数。初期値は0ではなく弾数  |
| mode_semi | bool |  セレクター：セミ  |
| mode_full | bool |  セレクター：フル  |
| chamber | bool |  チャンバー弾状態  |

###### PB操作系

| 名前 | 型 | 役割 |
| ---- | ---- | ---- |
| release_touch | bool |  ボルト解放ボタンクリック(コンタクトレシーバーを使います)   |
| handle_Stretch | float |  チャーハン引いた時  |
| handle_Stretch_flg | float |  チャーハン引いた時(同期用)  |
| reload | bool |  マガジンが接触(コンタクトレシーバーを使います)  |

##### アニメーション

|  名前  |  内容  |
| ---- | ---- |
|  gun_wait  |  待機用。適当なオブジェクトのオン(なんでもいい)を0:00~5:00(適当な時間)までつけたもの  |
|  gun_shot  |  １発射撃(フル用)  |
|  gun_shot_semi  |  １発射撃(セミ用)  |
|  gun_stop  |  射撃停止  |
|  mag_fall_normal  |  マガジンを非表示、マガジンパーティクル射出  |
|  mag_fall_tactical  |  マガジンを非表示、マガジン(弾入り)パーティクル射出  |
|  mag_set  |  マガジンを表示、マガジンパーティクル停止  |
|  gun_chamber_hide  |  チャンバー弾非表示  |
|  gun_chamber_show  |  チャンバー弾表示  |
|  gun_ammo_hide  |  マガジン弾非表示  |
|  gun_ammo_show  |  マガジン弾表示  |
|  gun_sound_0  |  発砲音全オフ  |
|  gun_sound_1  |  発砲音1オン、2オフ  |
|  gun_sound_2  |  発砲音2オン、3オフ  |
|  gun_sound_3  |  発砲音3オン、4オフ  |
|  gun_sound_4  |  発砲音4オン、1オフ  |
|  bolt_pb_off  |  ハンドルのPBをリセットする  |

##### パーティクル用意

フルオートには超兵器工房さんの[メカ弾エフェクト](url)のrapid shotを使います。ARから抜いてきてください。

##### 発砲アニメーション作成

Rose氏考案のパーティクル発射アニメーションです。ここでは1発だけ発射するアニメーションを作ります。

1. 銃の毎分発射数(レート、RPM)を把握する
   例として私の銃(MCX)は900RPMです。
2. 毎秒発射数を計算する
   900 / 60 = 15 発/秒 です。
3. 1発発射に必要なフレーム数を計算する
   1秒を60フレームとします。
   60 / 15 = 4 フレーム/発 です。
4. 算出フレーム数のアニメーションを作成する
   今回は全4フレームのアニメーションになります。
   0:00~0:04まで、弾パーティクルのEmission の Rate over Time を 1 にします。
   ボルトを動かしたい、煙を出したい、マズルフラッシュを入れたい人はここに1発分のアニメーションを入れてください。
5. 射撃停止のアニメーションを作成する。
   上で造ったアニメーションのオフバージョンを作ります。
   弾パーティクルのEmission の Rate over Time を 0 に、その他設定した値は初期値を設定します。

#### FXレイヤー作成

### レイヤー実装

* 遷移に条件がある場合は、Has Exit Time のチェックを外します。
* 遷移に何も書いていない場合は、Has Exit Time は1です。
* HETと書いている場合は、Has Exit Time にその秒数を設定します。たとえば HET=0.1 は0.1に設定してください。
* Can transition toself はチェックを外します(できる場合)。
* 項目に*がついている場合、Parameter Driver でパラメータを変更してください。

#### FireControlレイヤー

射撃はハンドサインで起動します。好きな手にしてください。

:::note info
遷移図がぐちゃぐちゃになるのでセミ⇔フル間の遷移は省略しました。
実際はそれぞれの方向で空のStateを通過させ、そこでフル→セミならmode_fullを、セミ→フルならmode_semiをfalseにするとスムーズにいくと思います。
:::

下図の各Stateに設定するアニメーションです。
|  名前  |  アニメーション  |
| ---- | ---- |
|  mode_safety  |  gun_wait  |
|  mode_full_stop  |  gun_stop  |
|  mode_full_shot  |  gun_shot  |
|  mode_full_lastshot  |  gun_shot_semi  |
|  mode_semi_stop  |  gun_stop  |
|  mode_semi_shot  |  gun_shot_semi  |
|  mode_semi_lastshot  |  gun_shot_semi  |

```mermaid
graph LR
  Start([Start])
  Exit([Exit])
  mode_safety([mode_safety])
  mode_full_stop([mode_full_stop])
  mode_full_shot([mode_full_shot \n * mag_ammo : add -1])
  mode_full_lastshot([mode_full_lastshot \n * chamber : set false])
  mode_semi_stop([mode_semi_stop])
  mode_semi_shot([mode_semi_shot \n * mag_ammo : add -1])
  mode_semi_lastshot([mode_semi_lastshot \n * chamber : set false])

  Start-->mode_safety
  mode_safety-->| mode_full == true \n mode_semi == false |mode_full_stop
  mode_full_stop-->|GestureRight = 1 \nmag_ammo > 0 \n chamber == true|mode_full_shot
  mode_full_stop-->|GestureRight = 1 \nmag_ammo == 0 \n chamber == true|mode_full_lastshot
  mode_full_shot-->| mag_ammo > 0 |mode_full_stop
  mode_full_stop-->|mode_full == false \n mode_semi == false| Exit
  mode_full_lastshot-->| mag_ammo > 0 \n release_touch == true | mode_full_stop

  mode_safety-->| mode_semi == true \n mode_full == false |mode_semi_stop
  mode_semi_stop-->|GestureRight = 1 \nmag_ammo > 0 \n chamber == true|mode_semi_shot
  mode_semi_stop-->|GestureRight = 1 \nmag_ammo == 0 \n chamber == true|mode_semi_lastshot
  mode_semi_shot-->| mag_ammo > 0 \n GestureRight != 1 |mode_semi_stop
  mode_semi_stop-->|mode_semi == false \n mode_full == false | Exit
  mode_semi_lastshot-->| mag_ammo > 0 \n GestureRight != 1 \n release_touch == true | mode_semi_stop
```

#### Reloadレイヤー

リロードはハンドサインで起動します。好きな手にしてください。
また、マガジンを引いたら～という条件にもできます。その場合はマガジンの_Stretchを条件にしたONOFFれいやーを作り、Parameter Driverでboolをオンオフし、それをこのレイヤーに使ってください(同期ズレ対策)。

:::note info
パラメータ節約のためにマガジンの弾数を直書きしてます。初期値を変えた時はこちらも変更してください。
:::

下図の各Stateに設定するアニメーションです。
|  名前  |  アニメーション  |
| ---- | ---- |
|  waiting  |  gun_wait  |
|  自重で落下_nor  |  mag_fall_normal  |
|  自重で落下_tac  |  mag_fall_tactical  |
|  reload_normal  |  mag_set  |
|  reload_tactic  |  mag_set  |

```mermaid
  graph LR
  Start([Start])
  Exit([Exit])
  waiting([waiting])
  自重で落下_tac([自重で落下_tac \n * mag_ammo : set 0])
  自重で落下_nor([自重で落下_nor \n * mag_ammo : set 0])
  reload_normal([reload_normal \n * mag_ammo : set 弾数])
  reload_tactic([reload_tactic \n * mag_ammo : set 弾数])
  bolt_open([bolt_open])
  bolt_close([bolt_close \n * chamber : set true])

  Start-->waiting
  waiting-->| GestureRignt == 5 \n mag_ammo > 0 |自重で落下_tac
  waiting-->| GestureRignt == 5 \n mag_ammo < 1 |自重で落下_nor
  自重で落下_tac-->| reload == true |reload_tactic
  自重で落下_nor-->| reload == true |reload_normal
  reload_tactic-->Exit
  reload_normal-->Exit

  waiting-->| chargingHandle_Stretch > 0.9 |bolt_open
  waiting-->| Handle_Stretch > 0.9 |bolt_open
  bolt_open-->| chargingHandle_Stretch < 0.1 |bolt_close
  bolt_open-->| release_touch == true |bolt_close
  bolt_close-->Exit

```

#### Chamberレイヤー

チャンバーの弾の表示非表示です。

下図の各Stateに設定するアニメーションです。
|  名前  |  アニメーション  |
| ---- | ---- |
|  chamber_hide  |  gun_chamber_hide  |
|  chamber_show  |  gun_chamber_show  |

```mermaid
  graph LR
  Start([Start])
  Exit([Exit])
  chamber_show([chamber_show])
  chamber_hide([chamber_hide])

  Start-->chamber_hide
  chamber_hide-->| chamber == true |chamber_show
  chamber_show-->| chamber == false |Exit

```

#### Magazineレイヤー

マガジンの弾の表示非表示です。

下図の各Stateに設定するアニメーションです。
|  名前  |  アニメーション  |
| ---- | ---- |
|  magazine_hide  |  gun_magazine_hide  |
|  magazine_show  |  gun_magazine_show  |

```mermaid
  graph LR
  Start([Start])
  Exit([Exit])
  magazine_show([magazine_show])
  magazine_hide([magazine_hide])

  Start-->magazine_show
  magazine_show-->| mag_ammo < 1 |magazine_hide
  magazine_hide-->| mag_ammo > 0 |Exit

```

#### Soundレイヤー

音声管理です。残響のために4つの音声ソース用意して3つ分は有効なままにする(VRCの仕様で同時再生は3つまで)という面倒なことをしています。そのせいで複雑なので2箇所に分けて説明します。
発砲機能には関係ないので拘る方向け。
使用する音源は単発で開始に余白がないようにしてください。
組み立てがわかりにくければ画像も参考にしてください。

##### フルオート

音声管理のフルオート部分を抜き出したものです。

下図の各Stateに設定するアニメーションです。
|  名前  |  アニメーション  |
| ---- | ---- |
|  idle  |  gun_sound_0  |
|  wait  |  gun_wait  |

```mermaid
  graph TB
  Start([Start])
  Exit([Exit])
  AnyState([AnyState])
  idle([idle])
  wait1([wait1])
  wait2([wait2])
  wait3([wait3])
  wait4([wait4])
  sound1([sound1])
  sound2([sound2])
  sound3([sound3])
  sound4([sound4])

  AnyState-->| mag_ammo < 1 |wait4
  AnyState-->| chamber == false |wait4
  wait4-->Exit

  Start-->idle
  idle-->| mode_full == true \n GestureRight == 1 \n chamber == true |sound1
  sound1-->sound2
  sound2-->sound3
  sound3-->sound4
  sound4-->sound1

  sound1-->| GestureRight != 1 |wait1
  wait1-->| GestureRight == 1 |sound2
  sound2-->| GestureRight != 1 |wait2
  wait2-->| GestureRight == 1 |sound3
  sound3-->| GestureRight != 1 |wait3
  wait3-->| GestureRight == 1 |sound4
  sound4-->| GestureRight != 1 |wait4
  wait4-->| GestureRight == 1 |sound1

```

##### セミオート

音声管理のセミオート部分を抜き出したものです。

下図の各Stateに設定するアニメーションです。
|  名前  |  アニメーション  |
| ---- | ---- |
|  idle  |  gun_sound_0  |
|  wait  |  gun_wait  |

```mermaid
  graph TB
  Start([Start])
  Exit([Exit])
  idle([idle])
  wait1([wait1])
  wait2([wait2])
  wait3([wait3])
  wait4([wait4])
  sound1([sound1])
  sound2([sound2])
  sound3([sound3])
  sound4([sound4])

  wait1-->Exit
  wait2-->Exit
  wait3-->Exit
  wait4-->Exit

  Start-->idle
  idle-->| mode_full == false \n GestureRight == 1 \n chamber == true |sound1

  sound1-->| GestureRight != 1 |wait1
  wait1-->| GestureRight == 1 |sound2
  sound2-->| GestureRight != 1 |wait2
  wait2-->| GestureRight == 1 |sound3
  sound3-->| GestureRight != 1 |wait3
  wait3-->| GestureRight == 1 |sound4
  sound4-->| GestureRight != 1 |wait4
  wait4-->| GestureRight == 1 |sound1

```
###### 参考画像

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3116924/ec6fb6cc-dbdc-5c70-3e6a-cd1f18999c0a.png)