Minecraftに空の王者を召喚する~Animated Javaを使ったモンスターの作り方~

# 1.はじめに

まず，拙作ですが以下の作品を見てください．

https://x.com/nukknu99/status/1651911890040238083?s=20

この作品，実は完全なバニラのMinecraftで動いています．（リソースパックをバニラと言うならだけど）
防具立ての頭にかぶせたモデルを良い感じに動かして，モンスターに見えるようにしています．

実はMinecraft ver1.19.4から追加されたitem_displayというEntityのおかげで，今では非常に簡単にこんなボスが作れるようになりました．
本記事では，「Animated Java」というBlockBenchのアドオンと，Minecraftのデータパックを組み合わせたモンスターの作り方を説明します．

この記事では、BlockBenchとコマンドを使って実際にモンスターを作ってみます。みんな大好き空の王者を作りながら解説するので，一緒に学んでいきましょう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/e791f597-9f31-102a-f5e9-9520a76ff58a.png)



# 2.前提知識

本記事ではあくまで「モンスターの作り方」についての解説のため，コマンドの基礎などの解説は行いません．
以下の知識があることを前提とします．

- Minecraftのコマンドを使ってある程度好きなものを作れるくらいの知識
    - tick，entity，nbtなどの用語が分かる
    - scoreboard，dataコマンドなどの使い方が分かる
    - 特にexecuteコマンドの使い方を理解していること
- Blockbenchの基本的な使用方法
    - モデル、テクスチャが作成できる
    - Element，Groupなどの用語が理解できる





# 3.使用ツール

- Minecraft Java Edition ver 1.20.0 以降
- BlockBench
- Animated Java（ツールではないけど）





# 4.作成手順

本章では，以下の段階に分けてモンスターの作り方を説明します．

1. Animated Javaでモデルとアニメーションを作る
    1-1. Animated Javaの基礎的な使い方（モデルの作り方・アニメーションの作り方）
    1-2. アニメーションを作るときに意識すること
    1-3. Locatorを使っていろいろする
    1-4. (おまけ)Variantを使った表現
2. Datapackでモンスターの動きを設定する
    2-1. Animated Java Modelの召喚方法
    2-2. Animated Java自動生成ファンクションの説明
    2-3. アニメーションとイベントをひもづける
    2-4. 簡単なAIを作る




    
## 4-1.Animated Javaでモデルとアニメーションを作る

### 基礎

まず，Animated Javaの基礎について解説すべきなのですが...これについて解説しているとかなり長くなってしまうので割愛します．
以下記事に，インストール～アニメーション作成～Minecraftへのエクスポートまでの流れを簡潔に書いています．少々分かりにくいですが，参考程度にしていただければ...

https://hackmd.io/@hVyU9rPVRv6gZYKMXeuCig/ryGe1DSk6

ということで，モデルとアニメーションを作ったものがこちらになります．
<img width="80%" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/8685810d-0087-9652-3bfb-429ab90ff69c.png>

ここからどうやってモンスターとして完成させていくのか，お教えします．

---

### アニメーションを作るときに意識すること

実際にMinecraftで動かしたときに不自然にならないように，アニメーションの「つなぎ」を意識しましょう．

具体的にどうするのかというと，まず基本姿勢を決定します．
以下のような直立したポーズにすると，様々な動きに派生しやすいです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/5fd66331-b547-e6cf-0f87-afc4c5ea91eb.png)

アニメーションは，基本姿勢から始まり，基本姿勢で終わるようにします．
こうすることで，どんなアニメーションをどんな順番で再生しても違和感なくつながっているように見えます．
![reus_anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/5bb29dff-2771-6030-a07a-427a6236af49.gif)

---

### Locatorの便利な使い方

Locatorとは，好きな位置に好きなEntityを配置することができる，Animated Javaの機能です．
パーツと同じように位置や親子関係を設定できます．

BlockBenchでは，以下のようにイカリ⚓のマークで表示されています．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/38972817-8c60-fe3c-4172-d88319525c2d.png)

Locatorは滅茶苦茶便利なので，その使用例をいくつか紹介します．

##### パーツの位置を取得する

Minecraft内で，item_displayのモデルの表示位置を取得することはできません．
そこで，モデルの位置を取りたい箇所にあらかじめLocatorを設定しておきます．

空の王者くんの場合は，口から炎を吐くときに正確に頭の座標から発射したいので，頭にLocatorを設置します．
頭部パーツのグループを右クリックし，`Add Locator`を選択してLocatorを追加します．Locatorには，適当なわかりやすい名前を付けておきます．
見えにくいですが，画像ではちょうど口の中あたりに来るようにLocatorの位置を調整しました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/44069384-2fd5-cffa-74fd-2c291e051706.png)

その後，Locatorを右クリックし，`Locator Config`を選択します．
頭の座標が分かれば良いだけなので，`Entity Type`にはmarkerを選択します．`NBT`は自由に設定してよいのですが，Tagsを書くとanimated javaが設定するTagsを上書きしてしまい，予期せぬ動作になってしまう可能性があります．Tags以外を設定しましょう．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/86ebea84-46b9-06d2-5ef9-6468923cb76e.png)

これで頭の座標を簡単に取得できるようになりました．コマンドの書き方については後ほど説明します．

##### 当たり判定を作る

LocatorのEntity Typeには，好きなEntityを設定できます．
せっかくなので，Animated Javaの段階でモンスターの当たり判定まで設定してしまいましょう．

まず，当たり判定用に使うEntityを決定します．空の王者の場合は，自由に大きさを変えられるのでslimeを使用します．
その後，以下の計算式でx,y,zそれぞれの数値を計算します．Entityのヒットボックスの大きさは，[こちら](https://technical-minecraft.fandom.com/wiki/Entity_Hitbox_Sizes)のサイトなどが参考になるかと思います．
なお、「item_displayのscale」とは，アニメーションで設定したScaleのことです（画像左下）．アニメーションごとにscaleを変える場合などは想定していないので，注意してください．
また，実際にMinecraftに召喚してみたら大きさが気に入らなかったのでscaleを変える，などすると計算がやり直しになってしまいます．最終的なscaleが決定してからこの作業をすることをお勧めします．

```
　Entityのヒットボックスの大きさ * 16 / item_displayのscale
```

<img width="80%" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/355821fc-e7bb-1d90-c935-663c7e15fc76.png>

その後は，BlockBenchのEDITタブにて，計算した数値のサイズとなるcubeを作り，値判定を付けたい位置に置いていきます．空の王者は，翼がかなり動くせいで当たり判定を付けるのが難しいため，胴体にのみ当たり判定を設定しました．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/fa5509c2-e29e-2fcc-31f7-3a4ad2cc1f4a.png)

cubeを置き終えたら，それぞれのcubeの底面の中心にLocatorを設置します．
これで，cubeの見え方と同じように当たり判定Entityが配置されるようになります．
なお，cubeとLocatorは，図のように同じグループに設定しておくと便利です．エクスポート時にcubeが見えてしまわないように，カメラのマーク（`Export`）は消してください．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/b95bdb0d-9106-7ff4-5e6a-0a4066b36e0a.png)

`Locator Config`も忘れずに設定してください．
`Entity Type`は，自分が当たり判定に使用することに決めたEntityにしましょう．
`NBT`は，前述のようにTagsを使うことはできませんが，それ以外は自分で使いやすいように設定します．
当たり判定のEntityが倒されてしまうと非常に困るのでHealthは高めの1000，自由に動かないためにNoAIは1b，見えなくするためにActiveEffectsを付与するなど...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/00a7f3e7-fbb7-b67b-bac7-0a497c88bfb2.png)

最後に，アニメーションを調整します．
当たり判定用Locatorのグループは，`Rotate in Global Space`をオンにします（`TIMELINE`ビューの地球🌎のマークをクリックし，斜線が消えた状態にする）．
こうしておけば，モデルが横を向いても，宙返りしても，おおよそ正確な位置に当たり判定を移動させ続けることができます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/12a03202-939a-3e51-12ac-3c37b89b8142.png)


##### 影をつける

影をつけるだけで，「バニラっぽさ」が増して完成度が高まります（たぶん）．

<img width="500" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/c5cd0323-d9e8-133f-ed7b-8db18f9fc708.png>

影を付けるには，item_displayのshadow_radiusを使います．
Project Settingsの`Root Entity NBT`に書いても良いのですが，Locatorを使うことで上下にジャンプするアニメーションを再生したとき，影が出たり消えたりするようになるので，よりバニラの挙動に近づきます．
空の王者の場合，Locatorはrootグループの直下に配置しています．
`Entity Type`は当然item_displayで，`NBT`にはモデルの大きさに応じてshadow_radiusを記述しましょう．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/68ae3cb1-e606-4834-d14b-79519a5569d2.png)

たったこれだけで，簡単にモンスターに影を付けることができます．

---

### （おまけ）Variantsの使い方

Variantsは，モデルのテクスチャを簡単に切り替えることのできる機能です．
RPGによくある色変えモンスターなど，そういったものを簡単に作ることができます．

Variantsを追加するには，EDITタブ左下のVARIANTSメニューの`ADD`をクリックします．その後，`Texture Map`から変更したいテクスチャを選択します(左が元々のテクスチャ，右が変化後のテクスチャ)．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/385748ff-c046-e15d-e6e0-b6d582a10651.png)

その後，VARIANTSメニューから作成したVariantsを選択すると，変更したテクスチャがモデルに反映されます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/771cf9c1-9259-49a9-6fff-80326908538f.png)

Variantsはアニメーションの中で変更することもできます．ので，自分はテクスチャの変更だけで済む表情の変化に使っています．通常`data modify`コマンドなどでの操作が必要なモデルの変更も，Animated Javaの操作だけで完結してしまいます．以下のような表情豊かな動きが作れます．
![anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/77457af0-3c5d-5d26-06d9-4f00ae2134f8.gif)

ただし，空の王者の場合，部位破壊表現をしたいので，テクスチャ変更だけでは不十分です．そのため，今回はVariantsについての解説は省かせていただきます．

:::note warn
Variantsを1種類作るごとに，すべてのパーツのItem Modelsが作成されます．
例えばパーツ10個から構成されるモンスターに10種類のVariantsを用意した場合，Item Modelsは合計で100個生成されることになります．
必要以上にモデルを増やしたくない場合は，手動でモデルを切り替える処理を実装するなどの手段を取ってください．
:::

## 4-2.Datapackでモンスターの動きを作る

これより，Datapack側での作業になります．
BlockBenchから`Animated Java > Export Project`を選択し，Datapackを生成した後の状態を前提とします．
Export方法などについては，4-1にて紹介した記事に詳しく書かれているので，そちらを参照してください．

:::note warn
Datapackの作り方は完全に個人の自由です！
以下筆者の思想が強く出ているので，あくまで参考程度にとどめ，自分のやりやすいような実装を心がけて下さい．
:::

### 汎用的な処理の実装

本格的にモンスターを作り始める前に，以下のような汎用的な処理を実装しておけば，効率的に製作ができます．
個々人で必要だと思うような処理を実装しましょう．

##### Animated Javaモデルの召喚処理

Animated Javaモデルは，`function animated_java:[モデル名]/summon`を実行するだけで簡単に召喚できます．
ただ，Animated Javaで自動生成されたタグ名は非常に長く，扱いにくいです．
例えば，空の王者の頭部位置取得用のLocatorは`aj.reus.locator_origin.pos_head`というタグを持っています．
Datapackではできる限り`execute on passengers`でLocatorにアクセスしたいので，わかりやすいタグを付けておきましょう．以下は召喚時の初期化処理の一例です．

なお，当たり判定のEntityには以下の4種類のタグを付与します．
1. 全ての当たり判定に共通する処理の対象とするためのタグ
1. モンスターの種類ごとに行う処理の対象とするためのタグ
1. ダメージ計算用の防御力を決定するためのタグ
1. どの部位の当たり判定か判別するためのタグ

```mcfuntcion:summon処理

# モデル召喚
    function animated_java:reus/summon

# init処理を実行する
    execute as @e[tag=aj.reus.root,sort=nearest,limit=1] run function {init処理}
```

```mcfuntcion:init処理

# 何らかのスコアでモンスターのHPを決定する
    scoreboard players set @s MonsterHealth 50000

# 位置取得用Locatorへのタグ付与
    execute on passengers if entity @s[tag=aj.reus.locator_origin.pos_head] run tag @s add LocatorPosHead

# 当たり判定用Locatorへのタグ付与
    execute on passengers if entity @s[tag=aj.reus.locator_origin.head_0] run tag @s add LocatorHealth
# 当たり判定EntityへのEntityにタグ付与
    execute on passengers if entity @s[tag=aj.reus.locator_origin.head_0] on origin run tag @s add MonsterParts
    execute on passengers if entity @s[tag=aj.reus.locator_origin.head_0] on origin run tag @s add ReusHealth
    execute on passengers if entity @s[tag=aj.reus.locator_origin.head_0] on origin run tag @s add HeadParts
    execute on passengers if entity @s[tag=aj.reus.locator_origin.head_0] on origin run tag @s add Head0

# 最初のアニメーションを再生する
    function animated_java:reus/animations/idle/tween_play
```

##### Animated Javaモデル消去時の処理

Animated Javaが自動生成する`function animated_java:[モデル名]/remove/this`処理は，Locatorと紐づけられたEntityも一緒にkillしてくれるので便利です．
ただし，空の王者のようにスライムを当たり判定に使用した場合，その場に小さくなったスライムが残ってしまいます．
直接removeを実行するのではなく，自前でremove処理を実装しましょう．

```mcfuntcion:remove処理

# 当たり判定を削除
# kill時に後述する被ダメージ処理が走ってしまうので，Deathタグを付与して誤動作を防ぐ
    execute on passengers if entity @s[tag=LocatorHealth] on origin if entity @s[tag=ReusHealth] run data modify entity @s {} merge value {Size:0,Tags:["ReusHealth","Death"]}
    kill @e[type=slime,tag=ReusHealth,tag=Death]

# パーツを削除
    function animated_java:reus/remove/this
```

##### 被ダメージ処理

空の王者のように，複数のEntityを当たり判定として使用する場合は，ダメージを受けたときにモンスターのHPを減らすような処理を実装する必要があります．
当たり判定Entityがダメージを受けたとき，そのEntityを対象として以下のような処理を実行すると良いでしょう．

```mcfuntcion:被ダメージ処理

# 受けたダメージ量を計算する
# EntityのHealthは常に1000であることを前提とする
    execute store result score #damage DummyScore run data get entity @s Health 100
    data remove storage mhdp: Temp.Health
    scoreboard players remove #damage DummyScore 100000

# root entityのHPを減らす
# ここではあくまで処理を簡潔に書くので，直接root entityを指定する
    execute as @e[tag=aj.reus.root,sort=nearest,limit=1] run scoreboard players operation @s MonsterHealth += #damage DummyScore

# 体力を回復する
    data modify entity @s Health set value 1000.0f
```

##### アニメーション再生処理

この処理に関しては個人の自由ですが，直接`function animated_java:[モデル名]/animations/[アニメーション名]/play`の処理でアニメーションを再生した場合，誤って複数のアニメーションを同時に再生してしまい，おかしな挙動になることがあります．
これは`function animated_java:[モデル名]/animations/pause_all`を実行した後にアニメーションを再生するだけで，簡単に防ぐことができます．
他のアニメーションを中断して再生することが多いアニメーションについては，以下のような再生処理を実装するとよいでしょう．

```mcfuntcion:アニメーション再生処理（怯み）

# 再生中のアニメーションを停止
    function animated_java:reus/animations/pause_all

# アニメーション再生
    function animated_java:reus/animations/damage/tween_play
```

---

### アニメーションに合わせて処理を行う

Animated Javaで作ったアニメーションは，root entityを実行主として`function animated_java:[モデル名]/animations/[アニメーション名]/play`を実行することで再生できます．
ただし，このままだとモデルが動くだけなので，プレイヤーがダメージを受けることはありません．

そこで，アニメーションの再生時間を受け取り，それに合わせた処理を行うfunctionを実装します（筆者は勝手に「イベントハンドラ」と呼んでいます）．
以下は実装例です．実行主はroot entityとします．

```mcfuntcion:各イベントハンドラへの分岐処理

# 噛みつきアニメーションが実行されているかどうか検知
    execute if entity @s[tag=aj.reus.animation.bite] run function {噛みつきアニメーションイベントハンドラ}
```

```mcfuntcion:噛みつきアニメーションイベントハンドラ

# プレイヤーを向く
    execute if score @s aj.reus.animation.bite.local_anim_time matches 1 facing entity @p feet rotated ~ 0 run tp @s ~ ~ ~ ~ ~
    
# 前進する
    execute if score @s aj.reus.animation.bite.local_anim_time matches 1..5 at @s run tp @s ^ ^ ^1

# 頭の近くにいるプレイヤーにダメージを与える
    execute if score @s aj.reus.animation.bite.local_anim_time matches 10 on passengers if entity @s[tag=LocatorPosHead] on origin at @s run damage @a[distance=..3] 8
```

BlockBenchで作ったアニメーションを見ながら，動きに合うような処理を実装してください．
なお，上記の処理でアニメーション再生時間を受け取るために使っているscoreboardは，Animated Javaが自動生成したものです（aj.[モデル名].animation.[アニメーション名].local_anim_time）．
自分で定義したscoreboardをタイマーとして処理を実装したい場合は，別の方法をとることをお勧めします．

---

### AIを作る

アニメーションのイベントハンドラを実装し終えたら，行動を決定するための簡易的なAIを作ります．
便宜上，ここでは「遷移処理」と呼びます．
アニメーション再生→遷移処理→アニメーション再生→遷移処理・・・と繰り返すことで，あたかもモンスターが生きているかのように動かすことができます．

空の王者は，プレイヤーとの位置関係に応じてアニメーションの選択確率を調整したいので，loot_tableを使ってアニメーションを選択しています．
lootコマンドでアイテムを出現させた後，そのアイテムのNBTを取得し，それに応じたアニメーションを再生するようにします．

以下は実装例です．reus:state/is_angerで怒り状態を検知しています．
通常は噛みつきとブレスを1：1の確率で選択しますが，怒り状態になると1：2の確率に変化します．
実際は，プレイヤーとの距離，プレイヤーが正面にいる，プレイヤーが背後にいるなど，複数条件を組み合わせています．

```mcfuntion:遷移処理.mcfunction

# 再生中のアニメーションを停止
    function animated_java:reus/animations/pause_all

# 再生するアニメーションを決定
    function {行動選択}

# アニメーション再生
    execute if entity @s[tag=AnmBite] function animated_java:reus/animations/bite/tween_play
    execute if entity @s[tag=AnmBreath] function animated_java:reus/animations/breath/tween_play

# 終了
    tag @s remove AnmBite
    tag @s remove AnmBreath
```

```mcfuntion:行動選択.mcfunction

# 抽選
    loot spawn ~ 0 ~ loot reus:animation_loot

# 噛みつき
    execute if entity @e[type=item,nbt={Item:{tag:{Act:1}}}] run tag @s add AnmBite
# ブレス
    execute if entity @e[type=item,nbt={Item:{tag:{Act:2}}}] run tag @s add AnmBreath

# 終了
    kill @e[type=item,nbt={Item:{tag:{ActPaper:1}}}]
```

```json:animation_loot.json
{
  "pools": [
    {
      "rolls": 1,
      "entries": [
        {
          "type": "minecraft:group",
          "children": [
            {
              "##comment":"噛みつき",
              "type": "minecraft:item",
              "name": "minecraft:paper",
              "weight": 5,
              "functions": [
                {
                "function": "minecraft:set_nbt",
                "tag": "{ActPaper:1,Act:1}"
                }
              ]
            },
            {
              "##comment":"ブレス",
              "type": "minecraft:item",
              "name": "minecraft:paper",
              "weight": 5,
              "functions": [
                {
                "function": "minecraft:set_nbt",
                "tag": "{ActPaper:1,Act:2}"
                }
              ]
            },
            {
              "##comment":"ブレス",
              "type": "minecraft:item",
              "name": "minecraft:paper",
              "weight": 5,
              "functions": [
                {
                "function": "minecraft:set_nbt",
                "tag": "{ActPaper:1,Act:2}"
                }
              ],
              "conditions": [
                {
                  "condition": "minecraft:reference",
                  "name": "reus:state/is_anger"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```

# 空の王者を召喚する

これまで作ってきた空の王者の完成形がこちらになります．

https://twitter.com/nukknu99/status/1730905901630972137

ボス製作にはかなりの時間がかかります．
主にexecuteコマンドの使い方を中心とした幅広い知識，BlockBenchの使い方やアニメーションの作り方など，一朝一夕で完成するものではありません．
当然，本記事に書いた内容だけでは完成しないため，自分の作っている配布マップの形式などに合わせた自分なりのボスの実装を考える必要もあります．
そのぶん完成した時の喜びも一入なので，是非挑戦してみてください．

# サンプルプロジェクト

Animated Javaを体験してみたいけど，モデル作りが面倒くさい…という人向けに！

モデル作りとボーン設定まで完了した状態のBlockBenchプロジェクトを配布します．
気軽に挑戦してみてください！

なお，サンプルに含まれているモデルおよびテクスチャは自作のため，配布マップなどでそのまま使用しても良いこととします．ただ，使用したい場合は連絡をお願いします．

[ダウンロード](https://drive.google.com/file/d/15PjF3-Ma2rVeqQ2GO5BGQqR4rfNAhVV1/view?usp=drive_link)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/84974ca0-5c91-fcff-6ec0-68ea49231c90.png)
ドラゴン（中級者向け）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/fac68d63-7678-44b0-6b53-98adba6908a4.png)
女の子（上級者向け）

# おまけ：Minecraftで推し活をする

:::note warn
この項は読まなくてもAnimated Javaは使えます！
時間を無駄にする覚悟のある人だけ読んでください！
:::

おれは絵が描けねェンんだコノヤロー!!!

作曲もできねェし!!

切り抜き動画も作れねェ!!

おれは推しに愛を伝えられねェ自身がある!!!

けど・・・

マイクラができる（ドン！）

### モデルを作る

BlockBenchにて，推しのモデルを作ります．
また，Animated Javaでダンスのアニメーションもいくつか作っておきます．
あなたの好きを原動力にして勢いよく作りましょう．

<img width="80%" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/a21bac40-293a-5d78-092f-fdf9558029d4.png>

### ステージを作る

推しにダンスさせるためのステージを作ります．

せっかくなのでコマンド技術を使って華やかにしてあげましょう．

![stage.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/0e7e8fa7-d103-6494-0490-921b982ad2fb.gif)

### 観客を作る

観客がいないのは寂しいのでMobを配置します．

クリーパーやオオカミは定期的にMotionを弄ってジャンプさせています．
ゾンビはfollow_rangeを短くし，目の前に透明の村人を配置することで攻撃モーションを再生しています．

![fun.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3591729/2051fce1-709b-6960-6fdf-abb878f3eea3.gif)

### 楽しむ

自分だけのライブを特等席で楽しみます．
以上！

https://twitter.com/nukknu99/status/1730831149789073738