KDOC 59: ECSを使ってサンプルゲームを作る
EntityComponentSystemを使って、サンプルゲームを作る。
実際のゲーム画面。ウィンドウにフォーカスして方向キーで操作できる。
- リポジトリ。kijimaD/ruins
方針
まずストーリー抜きで、RPGとしてゲームが成立するようにする。
Tasks
TODO 壁を通過できないようにする
- rectの中に入れないようにする。
- 線として持っておく必要はなくて、点4つだけを持っていればいい
- ただそれだとステージの壁を自動生成しづらくなるのだが…
- 範囲内のオブジェクトに対して衝突判定する
TODO いったんクリアできるようにする
戦闘抜きで、全体を作る。
TODO 階層移動できるようにする
- 触れた判定できるようにする
- 触れたときにイベントを発火する
TODO 階層設計
ランダムだと難しそうなのでとりあえずは手動か。
どうやって内外を判定すればいいのだろう。
TODO 汎用の選択コンテナを作成する
メニューなど、なにかを一覧して選択するのは多く使うので、作っておきたい。
メニュー。
- 選択肢のリスト
- 現在選択中の番号を示す変数
ゲージ。
- HP
- レベル
- 名前
どうやればいいのだろうか。
- 構造体で作っておいて、後で代入できるようにしとくといいのでは。あとその構造体に親子関係を作るメソッドを作ると。
type aa struct { root ui.Container desc ui.Container list ui.Container } func (aa *aa) assemble { aa.root.AddChild(aa.desc) aa.root.AddChild(aa.list) }
TODO リアルタイムなローグライクがよさそう
- フィールドはRay Casting - Ebitengineという感じ
- タイルごとにターン制で動くという感じでない。細かく移動できる
- 自分が動いたら時間が進行する
- シンボルエンカウントで、回避する方法がある。煙幕的な
- 電力と燃料がある
- 電力は短期的なスタミナ。フィールドでダッシュ、煙幕、掘削で減る。有利に進められるが、時間での制限がある
- 燃料は、腹減り度。電力を使うと早く消費する。なくなるとゲームオーバーになる。移動で減る
TODO 合成のレアリティスコア
性能にスコアをつけ、結果的に出来上がったものに対してレアリティランクをつけるとよさそう。
TODO イベント部分の設計
1章のうろつきをどうするか考える。
- ローグライト形式にすると物語に関してあまり考えなくてよい
- 繰り返しのゲームプレイに変化をつけやすい
- 設定とかが伝わりにくい可能性がある
- Tipsという形式でオプショナルに読めればよさそう
- Tipsだと自然に紹介できなさそうな感じもする
- あまり物語性はない
- 物語部分は背景やSEつきのメッセージ形式で良い
- 行けるところはランダムで選ばれた4つにする
- 行った回数によってイベントが起こる
- 背反なイベントがある
- 回数を重ねることで仲間になったりアイテムがもらえたりする
- 例
- 市場 x 2 => 整備士が仲間になる
- 広場 x 2 => 回復薬がもらえる
- 単調な感じもする
- イベントによって仲間になったり、アイテムが増えたり、ステータスが変動したりする
TODO アイテム使用・削除をsystem化する
wantsToUseエンティティを生成して、そのエンティティをsystemでキャッチする。
直接削除すると共通処理が追加しにくかったりする。
共通の関数化するだけでよさそうな感じもする。実行順とかがややこしくなるのかな。メッセージを伝える用のエンティティをいちいち作るのが面倒なんだよな。コードも増える。
TODO モジュール分けする
名前がかぶってややこしいものは分ける。
- system
- app
- message engine
TODO 味方一覧表示を共通化する
いろんなところで使いそうかつ、複数のパーツで構成されているので作成が面倒なので。
TODO ステート切り替えが怪しい部分がある
特にpopしている部分。
- pushで、文字があると重なる
- popしたときにOnStartは走らないので、前の画面を削除するのはダメ
TODO 図形 or 画像描画の方法を考える
UIのために図形描画したい。どうするか。画像を用意すればよいが、いい感じにやるためにはどうすればいいか。
TODO 生成をランダム化する
ある程度ランダム化したい。プレイヤー、モンスター、ワープゲートの出る位置をバラけさせる。
TODO 暗闇を追加する
未探検の部分は暗くなる。
TODO 照明を追加する
照明がある部分は色が変わる。
TODO キャラクタを生成する
味方/敵を生成する。
TODO タイルの種類を増やす
見た目がよくないので、2種類の通常フロアを用意する。
ステージ作成が少し面倒になるか。2種類のタイルの違いをファイルに書き出したくないな。勝手に判断して入れてくれるのが一番良い。壁が隣接してたら〜とか。
TODO ゲームループカウントをグローバル化する
数えてメッセージのアニメーションさせる用。汎用的なのでグローバルでやってよさそう。アニメーションのためのもっとよい方法がある可能性はある。ちゃんと調べないとな…。
TODO アニメーションのやり方を考える
どうやっているのだろう。
TODO 階の生成方法を考える
- ランダム選択の一般階層
- ダンジョンによって選ばれやすさに偏りがある
- 5の倍数の場合は帰還ワープも出す
- すべてのマップに帰還ワープを設定しておく
- ボスの階層
- 特殊マップ
- 固定
TODO メッセージシステムのパッケージを切り出す
今は1パッケージに入っていてわかりにくい。
Archives
DONE メッセージ表示できるようにする
x-hgg-x/sokoban-goを使って小さいサンプルを作る。
DONE メッセージシステムのリファクタ
使いにくいので直す。
DONE メッセージシステムに自動改行を入れる
飛び出すのを防ぐ。
DONE ファイルを埋め込む
デプロイで扱いやすいように。
DONE CI設定
テストとビルドとデプロイする。
デプロイしたけど、ブラウザで表示できてないな。
DONE フィールドで動けるようにする
- テキストで地図を読み込む
- コンポーネントを作る
- 地図を表示する
- 移動できるようにする
実行時エラーになる。表示できない。インターフェースが取り出せないよう。
- コンポーネントの初期化を忘れていた
- LoadLevel()によって読み込んだComponentListをAddEntities()->AddEntityComponent()に渡す。が、AddEntitiesで失敗する。テキストで読み込んだ内容をreflectでオブジェクト化するときに、新しく作成したコンポーネントを初期化するのに失敗している
- ecsComponentListを調べてみよう
- ecvでGameが入ってない
- world.Components.Game
- sokoban-go では main.goのw.InitWorld(&gc.Components{})の時点でworld.Components.Gameがセットされている
DONE マップを表示できるようにする
表示する。
DONE 階数を移動できるようにする
1階からはじまって、次の階層に移動する。
ワープホール。
DONE クロスコンパイルする
一応CIに設定して保証しておく。
DONE メッセージが飛び出すのを直す
ステート遷移イベントを作る。
DONE 次の階をランダムに選択する
一覧からランダムに選択する。
DONE HomeStateを作成する
ゲームプレイの基軸になるメニュー。
DONE 脱出できるようにする
脱出階層で脱出できるようにする。
DONE 背景を設定する
背景を追加する。スプライトはあるけど、同じでいいのか。いや、スプライトは1枚の画像を分割するものだから、同じ感じでは扱えないな。変えるとsystemも変えないといけない。面倒なのでとりあえずいいか。
DONE サブメニュー追加
拠点メニューにはサブメニューがある。どうやるか考える。
- 別stateでやる
- 大量にstateができるのどうなのという感じ。背景コンポーネントとかも同じ感じで準備しないといけない
- リファレンスではどうやっているのだろう。ポーズでは、後ろを透明に表示しつつ、メニューを表示している。あれと同じようなことができないか
- ポーズメニューでは、OnStopでポーズメニューのエンティティのみを削除しているようだ。ほかのstateでは、すべてのエンティティを削除することが異なる
DONE pauseステート作成
デバッグで便利なので。
DONE アイテムを生成する
アイテムを追加する。
- item
- consumable
- name
- description
まずそれぞれのコンポーネントの雛形をファイルで作成する。
- items
- entityA
- componentA(consumable)
- componentB(weight)
- entityB
- componentA(consumable)
- componentB(weight)
- entityA
で、そのデータを読み込んでエンティティとコンポーネントを生成する関数を作る。
componentList := loader.EntityComponentList{} // engineとgameは同数でなければならない。分割されているのが面倒だな… componentList.Engine = append(componentList.Engine, loader.EngineComponentList{}) componentList.Game = append(componentList.Game, gloader.GameComponentList{ Item: &gc.Item{}, }) loader.AddEntities(world, componentList)
pub fn spawn_named_item(
DONE UI設計
いちいちゲーム画面見るのもアレなので、書いておく。
DONE UIエンティティだけを消す
DeleteAllEntitiesでステート切り替え時のUIリセットをしている。entitiesが全部消えるので、困る。ほとんどの場合、UIだけをリセットすればよさそう。
UIコンポーネントと、UIコンポーネントを消す関数を作ればよさそう。
DONE 各メニューを作成する
仮の内容で全部作る。
DONE アイテムを使う
- キャラクタを作る
- ステータスを作る
- 影響を与えられるようにする
- memo
- 可変のアイテムリストについて、選択中の印をつける必要がある
- 選択中の座標をとってきて、選択印の位置を変化させればいいのかな
- ゲーム
- 戦車にしたいけど、戦闘システムがややこしくなる
- 合成とかで各自の装備メインにしたいんだよな
DONE アイテムを選択して使えるようにする
今は固定にしている。
DONE アイテムリストをebitenUIで作る
いい感じに、スクロールできるようにする。
DONE サイドメニューを表示する
性能を表示するサイドパネル。
- メニューバーが太いのを直す
DONE UIをリロードせずに反映できるようにする
アイテムを使用したときにUIをリロードしているが、スクロール位置が元へ戻ってしまうのでリロードしないようにする。
また、表示ジャンルの切替もあるので、リロードすると保持しなくて困る。
DONE ebitenUIを使う
使う。
DONE アイテムに対するアクションを選べるようにする
- 使う
- 捨てる
- キャンセル
- ebitenUIを組み込もうとしている
- うまくUpdateできてないからか、windowが開けない
- 今の構造だと、作成したuiをDrawとUpdateの2つができない
- UIもコンポーネント
- ebitenUIだとキーボード志向にしにくそう
- いや対応できるか
DONE メッセージシステムの命令追加
背景とか。
- 文字列に開始の合図がないから、識別子との判断ができてないみたい
- 画像を重ねる順番を指定できない
- 倉庫番のポーズではできてるからできそう
- ただポーズは表示順が後なので…。明らかにポーズ画面は後だ。メッセージシステムの場合は背景が後で変わる可能性がある。
DONE インベントリメニューでpanicになる
別のステートに遷移したあと、再び戻ってクリックするとエラーになる。
- アイテム選択
- 「使う」クリックでpanic
- partyContainerの数が2つずつ増えているようだ
- 1度しか付与されないようにしたら解決した
DONE アイテムを使う対象を選べるようにする
- 回復薬の場合は1人の味方を選ぶ
- 回復スプレーの場合は全員を選択している画面になる
- ロケット弾の場合は1人の敵を選ぶ
- 決めること
- 使う対象
- 敵
- 味方
- なし
- 対象数
- 単数
- 複数
- 使う場面
- 戦闘中のみと制限されるものがある
- 戦闘中
- フィールド / 拠点
- 使う対象
- パーティ一覧を表示する
- 選択したときに適用する
- ProvidesHealingがあるものは自動で仲間対象でも良い、が
DONE ゲーム設計
どうするか。
DONE UIのリファクタ
- 統一感をもって扱えるようにする
- 説明文とメニューの間隔を空ける
- resourceに各UI(idle, hover, pressed)を初期化しておく
- 参考コードを見てどうやっているかを調べる
- 完璧でなくてよい。やっても成果が見えなくて辛いので、次をやるか
- UI間に依存があって、思ったよりきれいに書けなかった感
- まあ、アイテム画面と同じスタイルで別のメニューを表示したくなったら考えればいい
DONE 武器を追加する
使うアイテムとは別枠で表示できる。
- 武器名
- 元となった武器名
- 攻撃力
- 命中
- 攻撃回数
- 属性
- 拳銃
- 小銃
- 刀剣
武器の性能にはばらつきがある。種類によってベースがある。ばらつきやすさが違う。
メニューをトグルさせるためにどうするか。既存のchildを削除して、再度追加すればいいか。
DONE 素材を追加する
- 素材は表示が違う。個数を表示することになっている。どうするか
- 素材はグローバルに個数カウントできればよい。そのへんはほかのエンティティと事情が違う
- 表示方法を変えないといけないがどうするか
- しょせん中のテキストが違うだけ
- 素材を追加する
- 素材は個数カウント。エンティティを追加する必要はあるか。単なるmapでもよい
- ただ、同じtomlで生成できるほうがわかりやすい。nameとdescriptionあるし
インターフェースから考える。
// tomlにあるものはカウント0で初期化される material.GetCount("ガラクタ") // => 3 material.IncCount("ガラクタ", 1) material.DeclCount("小さな花", 1)
DONE 合成画面を作る
まず画面を作って、そこから共通化していけばいいか。
- 装備画面
- 合成画面
- 使用画面
これらは似たようなUIを持つ。
- カテゴリ選択
- アイテムメニュー(左)
- 中身の取得ロジックは異なる
- 中に入るデータの種類が違うということ
- 性能メニュー(右)
あたりは共通。ボタンのアクションが違うくらいか。
合成に必要なもの。
- レシピ
- 素材の種類と個数
- 鉄の剣 =
[{鉄くず,2}, {木の棒,1}]
- レシピを表示する
- 合成する関数を作成する
- アイテム名からベースアイテムを作成する
- 加工する
- レシピをもとに作成できるようにする
- 所持数量とレシピを比較して満たしていると合成が選択できる
- 合成を選択すると、所持数量を減らし該当アイテムを追加する
gc := Craft("ハンドガン", 4) ecs.Entity // 品名、合成オプション Spawn(gc, spawntype.OnBackpack)
DONE アイテムUIまわりをリファクタする
- グローバル変数を構造体のフィールドに移す
合成とか装備品変更とか、よく似たUIで別画面を作ることになる。別で作ってたら大変なことになる。再利用するためにはどうすればよいか。
DONE 乗り物をどうするか
結論、小さなSFチックな機械を導入する。戦闘には参加しないがサポートする。知能は持たない。
パーティ全体を強化できるようなのがあると面白そうに思える。乗り物はそういう強化が自然にできて面白い。人だけだとつけ外し要素がない。ただし、戦車だとシステムが複雑になる可能性がある。アイテム合成が生きないような。
- ドローンやタレットとか、自律的な何か
- 戦闘で交じるのはややこしくて困る
- 非戦闘な乗り物ってないな
- 歩数制限のもっともな理由がほしい
- 燃料とか食べ物の類
- 小さなSFチックな機械を導入する。それがないと遺跡に入れない的な。いろいろ効果をつけられる
- 戦車は逆に敵が強くなるとかの理由をつけて遺跡に入らない。戦闘が面倒になるので
DONE タイル移動でなくするか
いやでもアニメーションやリアルタイムとなると大変そうだから、タイル移動のままがよさそう。
あまりローグライクさせる意味はなさそう。敵を避けにくい。banbandonを参考にして自由移動にするか。
DONE 一貫させるためインターフェースを定義する
stateごとにコードがバラバラで、直していくのが辛い。
一部共通部分もあるが、違う部分も多いので、しょうがないところではある。
インターフェース化して、ある程度同じにするか。とはいえ、アイテム画面がそこまで種類多いかと言われるとそうでもない。3、4個だからあまり神経質にならなくてもいい。
DONE 武器コンポーネントに属性を追加する
- 火炎(耐火)
- 電気(耐電)
- 光力(耐光)
だとそのまますぎるか。光は異色だが、SFらしさを出すのに良い。ややこしいのであまり属性を増やしたくない。冷気(耐冷)を追加した。
時代背景的に、SFではない。でも合成するとSFになるよな。SFよりの現代、でよいか。
DONE アイテム種別に防具を追加する
- 消耗品
- 武器
- 防具
- 素材
で、種別が揃う。
DONE 武器種別を追加する
剣とか銃とか。
DONE 合成画面をリファクタする
書き直す。
DONE 装備画面を作る
- スロットを作成する
- コードから装備させる
- 装備画面を作成する
- スロット表示画面。各キャラごと
- 選択画面を作成する
- ここで選択したものが前で選択したスロットに装備される
- モードをどう表現するか。これをstateとしてやるのはやりすぎな気もする
- 選択モードとだけしとけばいいか
- 選択モードだと、左側を武器リストにする。スライダーがあるから、全く同じにならなそうだな
DONE enumのバリデーション
楽にバリデーションできる書き方にする。
DONE カメラ追加
今はそのまま表示してる。プレイヤーの位置に追従してステージの一部だけを表示したい。
とりあえず、仮で追加した。
CLOSE UIと分離したい
完全にUIと一体化しているのでよくわからなくなる。
- UIを保持する構造体
- UIで表示されているボタンに設定されたイベントがトリガーされて、ECSクエリを実行して表示を切り替えたり追加したりする
- stateはviewだと考えてよさそうな感じがする
- データストアと直にやりとりしてるわけじゃないからいいのか。UIの変更だけだな
DONE 装備画面のリファクタ
汚いので直す。
どこから直せばいいのかよくわからないな。
DONE ステータスを追加する
生命力とか、力とか。
DONE 装備でステータスを変更する
防具を装備すると防御力が上がるなど。
- キャラ固有のステータスは、Attributes
- キャラごとに固有の値をもつ
- 装備によって上がることがある
- 防御力はどうするか
- キャラごとに固有の値をもたない。装備がなければみんな0となる
- 防御力以外が上がることもある。武器、防具どちらでも。
- 器用さ+1などのステータス値
- 火耐性+20%などの属性耐性
- 頑丈+1、貫通+2などのスキル
- 「救護」「乱射」などの行動追加
DONE 説明図を書く
見返してみるとけっこういい図がある。概念整理する。
DONE 回復薬を割合回復にする
- 固定値ではないようにする
- 割合回復の仕組みは作ったので、回復薬に適用する
- components, raw, effect をいい感じにしていく作業。大体同じ構造体になる
- 直にeffectを追加するのはよくないかもな。アイテムと共通に、いったんcomponentsを渡してeffectに変換させるようにする
DONE 戦闘部分の設計
未知の部分。どうするか。
- デッキ型にすると面白そうだなあ
- 取れる行動が毎回異なる
- マイナス行動は手札を圧迫する
- カードには消費コストが設定されているから、強いものを選べばいいというわけでもない
- ターンに行動カードは1枚選ぶ
- デッキに1枚しか設定されてないと、それしか出なくないか。10枚登録固定にすればいいか
- 白瀬
- 行動カード
- マシンガン(sp2) by 装備武器
- 防御(シールド装備, sp1)
- 回復(体力回復, アイテム消費) by 所持スキル
- 乱射(攻撃回数1.5倍, sp1) by 装備
- 狙撃(待ち時間1.5倍+攻撃力2倍, sp2) by 所持スキル
- パッシブスキル
- 連携LV2(連携率1.4倍) by 所持スキル
- 射撃LV1(命中率1.1倍, 射撃武器の攻撃力1.1倍) by 所持スキル
- 行動カード
- ピエロ
- 行動カード
- レーザーブレード(装備武器, sp2)
- 高出力(炎属性, sp2) by 装備
- 応援 by 固有行動
- 行動カード
- 選択
- 基本攻撃(白瀬)
- 基本攻撃(ピエロ)
デッキ。
参考。
- デッキは共通のことが多いようだ
- 特定の人ばかり攻撃することにならないのだろうか
- チームとは別に、人ごとの行動力もある
- 同じターンで複数行動はコスト増加する
- コスト増加しないものもある
- ドローしなくても使えるものがある
- ターンごとに行動力が回復する。戦闘ごとにリセット
- カードのストックはできない
- 毎回同じにならない
- カードの入手はランダム
- 装備が2枠ある
- 戦闘と関係ないサポートキャラが1人いる
それをふまえて。
- 調整が難しいので、もっとシンプルなルールがよさそう
- ランダム制はそこまでなくてよい
- 頭脳や運というよりはRPG的な、レベル上げて準備すれば勝てる要素強めにしたい
- カスタム性を高めたい
- アイテムのアップグレード要素はなし
- 特殊攻撃がついたカードはどう扱うか。あるなしどっちもほしい
- 2枚生成させるか
- 合成結果は複数になることがある
- アイテム取得全般が、複数あるのを考慮しておく
- ある武器に対して、アクションが複数選べるというのが自然だ
- アクションは、他のカードを強化するカードでよさそう
- カードとアクションは変えたいんだよな
- 防具とかどうする
- 基本パラメータは変わらないでいいのか
- デッキに含めるとパラメータUPでよさそう
- カードは直に入手できるのか、合成で入手するのか
- ダンジョン内で入手したやつを試せたほうがよさそう
- 制限ともいえるが…
- 入手は完全ランダム。1度入手すると合成で複数手に入れやすい
実装。
- じつはEffectと同じように、組み合わせてエンティティにしておけばいいだけか
- アクションカードは攻撃を与える性質や、回復する性質がついていればよい。あと対象が敵か味方か、単数か
- ブーストカードは、変化させる内容を保持していればよい。あと対象が敵か味方か、単数か
DONE UpdateSpecに渡すComponentsの更新を忘れる
オートで全コンポーネントを対象にすればよさそう。
componentListに渡せばよい。
DONE 防具ジャンルを消す
あまり区分けする必要はなさそうか。あの正方形のUIにすれば、混ざって入っていてもあまり違和感はない。
ただ、合成のときは分けたい感じも。
- アイテム
- 消耗品
- 売却アイテム
- 防具
- 手札
- アクションカード
- サポートカード
メモ。
- なぜかInBackpackの条件で、結果に入らない
- 装備してるせいだった…
CLOSE アイテム以外でeffectをトリガーする方法
今はまだ、アイテムトリガーしかない。AddItemで、コンポーネントに分解されてそれぞれEffectのキューに入り、実行される。
ただ、今後全回復とか、アイテム以外で何かしたいときが増える。そのときはどうするか。
DONE メインメニューを開いているとCPU使用率が爆増する
リークしている。
- フィールド画面でも起こるな
- ほかの画面では起こらない。どうもメニューの仕組みを使っているところで起きてそう
ps aux | head -n 1
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
- ほかの画面でも、タブを切り替えたときなどに発生する。生成した画面を生成できてないんだろうな
- RemoveChildren()で、表示されなくはなっているけど、それがガベージコレクションされてない
- プロファイラの設定した
- LoadFont()設定があると、残り続けるな。ないと、残らない
- ずっとコンテナの親子関係に問題があると考えていた(RemoveChildrenまわり)けど、そうではなかった
- Faceまわりを毎回初期化してたのを、リソース構造体に保存して、それを使うようにしたら解決した
- なんだかよくわからないな
- 相変わらず微妙に増えてるように見えるが、freeされてるようにも見える
DONE 画像回帰テスト
コマンドで各ステートの画像を取れるようにする。
DONE メモリリークをCIで検知したい
まあパフォーマンスを画面表示してるし、わかるだろう。
一定期間起動して、一定になるか確かめるとよさそう。
DONE ホーム画面をクリック対応する
キーボードはとりあえずなくした。
今はキーボードでしか移動できない。
でもキーボード移動も残したい。
DONE Ray Castingを参考にしてフィールドの原型を作る
- Ray Castingを原型に動くものを作る
- やりたいことに近い。2D Ray Casting
- ban-ban-donを参考にして各システムを実装する
- 特に目新しいことはなく、移動に関しては完全に参考にして作成できるように見える
ゲーム的。
- 動かしてみて、難易度的に難しい割に、そんなに移動にゲーム性生まれなくないかと感じた
- 不可視の範囲を作るのがそこまで面白いかと言われるとビミョー
- ほかのサンプルでは興味深く見えた。距離をつけてないから、明るすぎるためか
- シューティング要素はないからな。単なる追いかけっこが面白いかというと…ミンサガとかのイメージが近い。避けるためのスキルを使う
- お宝探し感は楽しい
- リアルタイムだと、シューティングの劣化版にしかならない。自分が動くと相手も動く形式であればよさそう。じっくり考えて駆け引きにする
実装。
- 三角形をグラデーションにすればよさそう
- jsの方ではどうやってるか
- 単にfillをグラデーションで設定してるだけに見える
- 画像
- 背景
- 影
- 三角
ECSとの兼ね合い。
- システムにしていきたいが、描画とかは厳しそうに見える。少なくとも1タイルごとに1エンティティとかにはできない
- 描画以外はECSにしていく
- なんだか難しすぎるように見えるけど、どうなのだろう
- たくさんの構造体をstateに書くのは違う。が、いじりたい状態フィールドがある
DONE フィールドの背景を全体に設定する
敷き詰めたい。
- やっぱりVRTで失敗するな
- 手元で動かすと発生しないのはなぜなんだろうな