この記事はSimutrans Advent Calendar 2022 12/10の記事です。
初めまして。AhozuraNS専属システムエンジニア(自称)のM_Kasumiと申します。「AhozuraNS」という名前のNet Simutrans(以下、NS)を運営しております。
2020年春、コロナ禍で暇を持て余したメンバーが集まってNSを始めてから、早いもので2年半以上が経過。現在は、今年10月末から始まった第5期をプレイ中です。
これまでの2年半のうち、最初の半年を除いたおよそ2年間、私M_Kasumiが技術的な部分をほぼ100%担当しています。もちろん最初から万全の態勢で運営ができたわけではなく、様々な問題が次から次へと発生してきました。
今回は、これまでの2年間で様々な課題と向き合った結果、現状のAhozuraNSがどのように運営されているのかをご紹介します。
「参加者にも運営者にもやさしいNS」をコンセプトに掲げ、「世界一快適なNS」を目指しています。
現在NSを運営されている方、これからNS運営を考えている方などの参考になれば幸いです。
本記事の登場人物
本記事では複数の要素が絡み合って登場しますが、基本的に登場するのは以下の5要素です。
必要に応じて図示しながら解説しますが、もし混乱が生じた場合は、この中のどこを視点とした話なのかを整理しながらお読みいただくと理解しやすいかと思います。
なお、本稿はSimutransの記事ですので、登場する技術要素については最低限のざっくりとした説明のみにとどめさせていただきます。一応、この記事を読むだけで「私がどういうことをやっているのか」は理解できるように書いたつもりですが、各技術要素についての詳細・厳密な解説が必要な方はご自分でお調べください。
ゲームサーバを稼働させる
NSでは、サーバ上でゲームの中心となるSimturansを稼働させなければなりません。参加者はこのサーバにアクセスして、サーバ上のゲームを操作することになります。したがって、NSを開催するにあたっては、どんな環境でサーバゲームを動かすかを決めることが第一歩となります。
そう書くと難しそうに聞こえるかもしれませんが、自宅のポートを解放できるのであれば、ノートパソコンでSimutransをサーバモードで実行するだけでも構いません。(24時間実行し続ける必要はありますが)
ただし、ポートを解放することにはセキュリティ上のリスクを伴います。筆者宅ではポートを解放できないことなどから、サーバを自宅で稼働させることは早々に断念しました。
サーバ機の選定:VPSを借りる
そこでAhozuraNSでは、VPSサービスを契約することにしました。VPSとは、サーバの分譲みたいなもんです。利用者目線では「ほうほう、Simutransを動かすためにサーバを借りたんだな」と思っていただいて問題ないです。
AhozuraNSでは、当初「Vultr」という海外の格安VPSサービスを契約していましたが、円安に伴う利用料の高騰から、現在では国内サービスの「WebARENA Indigo」に切り替えています。
契約しているのは、RAM4GBのプランで、Ubuntuをインストールして利用しています。
スペックについてですが、NSだけを稼働するサーバであればRAMを重視するとよいです。4GBを借りれば、2048*2048のマップを15人で1年間ガッツリ開発して遊び倒すことができました。
また、契約する際に、サーバにログインするために必要な秘密鍵が発行されますので、しっかり保管しておきましょう。失くすと再発行はできません。
本体をビルドする
VPSにリモートログインし、以下の記事の「ビルド」の項目を参考に本体をビルドします。
2024/08/21追記:ビルドがうまくいかない場合は以下を参考にしてください。
なお、WebARENA Indigoではデフォルトでユーザが用意されていますのでそのままでいいのですが、もしrootユーザしかない環境の場合は新しくユーザを作ったほうがよいでしょう。
サーバでゲームを起動する
それではゲームを起動していきましょう。
サーバ機側でのSimutransの起動はCLI(コマンド)で行います。まずはVPSにログインし、Simutrans本体の配置されているディレクトリまで移動し、以下のコマンドを入力します。
./simutrans -server 13353 -server_admin_pw **** -objects pak128japan
ここで、「****」の部分は任意のパスワードです。ゲームを開始するときに設定し、以後ゲームに対して操作を加えるときに入力を求められます。以下の文章でも、「****」が登場したらすべて任意のパスワードだと思ってください。また、「pak128japan」の部分も任意のpakセット名が入ります。
これでゲームを起動できました。ただしこのままでは、VPSとの接続を解除した瞬間にゲームも終了されてしまいます。そのため、「nohup」コマンドを利用し、VPSとの接続が切断されてからもゲームが継続して動作するようにしました。
nohup ./simutrans -server 13353 -server_admin_pw **** -objects pak128japan > simutrans.log &
定期セーブを実施する
Simutransは安定板でも結構バグの多いゲームです。普通に操作しているつもりで変なバグを踏んでサーバが落ちてしまうことはしばしばあります。セーブデータがない状態でサーバが落ちると、今までのゲームが水の泡になってしまいます。(実際、AhozuraNS2期はそのせいで何の前触れもなく突然終了しました)
ということで、予期せぬサーバダウン時の被害を最小限に抑えるため、一定間隔で自動的にセーブが行われるようにしたいと思います。
nettoolとは
セーブ機能を実装する上で欠かせないのが、「nettool」です。nettoolとは、Simutransに付属している、ネットゲーム制御用のプログラムです。
詳しくは以下の記事に解説されています。
nettoolを利用したゲームのセーブ
nettoolを使ってセーブを行うコマンドは以下の通り。
./nettool -p "****" force-sync
これを実行すると、simutransディレクトリの直下に「server13353-network.sve」という名前でセーブデータが保存されます。
セーブデータのコピー
このセーブデータを、バックアップのためにsimutransディレクトリ内の「save」フォルダにコピーします。こうすることで、万が一最新のセーブデータが破損しても、1個前までロールバックすることができます。
コマンドは以下の通り。名前が重複しないよう、ファイル名にタイムスタンプをつけてコピーしています。
cp -f /home/なんたらかんたら/simutrans/server13353-network.sve /home/なんたらかんたら/simutrans/save/server13353-network-`date "+%Y%m%d%H%M%S"`.sve
ここで出てくる「なんたらかんたら」はフルパスです。以下の文章でも、「なんたらかんたら」が登場したら全部フルパスだと思ってください。
シェルスクリプトファイルの作成
次に、上記2つのコマンドをまとめます。
「autosave.sh」というファイルを作り、以下の内容を書き込みます。
#!/bin/sh
/home/なんたらかんたら/simutrans/nettool -p "****" force-sync
wait
sleep 10
cp -f /home/なんたらかんたら/simutrans/server13353-network.sve /home/なんたらかんたら/simutrans/save/server13353-network-`date "+%Y%m%d%H%M%S"`.sve
先程の2種類のコマンドのほかに「wait」「sleep 10」が入っています。セーブに時間がかかることを想定して、10秒間待ってからバックアップを実行するためのコマンドです。最低限のスペックのVPS上で稼働している関係上、たまにセーブが完了するより先にコピーが始まってしまうことがあり、これを回避するねらいです。経験上、10秒も待てば大丈夫です。
また、セーブをいきなり実施すると、ゲームにログイン中の人がびっくりしてしまう可能性があります。そこで、参加者にメッセージで通知してからセーブするようにしましょう。参加者にメッセージを送信するには、nettoolの「say」という機能を利用します。
#!/bin/sh
/home/なんたらかんたら/simutrans/nettool -p "****" say "20秒後にセーブを行います。"
sleep 20
/home/なんたらかんたら/simutrans/nettool -p "****" force-sync
wait
sleep 10
cp -f /home/なんたらかんたら/simutrans/server13353-network.sve /home/なんたらかんたら/simutrans/save/server13353-network-`date "+%Y%m%d%H%M%S"`.sve
以上が「autosave.sh」の完成形となります。
セーブ&コピーの定期実行
前項までに作ってきた「autosave.sh」を定期的に実行できるようにします。方法はいたって簡単で、Linuxに標準搭載されている「cron」という機能を利用します。
まずは、以下のコマンドを実行します。
crontab -e
間違っても「crontab」とか「crontab -r」と実行しないように!登録されているcronが全部消えます。
エディタ画面が出てきますので、たとえば15分おきに行う場合は、以下のように登録します。
*/15 * * * * /home/なんたらかんたら/autosave.sh
これで15分ごとにゲームのセーブが行われるようになりました。
Discord Botを制作する
サーバが落ちてしまったとき、管理者(私)がサーバを操作できる時なら手動で再起動すればいいのですが、不在時にサーバが落ちてしまうと、その間メンバーは遊ぶことができません。
参加者にサーバを再起動する手段を与える必要があります。とはいえ、参加者もサーバにリモートログインしてCLIで直接操作しろというのは酷ですし、何より広範囲に対して不必要に権限を与えるのはセキュリティ的によろしくありません。
ということで、参加者に様々な機能を提供するインターフェースを用意する必要がありました。今回はその手段として、Discordのbotという方法を選定しました。理由は単純で、日頃AhozuraNSのメンバー同士でコミュニケーションを取る際はDiscord上で行っているからです。Discordでサーバ管理人にリプライを飛ばして「サーバが落ちたぞ!」と報告するのではなく、Discordのbotにコマンドを送ればサーバを叩き起こせるようにするわけですね。
参加者がサーバを再起動できるようにする
botの作業の流れは以下の通り。
VPS上ではメモリが足りずbotを動かせなかったため、botは自宅のRaspberryPi上で動かし、必要に応じてVPSと通信させることにしました。
botはPython3.8で実装しました。
まずはDiscordは一旦置いておいて、VPSにログインしてSimutransサーバを操作するところから書いていきましょう。
VPSとのSSH接続には「paramiko」を利用しました。
以下、PythonでRaspberryPiからVPSにリモートログインしてSimutransサーバを叩き起こすコードです。
import paramiko
# 各種設定
IP = 'VPSのIPアドレス'
USERNAME = 'VPSにログインするアカウント'
KEY = '秘密鍵のファイルパス'
# VPSにログイン
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())
ssh_client.connect(IP,username=USERNAME,key_filename=KEY, timeout=10.0)
stdin, stdout, stderr = ssh_client.exec_command('nohup /home/なんたらかんたら/simutrans -server 13353 -server_admin_pw **** -objects pak128japan > simutrans.log &')
こちらも先程紹介したnettoolの機能を使用しています。(本来はVPSからのレスポンスを確認してエラー処理等を行うことが望ましいですが、今回はあくまでもSimutransに関する部分の解説ということで割愛。)
それではこのコードをDiscord botに組み込んでいきます。
botの作成にはPythonのライブラリ「discord.py」を使用しました。また、botの自動起動&多重起動防止用に、「setlock」というものをRaspberry Piにインストールしました。
以下がごく簡単なbotのコードです。「^reboot」という投稿に反応してサーバを再起動するようにしています。ちなみに、この後botをcronで起動することになる都合上、ファイルパスの類はフルパスで記述するのが望ましいです。
import discord
import paramiko
# 各種設定
IP = 'VPSのIPアドレス'
USERNAME = 'VPSにログインするアカウント'
KEY = '秘密鍵のファイルパス'
TOKEN = 'Discord botのトークン'
client = discord.Client()
# メッセージ受信時
@client.event
async def on_message(message):
# メッセージ発信元がbotである場合は無視
if message.author.bot:
return
# bot以外の誰かが「^reboot」と発言したらサーバを再起動する
elif message.content == '^reboot':
await message.channel.send("サーバを再起動します。")
# VPSにログイン
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())
ssh_client.connect(IP,username=USERNAME,key_filename=KEY, timeout=10.0)
stdin, stdout, stderr = ssh_client.exec_command('nohup /home/なんたらかんたら/simutrans -server 13353 -server_admin_pw **** -objects pak128japan > simutrans.log &')
return
# botを起動
client.run(TOKEN)
これで必要最低限の機能は揃いました。あとはDiscord botを起動するだけです。
ただし、既にbotが起動している場合にもう一度起動してしまうと、同じ処理が二回行われるなどの不都合が生じます。
二重起動を防ぐ方法は様々ありますが、ここでは先程ほんの少し触れた「setlock」を利用する方法を採用しています。
setlockというのは、第1引数に指定したファイルをロックした状態で第2以降の引数に書いたプロセスを起動するというコマンドです。指定したファイルがロックされている間は別のプロセスがそのファイルを使用することはできませんから、二重起動の防止になるわけです。
setlockを利用したDiscord botの起動方法は以下の通りです。
setlock -Xn (ロックしたいファイルのフルパス) python3 (botファイルのフルパス)
第一引数のファイルは使ってない適当なファイルでOKです。touchコマンドなどで適当な場所に適当なファイルを作って指定するとよいでしょう。
続いて、botを自動起動する設定を書き足していきます。例によってcronにbot起動の設定を書いていきましょう。まずは先程VPSで行ったのと同様に、RaspberryPi上でも以下のコマンドでcronの設定を編集する画面に入ります。
crontab -e
crontabに書き込む内容は以下の通りになります。
* * * * * setlock -Xn (ロックしたいファイルのフルパス) python3 (botファイルのフルパス)
それではbotを使って鯖を叩き起こしてみましょう。
「^reboot」というコマンドを受け取ったbotが、サーバを再稼働させることに成功しました。(スクリーンショットだけではわかりませんが、実際に再起動に成功しています。また、本記事では割愛した「VPSからのレスポンスを調べて行う処理」についても実装しています)
もちろん、サーバの管理人以外でも再起動を行うことができます。
以上でDiscord botの基本的な実装は終了です。
アドオン追加を自動化する
AhozuraNSではアドオン製作を行っているプレイヤーが多く、アドオンを追加する頻度も非常に高くなっています。多い時には1日1回のペースで新作アドオンが追加されていきます。
しかしNSでは、参加者の全員がサーバ側と同じアドオンを過不足なく入れている必要があります。そのため、新しいアドオンを追加する際は、サーバ管理者がアドオンを取りまとめ、サーバにアドオンを追加するタイミングで参加者全員が一斉に追加作業を行います。
こんな作業を毎日行っていたのでは、参加者側も運営者側も面倒で仕方ないでしょう。しかしAhozuraNSでは、これらの流れがいたってスムーズに行われています。どのように実装したのかご紹介しましょう。
参加者側:アドオン追加申請
参加者が「サーバにアドオンを追加したい!」と思い立った時、参加者のやることは、指定されたDiscordのチャンネルにアドオンファイルを投稿するだけです。
アドオン追加用チャンネルにアドオンが投稿されると、Discord botがそのファイルに対して処理を開始します。
botの実行する処理は以下のような流れになります。
実際にDiscordで送信された添付ファイルをPythonで保存(zipファイルなら解凍)するごく簡単な例が以下の通り。
import discord
import shutil
TOKEN = 'Discord botのトークン'
client = discord.Client()
# メッセージ受信時
@discord_client.event
async def on_message(message):
# メッセージに添付ファイルがある場合
if message.attachments:
save_file_path = '保存先ファイルパス'
for attachment in message.attachments:
# 添付ファイルを保存する
await attachment.save(save_file_path)
# 拡張子がzipの場合
if attachment.url.endswith('.zip'):
shutil.unpack_archive(save_file_path, save_file_path.split('/')[-1][:-4])
return
# botを起動
client.run(TOKEN)
なお、このコードではzipを無条件に解凍してしまっているため、ウイルス感染していた場合などのリスクがあります。この辺は要改善。
実際の動作の様子は以下の通り。
実際には上記のコードのほかに、pakファイルやja.tabを抽出して特定ディレクトリにまとめるなどの処理も追加しています。
これで、参加者が思い思いに投げたアドオンファイルを一か所に纏めることができました。
運営者側:pakファイル取りまとめ・配布 (GitHub Actions)
さて、AhozuraNSのアドオンファイル群、通称「pakセット」は、GitHubを利用して、以下のように、いつどのような変更が行われたのかを常に記録しています。
このようにバージョンを管理することで、たとえば「Simutransが起動しなくなってしまった」といった場合にも原因となったアドオンを探しやすくなりますし、原因を特定したらその部分だけを選んで取り消すこともできます。また、pakセットを複数人で共同で管理することもできるようになります。AhozuraNSでは、私とあるみどり氏でこのリポジトリを管理しています。
前項までで解説した通り、botにアドオンファイルを纏めさせた後は、それらのアドオンをこのリポジトリに手動でコミット・プッシュ(アップロード)していきます。コミットの際には変更内容の説明も添えるのですが、そのときにどのような変更を行ったのかは人間が判断しなければならないことですから、この作業に関してはどうしても自動化できません。
さて、皆さんがお使いのpakフォルダの中にはたくさんのpakファイルがあると思いますが、実はこのpakファイル群、一つのファイルにマージした方が起動が高速化します。この件に関しての詳しい説明はこちらの記事をご覧いただくとして、AhozuraNSでは、起動高速化のため大量のpakファイルを一つのファイルにマージしてから参加者に配布しています。つまり、アドオンが追加されるたびにこの作業も行わなければならないわけです。しかし毎回毎回手動でやっていてはとても面倒なので、この作業は「GitHub Actions」を利用して自動化しています。
GitHub Actionsとは、GitHubの提供する機能の一つで、GitHubで発生する様々なイベントをトリガとして、様々なコマンドを実行することができます。
AhozuraNSでは、「mainブランチにタグ付きでプッシュされた時」に「pakファイルのマージ・zip化・ファイル配置用Webサーバへのアップロード」までを自動で行う設定にしています。
GitHub Actionsを設定する方法についてはこちらなどをご覧いただくとして、ここではSimutrans向けにどのようなフローを設定しているのかをご紹介します。
具体的に「/.github/workflows」に配置する「main.yml」の内容が以下の通り。
name: Merge Pakfiles
on:
push:
branches:
- main
tags: ["**"]
workflow_dispatch:
jobs:
merge-pakfiles:
if: contains(github.ref, 'refs/tags/')
runs-on: ubuntu-20.04
steps:
- name: Install packages
run: sudo apt update;sudo apt install -y simutrans-makeobj
- uses: actions/checkout@v2
- name: Merge pakfiles
run: |
/usr/lib/simutrans/makeobj merge ./release/boot.pak ./pak-data/*.pak >./null
- name: Make Zip file
run: |
mkdir ./zips
cd release
zip -r ../zips/boot.zip *
- name: FTP deploy
run: |
cd zips
ftp -n -p <<END
open ${{ secrets.FTP_HOST }}
user ${{ secrets.FTP_USERNAME }} ${{ secrets.FTP_PASSWORD }}
binary
prompt
cd ${{ secrets.FTP_REMOTE_ROOT }}
put ./boot.zip
END
うーん、これでいいのか?←
実はGitHub Actionsを使ったのはこれが初めてなもので。
もっとスマートなやり方があったら教えてください。
これを設定した状態で、条件を満たすコミットがプッシュされると、zipファイルがwebサーバ上の指定した場所に配置されます。
この例では、実際にpakファイルを配置するディレクトリを「pak-data」、zip化の対象とするディレクトリを「release」として、Gitのリポジトリ上にあらかじめ配置しています。また、1つにマージされたpakファイルはreleaseディレクトリ内に「boot.pak」という名前で出力されます。releaseディレクトリの中にja.tabなどを配置しておけば、boot.pakと一緒に圧縮します。
ちなみに、ファイルを配置するWebサーバのパスワードなどはGitHub Secretsに格納してあります。(※GitHub Secrets……GitHubの機能。一度設定すると絶対に人間には中身の見えない変数みたいなもん)
Actionsの実行が成功したかどうかはGitHubの画面からも以下のように確認することができます。
上の画像では成功したので緑色のチェック印が付いています。失敗すると×印が付きます。
参加者側:Simutransを起動
さて、無事に新しいpakセットがWebサーバに配置されました。参加者たちは、自分たちのSimutransフォルダにそのpakセットをダウンロードする必要があります。
従来は、運営者から「pakセット更新」の告知を受けた参加者が次回Simutransを起動する際、所定のリンクからアドオンセットをzipでダウンロードし、それを解凍してpakフォルダ内に配置する、という作業が必要になっていました。
しかし、面倒ですね。ゲームの起動ぐらい、何も考えずボタン一つのクリックで済ませたいものです。
そこで考えたのは、NSを「ソシャゲ」のようにしてしまうことです。
少しでもソシャゲに触れたことがある方ならご存知のことと思いますが、ソシャゲでは頻繁にデータのダウンロードが発生します。しかし当然、手動で「所定のリンクへのアクセス」「zipの解凍」「正しいディレクトリへの配置」といった作業を行う必要はありません。
ソシャゲのデータ更新においてプレイヤーのすべきことは「普段通りにアプリを起動して待っている」ことだけで、データのダウンロードが終わればいつも通りゲームが起動します。
NSのアドオン追加でもこれができたら便利ですね。
実装していきましょう。
プログラムの流れは単純明快で以下の通り。
①サーバ側のpakセットのバージョンを確認する。
②アップデートがあれば更新データをダウンロードする。(アップデートがなければ④に飛ぶ)
③zipを解凍してpakフォルダ内に配置する。
④Simutrans本体を起動する。
コードは地味に長いので割愛します。基本的には上記の処理を書いているだけです。
完成したものがこちら。
これら3ファイルをSimutrans本体と同じディレクトリに配置すれば準備完了です。今後、サーバに入る際はSimutrans本体ではなくPakUpdateChecker.exeを起動するようにします。
サーバ側のpakセットの更新がなければ、そのまま同じディレクトリに存在するSimutransが起動されます。しかし、サーバ側に更新データがあった場合は以下のような表示になり、zipのダウンロード・解凍が実施され、その後にSimutransが起動されます。
ちなみに、表示されている「20220104」といった数字は、pakセットを管理しているGitHubで付けたタグ名であり、便宜上日付で命名していますが、他の名前にすることも可能です。
なお、先述したGitHub Actionsにも、サーバ側のバージョン情報を自動的に更新する処理を追記しています。
Discord bot その他の機能
せっかくbotがあるので、他にも様々な機能を実装してみました。
メンテナンスお知らせ機能
サーバメンテナンス中(事故ではなく、意図的にサーバを落としている状態のとき)にはこのようなメッセージを出して再起動操作を受け付けなくします。
メンテナンスかどうかの判定は、サーバ上の作業ディレクトリにファイルが入っているかどうかを参照して行っています。
NS接続中クライアント確認機能
今接続中のクライアントを確認できます。IPアドレスしか分からないので使いどころがいまいち不明。
地名生成機能
Simutransで必要不可欠な「地名」を錬成します。全国の地名から頻出漢字を適当に選んでランダムに出力します。
ただ、完全ランダムだとなかなか良い地名を引くことができないので、全国のバス停全部の中からランダムに吐き出す機能も付けました。
今後の展望
現状のシステムでは、ゲームそのものを操作することはできますが、ゲーム内の状況(駅の待機人数やデッドロックの発生状況等)に関してはノータッチです。今後はひめし様のSimutrans World Monitorを利用するか、あるいは類似の機能を既存botに追加実装することも検討していきます。
このほか、Discordで推奨されているスラッシュコマンドの導入についても前向きに検討を加速したいと思います。
また、今回紹介したそれぞれの機能はそれぞれ別々のタイミングで実装したものであり、現状のコードではIPアドレスなど各種情報の記述場所がバラバラで変更作業がやや煩雑になってしまっています。今後はこれを環境変数に設定するなどして一本化し、メンテナンス性・汎用性を高くすることなども実施していきたいです。
最終的には誰でも簡単にインストールして使えるパッケージにして配布するところまで持っていきたいのですが……前途多難。
さいごに
おそらく、皆さんそれぞれの環境によって、ここに書いてある通りにやるだけでは上手くいかないことも多々あると思います。しかし、ことコンピュータの世界においては特に、悩むより手を動かすが吉です。失敗を恐れず手を動かしてトライアンドエラーを繰り返し、PDCAサイクルを超高速回転させていきましょう。ただし、失敗した時のために、こまめなバックアップは忘れずに!(逆に、「バックアップあるからいくらぶっ壊してもいいや」ぐらいの心構えでいるのがよいと個人的には思います)
長々とありがとうございました。以下、読む必要はありません。
余談①
筆者は2022年4月に就職し、ネットワークエンジニアとして活躍しています。ですが業務内容の領域が違いすぎて本稿で紹介した内容を含むSimutransライフには何の影響も及ぼしていません。
個人的な希望としてはアプリケーション開発をやりたいんですがね……。
余談②
本稿を執筆中、一部参考にしようと思って2020年のアドカレの自分の記事を読みなおしていたところ、「『CUE!』始めました」とか書いてあって涙が止まりませんでした。『CUE!』は2021年4月にソシャゲのサービスを「休止」という形でいったん終了。さらに2022年7月には展開の終了が発表されており、つい先日の2022年11月19日にラストライブを迎えたのでした。嘘だよな……???????????????????????????
実はそのライブにフラスタを出しましてね。僭越ながら絵を描かせていただきました。決して上手くは描けませんでしたけれども、最後に感謝の気持ちを伝えることはできたかなと。大変でしたけど、描いて本当によかったと思います。
ソシャゲなどが好きな方、スクショなどたくさんしておくことを切にお勧めいたします。サービス終了してしまってからでは後悔してもしきれませんからね……。筆者は後悔しきりです。
やっぱり現実は受け入れがたいですね。今でも本当はサービス終了していないんだと思っている自分がいます。
ということで、CUE!のおすすめ楽曲を紹介したいと思います。YouTube Musicのリンクも貼っておきますので、まずは曲からこのコンテンツへの興味を持っていただき、そして一人でも多くの方にCUE!に触れて頂けたら嬉しいです。(白目)
ほんとは他にもおすすめの曲は無限にあるんですけどね!キリがないので超厳選しました!気になった方は色々聴いてみてください!そしてこのコンテンツが二度と動かないという地獄を共に味わおうではないか。
いかがでしたか?それではまたお会いしましょう!
コメント