# 後藤ひとりを飛ばそう
こんにちは、MIS.W 50代の ZAKI です。
こちらは、[みす老人会 Advent Calendar 2024](https://adventar.org/calendars/10110) 最終日の記事です。
<span style="font-size: 75%;">※ 記事中のマンガ画像は、まんがタイムきらら編集部公式より「ツイートに張り付けるなどしてご自由にお使いください」と何故か配布されたものです。</span>
<br>
## はじめに
皆さんは『ぼっち・ざ・ろっく!』というアニメをご存じでしょうか。

<br>
本作品は、極度の人見知りで陰キャな少女・後藤ひとりが「結束バンド」というガールズバンドに加入し、伊地知虹夏、山田リョウ、喜多郁代の3人の個性的なメンバーとともに成長していく様子を描いた作品です。
原作は『まんがタイムきらら MAX』に連載されているマンガ作品なのですが、2022年秋にテレビアニメが放送されたのをキッカケに人気が爆発。
2年経った今でも、映画総集編の公開やライブの開催など、盛り上がりは留まるところを知りません。
<br>
盛り上がりすぎなので、私はローチケからとんでもない量のお祈りメールをいただきました。

<br>
## ところで
『ぼっち・ざ・ろっく!』には様々な後藤ひとりが登場しますが、皆さんはどの後藤ひとりがお気に入りですか?
<br>
### **候補1:承認欲求モンスターになる後藤ひとり**

<br>
### **候補2:パリピに擬態する後藤ひとり**

<br>
### **候補3:金欠の後藤ひとり**

<br>
どれも可愛いですが、やはり私は
<span style="font-size: 200%;">**「ローポリ化してぶっ飛んで行き箱に衝突する後藤ひとり」**</span>
が好きですね。
何のことか分からない方は、各種動画サービスで『ぼっち・ざ・ろっく!』11話を13:25から5秒だけ視聴してください。
私は幻覚を見ている訳ではありません。
<br>
## ということで
今回はこちらの「ローポリ化してぶっ飛んで行き箱に衝突する後藤ひとり」を Unity を使って再現しようと思います。
最近 Unity 6 も公開されたらしいので、触ってみる良い機会なのではないでしょうか。
<br>
## 後藤ひとりを用意する
まずは兎にも角にも「ローポリの後藤ひとり」のモデルを用意する必要があります。
まあでも、こんなニッチな素材がネットに転がってるわけがないので、Blenderかなんかで自作するしか……
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

<span style="font-size: 300%;">**ありました。**</span>
[ニコニコ立体](https://3d.nicovideo.jp/works/td87197)でローポリぼっちちゃんを作成している方が普通にいらっしゃいました。
しかも、ご丁寧に概要欄には「積み上げたキューブにブチ込むと良いのではないでしょうか」との記載が。
分かりました! そのように使わせていただきます!
<br>
## Unity プロジェクトを作成する
Unity Hub を立ち上げて、プロジェクトを新規作成します。
今回は「LaunchGotoh」というプロジェクト名で HDRP プロジェクトを作成します。
ちなみに、HDRP プロジェクトはハイエンドゲーム向けの設定ですから、こちらの設定にすることでハイエンドなローポリぼっちちゃんをお届けすることができます。

<br>
## 後藤ひとりとキューブを配置する
プロジェクトが開いたら、後藤ひとりとキューブをシーン内に配置していきます。
とりあえず、ぼっちちゃんを原点に置いておきます。

<br>
アニメ11話を確認したところ、後藤ひとりの衝突目標は 10×10×10 の小さなキューブから構成されているので、合計1000個のキューブをシーン内に配置する必要があります。
後々の仕様変更に対応するため、キューブくらいはプレハブ化しておきましょう。
プレハブ化して、それらを 10×10×10 に組み立てたら、図のように無造作に置いておきます。

<br>
後藤ひとりと衝突目標との距離がアニメからは判断できなかったので、ひとまず100mとしておきます。

<br>
## 後藤ひとりとキューブにコンポーネントをアタッチする
今回は、皆さん大好き物理演算シミュレーションを使用して衝突させていきます。
こちらを用いる上で、最低限必要なコンポーネントを後藤ひとりとキューブにアタッチしていきます。
<br>
### **[Collider]**
俗に言う当たり判定というやつです。
これがないと両者は衝突せずに、すり抜けてしまいます。
キューブの方には Box Collider をアタッチすれば事足りますが、問題は後藤ひとりの方です。
一般的にキャラクターの判定は、Sphere や Box などのプリミティブな形状のコライダを組み合わせて作りますが、今回は面倒なので全部 Mesh Collider で済まします。
Mesh Collider は他のコライダに比べて処理負荷が高いですが、調べなければ分からないので大丈夫です。
こちらが後藤ひとりのメッシュ形状に合わせて自動的に作られたコライダになります。
Convex (凸状) を有効にしているので、このような形状に落ち着いています。

<br>
### **[Rigidbody]**
アニメを確認する限りでは、衝突の前後で後藤ひとりとキューブの形状は変化していません。
このことから、キューブはもちろん <span style="font-size: 200%;">**後藤ひとりも剛体**</span> であることが分かります。
剛体の物理シミュレーションと言えば Rigidbody なので、こちらをアタッチしていきます。
Rigidbody には Mass というオブジェクトの質量を設定する項目があります。
キューブの質量はこちらで勝手に設定できますが、後藤ひとりの質量が分からないので今から調べます。
<iframe width="560" height="315" src="https://www.youtube.com/embed/wW_QhknCISg?si=1EYC_8WZOB8QxCJp&start=6620" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<br>
<br>
原作者・はまじあき氏の配信によると、どうやら <span style="font-size: 200%;">**後藤ひとりの質量は50kg**</span> だそうです。
こちらの情報通り、Rigidbody に Mass = 50 と設定します。

<br>
## Cinemachine で簡易的なカメラワークを作成する
アニメでのカメラワークを確認すると、まず後藤ひとりの上半身のアップが映ってから、
5秒ほどかけて徐々にキューブの方にカメラが動いているような挙動をとっています。
スクリプトで実装しても許容範囲ではありますが、せっかくなので Cinemachine を使ってみましょう。
後藤ひとりの位置からキューブの位置までの遷移なので、2点にそれぞれ CinemachineCamera を置きます。
CinemachineCamera は良くバーチャルカメラと呼ばれるもので、
映っているものが即座にゲームビューに反映されるわけではありません。
メインカメラを切り替えるための目印として機能するので、シーン中に複数置いてあっても大丈夫です。

<br>
CinemachineCamera を置いたら、メインカメラに CinemachineBrain をアタッチします。
CinemachineBrain は、全てのアクティブなバーチャルカメラを監視する役割があります。
ここで重要なのは「Custom Blends」という項目です。

<br>
上記のように設定すると、From に設定したバーチャルカメラの位置から、To に設定したバーチャルカメラの位置まで、5秒かけてメインカメラが位置を補完できるようになります。
カメラの遷移処理を制御するスクリプトを作ったら、シーンを再生して確認してみましょう。
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y_SkAo7cmcQ?si=IQl3nhu7e2riFmAR" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<br>
<br>
## 後藤ひとりに力を加える
ここまで来たら、あとは後藤ひとりに力を加えて発射するだけです。
Rigidbody に力を加える手段は、大きく分けて2種類あります。
<br>
### **[AddForce]**
Rigidbody の AddForce メソッドを使用することで、物体に直線的な力を加えることができます。
ここで加えた力の大きさで、後藤ひとりが衝突目標に到達できるかどうかが決まります。
<br>
### **[AddTorque]**
Rigidbody の AddTorque メソッドを使用することで、物体に回転力を加えることができます。
これがないと、後藤ひとりが直立不動のまま発射され、ただでさえシュールな光景がより一層シュールになります。
シーン上では後藤ひとりはZ軸方向を向いているので、こちらが回転軸になるようにトルクを加えてあげます。
<br>
## サンプルコード
シーンスクリプトを以下に置いておきます。
キーボード上で Enter が押下されると、Input System を介して後藤ひとりが発射される仕組みになっています。
それと、ついでに発射時と衝突時にフリー素材のSEも再生するようにしました。
```csharp=
using Unity.Cinemachine;
using Unity.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class LaunchGotohBehaviour : MonoBehaviour
{
/*==================================
Private Values
==================================*/
[SerializeField]
private Rigidbody _gotoh = null;
[SerializeField]
private Vector3 _force = Vector3.zero;
[SerializeField]
private Vector3 _torque = Vector3.zero;
[SerializeField]
private CinemachineVirtualCameraBase[] _virtualCameras;
[SerializeField]
private AudioSource _launchSE = null;
[SerializeField]
private AudioSource _crushSE = null;
private int _currentCameraIndex = 0;
private const int c_activeCameraPriority = 100;
private const int c_inactiveCameraPriority = 0;
/*==================================
Unity Event Functions
==================================*/
void Start()
{
Physics.ContactEvent += OnContact;
InitCamera(_currentCameraIndex);
}
/*==================================
Public Functions
==================================*/
public void OnPlay(InputAction.CallbackContext context)
{
if (context.phase == InputActionPhase.Performed)
{
_gotoh.isKinematic = false;
_gotoh.AddForce(_force);
_gotoh.AddTorque(_torque);
ResumeLaunchSE();
SwitchCamera(1);
}
}
/*==================================
Private Functions
==================================*/
private void InitCamera(int setIndex)
{
for (int i = 0; i < _virtualCameras.Length; i++)
{
if (setIndex == i)
{
_virtualCameras[i].Priority = c_activeCameraPriority;
}
else
{
_virtualCameras[i].Priority = c_inactiveCameraPriority;
}
}
}
private void SwitchCamera(int nextCameraIndex)
{
_virtualCameras[_currentCameraIndex].Priority = c_inactiveCameraPriority;
_virtualCameras[nextCameraIndex].Priority = c_activeCameraPriority;
_currentCameraIndex = nextCameraIndex;
}
private void ResumeLaunchSE()
{
_launchSE.Play();
}
private void ResumeCrushSE()
{
_crushSE.Play();
Physics.ContactEvent -= OnContact;
}
private void OnContact(PhysicsScene scene, NativeArray<ContactPairHeader>.ReadOnly headers)
{
foreach (var header in headers)
{
for (int i = 0; i < header.pairCount; i++)
{
var contactPair = header.GetContactPair(i);
if (contactPair.collider.gameObject.layer == LayerMask.NameToLayer($"Gotoh") && contactPair.otherCollider.gameObject.layer == LayerMask.NameToLayer($"Cube"))
{
ResumeCrushSE();
}
}
}
}
}
```
<br>
## 実行結果
最終的な成果物がこちらになります。
美しい軌道ですね。
<iframe width="560" height="315" src="https://www.youtube.com/embed/r8vJ81LKmyE?si=p8HbEJGaFQacT-Sl" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<br>
<br>
## おわりに
ここまでお付き合いいただきありがとうございました。
この記事を通して『ぼっち・ざ・ろっく!』の魅力は、十二分に伝わったかと思います。
そういえば、本日『結束バンドの歌ってみた』というカバーアルバムが配信開始されたそうです。
皆さんも是非聴いてみてはいかがでしょうか。
個人的にはリョウの『Daydream café』がツボでした。
<iframe style="border-radius:12px" src="https://open.spotify.com/embed/album/5OJtVcOAvk2dkkjOShUFxs?utm_source=generator" width="100%" height="352" frameBorder="0" allowfullscreen="" allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" loading="lazy"></iframe>
<br>
<br>
さて次回の記事は――
おっと、今日は最終日でしたね。
アドベントカレンダー最終日ということは、今日は12月25日……?
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
## クリスマスアップデート
12月25日ということで、クリスマスアップデートを実施しました。
パッチノートは以下の通りです。
:::info
- 環境光を太陽光から月光に変更し、クリスマスらしさを演出しました。
- 後藤ひとりにサンタ帽をかぶせることで、クリスマスらしさを演出しました。
- 衝突対象をクリスマスツリーに置き換えることで、クリスマスらしさを演出しました。
- 衝突対象を無駄に光らせることによって、クリスマスらしさを演出しました。
- SEにベルやオルガンを採用することで、クリスマスらしさを演出しました。
:::
アップデート後の実行結果はこちらになります。
<iframe width="560" height="315" src="https://www.youtube.com/embed/OFuvVY3Bods?si=A2rROeQ3Z4ob8MYz" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<br>
<br>
## それでは皆さん良いお年を
